mirror of
https://github.com/photoprism/photoprism.git
synced 2025-10-05 08:47:12 +08:00
Also improves migrations and updates the db schema docs. Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
4
Makefile
4
Makefile
@@ -444,9 +444,11 @@ latest:
|
||||
docker-compose -f docker-compose.latest.yml stop photoprism-latest
|
||||
docker-compose -f docker-compose.latest.yml up -d --wait photoprism-latest
|
||||
start-latest:
|
||||
docker-compose -f docker-compose.latest.yml up -d --wait photoprism-latest
|
||||
docker-compose -f docker-compose.latest.yml up photoprism-latest
|
||||
stop-latest:
|
||||
docker-compose -f docker-compose.latest.yml stop photoprism-latest
|
||||
terminal-latest:
|
||||
docker-compose -f docker-compose.latest.yml exec photoprism-latest bash
|
||||
logs-latest:
|
||||
docker-compose -f docker-compose.latest.yml logs -f photoprism-latest
|
||||
docker-local: docker-local-bookworm
|
||||
|
@@ -80,7 +80,7 @@
|
||||
}
|
||||
],
|
||||
"scope": "{{ .config.BaseUri }}/",
|
||||
"start_url": "{{ .config.BaseUri }}/",
|
||||
"start_url": "{{ .config.BaseUri }}/library/",
|
||||
"display": "{{ .config.AppMode }}",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#000000",
|
||||
|
@@ -25,8 +25,8 @@ services:
|
||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||
## Public server URL incl http:// or https:// and /path, :port is optional
|
||||
PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/"
|
||||
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
||||
PHOTOPRISM_SITE_DESCRIPTION: "Open-Source Photo Management"
|
||||
PHOTOPRISM_SITE_CAPTION: "Latest"
|
||||
PHOTOPRISM_SITE_DESCRIPTION: "Tags and finds pictures without getting in your way!"
|
||||
PHOTOPRISM_SITE_AUTHOR: "@photoprism_app"
|
||||
PHOTOPRISM_DEBUG: "true"
|
||||
PHOTOPRISM_READONLY: "false"
|
||||
@@ -60,8 +60,8 @@ services:
|
||||
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
|
||||
working_dir: "/photoprism"
|
||||
volumes:
|
||||
- "/photoprism/storage"
|
||||
- "/photoprism/originals"
|
||||
- "./storage:/photoprism/storage"
|
||||
- "./storage/originals:/photoprism/originals"
|
||||
|
||||
## Join shared "photoprism-develop" network
|
||||
networks:
|
||||
|
@@ -123,7 +123,7 @@ config.load().finally(() => {
|
||||
const router = new Router({
|
||||
routes: Routes,
|
||||
mode: "history",
|
||||
base: config.baseUri + "/",
|
||||
base: config.baseUri + "/library/",
|
||||
saveScrollPosition: true,
|
||||
scrollBehavior: (to, from, savedPosition) => {
|
||||
if (savedPosition) {
|
||||
|
@@ -27,14 +27,14 @@ import Photos from "pages/photos.vue";
|
||||
import Albums from "pages/albums.vue";
|
||||
import AlbumPhotos from "pages/album/photos.vue";
|
||||
import Places from "pages/places.vue";
|
||||
import Files from "pages/library/files.vue";
|
||||
import Errors from "pages/library/errors.vue";
|
||||
import Browse from "pages/files/browse.vue";
|
||||
import Errors from "pages/files/errors.vue";
|
||||
import Labels from "pages/labels.vue";
|
||||
import People from "pages/people.vue";
|
||||
import Library from "pages/library.vue";
|
||||
import Files from "pages/files.vue";
|
||||
import Settings from "pages/settings.vue";
|
||||
import Login from "pages/auth/login.vue";
|
||||
import Connect from "pages/auth/connect.vue";
|
||||
import Login from "pages/login.vue";
|
||||
import Connect from "pages/connect.vue";
|
||||
import Discover from "pages/discover.vue";
|
||||
import About from "pages/about/about.vue";
|
||||
import Feedback from "pages/about/feedback.vue";
|
||||
@@ -60,7 +60,7 @@ export default [
|
||||
},
|
||||
{
|
||||
name: "license",
|
||||
path: "/about/license",
|
||||
path: "/license",
|
||||
component: License,
|
||||
meta: { title: siteTitle, auth: false },
|
||||
},
|
||||
@@ -78,7 +78,7 @@ export default [
|
||||
},
|
||||
{
|
||||
name: "login",
|
||||
path: "/auth/login",
|
||||
path: "/login",
|
||||
component: Login,
|
||||
meta: { title: siteTitle, auth: false, hideNav: true },
|
||||
beforeEnter: (to, from, next) => {
|
||||
@@ -265,20 +265,20 @@ export default [
|
||||
},
|
||||
{
|
||||
name: "files",
|
||||
path: "/library/files*",
|
||||
component: Files,
|
||||
path: "/index/files*",
|
||||
component: Browse,
|
||||
meta: { title: $gettext("File Browser"), auth: true },
|
||||
},
|
||||
{
|
||||
name: "hidden",
|
||||
path: "/library/hidden",
|
||||
path: "/hidden",
|
||||
component: Photos,
|
||||
meta: { title: $gettext("Hidden Files"), auth: true },
|
||||
props: { staticFilter: { hidden: "true" } },
|
||||
},
|
||||
{
|
||||
name: "errors",
|
||||
path: "/library/errors",
|
||||
path: "/errors",
|
||||
component: Errors,
|
||||
meta: { title: $gettext("Errors"), auth: true },
|
||||
},
|
||||
@@ -319,25 +319,25 @@ export default [
|
||||
meta: { title: $gettext("People"), auth: true, background: "application-light" },
|
||||
},
|
||||
{
|
||||
name: "library",
|
||||
path: "/library",
|
||||
component: Library,
|
||||
name: "index",
|
||||
path: "/index",
|
||||
component: Files,
|
||||
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||
props: { tab: "library-index" },
|
||||
props: { tab: "index" },
|
||||
},
|
||||
{
|
||||
name: "library_import",
|
||||
path: "/library/import",
|
||||
component: Library,
|
||||
name: "index_import",
|
||||
path: "/import",
|
||||
component: Files,
|
||||
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||
props: { tab: "library-import" },
|
||||
props: { tab: "import" },
|
||||
},
|
||||
{
|
||||
name: "library_logs",
|
||||
path: "/library/logs",
|
||||
component: Library,
|
||||
name: "index_logs",
|
||||
path: "/logs",
|
||||
component: Files,
|
||||
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||
props: { tab: "library-logs" },
|
||||
props: { tab: "logs" },
|
||||
},
|
||||
{
|
||||
name: "settings",
|
||||
@@ -352,40 +352,17 @@ export default [
|
||||
props: { tab: "settings-general" },
|
||||
},
|
||||
{
|
||||
name: "settings_library",
|
||||
path: "/settings/library",
|
||||
name: "settings_files",
|
||||
path: "/settings/files",
|
||||
component: Settings,
|
||||
meta: {
|
||||
title: $gettext("Settings"),
|
||||
auth: true,
|
||||
admin: true,
|
||||
settings: true,
|
||||
background: "application-light",
|
||||
},
|
||||
props: { tab: "settings-library" },
|
||||
},
|
||||
{
|
||||
name: "settings_sync",
|
||||
path: "/settings/sync",
|
||||
component: Settings,
|
||||
meta: {
|
||||
title: $gettext("Settings"),
|
||||
auth: true,
|
||||
settings: true,
|
||||
background: "application-light",
|
||||
},
|
||||
props: { tab: "settings-sync" },
|
||||
},
|
||||
{
|
||||
name: "settings_account",
|
||||
path: "/settings/account",
|
||||
component: Settings,
|
||||
meta: {
|
||||
title: $gettext("Settings"),
|
||||
auth: true,
|
||||
settings: true,
|
||||
background: "application-light",
|
||||
},
|
||||
props: { tab: "settings-account" },
|
||||
props: { tab: "settings-files" },
|
||||
},
|
||||
{
|
||||
name: "settings_advanced",
|
||||
@@ -400,6 +377,30 @@ export default [
|
||||
},
|
||||
props: { tab: "settings-advanced" },
|
||||
},
|
||||
{
|
||||
name: "settings_services",
|
||||
path: "/settings/services",
|
||||
component: Settings,
|
||||
meta: {
|
||||
title: $gettext("Settings"),
|
||||
auth: true,
|
||||
settings: true,
|
||||
background: "application-light",
|
||||
},
|
||||
props: { tab: "settings-services" },
|
||||
},
|
||||
{
|
||||
name: "settings_account",
|
||||
path: "/settings/account",
|
||||
component: Settings,
|
||||
meta: {
|
||||
title: $gettext("Settings"),
|
||||
auth: true,
|
||||
settings: true,
|
||||
background: "application-light",
|
||||
},
|
||||
props: { tab: "settings-account" },
|
||||
},
|
||||
{
|
||||
name: "discover",
|
||||
path: "/discover",
|
||||
|
@@ -546,6 +546,10 @@ export default class Config {
|
||||
return this.values && this.values.public;
|
||||
}
|
||||
|
||||
isDemo() {
|
||||
return this.values && this.values.demo;
|
||||
}
|
||||
|
||||
isSponsor() {
|
||||
if (!this.values || !this.values.sponsor) {
|
||||
return false;
|
||||
|
@@ -220,10 +220,10 @@ export default class Session {
|
||||
getHome() {
|
||||
if (this.loginRequired()) {
|
||||
return "login";
|
||||
} else if (this.user.Role === "guest") {
|
||||
return "albums";
|
||||
} else {
|
||||
} else if (this.config.allow("photos", "access_library")) {
|
||||
return "browse";
|
||||
} else {
|
||||
return "albums";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -227,7 +227,7 @@
|
||||
<v-list-tile :to="{name: 'live'}" class="nav-live" @click.stop="">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`">
|
||||
<translate>Live</translate>
|
||||
<translate>Live Photos</translate>
|
||||
<span v-show="config.count.live > 0"
|
||||
:class="`nav-count ${rtl ? '--rtl' : ''}`">{{ config.count.live | abbreviateCount }}</span>
|
||||
</v-list-tile-title>
|
||||
@@ -387,7 +387,7 @@
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile v-if="isMini && $config.feature('library')" to="/library" class="nav-library" @click.stop="">
|
||||
<v-list-tile v-if="isMini && $config.feature('library')" :to="{ name: 'index' }" class="nav-library" @click.stop="">
|
||||
<v-list-tile-action :title="$gettext('Library')">
|
||||
<v-icon>camera_roll</v-icon>
|
||||
</v-list-tile-action>
|
||||
@@ -401,7 +401,7 @@
|
||||
|
||||
<v-list-group v-if="!isMini && $config.feature('library')" prepend-icon="camera_roll" no-action>
|
||||
<template #activator>
|
||||
<v-list-tile to="/library" class="nav-library" @click.stop="">
|
||||
<v-list-tile :to="{ name: 'index' }" class="nav-library" @click.stop="">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title class="p-flex-menuitem">
|
||||
<translate key="Library">Library</translate>
|
||||
@@ -410,7 +410,7 @@
|
||||
</v-list-tile>
|
||||
</template>
|
||||
|
||||
<v-list-tile v-show="$config.feature('files')" to="/library/files" class="nav-originals" @click.stop="">
|
||||
<v-list-tile v-show="$config.feature('files')" to="/index/files" class="nav-originals" @click.stop="">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`">
|
||||
<translate key="Originals">Originals</translate>
|
||||
@@ -420,7 +420,7 @@
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/library/hidden" class="nav-hidden" @click.stop="">
|
||||
<v-list-tile :to="{ name: 'hidden' }" class="nav-hidden" @click.stop="">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`">
|
||||
<translate key="Hidden">Hidden</translate>
|
||||
@@ -430,7 +430,7 @@
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/library/errors" class="nav-errors" @click.stop="">
|
||||
<v-list-tile :to="{ name: 'errors' }" class="nav-errors" @click.stop="">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`">
|
||||
<translate key="Errors">Errors</translate>
|
||||
@@ -440,7 +440,7 @@
|
||||
</v-list-group>
|
||||
|
||||
<template v-if="!config.disable.settings">
|
||||
<v-list-tile v-if="isMini" v-show="$config.feature('settings')" to="/settings" class="nav-settings" @click.stop="">
|
||||
<v-list-tile v-if="isMini" v-show="$config.feature('settings')" :to="{ name: 'settings' }" class="nav-settings" @click.stop="">
|
||||
<v-list-tile-action :title="$gettext('Settings')">
|
||||
<v-icon>settings</v-icon>
|
||||
</v-list-tile-action>
|
||||
@@ -454,7 +454,7 @@
|
||||
|
||||
<v-list-group v-else v-show="$config.feature('settings')" prepend-icon="settings" no-action>
|
||||
<template #activator>
|
||||
<v-list-tile to="/settings" class="nav-settings" @click.stop="">
|
||||
<v-list-tile :to="{ name: 'settings' }" class="nav-settings" @click.stop="">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<translate key="Settings">Settings</translate>
|
||||
@@ -565,11 +565,11 @@
|
||||
class="menu-action nav-login">
|
||||
<v-icon>login</v-icon>
|
||||
</router-link>
|
||||
<router-link v-if="auth && !routeName('library') && $config.feature('library') && $config.feature('logs')"
|
||||
:to="{ name: 'library_logs' }" :title="$gettext('Logs')" class="menu-action nav-logs">
|
||||
<v-icon>notes</v-icon>
|
||||
<router-link v-if="auth && !routeName('index') && $config.feature('library') && $config.feature('logs')"
|
||||
:to="{ name: 'logs' }" :title="$gettext('Logs')" class="menu-action nav-logs">
|
||||
<v-icon>grading</v-icon>
|
||||
</router-link>
|
||||
<router-link v-if="auth && $config.feature('settings') && !routeName('settings')" to="/settings"
|
||||
<router-link v-if="auth && $config.feature('settings') && !routeName('settings')" :to="{ name: 'settings' }"
|
||||
:title="$gettext('Settings')" class="menu-action nav-settings">
|
||||
<v-icon>settings</v-icon>
|
||||
</router-link>
|
||||
@@ -605,13 +605,13 @@
|
||||
</div>
|
||||
<div v-if="auth && !routeName('files') && $config.feature('files') && $config.feature('library')"
|
||||
class="menu-action nav-files">
|
||||
<router-link to="/library/files">
|
||||
<router-link to="/index/files">
|
||||
<v-icon>folder</v-icon>
|
||||
<translate>Files</translate>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-if="auth && !routeName('library') && $config.feature('library')" class="menu-action nav-library">
|
||||
<router-link :to="{ name: 'library' }">
|
||||
<div v-if="auth && !routeName('index') && $config.feature('library')" class="menu-action nav-library">
|
||||
<router-link :to="{ name: 'index' }">
|
||||
<v-icon>camera_roll</v-icon>
|
||||
<translate>Index</translate>
|
||||
</router-link>
|
||||
|
@@ -6,6 +6,10 @@ nav .v-list__tile__title.title {
|
||||
line-height: normal!important;
|
||||
}
|
||||
|
||||
.v-navigation-drawer>.theme--dark.v-list .v-list__tile__sub-title {
|
||||
color: #ffffff77;
|
||||
}
|
||||
|
||||
#p-navigation .nav-title {
|
||||
text-align: left;
|
||||
/* font-weight: bold; */
|
||||
|
@@ -23,9 +23,9 @@ Additional information can be found in our Developer Guide:
|
||||
|
||||
*/
|
||||
|
||||
import PAccountAddDialog from "dialog/account/add.vue";
|
||||
import PAccountRemoveDialog from "dialog/account/remove.vue";
|
||||
import PAccountEditDialog from "dialog/account/edit.vue";
|
||||
import PAccountAddDialog from "dialog/service/add.vue";
|
||||
import PAccountRemoveDialog from "dialog/service/remove.vue";
|
||||
import PAccountEditDialog from "dialog/service/edit.vue";
|
||||
import PPhotoArchiveDialog from "dialog/photo/archive.vue";
|
||||
import PPhotoAlbumDialog from "dialog/photo/album.vue";
|
||||
import PPhotoEditDialog from "dialog/photo/edit.vue";
|
||||
|
@@ -66,7 +66,7 @@
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import Account from "model/account";
|
||||
import Service from "model/service";
|
||||
import * as options from "options/options";
|
||||
|
||||
export default {
|
||||
@@ -80,7 +80,7 @@ export default {
|
||||
showPassword: false,
|
||||
loading: false,
|
||||
search: null,
|
||||
model: new Account(),
|
||||
model: new Service(false),
|
||||
label: {
|
||||
cancel: this.$gettext("Cancel"),
|
||||
confirm: this.$gettext("Connect"),
|
@@ -19,15 +19,15 @@
|
||||
<v-layout row wrap>
|
||||
<v-flex xs12 text-xs-left class="pt-2">
|
||||
<v-select
|
||||
v-model="account"
|
||||
v-model="service"
|
||||
color="secondary-dark" hide-details hide-no-data
|
||||
flat
|
||||
:label="$gettext('Account')"
|
||||
item-text="AccName"
|
||||
item-value="ID"
|
||||
return-object
|
||||
:disabled="loading || noAccounts"
|
||||
:items="accounts"
|
||||
:disabled="loading || noServices"
|
||||
:items="services"
|
||||
@change="onChange">
|
||||
</v-select>
|
||||
</v-flex>
|
||||
@@ -41,7 +41,7 @@
|
||||
:search-input.sync="search"
|
||||
:items="pathItems"
|
||||
:loading="loading"
|
||||
:disabled="loading || noAccounts"
|
||||
:disabled="loading || noServices"
|
||||
item-text="abs"
|
||||
item-value="abs"
|
||||
:label="$gettext('Folder')"
|
||||
@@ -52,11 +52,11 @@
|
||||
<v-btn depressed color="secondary-light" class="action-cancel ml-0 mt-0 mb-0 mr-2" @click.stop="cancel">
|
||||
<translate>Cancel</translate>
|
||||
</v-btn>
|
||||
<v-btn v-if="noAccounts" :disabled="isPublic && !isDemo" color="primary-button" depressed dark
|
||||
<v-btn v-if="noServices" :disabled="isPublic && !isDemo" color="primary-button" depressed dark
|
||||
class="action-setup ma-0" @click.stop="setup">
|
||||
<translate>Setup</translate>
|
||||
</v-btn>
|
||||
<v-btn v-else :disabled="noAccounts" color="primary-button" depressed dark
|
||||
<v-btn v-else :disabled="noServices" color="primary-button" depressed dark
|
||||
class="action-upload ma-0" @click.stop="confirm">
|
||||
<translate>Upload</translate>
|
||||
</v-btn>
|
||||
@@ -67,7 +67,7 @@
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import Account from "model/account";
|
||||
import Service from "model/service";
|
||||
import Selection from "common/selection";
|
||||
|
||||
export default {
|
||||
@@ -87,11 +87,11 @@ export default {
|
||||
return {
|
||||
isDemo: this.$config.get("demo"),
|
||||
isPublic: this.$config.get("public"),
|
||||
noAccounts: false,
|
||||
noServices: false,
|
||||
loading: true,
|
||||
search: null,
|
||||
account: {},
|
||||
accounts: [],
|
||||
service: {},
|
||||
services: [],
|
||||
selection: new Selection({}),
|
||||
path: "/",
|
||||
paths: [
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
this.$router.push({name: "settings_sync"});
|
||||
},
|
||||
confirm() {
|
||||
if (this.noAccounts) {
|
||||
if (this.noServices) {
|
||||
this.$notify.warn(this.$gettext('No servers configured.'));
|
||||
return;
|
||||
} else if (this.loading) {
|
||||
@@ -140,7 +140,7 @@ export default {
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.account.Share(this.selection, this.path).then(
|
||||
this.service.Upload(this.selection, this.path).then(
|
||||
(files) => {
|
||||
this.loading = false;
|
||||
|
||||
@@ -150,7 +150,7 @@ export default {
|
||||
this.$notify.success(this.$gettextInterpolate(this.$gettext("%{n} files uploaded"), {n: files.length}));
|
||||
}
|
||||
|
||||
this.$emit('confirm', this.account);
|
||||
this.$emit('confirm', this.service);
|
||||
}
|
||||
).catch(() => this.loading = false);
|
||||
},
|
||||
@@ -158,13 +158,13 @@ export default {
|
||||
this.paths = [{"abs": "/"}];
|
||||
|
||||
this.loading = true;
|
||||
this.account.Folders().then(p => {
|
||||
this.service.Folders().then(p => {
|
||||
for (let i = 0; i < p.length; i++) {
|
||||
this.paths.push(p[i]);
|
||||
}
|
||||
|
||||
this.pathItems = [...this.paths];
|
||||
this.path = this.account.SharePath;
|
||||
this.path = this.service.SharePath;
|
||||
}).finally(() => this.loading = false);
|
||||
},
|
||||
load() {
|
||||
@@ -188,13 +188,13 @@ export default {
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
Account.search(params).then(response => {
|
||||
Service.search(params).then(response => {
|
||||
if (!response.models.length) {
|
||||
this.noAccounts = true;
|
||||
this.noServices = true;
|
||||
this.loading = false;
|
||||
} else {
|
||||
this.account = response.models[0];
|
||||
this.accounts = response.models;
|
||||
this.service = response.models[0];
|
||||
this.services = response.models;
|
||||
this.onChange();
|
||||
}
|
||||
}).catch(() => this.loading = false);
|
||||
|
@@ -28,7 +28,7 @@ import Api from "common/api";
|
||||
import { $gettext } from "common/vm";
|
||||
import { config } from "app/session";
|
||||
|
||||
export class Account extends RestModel {
|
||||
export class Service extends RestModel {
|
||||
getDefaults() {
|
||||
return {
|
||||
ID: 0,
|
||||
@@ -76,7 +76,7 @@ export class Account extends RestModel {
|
||||
);
|
||||
}
|
||||
|
||||
Share(selection, folder) {
|
||||
Upload(selection, folder) {
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
@@ -85,13 +85,13 @@ export class Account extends RestModel {
|
||||
selection = { Photos: selection };
|
||||
}
|
||||
|
||||
return Api.post(this.getEntityResource() + "/share", { selection, folder }).then((response) =>
|
||||
return Api.post(this.getEntityResource() + "/upload", { selection, folder }).then((response) =>
|
||||
Promise.resolve(response.data)
|
||||
);
|
||||
}
|
||||
|
||||
static getCollectionResource() {
|
||||
return "accounts";
|
||||
return "services";
|
||||
}
|
||||
|
||||
static getModelName() {
|
||||
@@ -99,4 +99,4 @@ export class Account extends RestModel {
|
||||
}
|
||||
}
|
||||
|
||||
export default Account;
|
||||
export default Service;
|
@@ -112,28 +112,28 @@ export class User extends RestModel {
|
||||
return this.Details.NickName;
|
||||
} else if (this.Details && this.Details.GivenName) {
|
||||
return this.Details.GivenName;
|
||||
} else if (this.Name) {
|
||||
return Util.capitalize(this.Name);
|
||||
} else if (this.Role) {
|
||||
return T(Util.capitalize(this.Role));
|
||||
} else if (this.Details && this.Details.JobTitle) {
|
||||
return this.Details.JobTitle;
|
||||
} else if (this.Email) {
|
||||
return this.Email;
|
||||
} else if (this.Role) {
|
||||
return T(Util.capitalize(this.Role));
|
||||
} else if (this.Name) {
|
||||
return `@${this.Name}`;
|
||||
}
|
||||
|
||||
return $gettext("Unregistered");
|
||||
}
|
||||
|
||||
getAccountInfo() {
|
||||
if (this.Email) {
|
||||
if (this.Name) {
|
||||
return `@${this.Name}`;
|
||||
} else if (this.Email) {
|
||||
return this.Email;
|
||||
} else if (this.Details && this.Details.JobTitle) {
|
||||
return this.Details.JobTitle;
|
||||
} else if (this.Role) {
|
||||
return T(Util.capitalize(this.Role));
|
||||
} else if (this.Name) {
|
||||
return this.Name;
|
||||
}
|
||||
|
||||
return $gettext("Account");
|
||||
|
@@ -87,7 +87,7 @@
|
||||
</p>
|
||||
|
||||
<p class="text-xs-center pt-2 ma-0 pb-0">
|
||||
<router-link to="/about/license">
|
||||
<router-link to="/license">
|
||||
<img :src="$config.staticUri + '/img/badge-agpl.svg'" alt="License AGPL v3" style="max-width:100%;"/>
|
||||
</router-link>
|
||||
<a target="_blank" href="https://docs.photoprism.app/"><img :src="$config.staticUri + '/img/badge-docs.svg'"
|
||||
|
@@ -8,7 +8,7 @@
|
||||
slider-color="secondary-dark"
|
||||
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
||||
>
|
||||
<v-tab v-for="(item, index) in tabs" :id="'tab-' + item.name" :key="index" :class="item.class" ripple
|
||||
<v-tab v-for="(item, index) in tabs" :id="'tab-' + item.name" :key="manage" :class="item.class" ripple
|
||||
@click="changePath(item.path)">
|
||||
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="item.label">{{ item.icon }}</v-icon>
|
||||
<template v-else>
|
||||
@@ -17,7 +17,7 @@
|
||||
</v-tab>
|
||||
|
||||
<v-tabs-items touchless>
|
||||
<v-tab-item v-for="(item, index) in tabs" :key="index" lazy>
|
||||
<v-tab-item v-for="(item, index) in tabs" :key="manage" lazy>
|
||||
<component :is="item.component"></component>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
@@ -26,9 +26,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Import from "pages/library/import.vue";
|
||||
import Index from "pages/library/index.vue";
|
||||
import Logs from "pages/library/logs.vue";
|
||||
import Import from "pages/files/import.vue";
|
||||
import Index from "pages/files/index.vue";
|
||||
import Logs from "pages/files/logs.vue";
|
||||
|
||||
function initTabs(flag, tabs) {
|
||||
let i = 0;
|
||||
@@ -58,21 +58,21 @@ export default {
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
'name': 'library-index',
|
||||
'name': 'index',
|
||||
'component': Index,
|
||||
'label': this.$gettext('Index'),
|
||||
'class': '',
|
||||
'path': '/library',
|
||||
'path': '/index',
|
||||
'icon': 'camera_roll',
|
||||
'readonly': true,
|
||||
'demo': true,
|
||||
},
|
||||
{
|
||||
'name': 'library-import',
|
||||
'name': 'import',
|
||||
'component': Import,
|
||||
'label': this.$gettext('Import'),
|
||||
'class': '',
|
||||
'path': '/library/import',
|
||||
'path': '/import',
|
||||
'icon': 'create_new_folder',
|
||||
'readonly': false,
|
||||
'demo': true,
|
||||
@@ -81,12 +81,12 @@ export default {
|
||||
|
||||
if(this.$config.feature('logs')) {
|
||||
tabs.push({
|
||||
'name': 'library-logs',
|
||||
'name': 'logs',
|
||||
'component': Logs,
|
||||
'label': this.$gettext('Logs'),
|
||||
'class': '',
|
||||
'path': '/library/logs',
|
||||
'icon': 'notes',
|
||||
'path': '/logs',
|
||||
'icon': 'grading',
|
||||
'readonly': true,
|
||||
'demo': true,
|
||||
});
|
@@ -3,7 +3,7 @@
|
||||
<v-form ref="form" class="p-files-search" lazy-validation dense @submit.prevent="updateQuery">
|
||||
<v-toolbar flat color="secondary" :dense="$vuetify.breakpoint.smAndDown">
|
||||
<v-toolbar-title>
|
||||
<router-link to="/library/files">
|
||||
<router-link to="/index/files">
|
||||
<translate key="Originals">Originals</translate>
|
||||
</router-link>
|
||||
|
||||
@@ -189,7 +189,7 @@ export default {
|
||||
methods: {
|
||||
getBreadcrumbs() {
|
||||
let result = [];
|
||||
let path = "/library/files";
|
||||
let path = "/index/files";
|
||||
|
||||
const crumbs = this.path.split("/");
|
||||
|
||||
@@ -209,7 +209,7 @@ export default {
|
||||
// Open Edit Dialog
|
||||
Event.publish("dialog.edit", {selection: [model.PhotoUID], album: null, index: 0});
|
||||
} else {
|
||||
this.$router.push({path: '/library/files/' + model.Path});
|
||||
this.$router.push({path: '/index/files/' + model.Path});
|
||||
}
|
||||
},
|
||||
downloadFile(index) {
|
@@ -29,9 +29,9 @@
|
||||
|
||||
<script>
|
||||
import General from "pages/settings/general.vue";
|
||||
import Library from "pages/settings/library.vue";
|
||||
import Files from "pages/settings/files.vue";
|
||||
import Advanced from "pages/settings/advanced.vue";
|
||||
import Sync from "pages/settings/sync.vue";
|
||||
import Services from "pages/settings/services.vue";
|
||||
import Account from "pages/settings/account.vue";
|
||||
import {config} from "app/session";
|
||||
|
||||
@@ -55,8 +55,8 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const isDemo = this.$config.get("demo");
|
||||
const isPublic = this.$config.get("public");
|
||||
const isDemo = this.$config.isDemo();
|
||||
const isPublic = this.$config.isPublic();
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
@@ -72,16 +72,16 @@ export default {
|
||||
'show': config.feature('settings'),
|
||||
},
|
||||
{
|
||||
'name': 'settings-library',
|
||||
'component': Library,
|
||||
'name': 'settings-files',
|
||||
'component': Files,
|
||||
'label': this.$gettext('Library'),
|
||||
'class': '',
|
||||
'path': '/settings/library',
|
||||
'path': '/settings/files',
|
||||
'icon': 'camera_roll',
|
||||
'public': true,
|
||||
'admin': true,
|
||||
'demo': true,
|
||||
'show': config.feature('advanced'),
|
||||
'show': config.allow("config", "manage"),
|
||||
},
|
||||
{
|
||||
'name': 'settings-advanced',
|
||||
@@ -93,19 +93,19 @@ export default {
|
||||
'public': false,
|
||||
'admin': true,
|
||||
'demo': true,
|
||||
'show': config.feature('advanced'),
|
||||
'show': config.allow("config", "manage"),
|
||||
},
|
||||
{
|
||||
'name': 'settings-sync',
|
||||
'component': Sync,
|
||||
'label': this.$gettext('Sync'),
|
||||
'name': 'settings-services',
|
||||
'component': Services,
|
||||
'label': this.$gettext('Services'),
|
||||
'class': '',
|
||||
'path': '/settings/sync',
|
||||
'path': '/settings/services',
|
||||
'icon': 'sync_alt',
|
||||
'public': false,
|
||||
'admin': true,
|
||||
'demo': true,
|
||||
'show': config.feature('sync'),
|
||||
'show': config.feature('services') && config.allow("services", "manage"),
|
||||
},
|
||||
{
|
||||
'name': 'settings-account',
|
||||
@@ -113,7 +113,7 @@ export default {
|
||||
'label': this.$gettext('Account'),
|
||||
'class': '',
|
||||
'path': '/settings/account',
|
||||
'icon': 'person',
|
||||
'icon': 'admin_panel_settings',
|
||||
'public': false,
|
||||
'admin': true,
|
||||
'demo': true,
|
||||
|
@@ -57,7 +57,7 @@
|
||||
|
||||
<v-flex xs12 class="pa-2">
|
||||
<p class="caption pa-0">
|
||||
<translate>Note: Updating the password will not revoke access from already authenticated users.</translate>
|
||||
<translate>Note: Updating your password will invalidate other browser sessions, so you will have to log in again.</translate>
|
||||
</p>
|
||||
</v-flex>
|
||||
|
||||
|
@@ -46,141 +46,6 @@
|
||||
<v-card v-if="isDemo || isAdmin" flat tile class="mt-0 px-1 application">
|
||||
<v-card-actions>
|
||||
<v-layout wrap align-top>
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.upload"
|
||||
:disabled="busy || config.readonly || isDemo"
|
||||
class="ma-0 pa-0 input-upload"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Upload')"
|
||||
:hint="$gettext('Add files to your library via Web Upload.')"
|
||||
prepend-icon="cloud_upload"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.download"
|
||||
:disabled="busy || isDemo"
|
||||
class="ma-0 pa-0 input-download"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Download')"
|
||||
:hint="$gettext('Download single files and zip archives.')"
|
||||
prepend-icon="get_app"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.edit"
|
||||
:disabled="busy || isDemo"
|
||||
class="ma-0 pa-0 input-edit"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Edit')"
|
||||
:hint="$gettext('Change photo titles, locations, and other metadata.')"
|
||||
prepend-icon="edit"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.delete"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-delete"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Delete')"
|
||||
:hint="$gettext('Permanently remove files to free up storage.')"
|
||||
prepend-icon="delete"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.import"
|
||||
:disabled="busy || config.readonly || isDemo"
|
||||
class="ma-0 pa-0 input-import"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Import')"
|
||||
:hint="$gettext('Imported files will be sorted by date and given a unique name.')"
|
||||
prepend-icon="create_new_folder"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.share"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-share"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Share')"
|
||||
:hint="$gettext('Upload to WebDAV and share links with friends.')"
|
||||
prepend-icon="share"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.private"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-private"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Private')"
|
||||
:hint="$gettext('Exclude content marked as private from search results, shared albums, labels, and places.')"
|
||||
prepend-icon="lock"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.archive"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-archive"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Archive')"
|
||||
:hint="$gettext('Hide photos that have been moved to archive.')"
|
||||
prepend-icon="archive"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.files"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-files"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Originals')"
|
||||
:hint="$gettext('Browse indexed files and folders in Library.')"
|
||||
prepend-icon="snippet_folder"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.people"
|
||||
@@ -226,6 +91,141 @@
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.private"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-private"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Private')"
|
||||
:hint="$gettext('Exclude content marked as private from search results, shared albums, labels, and places.')"
|
||||
prepend-icon="lock"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.upload"
|
||||
:disabled="busy || config.readonly || isDemo"
|
||||
class="ma-0 pa-0 input-upload"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Upload')"
|
||||
:hint="$gettext('Add files to your library via Web Upload.')"
|
||||
prepend-icon="cloud_upload"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.download"
|
||||
:disabled="busy || isDemo"
|
||||
class="ma-0 pa-0 input-download"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Download')"
|
||||
:hint="$gettext('Download single files and zip archives.')"
|
||||
prepend-icon="get_app"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.import"
|
||||
:disabled="busy || config.readonly || isDemo"
|
||||
class="ma-0 pa-0 input-import"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Import')"
|
||||
:hint="$gettext('Imported files will be sorted by date and given a unique name.')"
|
||||
prepend-icon="create_new_folder"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.share"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-share"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Share')"
|
||||
:hint="$gettext('Upload to WebDAV and share links with friends.')"
|
||||
prepend-icon="share"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.edit"
|
||||
:disabled="busy || isDemo"
|
||||
class="ma-0 pa-0 input-edit"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Edit')"
|
||||
:hint="$gettext('Change photo titles, locations, and other metadata.')"
|
||||
prepend-icon="edit"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.archive"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-archive"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Archive')"
|
||||
:hint="$gettext('Hide photos that have been moved to archive.')"
|
||||
prepend-icon="archive"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.delete"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-delete"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Delete')"
|
||||
:hint="$gettext('Permanently remove files to free up storage.')"
|
||||
prepend-icon="delete"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.services"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-services"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Services')"
|
||||
:hint="$gettext('Share your pictures with other apps and services.')"
|
||||
prepend-icon="sync_alt"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.library"
|
||||
@@ -233,7 +233,7 @@
|
||||
class="ma-0 pa-0 input-library"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Library')"
|
||||
:hint="$gettext('Show Library in navigation menu.')"
|
||||
:hint="$gettext('Index and import files through the user interface.')"
|
||||
prepend-icon="camera_roll"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
@@ -241,6 +241,21 @@
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.files"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-files"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Originals')"
|
||||
:hint="$gettext('Browse indexed files and folders in Library.')"
|
||||
prepend-icon="snippet_folder"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.logs"
|
||||
@@ -249,7 +264,22 @@
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Logs')"
|
||||
:hint="$gettext('Show server logs in Library.')"
|
||||
prepend-icon="notes"
|
||||
prepend-icon="grading"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.account"
|
||||
:disabled="busy || isDemo"
|
||||
class="ma-0 pa-0 input-places"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Account')"
|
||||
:hint="$gettext('Change personal profile and security settings.')"
|
||||
prepend-icon="admin_panel_settings"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
|
@@ -91,7 +91,7 @@
|
||||
|
||||
<script>
|
||||
import Settings from "model/settings";
|
||||
import Account from "model/account";
|
||||
import Service from "model/service";
|
||||
import {DateTime} from "luxon";
|
||||
|
||||
export default {
|
||||
@@ -150,7 +150,7 @@ export default {
|
||||
return DateTime.fromISO(time).toLocaleString(DateTime.DATE_FULL);
|
||||
},
|
||||
load() {
|
||||
Account.search({count: 2000}).then(r => this.results = r.models);
|
||||
Service.search({count: 2000}).then(r => this.results = r.models);
|
||||
},
|
||||
remove(model) {
|
||||
this.model = model.clone();
|
@@ -132,7 +132,7 @@ describe("common/session", () => {
|
||||
};
|
||||
session.setData(values2);
|
||||
const result2 = session.getDisplayName();
|
||||
assert.equal(result2, "Bar");
|
||||
assert.equal(result2, "Admin");
|
||||
session.deleteData();
|
||||
});
|
||||
|
||||
|
@@ -140,8 +140,8 @@ Mock.onPost("api/v1/session").reply(200, { id: "123", data: { token: "123token"
|
||||
Mock.onGet("api/v1/settings").reply(200, { download: true, language: "de" }, mockHeaders);
|
||||
Mock.onPost("api/v1/settings").reply(200, { download: true, language: "en" }, mockHeaders);
|
||||
|
||||
Mock.onGet("api/v1/accounts/123/folders").reply(200, { foo: "folders" }, mockHeaders);
|
||||
Mock.onPost("api/v1/accounts/123/share").reply(200, { foo: "share" }, mockHeaders);
|
||||
Mock.onGet("api/v1/services/123/folders").reply(200, { foo: "folders" }, mockHeaders);
|
||||
Mock.onPost("api/v1/services/123/upload").reply(200, { foo: "upload" }, mockHeaders);
|
||||
|
||||
Mock.onGet("api/v1/folders/2011/10-Halloween", {
|
||||
params: { recursive: true, uncached: true },
|
||||
|
@@ -1,39 +1,39 @@
|
||||
import "../fixtures";
|
||||
|
||||
import Account from "model/account";
|
||||
import Service from "model/service";
|
||||
import Photo from "model/photo";
|
||||
|
||||
let chai = require("chai/chai");
|
||||
let assert = chai.assert;
|
||||
|
||||
describe("model/account", () => {
|
||||
it("should get account defaults", () => {
|
||||
describe("model/service", () => {
|
||||
it("should get service defaults", () => {
|
||||
const values = { ID: 5 };
|
||||
const account = new Account(values);
|
||||
const result = account.getDefaults();
|
||||
const service = new Service(values);
|
||||
const result = service.getDefaults();
|
||||
assert.equal(result.ID, 0);
|
||||
assert.equal(result.AccShare, true);
|
||||
assert.equal(result.AccName, "");
|
||||
});
|
||||
|
||||
it("should get account entity name", () => {
|
||||
it("should get service entity name", () => {
|
||||
const values = { ID: 5, AccName: "Test Name" };
|
||||
const account = new Account(values);
|
||||
const result = account.getEntityName();
|
||||
const service = new Service(values);
|
||||
const result = service.getEntityName();
|
||||
assert.equal(result, "Test Name");
|
||||
});
|
||||
|
||||
it("should get account id", () => {
|
||||
it("should get service id", () => {
|
||||
const values = { ID: 5, AccName: "Test Name" };
|
||||
const account = new Account(values);
|
||||
const result = account.getId();
|
||||
const service = new Service(values);
|
||||
const result = service.getId();
|
||||
assert.equal(result, 5);
|
||||
});
|
||||
|
||||
it("should get folders", (done) => {
|
||||
const values = { ID: 123, AccName: "Test Name" };
|
||||
const account = new Account(values);
|
||||
account
|
||||
const service = new Service(values);
|
||||
service
|
||||
.Folders()
|
||||
.then((response) => {
|
||||
assert.equal(response.foo, "folders");
|
||||
@@ -46,16 +46,16 @@ describe("model/account", () => {
|
||||
|
||||
it("should get share photos", (done) => {
|
||||
const values = { ID: 123, AccName: "Test Name" };
|
||||
const account = new Account(values);
|
||||
const service = new Service(values);
|
||||
const values1 = { ID: 5, Title: "Crazy Cat", UID: 789 };
|
||||
const photo = new Photo(values1);
|
||||
const values2 = { ID: 6, Title: "Crazy Cat 2", UID: 783 };
|
||||
const photo2 = new Photo(values2);
|
||||
const Photos = [photo, photo2];
|
||||
account
|
||||
.Share(Photos, "destination")
|
||||
service
|
||||
.Upload(Photos, "destination")
|
||||
.then((response) => {
|
||||
assert.equal(response.foo, "share");
|
||||
assert.equal(response.foo, "upload");
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -64,12 +64,12 @@ describe("model/account", () => {
|
||||
});
|
||||
|
||||
it("should get collection resource", () => {
|
||||
const result = Account.getCollectionResource();
|
||||
assert.equal(result, "accounts");
|
||||
const result = Service.getCollectionResource();
|
||||
assert.equal(result, "services");
|
||||
});
|
||||
|
||||
it("should get model name", () => {
|
||||
const result = Account.getModelName();
|
||||
const result = Service.getModelName();
|
||||
assert.equal(result, "Account");
|
||||
});
|
||||
});
|
@@ -58,7 +58,7 @@ var Resources = ACL{
|
||||
ResourceShares: Roles{
|
||||
RoleAdmin: GrantFullAccess,
|
||||
},
|
||||
ResourceAccounts: Roles{
|
||||
ResourceServices: Roles{
|
||||
RoleAdmin: GrantFullAccess,
|
||||
},
|
||||
ResourceUsers: Roles{
|
||||
|
@@ -18,7 +18,7 @@ const (
|
||||
ResourceSettings Resource = "settings"
|
||||
ResourcePassword Resource = "password"
|
||||
ResourceUsers Resource = "users"
|
||||
ResourceAccounts Resource = "accounts"
|
||||
ResourceServices Resource = "services"
|
||||
ResourceFiles Resource = "files"
|
||||
ResourceFolders Resource = "folders"
|
||||
ResourceShares Resource = "shares"
|
||||
|
@@ -11,10 +11,10 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ var albumMutex = sync.Mutex{}
|
||||
|
||||
// SaveAlbumAsYaml saves album data as YAML file.
|
||||
func SaveAlbumAsYaml(a entity.Album) {
|
||||
c := service.Config()
|
||||
c := get.Config()
|
||||
|
||||
// Write YAML sidecar file (optional).
|
||||
if !c.BackupYaml() {
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ func SearchAlbums(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
// Ignore private flag if feature is disabled.
|
||||
if !conf.Settings().Features.Private {
|
||||
|
@@ -6,12 +6,12 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
)
|
||||
|
||||
// UpdateClientConfig publishes updated client configuration values over the websocket connections.
|
||||
func UpdateClientConfig() {
|
||||
event.Publish("config.updated", event.Data{"config": service.Config().ClientUser(false)})
|
||||
event.Publish("config.updated", event.Data{"config": get.Config().ClientUser(false)})
|
||||
}
|
||||
|
||||
// GetClientConfig returns the client configuration values as JSON.
|
||||
@@ -20,7 +20,7 @@ func UpdateClientConfig() {
|
||||
func GetClientConfig(router *gin.RouterGroup) {
|
||||
router.GET("/config", func(c *gin.Context) {
|
||||
s := Session(SessionID(c))
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if s == nil {
|
||||
c.JSON(http.StatusOK, conf.ClientPublic())
|
||||
|
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -21,7 +21,7 @@ func TestMain(m *testing.M) {
|
||||
event.AuditLog = log
|
||||
|
||||
c := config.TestConfig()
|
||||
service.SetConfig(c)
|
||||
get.SetConfig(c)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
@@ -37,7 +37,7 @@ func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, conf *config.Config
|
||||
app = gin.New()
|
||||
router = app.Group("/api/v1")
|
||||
|
||||
return app, router, service.Config()
|
||||
return app, router, get.Config()
|
||||
}
|
||||
|
||||
// Executes an API request with an empty request body.
|
||||
|
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
@@ -57,7 +57,7 @@ func WebSocket(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf == nil {
|
||||
return
|
||||
|
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// PUT /api/v1/users/:uid/password
|
||||
func ChangePassword(router *gin.RouterGroup) {
|
||||
router.PUT("/users/:uid/password", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
// You cannot change any passwords without authentication and settings enabled.
|
||||
if conf.Public() || conf.DisableSettings() {
|
||||
|
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/session"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
@@ -23,14 +23,14 @@ func SessionID(c *gin.Context) (sessId string) {
|
||||
// Session finds the client session for the given ID or returns nil otherwise.
|
||||
func Session(id string) *entity.Session {
|
||||
// Return default session when public mode is enabled.
|
||||
if service.Config().Public() {
|
||||
return service.Session().Public()
|
||||
if get.Config().Public() {
|
||||
return get.Session().Public()
|
||||
} else if id == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find session or otherwise return nil.
|
||||
s, err := service.Session().Get(id)
|
||||
s, err := get.Session().Get(id)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@@ -8,9 +8,9 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// CreateSession creates a new client session and returns it as JSON if authentication was successful.
|
||||
@@ -26,11 +26,11 @@ func CreateSession(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
// Skip authentication if app is running in public mode.
|
||||
if conf.Public() {
|
||||
sess := service.Session().Public()
|
||||
sess := get.Session().Public()
|
||||
data := gin.H{
|
||||
"status": "ok",
|
||||
"id": sess.ID,
|
||||
@@ -57,7 +57,7 @@ func CreateSession(router *gin.RouterGroup) {
|
||||
sess = s
|
||||
} else {
|
||||
// Create new session.
|
||||
sess = service.Session().New(c)
|
||||
sess = get.Session().New(c)
|
||||
isNew = true
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func CreateSession(router *gin.RouterGroup) {
|
||||
if err := sess.LogIn(f, c); err != nil {
|
||||
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||
return
|
||||
} else if sess, err = service.Session().Save(sess); err != nil {
|
||||
} else if sess, err = get.Session().Save(sess); err != nil {
|
||||
event.AuditErr([]string{ClientIP(c), "%s"}, err)
|
||||
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||
return
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -20,12 +20,12 @@ func DeleteSession(router *gin.RouterGroup) {
|
||||
if id == "" {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
} else if service.Config().Public() {
|
||||
} else if get.Config().Public() {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "authentication disabled", "id": id})
|
||||
return
|
||||
}
|
||||
|
||||
if err := service.Session().Delete(id); err != nil {
|
||||
if err := get.Session().Delete(id); err != nil {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s"}, err)
|
||||
} else {
|
||||
event.AuditDebug([]string{ClientIP(c), "session deleted"})
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ func GetSession(router *gin.RouterGroup) {
|
||||
"id": sess.ID,
|
||||
"user": sess.User(),
|
||||
"data": sess.Data(),
|
||||
"config": service.Config().ClientSession(sess),
|
||||
"config": get.Config().ClientSession(sess),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, data)
|
||||
|
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
// GET /s/:token/...
|
||||
func Shares(router *gin.RouterGroup) {
|
||||
router.GET("/:token", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
token := clean.Token(c.Param("token"))
|
||||
links := entity.FindValidLinks(token, "")
|
||||
@@ -37,7 +37,7 @@ func Shares(router *gin.RouterGroup) {
|
||||
})
|
||||
|
||||
router.GET("/:token/:shared", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
token := clean.Token(c.Param("token"))
|
||||
shared := clean.Token(c.Param("shared"))
|
||||
|
@@ -13,10 +13,10 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
|
||||
|
||||
log.Infof("photos: archiving %s", clean.Log(f.String()))
|
||||
|
||||
if service.Config().BackupYaml() {
|
||||
if get.Config().BackupYaml() {
|
||||
// Fetch selection from index.
|
||||
photos, err := query.SelectedPhotos(f)
|
||||
|
||||
@@ -108,7 +108,7 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
|
||||
|
||||
log.Infof("photos: restoring %s", clean.Log(f.String()))
|
||||
|
||||
if service.Config().BackupYaml() {
|
||||
if get.Config().BackupYaml() {
|
||||
// Fetch selection from index.
|
||||
photos, err := query.SelectedPhotos(f)
|
||||
|
||||
@@ -346,7 +346,7 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.ReadOnly() || !conf.Settings().Features.Delete {
|
||||
AbortFeatureDisabled(c)
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
)
|
||||
|
||||
@@ -40,7 +40,7 @@ func CacheKey(ns, uid, name string) string {
|
||||
|
||||
// RemoveFromFolderCache removes an item from the folder cache e.g. after indexing.
|
||||
func RemoveFromFolderCache(rootName string) {
|
||||
cache := service.FolderCache()
|
||||
cache := get.FolderCache()
|
||||
|
||||
cacheKey := fmt.Sprintf("folder:%s:%t:%t", rootName, true, false)
|
||||
|
||||
@@ -55,7 +55,7 @@ func RemoveFromFolderCache(rootName string) {
|
||||
|
||||
// RemoveFromAlbumCoverCache removes covers by album UID e.g. after adding or removing photos.
|
||||
func RemoveFromAlbumCoverCache(uid string) {
|
||||
cache := service.CoverCache()
|
||||
cache := get.CoverCache()
|
||||
|
||||
for thumbName := range thumb.Sizes {
|
||||
cacheKey := CacheKey(albumCover, uid, string(thumbName))
|
||||
@@ -72,7 +72,7 @@ func RemoveFromAlbumCoverCache(uid string) {
|
||||
|
||||
// FlushCoverCache clears the complete cover cache.
|
||||
func FlushCoverCache() {
|
||||
service.CoverCache().Flush()
|
||||
get.CoverCache().Flush()
|
||||
|
||||
if err := query.UpdateCovers(); err != nil {
|
||||
log.Error(err)
|
||||
|
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
func GetConfigOptions(router *gin.RouterGroup) {
|
||||
router.GET("/config/options", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceConfig, acl.AccessAll)
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
// Abort if permission was not granted.
|
||||
if s.Invalid() || conf.Public() || conf.DisableSettings() {
|
||||
@@ -40,7 +40,7 @@ func GetConfigOptions(router *gin.RouterGroup) {
|
||||
func SaveConfigOptions(router *gin.RouterGroup) {
|
||||
router.POST("/config/options", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceConfig, acl.ActionManage)
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if s.Invalid() || conf.Public() || conf.DisableSettings() {
|
||||
AbortForbidden(c)
|
||||
|
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/customize"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// GetSettings returns the user app settings as JSON.
|
||||
@@ -24,7 +24,7 @@ func GetSettings(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
settings := service.Config().SessionSettings(s)
|
||||
settings := get.Config().SessionSettings(s)
|
||||
|
||||
if settings == nil {
|
||||
Abort(c, http.StatusNotFound, i18n.ErrNotFound)
|
||||
@@ -47,7 +47,7 @@ func SaveSettings(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.DisableSettings() {
|
||||
AbortForbidden(c)
|
||||
@@ -101,6 +101,6 @@ func SaveSettings(router *gin.RouterGroup) {
|
||||
|
||||
event.InfoMsg(i18n.MsgSettingsSaved)
|
||||
|
||||
c.JSON(http.StatusOK, service.Config().SessionSettings(s))
|
||||
c.JSON(http.StatusOK, get.Config().SessionSettings(s))
|
||||
})
|
||||
}
|
||||
|
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ func Connect(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.Public() {
|
||||
Abort(c, http.StatusForbidden, i18n.ErrPublic)
|
||||
|
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
@@ -38,7 +38,7 @@ func AlbumCover(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
thumbName := thumb.Name(clean.Token(c.Param("size")))
|
||||
uid := clean.UID(c.Param("uid"))
|
||||
|
||||
@@ -50,7 +50,7 @@ func AlbumCover(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
cache := service.CoverCache()
|
||||
cache := get.CoverCache()
|
||||
cacheKey := CacheKey(albumCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
@@ -151,7 +151,7 @@ func LabelCover(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
thumbName := thumb.Name(clean.Token(c.Param("size")))
|
||||
uid := clean.UID(c.Param("uid"))
|
||||
|
||||
@@ -163,7 +163,7 @@ func LabelCover(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
cache := service.CoverCache()
|
||||
cache := get.CoverCache()
|
||||
cacheKey := CacheKey(labelCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
|
@@ -8,11 +8,11 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
@@ -28,7 +28,7 @@ func DownloadAlbum(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if !conf.Settings().Features.Download {
|
||||
AbortFeatureDisabled(c)
|
||||
|
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
@@ -29,7 +29,7 @@ func DownloadName(c *gin.Context) customize.DownloadName {
|
||||
case "original":
|
||||
return customize.DownloadNameOriginal
|
||||
default:
|
||||
return service.Config().Settings().Download.Name
|
||||
return get.Config().Settings().Download.Name
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,9 +6,9 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ func GetErrors(router *gin.RouterGroup) {
|
||||
// DELETE /api/v1/errors
|
||||
func DeleteErrors(router *gin.RouterGroup) {
|
||||
router.DELETE("/errors", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
// Disabled in public mode so that attackers cannot cover their tracks.
|
||||
if conf.Public() {
|
||||
|
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// SendFeedback sends a feedback message.
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// POST /api/v1/feedback
|
||||
func SendFeedback(router *gin.RouterGroup) {
|
||||
router.POST("/feedback", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.Public() {
|
||||
Abort(c, http.StatusForbidden, i18n.ErrPublic)
|
||||
|
@@ -9,10 +9,10 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// DeleteFile removes a file from storage.
|
||||
@@ -30,7 +30,7 @@ func DeleteFile(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.ReadOnly() || !conf.Settings().Features.Edit {
|
||||
Abort(c, http.StatusForbidden, i18n.ErrReadOnly)
|
||||
|
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
@@ -36,7 +36,7 @@ func FolderCover(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
uid := c.Param("uid")
|
||||
thumbName := thumb.Name(clean.Token(c.Param("size")))
|
||||
download := c.Query("download") != ""
|
||||
@@ -59,7 +59,7 @@ func FolderCover(router *gin.RouterGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
cache := service.CoverCache()
|
||||
cache := get.CoverCache()
|
||||
cacheKey := CacheKey(folderCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
|
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ type FoldersResponse struct {
|
||||
//
|
||||
// GET /api/v1/folders/originals
|
||||
func SearchFoldersOriginals(router *gin.RouterGroup) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
SearchFolders(router, "originals", entity.RootOriginals, conf.OriginalsPath())
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func SearchFoldersOriginals(router *gin.RouterGroup) {
|
||||
//
|
||||
// GET /api/v1/folders/import
|
||||
func SearchFoldersImport(router *gin.RouterGroup) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
SearchFolders(router, "import", entity.RootImport, conf.ImportPath())
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
|
||||
return
|
||||
}
|
||||
|
||||
cache := service.FolderCache()
|
||||
cache := get.FolderCache()
|
||||
recursive := f.Recursive
|
||||
listFiles := f.Files
|
||||
uncached := listFiles || f.Uncached
|
||||
|
@@ -15,10 +15,10 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ func StartImport(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.ReadOnly() || !conf.Settings().Features.Import {
|
||||
AbortFeatureDisabled(c)
|
||||
@@ -76,7 +76,7 @@ func StartImport(router *gin.RouterGroup) {
|
||||
|
||||
importPath = path.Join(importPath, srcFolder)
|
||||
|
||||
imp := service.Import()
|
||||
imp := get.Import()
|
||||
|
||||
RemoveFromFolderCache(entity.RootImport)
|
||||
|
||||
@@ -125,7 +125,7 @@ func StartImport(router *gin.RouterGroup) {
|
||||
log.Infof("import: no new files found to import", clean.Log(importPath))
|
||||
} else {
|
||||
log.Infof("import: imported %s", english.Plural(n, "file", "files"))
|
||||
if moments := service.Moments(); moments == nil {
|
||||
if moments := get.Moments(); moments == nil {
|
||||
log.Warnf("import: moments service not set - possible bug")
|
||||
} else if err := moments.Start(); err != nil {
|
||||
log.Warnf("moments: %s", err)
|
||||
@@ -168,14 +168,14 @@ func CancelImport(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.ReadOnly() || !conf.Settings().Features.Import {
|
||||
AbortFeatureDisabled(c)
|
||||
return
|
||||
}
|
||||
|
||||
imp := service.Import()
|
||||
imp := get.Import()
|
||||
|
||||
imp.Cancel()
|
||||
|
||||
|
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
@@ -29,7 +29,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
settings := conf.Settings()
|
||||
|
||||
if !settings.Features.Library {
|
||||
@@ -60,7 +60,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Start indexing.
|
||||
ind := service.Index()
|
||||
ind := get.Index()
|
||||
indexed := ind.Start(indOpt)
|
||||
|
||||
RemoveFromFolderCache(entity.RootOriginals)
|
||||
@@ -72,7 +72,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// Start purging.
|
||||
prg := service.Purge()
|
||||
prg := get.Purge()
|
||||
|
||||
if files, photos, err := prg.Start(prgOpt); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||
@@ -85,7 +85,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
"step": "moments",
|
||||
})
|
||||
|
||||
moments := service.Moments()
|
||||
moments := get.Moments()
|
||||
|
||||
if err := moments.Start(); err != nil {
|
||||
log.Warnf("moments: %s", err)
|
||||
@@ -115,14 +115,14 @@ func CancelIndexing(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if !conf.Settings().Features.Library {
|
||||
AbortFeatureDisabled(c)
|
||||
return
|
||||
}
|
||||
|
||||
ind := service.Index()
|
||||
ind := get.Index()
|
||||
|
||||
ind.Cancel()
|
||||
|
||||
|
@@ -12,15 +12,15 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// Checks if background worker runs less than once per hour.
|
||||
func wakeupIntervalTooHigh(c *gin.Context) bool {
|
||||
if conf := service.Config(); conf.Unsafe() {
|
||||
if conf := get.Config(); conf.Unsafe() {
|
||||
return false
|
||||
} else if i := conf.WakeupInterval(); i > time.Hour {
|
||||
Abort(c, http.StatusForbidden, i18n.ErrWakeupInterval, i.String())
|
||||
@@ -41,7 +41,7 @@ func findFileMarker(c *gin.Context) (file *entity.File, marker *entity.Marker, e
|
||||
}
|
||||
|
||||
// Check feature flags.
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
if !conf.Settings().Features.People {
|
||||
AbortFeatureDisabled(c)
|
||||
return nil, nil, fmt.Errorf("feature disabled")
|
||||
@@ -119,7 +119,7 @@ func UpdateMarker(router *gin.RouterGroup) {
|
||||
return
|
||||
} else if changed {
|
||||
if marker.FaceID != "" && marker.SubjUID != "" && marker.SubjSrc == entity.SrcManual {
|
||||
if res, err := service.Faces().Optimize(); err != nil {
|
||||
if res, err := get.Faces().Optimize(); err != nil {
|
||||
log.Errorf("faces: %s (optimize)", err)
|
||||
} else if res.Merged > 0 {
|
||||
log.Infof("faces: merged %s", english.Plural(res.Merged, "cluster", "clusters"))
|
||||
|
@@ -6,8 +6,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ func GetMomentsTime(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
result, err := query.MomentsTime(1, conf.Settings().Features.Private)
|
||||
|
||||
|
@@ -10,10 +10,10 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
fileUid := clean.UID(c.Param("file_uid"))
|
||||
file, err := query.FileByUID(fileUid)
|
||||
|
||||
@@ -170,7 +170,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
ind := service.Index()
|
||||
ind := get.Index()
|
||||
|
||||
// Index unstacked files.
|
||||
if res := ind.FileName(unstackFile.FileName(), photoprism.IndexOptionsSingle()); res.Failed() {
|
||||
|
@@ -10,17 +10,17 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
// SavePhotoAsYaml saves photo data as YAML file.
|
||||
func SavePhotoAsYaml(p entity.Photo) {
|
||||
c := service.Config()
|
||||
c := get.Config()
|
||||
|
||||
// Write YAML sidecar file (optional).
|
||||
if !c.BackupYaml() {
|
||||
|
@@ -10,9 +10,9 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// SearchPhotos searches the pictures index and returns the result as JSON.
|
||||
@@ -36,7 +36,7 @@ func SearchPhotos(router *gin.RouterGroup) {
|
||||
return f, s, err
|
||||
}
|
||||
|
||||
settings := service.Config().Settings()
|
||||
settings := get.Config().Settings()
|
||||
|
||||
// Ignore private flag if feature is disabled.
|
||||
if !settings.Features.Private {
|
||||
@@ -91,7 +91,7 @@ func SearchPhotos(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
result, count, err := search.UserPhotosViewerResults(f, s, conf.ContentUri(), conf.ApiUri(), s.PreviewToken, s.DownloadToken)
|
||||
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ func SearchGeo(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
settings := conf.Settings()
|
||||
|
||||
// Ignore private flag if feature is disabled.
|
||||
|
@@ -6,8 +6,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/react"
|
||||
)
|
||||
@@ -31,7 +31,7 @@ func LikePhoto(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
if service.Config().Experimental() && acl.Resources.Allow(acl.ResourcePhotos, s.User().AclRole(), acl.ActionReact) {
|
||||
if get.Config().Experimental() && acl.Resources.Allow(acl.ResourcePhotos, s.User().AclRole(), acl.ActionReact) {
|
||||
logWarn("react", m.React(s.User(), react.Find("love")))
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func DislikePhoto(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
if service.Config().Experimental() && acl.Resources.Allow(acl.ResourcePhotos, s.User().AclRole(), acl.ActionReact) {
|
||||
if get.Config().Experimental() && acl.Resources.Allow(acl.ResourcePhotos, s.User().AclRole(), acl.ActionReact) {
|
||||
logWarn("react", m.UnReact(s.User()))
|
||||
}
|
||||
|
||||
|
@@ -3,8 +3,6 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -13,9 +11,9 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/workers"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
@@ -23,25 +21,21 @@ import (
|
||||
|
||||
// Namespaces for caching and logs.
|
||||
const (
|
||||
accountFolder = "account-folder"
|
||||
serviceFolder = "service-folder"
|
||||
)
|
||||
|
||||
// GetAccount returns an account as JSON.
|
||||
// GetService returns an account as JSON.
|
||||
//
|
||||
// GET /api/v1/accounts/:id
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// id: string Account ID as returned by the API
|
||||
func GetAccount(router *gin.RouterGroup) {
|
||||
router.GET("/accounts/:id", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAccounts, acl.ActionView)
|
||||
// GET /api/v1/services/:id
|
||||
func GetService(router *gin.RouterGroup) {
|
||||
router.GET("/services/:id", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceServices, acl.ActionView)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.Demo() || conf.DisableSettings() {
|
||||
AbortForbidden(c)
|
||||
@@ -58,22 +52,18 @@ func GetAccount(router *gin.RouterGroup) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetAccountFolders returns folders that belong to an account as JSON.
|
||||
// GetServiceFolders returns folders that belong to an account as JSON.
|
||||
//
|
||||
// GET /api/v1/accounts/:id/folders
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// id: string Account ID as returned by the API
|
||||
func GetAccountFolders(router *gin.RouterGroup) {
|
||||
router.GET("/accounts/:id/folders", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAccounts, acl.ActionView)
|
||||
// GET /api/v1/services/:id/folders
|
||||
func GetServiceFolders(router *gin.RouterGroup) {
|
||||
router.GET("/services/:id/folders", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceServices, acl.ActionView)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.Demo() || conf.DisableSettings() {
|
||||
AbortForbidden(c)
|
||||
@@ -82,8 +72,8 @@ func GetAccountFolders(router *gin.RouterGroup) {
|
||||
|
||||
start := time.Now()
|
||||
id := clean.IdUint(c.Param("id"))
|
||||
cache := service.FolderCache()
|
||||
cacheKey := fmt.Sprintf("%s:%d", accountFolder, id)
|
||||
cache := get.FolderCache()
|
||||
cacheKey := fmt.Sprintf("%s:%d", serviceFolder, id)
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
cached := cacheData.(fs.FileInfos)
|
||||
@@ -104,7 +94,7 @@ func GetAccountFolders(router *gin.RouterGroup) {
|
||||
list, err := m.Directories()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("%s: %s", accountFolder, err.Error())
|
||||
log.Errorf("%s: %s", serviceFolder, err.Error())
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrConnectionFailed)
|
||||
return
|
||||
}
|
||||
@@ -116,101 +106,38 @@ func GetAccountFolders(router *gin.RouterGroup) {
|
||||
})
|
||||
}
|
||||
|
||||
// ShareWithAccount uploads files to the selected account.
|
||||
// AddService creates a new remote account configuration.
|
||||
//
|
||||
// GET /api/v1/accounts/:id/share
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// id: string Account ID as returned by the API
|
||||
func ShareWithAccount(router *gin.RouterGroup) {
|
||||
router.POST("/accounts/:id/share", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAccounts, acl.ActionUpload)
|
||||
// POST /api/v1/services
|
||||
func AddService(router *gin.RouterGroup) {
|
||||
router.POST("/services", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceServices, acl.ActionCreate)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
id := clean.IdUint(c.Param("id"))
|
||||
|
||||
m, err := query.AccountByID(id)
|
||||
|
||||
if err != nil {
|
||||
Abort(c, http.StatusNotFound, i18n.ErrAccountNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var f form.AccountShare
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
folder := f.Folder
|
||||
|
||||
// Find files to share.
|
||||
selection := query.ShareSelection(m.ShareOriginals())
|
||||
files, err := query.SelectedFiles(f.Selection, selection)
|
||||
|
||||
if err != nil {
|
||||
AbortEntityNotFound(c)
|
||||
return
|
||||
}
|
||||
|
||||
var aliases = make(map[string]int)
|
||||
|
||||
for _, file := range files {
|
||||
alias := path.Join(folder, file.ShareBase(0))
|
||||
key := strings.ToLower(alias)
|
||||
|
||||
if seq := aliases[key]; seq > 0 {
|
||||
alias = file.ShareBase(seq)
|
||||
}
|
||||
|
||||
aliases[key] += 1
|
||||
|
||||
entity.FirstOrCreateFileShare(entity.NewFileShare(file.ID, m.ID, alias))
|
||||
}
|
||||
|
||||
workers.RunShare(service.Config())
|
||||
|
||||
c.JSON(http.StatusOK, files)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateAccount creates a new remote account configuration.
|
||||
//
|
||||
// POST /api/v1/accounts
|
||||
func CreateAccount(router *gin.RouterGroup) {
|
||||
router.POST("/accounts", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAccounts, acl.ActionCreate)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.Demo() || conf.DisableSettings() {
|
||||
AbortForbidden(c)
|
||||
return
|
||||
}
|
||||
|
||||
var f form.Account
|
||||
var f form.Service
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.ServiceDiscovery(); err != nil {
|
||||
if err := f.Discovery(); err != nil {
|
||||
log.Error(err)
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrConnectionFailed)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := entity.CreateAccount(f)
|
||||
m, err := entity.AddService(f)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -224,22 +151,18 @@ func CreateAccount(router *gin.RouterGroup) {
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAccount updates a remote account configuration.
|
||||
// UpdateService updates a remote account configuration.
|
||||
//
|
||||
// PUT /api/v1/accounts/:id
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// id: string Account ID as returned by the API
|
||||
func UpdateAccount(router *gin.RouterGroup) {
|
||||
router.PUT("/accounts/:id", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAccounts, acl.ActionUpdate)
|
||||
// PUT /api/v1/services/:id
|
||||
func UpdateService(router *gin.RouterGroup) {
|
||||
router.PUT("/services/:id", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceServices, acl.ActionUpdate)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.Demo() || conf.DisableSettings() {
|
||||
AbortForbidden(c)
|
||||
@@ -256,7 +179,7 @@ func UpdateAccount(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
// 1) Init form with model values
|
||||
f, err := form.NewAccount(m)
|
||||
f, err := form.NewService(m)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -288,29 +211,25 @@ func UpdateAccount(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
if m.AccSync {
|
||||
workers.RunSync(service.Config())
|
||||
workers.RunSync(get.Config())
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAccount removes a remote account configuration.
|
||||
// DeleteService removes a remote account configuration.
|
||||
//
|
||||
// DELETE /api/v1/accounts/:id
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// id: string Account ID as returned by the API
|
||||
func DeleteAccount(router *gin.RouterGroup) {
|
||||
router.DELETE("/accounts/:id", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAccounts, acl.ActionDelete)
|
||||
// DELETE /api/v1/services/:id
|
||||
func DeleteService(router *gin.RouterGroup) {
|
||||
router.DELETE("/services/:id", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceServices, acl.ActionDelete)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.Demo() || conf.DisableSettings() {
|
||||
AbortForbidden(c)
|
@@ -9,29 +9,29 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// SearchAccounts finds accounts and returns them as JSON.
|
||||
// SearchServices finds account settings and returns them as JSON.
|
||||
//
|
||||
// GET /api/v1/accounts
|
||||
func SearchAccounts(router *gin.RouterGroup) {
|
||||
router.GET("/accounts", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAccounts, acl.ActionSearch)
|
||||
// GET /api/v1/services
|
||||
func SearchServices(router *gin.RouterGroup) {
|
||||
router.GET("/services", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceServices, acl.ActionSearch)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.Demo() || conf.DisableSettings() {
|
||||
c.JSON(http.StatusOK, entity.Accounts{})
|
||||
c.JSON(http.StatusOK, entity.Services{})
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SearchAccounts
|
||||
var f form.SearchServices
|
||||
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
|
@@ -9,12 +9,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSearchAccounts(t *testing.T) {
|
||||
func TestSearchServices(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
SearchAccounts(router)
|
||||
SearchServices(router)
|
||||
sess := AuthenticateAdmin(app, router)
|
||||
r := AuthenticatedRequest(app, "GET", "/api/v1/accounts?count=10", sess)
|
||||
r := AuthenticatedRequest(app, "GET", "/api/v1/services?count=10", sess)
|
||||
val := gjson.Get(r.Body.String(), "#(AccName=\"Test Account\").AccURL")
|
||||
count := gjson.Get(r.Body.String(), "#")
|
||||
assert.LessOrEqual(t, int64(1), count.Int())
|
||||
@@ -23,8 +23,8 @@ func TestSearchAccounts(t *testing.T) {
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
SearchAccounts(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/accounts?xxx=10")
|
||||
SearchServices(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/services?xxx=10")
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
@@ -10,88 +10,69 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
)
|
||||
|
||||
func TestGetAccount(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
func TestGetService(t *testing.T) {
|
||||
t.Run("Ok", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetAccount(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/accounts/1000000")
|
||||
GetService(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/services/1000000")
|
||||
val := gjson.Get(r.Body.String(), "AccName")
|
||||
assert.Equal(t, "Test Account", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("account not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetAccount(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/accounts/999000")
|
||||
GetService(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/services/999000")
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAccountFolders(t *testing.T) {
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
func TestGetServiceFolders(t *testing.T) {
|
||||
t.Run("Ok", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetAccountFolders(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/accounts/1000000/folders")
|
||||
GetServiceFolders(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/services/1000000/folders")
|
||||
count := gjson.Get(r.Body.String(), "#")
|
||||
assert.LessOrEqual(t, int64(2), count.Int())
|
||||
val := gjson.Get(r.Body.String(), "0.abs")
|
||||
assert.Equal(t, "/Photos", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("account not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetAccountFolders(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/accounts/999000/folders")
|
||||
GetServiceFolders(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/services/999000/folders")
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShareWithAccount(t *testing.T) {
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
func TestCreateService(t *testing.T) {
|
||||
t.Run("BadRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
ShareWithAccount(router)
|
||||
r := PerformRequest(app, "POST", "/api/v1/accounts/1000000/share")
|
||||
AddService(router)
|
||||
r := PerformRequest(app, "POST", "/api/v1/services")
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrBadRequest), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("account not found", func(t *testing.T) {
|
||||
t.Run("ConnectFailed", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
ShareWithAccount(router)
|
||||
r := PerformRequest(app, "POST", "/api/v1/accounts/999000/share")
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateAccount(t *testing.T) {
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CreateAccount(router)
|
||||
r := PerformRequest(app, "POST", "/api/v1/accounts")
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrBadRequest), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("could not connect", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CreateAccount(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/accounts", `{"AccName": "CreateTest1", "AccOwner": "Test", "AccUrl": "http://webdav123/", "AccType": "webdav",
|
||||
AddService(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/services", `{"AccName": "CreateTest1", "AccOwner": "Test", "AccUrl": "http://webdav123/", "AccType": "webdav",
|
||||
"AccKey": "123", "AccUser": "testuser", "AccPass": "testpasswd", "AccError": "", "AccShare": false, "AccSync": false, "RetryLimit": 3, "SharePath": "", "ShareSize": "", "ShareExpires": 0,
|
||||
"SyncPath": "", "SyncInterval": 3, "SyncUpload": false, "SyncDownload": false, "SyncFilenames": false, "SyncRaw": false}`)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrConnectionFailed), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Ok", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CreateAccount(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/accounts", `{"AccName": "CreateTest", "AccOwner": "Test", "AccUrl": "http://dummy-webdav/", "AccType": "webdav",
|
||||
AddService(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/services", `{"AccName": "CreateTest", "AccOwner": "Test", "AccUrl": "http://dummy-webdav/", "AccType": "webdav",
|
||||
"AccKey": "123", "AccUser": "admin", "AccPass": "photoprism", "AccError": "", "AccShare": false, "AccSync": false, "RetryLimit": 3, "SharePath": "", "ShareSize": "", "ShareExpires": 0,
|
||||
"SyncPath": "", "SyncInterval": 3, "SyncUpload": false, "SyncDownload": false, "SyncFilenames": false, "SyncRaw": false}`)
|
||||
val := gjson.Get(r.Body.String(), "AccOwner")
|
||||
@@ -100,10 +81,10 @@ func TestCreateAccount(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateAccount(t *testing.T) {
|
||||
func TestUpdateService(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CreateAccount(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/accounts", `{"AccName": "CreateTest3", "AccOwner": "TestUpdate", "AccUrl": "http://dummy-webdav/", "AccType": "webdav",
|
||||
AddService(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/services", `{"AccName": "CreateTest3", "AccOwner": "TestUpdate", "AccUrl": "http://dummy-webdav/", "AccType": "webdav",
|
||||
"AccKey": "123", "AccUser": "admin", "AccPass": "photoprism", "AccError": "", "AccShare": false, "AccSync": false, "RetryLimit": 3, "SharePath": "", "ShareSize": "", "ShareExpires": 0,
|
||||
"SyncPath": "", "SyncInterval": 5, "SyncUpload": false, "SyncDownload": false, "SyncFilenames": false, "SyncRaw": false}`)
|
||||
val := gjson.Get(r.Body.String(), "AccOwner")
|
||||
@@ -115,10 +96,10 @@ func TestUpdateAccount(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
id := gjson.Get(r.Body.String(), "ID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Ok", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateAccount(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/accounts/"+id, `{"AccName": "CreateTestUpdated", "AccOwner": "TestUpdated123", "SyncInterval": 9}`)
|
||||
UpdateService(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/services/"+id, `{"AccName": "CreateTestUpdated", "AccOwner": "TestUpdated123", "SyncInterval": 9}`)
|
||||
val := gjson.Get(r.Body.String(), "AccOwner")
|
||||
assert.Equal(t, "TestUpdated123", val.String())
|
||||
val2 := gjson.Get(r.Body.String(), "SyncInterval")
|
||||
@@ -128,52 +109,52 @@ func TestUpdateAccount(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateAccount(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/accounts/xxx", `{"AccName": "CreateTestUpdated", "AccOwner": "TestUpdated123", "SyncInterval": 9}`)
|
||||
UpdateService(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/services/xxx", `{"AccName": "CreateTestUpdated", "AccOwner": "TestUpdated123", "SyncInterval": 9}`)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
|
||||
t.Run("changes could not be saved", func(t *testing.T) {
|
||||
t.Run("SaveFailed", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UpdateAccount(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/accounts/"+id, `{"AccName": 6, "AccOwner": "TestUpdated123", "SyncInterval": 9, "AccUrl": "https:xxx.com"}`)
|
||||
UpdateService(router)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/services/"+id, `{"AccName": 6, "AccOwner": "TestUpdated123", "SyncInterval": 9, "AccUrl": "https:xxx.com"}`)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrBadRequest), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteAccount(t *testing.T) {
|
||||
func TestDeleteService(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
CreateAccount(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/accounts", `{"AccName": "DeleteTest", "AccOwner": "TestDelete", "AccUrl": "http://dummy-webdav/", "AccType": "webdav",
|
||||
AddService(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/services", `{"AccName": "DeleteTest", "AccOwner": "TestDelete", "AccUrl": "http://dummy-webdav/", "AccType": "webdav",
|
||||
"AccKey": "123", "AccUser": "admin", "AccPass": "photoprism", "AccError": "", "AccShare": false, "AccSync": false, "RetryLimit": 3, "SharePath": "", "ShareSize": "", "ShareExpires": 0,
|
||||
"SyncPath": "", "SyncInterval": 5, "SyncUpload": false, "SyncDownload": false, "SyncFilenames": false, "SyncRaw": false}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
id := gjson.Get(r.Body.String(), "ID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
t.Run("Ok", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
DeleteAccount(router)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/accounts/"+id)
|
||||
DeleteService(router)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/services/"+id)
|
||||
val := gjson.Get(r.Body.String(), "AccOwner")
|
||||
assert.Equal(t, "TestDelete", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
GetAccount(router)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/accounts/"+id)
|
||||
GetService(router)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/services/"+id)
|
||||
val2 := gjson.Get(r2.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val2.String())
|
||||
assert.Equal(t, http.StatusNotFound, r2.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
DeleteAccount(router)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/accounts/xxx")
|
||||
DeleteService(router)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/services/xxx")
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
77
internal/api/services_upload.go
Normal file
77
internal/api/services_upload.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/workers"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
// UploadToService uploads files to the selected account.
|
||||
//
|
||||
// GET /api/v1/services/:id/upload
|
||||
func UploadToService(router *gin.RouterGroup) {
|
||||
router.POST("/services/:id/upload", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceServices, acl.ActionUpload)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
id := clean.IdUint(c.Param("id"))
|
||||
|
||||
m, err := query.AccountByID(id)
|
||||
|
||||
if err != nil {
|
||||
Abort(c, http.StatusNotFound, i18n.ErrAccountNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var f form.SyncUpload
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
AbortBadRequest(c)
|
||||
return
|
||||
}
|
||||
|
||||
folder := f.Folder
|
||||
|
||||
// Find files to share.
|
||||
selection := query.ShareSelection(m.ShareOriginals())
|
||||
files, err := query.SelectedFiles(f.Selection, selection)
|
||||
|
||||
if err != nil {
|
||||
AbortEntityNotFound(c)
|
||||
return
|
||||
}
|
||||
|
||||
var aliases = make(map[string]int)
|
||||
|
||||
for _, file := range files {
|
||||
alias := path.Join(folder, file.ShareBase(0))
|
||||
key := strings.ToLower(alias)
|
||||
|
||||
if seq := aliases[key]; seq > 0 {
|
||||
alias = file.ShareBase(seq)
|
||||
}
|
||||
|
||||
aliases[key] += 1
|
||||
|
||||
entity.FirstOrCreateFileShare(entity.NewFileShare(file.ID, m.ID, alias))
|
||||
}
|
||||
|
||||
workers.RunShare(get.Config())
|
||||
|
||||
c.JSON(http.StatusOK, files)
|
||||
})
|
||||
}
|
30
internal/api/services_upload_test.go
Normal file
30
internal/api/services_upload_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
)
|
||||
|
||||
func TestUploadToService(t *testing.T) {
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UploadToService(router)
|
||||
r := PerformRequest(app, "POST", "/api/v1/services/1000000/upload")
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrBadRequest), val.String())
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("account not found", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
UploadToService(router)
|
||||
r := PerformRequest(app, "POST", "/api/v1/services/999000/upload")
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
@@ -14,9 +14,9 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
// TODO: Proof of concept, needs refactoring.
|
||||
func SharePreview(router *gin.RouterGroup) {
|
||||
router.GET("/:token/:shared/preview", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
token := clean.Token(c.Param("token"))
|
||||
shared := clean.Token(c.Param("shared"))
|
||||
|
@@ -8,9 +8,9 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/crop"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
@@ -35,7 +35,7 @@ func GetThumb(router *gin.RouterGroup) {
|
||||
logPrefix := "thumb"
|
||||
|
||||
start := time.Now()
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
download := c.Query("download") != ""
|
||||
fileHash, cropArea := crop.ParseThumb(clean.Token(c.Param("thumb")))
|
||||
|
||||
@@ -93,7 +93,7 @@ func GetThumb(router *gin.RouterGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
cache := service.ThumbCache()
|
||||
cache := get.ThumbCache()
|
||||
cacheKey := CacheKey("thumbs", fileHash, string(sizeName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
@@ -196,7 +196,7 @@ func GetThumb(router *gin.RouterGroup) {
|
||||
thumbName, err = size.FromCache(fileName, f.FileHash, conf.ThumbCachePath())
|
||||
}
|
||||
|
||||
// Failed?
|
||||
// RunFailed?
|
||||
if err != nil {
|
||||
log.Errorf("%s: %s", logPrefix, err)
|
||||
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
||||
|
@@ -10,8 +10,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// POST /api/v1/upload/:path
|
||||
func Upload(router *gin.RouterGroup) {
|
||||
router.POST("/upload/:token", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.ReadOnly() || !conf.Settings().Features.Upload {
|
||||
Abort(c, http.StatusForbidden, i18n.ErrReadOnly)
|
||||
@@ -74,7 +74,7 @@ func Upload(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
if !conf.UploadNSFW() {
|
||||
nd := service.NsfwDetector()
|
||||
nd := get.NsfwDetector()
|
||||
|
||||
containsNSFW := false
|
||||
|
||||
|
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ func GetVideo(router *gin.RouterGroup) {
|
||||
supported := f.FileCodec != "" && f.FileCodec == string(format.Codec) || format.Codec == video.UnknownCodec && f.FileType == string(format.File)
|
||||
|
||||
// File bitrate too high (for streaming)?
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
transcode := !supported || conf.FFmpegEnabled() && conf.FFmpegBitrateExceeded(fileBitrate)
|
||||
|
||||
if mf, err := photoprism.NewMediaFile(fileName); err != nil {
|
||||
@@ -81,7 +81,7 @@ func GetVideo(router *gin.RouterGroup) {
|
||||
|
||||
// Log error and default to 404.mp4
|
||||
log.Errorf("video: file %s is missing", clean.Log(f.FileName))
|
||||
fileName = service.Config().StaticFile("video/404.mp4")
|
||||
fileName = get.Config().StaticFile("video/404.mp4")
|
||||
AddContentTypeHeader(c, ContentTypeAvc)
|
||||
} else if transcode {
|
||||
if f.FileCodec != "" {
|
||||
@@ -90,12 +90,12 @@ func GetVideo(router *gin.RouterGroup) {
|
||||
log.Debugf("video: %s cannot be streamed directly, average bitrate %.1f MBit/s", clean.Log(f.FileName), fileBitrate)
|
||||
}
|
||||
|
||||
conv := service.Convert()
|
||||
conv := get.Convert()
|
||||
|
||||
if avcFile, err := conv.ToAvc(mf, service.Config().FFmpegEncoder(), false, false); err != nil {
|
||||
if avcFile, err := conv.ToAvc(mf, get.Config().FFmpegEncoder(), false, false); err != nil {
|
||||
// Log error and default to 404.mp4
|
||||
log.Errorf("video: transcoding %s failed", clean.Log(f.FileName))
|
||||
fileName = service.Config().StaticFile("video/404.mp4")
|
||||
fileName = get.Config().StaticFile("video/404.mp4")
|
||||
} else {
|
||||
fileName = avcFile.FileName()
|
||||
}
|
||||
|
@@ -15,10 +15,10 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
@@ -35,7 +35,7 @@ func ZipCreate(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if !conf.Settings().Features.Download {
|
||||
AbortFeatureDisabled(c)
|
||||
@@ -149,7 +149,7 @@ func ZipDownload(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
zipBaseName := clean.FileName(filepath.Base(c.Param("filename")))
|
||||
zipPath := path.Join(conf.TempPath(), "zip")
|
||||
zipFileName := path.Join(zipPath, zipBaseName)
|
||||
|
@@ -8,10 +8,10 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/api"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@ func Import() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
|
||||
if conf.ReadOnly() || !conf.Settings().Features.Import {
|
||||
return nil
|
||||
@@ -62,7 +62,7 @@ func Import() error {
|
||||
|
||||
path := filepath.Clean(conf.ImportPath())
|
||||
|
||||
imp := service.Import()
|
||||
imp := get.Import()
|
||||
|
||||
api.RemoveFromFolderCache(entity.RootImport)
|
||||
|
||||
@@ -82,7 +82,7 @@ func Import() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
moments := service.Moments()
|
||||
moments := get.Moments()
|
||||
|
||||
if err := moments.Start(); err != nil {
|
||||
log.Warnf("moments: %s", err)
|
||||
|
@@ -8,10 +8,10 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/api"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
var autoIndex = time.Time{}
|
||||
@@ -51,14 +51,14 @@ func Index() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
conf := service.Config()
|
||||
conf := get.Config()
|
||||
settings := conf.Settings()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
path := conf.OriginalsPath()
|
||||
|
||||
ind := service.Index()
|
||||
ind := get.Index()
|
||||
|
||||
convert := settings.Index.Convert && conf.SidecarWritable()
|
||||
indOpt := photoprism.NewIndexOptions(entity.RootPath, false, convert, true, false, true)
|
||||
@@ -71,7 +71,7 @@ func Index() error {
|
||||
|
||||
api.RemoveFromFolderCache(entity.RootOriginals)
|
||||
|
||||
prg := service.Purge()
|
||||
prg := get.Purge()
|
||||
|
||||
prgOpt := photoprism.PurgeOptions{
|
||||
Path: filepath.Clean(entity.RootPath),
|
||||
@@ -88,7 +88,7 @@ func Index() error {
|
||||
"step": "moments",
|
||||
})
|
||||
|
||||
moments := service.Moments()
|
||||
moments := get.Moments()
|
||||
|
||||
if err := moments.Start(); err != nil {
|
||||
log.Warnf("moments: %s", err)
|
||||
|
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/dustin/go-humanize/english"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// CleanUpCommand registers the cleanup command.
|
||||
@@ -46,7 +46,7 @@ func cleanUpAction(ctx *cli.Context) error {
|
||||
log.Infof("config: read-only mode enabled")
|
||||
}
|
||||
|
||||
w := service.CleanUp()
|
||||
w := get.CleanUp()
|
||||
|
||||
opt := photoprism.CleanUpOptions{
|
||||
Dry: ctx.Bool("dry"),
|
||||
|
@@ -76,14 +76,14 @@ func childAlreadyRunning(filePath string) (pid int, running bool) {
|
||||
|
||||
pid, err := daemon.ReadPidFile(filePath)
|
||||
|
||||
// Failed?
|
||||
// RunFailed?
|
||||
if err != nil {
|
||||
return pid, false
|
||||
}
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
|
||||
// Failed?
|
||||
// RunFailed?
|
||||
if err != nil {
|
||||
return pid, false
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -18,7 +18,7 @@ func TestMain(m *testing.M) {
|
||||
event.AuditLog = log
|
||||
|
||||
c := config.NewTestConfig("commands")
|
||||
service.SetConfig(c)
|
||||
get.SetConfig(c)
|
||||
|
||||
InitConfig = func(ctx *cli.Context) (*config.Config, error) {
|
||||
return c, c.Init()
|
||||
|
@@ -4,12 +4,12 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
)
|
||||
|
||||
// InitConfig initializes the command config.
|
||||
var InitConfig = func(ctx *cli.Context) (*config.Config, error) {
|
||||
c := config.NewConfig(ctx)
|
||||
service.SetConfig(c)
|
||||
get.SetConfig(c)
|
||||
return c, c.Init()
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@ func convertAction(ctx *cli.Context) error {
|
||||
|
||||
log.Infof("converting originals in %s", clean.Log(convertPath))
|
||||
|
||||
w := service.Convert()
|
||||
w := get.Convert()
|
||||
|
||||
// Start file conversion.
|
||||
if err := w.Start(convertPath, ctx.Bool("force")); err != nil {
|
||||
|
@@ -10,8 +10,8 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -79,7 +79,7 @@ func copyAction(ctx *cli.Context) error {
|
||||
|
||||
log.Infof("copying media files from %s to %s", sourcePath, filepath.Join(conf.OriginalsPath(), destFolder))
|
||||
|
||||
w := service.Import()
|
||||
w := get.Import()
|
||||
opt := photoprism.ImportOptionsCopy(sourcePath, destFolder)
|
||||
|
||||
w.Start(opt)
|
||||
|
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
@@ -91,7 +91,7 @@ func facesStatsAction(ctx *cli.Context) error {
|
||||
conf.InitDb()
|
||||
defer conf.Shutdown()
|
||||
|
||||
w := service.Faces()
|
||||
w := get.Faces()
|
||||
|
||||
if err := w.Stats(); err != nil {
|
||||
return err
|
||||
@@ -109,7 +109,7 @@ func facesAuditAction(ctx *cli.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
conf := config.NewConfig(ctx)
|
||||
service.SetConfig(conf)
|
||||
get.SetConfig(conf)
|
||||
|
||||
_, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -121,7 +121,7 @@ func facesAuditAction(ctx *cli.Context) error {
|
||||
conf.InitDb()
|
||||
defer conf.Shutdown()
|
||||
|
||||
w := service.Faces()
|
||||
w := get.Faces()
|
||||
|
||||
if err := w.Audit(ctx.Bool("fix")); err != nil {
|
||||
return err
|
||||
@@ -152,7 +152,7 @@ func facesResetAction(ctx *cli.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
conf := config.NewConfig(ctx)
|
||||
service.SetConfig(conf)
|
||||
get.SetConfig(conf)
|
||||
|
||||
_, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -164,7 +164,7 @@ func facesResetAction(ctx *cli.Context) error {
|
||||
conf.InitDb()
|
||||
defer conf.Shutdown()
|
||||
|
||||
w := service.Faces()
|
||||
w := get.Faces()
|
||||
|
||||
if err := w.Reset(); err != nil {
|
||||
return err
|
||||
@@ -191,7 +191,7 @@ func facesResetAllAction(ctx *cli.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
conf := config.NewConfig(ctx)
|
||||
service.SetConfig(conf)
|
||||
get.SetConfig(conf)
|
||||
|
||||
_, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -219,7 +219,7 @@ func facesIndexAction(ctx *cli.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
conf := config.NewConfig(ctx)
|
||||
service.SetConfig(conf)
|
||||
get.SetConfig(conf)
|
||||
|
||||
_, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -248,14 +248,14 @@ func facesIndexAction(ctx *cli.Context) error {
|
||||
|
||||
settings := conf.Settings()
|
||||
|
||||
if w := service.Index(); w != nil {
|
||||
if w := get.Index(); w != nil {
|
||||
convert := settings.Index.Convert && conf.SidecarWritable()
|
||||
opt := photoprism.NewIndexOptions(subPath, true, convert, true, true, true)
|
||||
|
||||
indexed = w.Start(opt)
|
||||
}
|
||||
|
||||
if w := service.Purge(); w != nil {
|
||||
if w := get.Purge(); w != nil {
|
||||
opt := photoprism.PurgeOptions{
|
||||
Path: subPath,
|
||||
Ignore: indexed,
|
||||
@@ -280,7 +280,7 @@ func facesUpdateAction(ctx *cli.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
conf := config.NewConfig(ctx)
|
||||
service.SetConfig(conf)
|
||||
get.SetConfig(conf)
|
||||
|
||||
_, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -296,7 +296,7 @@ func facesUpdateAction(ctx *cli.Context) error {
|
||||
Force: ctx.Bool("force"),
|
||||
}
|
||||
|
||||
w := service.Faces()
|
||||
w := get.Faces()
|
||||
|
||||
if err := w.Start(opt); err != nil {
|
||||
return err
|
||||
@@ -314,7 +314,7 @@ func facesOptimizeAction(ctx *cli.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
conf := config.NewConfig(ctx)
|
||||
service.SetConfig(conf)
|
||||
get.SetConfig(conf)
|
||||
|
||||
_, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -326,7 +326,7 @@ func facesOptimizeAction(ctx *cli.Context) error {
|
||||
conf.InitDb()
|
||||
defer conf.Shutdown()
|
||||
|
||||
w := service.Faces()
|
||||
w := get.Faces()
|
||||
|
||||
if res, err := w.Optimize(); err != nil {
|
||||
return err
|
||||
|
@@ -10,8 +10,8 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -79,7 +79,7 @@ func importAction(ctx *cli.Context) error {
|
||||
|
||||
log.Infof("moving media files from %s to %s", sourcePath, filepath.Join(conf.OriginalsPath(), destFolder))
|
||||
|
||||
w := service.Import()
|
||||
w := get.Import()
|
||||
opt := photoprism.ImportOptionsMove(sourcePath, destFolder)
|
||||
|
||||
w.Start(opt)
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/dustin/go-humanize/english"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
@@ -70,14 +70,14 @@ func indexAction(ctx *cli.Context) error {
|
||||
|
||||
var indexed fs.Done
|
||||
|
||||
if w := service.Index(); w != nil {
|
||||
if w := get.Index(); w != nil {
|
||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
||||
opt := photoprism.NewIndexOptions(subPath, ctx.Bool("force"), convert, true, false, !ctx.Bool("archived"))
|
||||
|
||||
indexed = w.Start(opt)
|
||||
}
|
||||
|
||||
if w := service.Purge(); w != nil {
|
||||
if w := get.Purge(); w != nil {
|
||||
purgeStart := time.Now()
|
||||
opt := photoprism.PurgeOptions{
|
||||
Path: subPath,
|
||||
@@ -93,7 +93,7 @@ func indexAction(ctx *cli.Context) error {
|
||||
|
||||
if ctx.Bool("cleanup") {
|
||||
cleanupStart := time.Now()
|
||||
w := service.CleanUp()
|
||||
w := get.CleanUp()
|
||||
|
||||
opt := photoprism.CleanUpOptions{
|
||||
Dry: false,
|
||||
|
@@ -3,6 +3,8 @@ package commands
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -17,7 +19,7 @@ import (
|
||||
var MigrationsStatusCommand = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"status", "show"},
|
||||
Usage: "Lists the status of schema migrations",
|
||||
Usage: "Displays the status of schema migrations",
|
||||
ArgsUsage: "[migrations...]",
|
||||
Flags: report.CliFlags,
|
||||
Action: migrationsStatusAction,
|
||||
@@ -81,13 +83,19 @@ func migrationsStatusAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
// Report columns.
|
||||
cols := []string{"ID", "Dialect", "Started At", "Finished At", "Status"}
|
||||
cols := []string{"ID", "Dialect", "Stage", "Started At", "Finished At", "Status"}
|
||||
|
||||
// Report rows.
|
||||
rows := make([][]string, 0, len(status))
|
||||
|
||||
for _, m := range status {
|
||||
var started, finished, info string
|
||||
var stage, started, finished, info string
|
||||
|
||||
if m.Stage == "" {
|
||||
stage = "main"
|
||||
} else {
|
||||
stage = m.Stage
|
||||
}
|
||||
|
||||
if m.StartedAt.IsZero() {
|
||||
started = "-"
|
||||
@@ -113,7 +121,7 @@ func migrationsStatusAction(ctx *cli.Context) error {
|
||||
info = "Running?"
|
||||
}
|
||||
|
||||
rows = append(rows, []string{m.ID, m.Dialect, started, finished, info})
|
||||
rows = append(rows, []string{m.ID, m.Dialect, stage, started, finished, info})
|
||||
}
|
||||
|
||||
// Display report.
|
||||
@@ -130,6 +138,10 @@ func migrationsStatusAction(ctx *cli.Context) error {
|
||||
|
||||
// migrationsRunAction executes database schema migrations.
|
||||
func migrationsRunAction(ctx *cli.Context) error {
|
||||
if ctx.Args().First() == "ls" {
|
||||
return fmt.Errorf("run '%s migrations ls' to display the status of schema migrations", filepath.Base(os.Args[0]))
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
conf := config.NewConfig(ctx)
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
)
|
||||
|
||||
// MomentsCommand registers the moments command.
|
||||
@@ -36,7 +36,7 @@ func momentsAction(ctx *cli.Context) error {
|
||||
log.Infof("config: read-only mode enabled")
|
||||
}
|
||||
|
||||
w := service.Moments()
|
||||
w := get.Moments()
|
||||
|
||||
if err := w.Start(); err != nil {
|
||||
return err
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// PlacesCommand registers the places subcommands.
|
||||
@@ -68,7 +68,7 @@ func placesUpdateAction(ctx *cli.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
// Run places worker.
|
||||
if w := service.Places(); w != nil {
|
||||
if w := get.Places(); w != nil {
|
||||
_, err := w.Start()
|
||||
|
||||
if err != nil {
|
||||
@@ -77,7 +77,7 @@ func placesUpdateAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
// Run moments worker.
|
||||
if w := service.Moments(); w != nil {
|
||||
if w := get.Moments(); w != nil {
|
||||
err := w.Start()
|
||||
|
||||
if err != nil {
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/dustin/go-humanize/english"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
@@ -63,7 +63,7 @@ func purgeAction(ctx *cli.Context) error {
|
||||
log.Infof("config: read-only mode enabled")
|
||||
}
|
||||
|
||||
w := service.Purge()
|
||||
w := get.Purge()
|
||||
|
||||
opt := photoprism.PurgeOptions{
|
||||
Path: subPath,
|
||||
|
@@ -8,6 +8,8 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/migrate"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
@@ -175,7 +177,7 @@ func resetIndexDb(c *config.Config) {
|
||||
tables.Drop(c.Db())
|
||||
|
||||
log.Infoln("restoring default schema")
|
||||
entity.InitDb(true, false, nil)
|
||||
entity.InitDb(migrate.Opt(false, nil))
|
||||
|
||||
// Reset admin account?
|
||||
if c.AdminPassword() == "" {
|
||||
|
@@ -16,8 +16,8 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
@@ -198,7 +198,7 @@ func restoreAction(ctx *cli.Context) error {
|
||||
conf.InitDb()
|
||||
|
||||
if restoreAlbums {
|
||||
service.SetConfig(conf)
|
||||
get.SetConfig(conf)
|
||||
|
||||
if albumsPath == "" {
|
||||
albumsPath = conf.AlbumsPath()
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/report"
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ var ConfigReports = []Report{
|
||||
func showConfigAction(ctx *cli.Context) error {
|
||||
conf := config.NewConfig(ctx)
|
||||
conf.SetLogLevel(logrus.FatalLevel)
|
||||
service.SetConfig(conf)
|
||||
get.SetConfig(conf)
|
||||
|
||||
if err := conf.Init(); err != nil {
|
||||
log.Debug(err)
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ func thumbsAction(ctx *cli.Context) error {
|
||||
|
||||
log.Infof("creating thumbs in %s", clean.Log(conf.ThumbCachePath()))
|
||||
|
||||
rs := service.Thumbs()
|
||||
rs := get.Thumbs()
|
||||
|
||||
if err := rs.Start(ctx.Bool("force"), ctx.Bool("originals")); err != nil {
|
||||
log.Error(err)
|
||||
|
@@ -95,8 +95,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
|
||||
assert.Equal(t, adminFeatures, f)
|
||||
|
||||
expected := customize.FeatureSettings{
|
||||
Account: false,
|
||||
Advanced: false,
|
||||
Account: true,
|
||||
Albums: true,
|
||||
Archive: true,
|
||||
Delete: false,
|
||||
@@ -120,7 +119,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
|
||||
Search: true,
|
||||
Settings: true,
|
||||
Share: true,
|
||||
Sync: true,
|
||||
Services: true,
|
||||
Upload: true,
|
||||
Videos: true,
|
||||
}
|
||||
@@ -135,7 +134,6 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
|
||||
|
||||
expected := customize.FeatureSettings{
|
||||
Account: false,
|
||||
Advanced: false,
|
||||
Albums: true,
|
||||
Archive: false,
|
||||
Delete: false,
|
||||
@@ -159,7 +157,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
|
||||
Search: false,
|
||||
Settings: false,
|
||||
Share: false,
|
||||
Sync: false,
|
||||
Services: false,
|
||||
Upload: false,
|
||||
Videos: false,
|
||||
}
|
||||
@@ -180,10 +178,9 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
|
||||
assert.False(t, f.Settings)
|
||||
assert.False(t, f.Edit)
|
||||
assert.False(t, f.Private)
|
||||
assert.False(t, f.Advanced)
|
||||
assert.False(t, f.Upload)
|
||||
assert.False(t, f.Download)
|
||||
assert.False(t, f.Sync)
|
||||
assert.False(t, f.Services)
|
||||
assert.False(t, f.Delete)
|
||||
assert.False(t, f.Import)
|
||||
assert.False(t, f.Library)
|
||||
@@ -223,10 +220,9 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
|
||||
assert.True(t, f.Settings)
|
||||
assert.True(t, f.Edit)
|
||||
assert.True(t, f.Private)
|
||||
assert.False(t, f.Advanced)
|
||||
assert.True(t, f.Upload)
|
||||
assert.True(t, f.Download)
|
||||
assert.True(t, f.Sync)
|
||||
assert.True(t, f.Services)
|
||||
assert.False(t, f.Delete)
|
||||
assert.True(t, f.Import)
|
||||
assert.True(t, f.Library)
|
||||
@@ -255,10 +251,9 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
|
||||
assert.False(t, f.Settings)
|
||||
assert.False(t, f.Edit)
|
||||
assert.False(t, f.Private)
|
||||
assert.False(t, f.Advanced)
|
||||
assert.False(t, f.Upload)
|
||||
assert.True(t, f.Download)
|
||||
assert.False(t, f.Sync)
|
||||
assert.False(t, f.Services)
|
||||
assert.False(t, f.Delete)
|
||||
assert.False(t, f.Import)
|
||||
assert.False(t, f.Library)
|
||||
@@ -288,10 +283,9 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
|
||||
assert.False(t, f.Settings)
|
||||
assert.False(t, f.Edit)
|
||||
assert.False(t, f.Private)
|
||||
assert.False(t, f.Advanced)
|
||||
assert.False(t, f.Upload)
|
||||
assert.False(t, f.Download)
|
||||
assert.False(t, f.Sync)
|
||||
assert.False(t, f.Services)
|
||||
assert.False(t, f.Delete)
|
||||
assert.False(t, f.Import)
|
||||
assert.False(t, f.Library)
|
||||
@@ -317,10 +311,9 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
|
||||
assert.True(t, f.Settings)
|
||||
assert.True(t, f.Edit)
|
||||
assert.True(t, f.Private)
|
||||
assert.False(t, f.Advanced)
|
||||
assert.True(t, f.Upload)
|
||||
assert.True(t, f.Download)
|
||||
assert.True(t, f.Sync)
|
||||
assert.True(t, f.Services)
|
||||
assert.False(t, f.Delete)
|
||||
assert.True(t, f.Import)
|
||||
assert.True(t, f.Library)
|
||||
|
@@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/migrate"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
@@ -281,7 +283,7 @@ func (c *Config) InitDb() {
|
||||
// MigrateDb initializes the database and migrates the schema if needed.
|
||||
func (c *Config) MigrateDb(runFailed bool, ids []string) {
|
||||
entity.Admin.UserName = c.AdminUser()
|
||||
entity.InitDb(true, runFailed, ids)
|
||||
entity.InitDb(migrate.Opt(runFailed, ids))
|
||||
|
||||
// Init admin account?
|
||||
if c.AdminPassword() == "" {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user