Routing: Prefix frontend UI routes with /library #840 #2466

Also improves migrations and updates the db schema docs.

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2022-10-15 21:54:11 +02:00
parent 3bad6820d7
commit e3bb8b19dd
202 changed files with 3189 additions and 2608 deletions

View File

@@ -444,9 +444,11 @@ latest:
docker-compose -f docker-compose.latest.yml stop photoprism-latest docker-compose -f docker-compose.latest.yml stop photoprism-latest
docker-compose -f docker-compose.latest.yml up -d --wait photoprism-latest docker-compose -f docker-compose.latest.yml up -d --wait photoprism-latest
start-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: stop-latest:
docker-compose -f docker-compose.latest.yml stop photoprism-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: logs-latest:
docker-compose -f docker-compose.latest.yml logs -f photoprism-latest docker-compose -f docker-compose.latest.yml logs -f photoprism-latest
docker-local: docker-local-bookworm docker-local: docker-local-bookworm

View File

@@ -80,7 +80,7 @@
} }
], ],
"scope": "{{ .config.BaseUri }}/", "scope": "{{ .config.BaseUri }}/",
"start_url": "{{ .config.BaseUri }}/", "start_url": "{{ .config.BaseUri }}/library/",
"display": "{{ .config.AppMode }}", "display": "{{ .config.AppMode }}",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#000000", "background_color": "#000000",

View File

@@ -25,8 +25,8 @@ services:
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password) PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
## Public server URL incl http:// or https:// and /path, :port is optional ## Public server URL incl http:// or https:// and /path, :port is optional
PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/" PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/"
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_CAPTION: "Latest"
PHOTOPRISM_SITE_DESCRIPTION: "Open-Source Photo Management" PHOTOPRISM_SITE_DESCRIPTION: "Tags and finds pictures without getting in your way!"
PHOTOPRISM_SITE_AUTHOR: "@photoprism_app" PHOTOPRISM_SITE_AUTHOR: "@photoprism_app"
PHOTOPRISM_DEBUG: "true" PHOTOPRISM_DEBUG: "true"
PHOTOPRISM_READONLY: "false" PHOTOPRISM_READONLY: "false"
@@ -60,8 +60,8 @@ services:
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
working_dir: "/photoprism" working_dir: "/photoprism"
volumes: volumes:
- "/photoprism/storage" - "./storage:/photoprism/storage"
- "/photoprism/originals" - "./storage/originals:/photoprism/originals"
## Join shared "photoprism-develop" network ## Join shared "photoprism-develop" network
networks: networks:

View File

@@ -123,7 +123,7 @@ config.load().finally(() => {
const router = new Router({ const router = new Router({
routes: Routes, routes: Routes,
mode: "history", mode: "history",
base: config.baseUri + "/", base: config.baseUri + "/library/",
saveScrollPosition: true, saveScrollPosition: true,
scrollBehavior: (to, from, savedPosition) => { scrollBehavior: (to, from, savedPosition) => {
if (savedPosition) { if (savedPosition) {

View File

@@ -27,14 +27,14 @@ import Photos from "pages/photos.vue";
import Albums from "pages/albums.vue"; import Albums from "pages/albums.vue";
import AlbumPhotos from "pages/album/photos.vue"; import AlbumPhotos from "pages/album/photos.vue";
import Places from "pages/places.vue"; import Places from "pages/places.vue";
import Files from "pages/library/files.vue"; import Browse from "pages/files/browse.vue";
import Errors from "pages/library/errors.vue"; import Errors from "pages/files/errors.vue";
import Labels from "pages/labels.vue"; import Labels from "pages/labels.vue";
import People from "pages/people.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 Settings from "pages/settings.vue";
import Login from "pages/auth/login.vue"; import Login from "pages/login.vue";
import Connect from "pages/auth/connect.vue"; import Connect from "pages/connect.vue";
import Discover from "pages/discover.vue"; import Discover from "pages/discover.vue";
import About from "pages/about/about.vue"; import About from "pages/about/about.vue";
import Feedback from "pages/about/feedback.vue"; import Feedback from "pages/about/feedback.vue";
@@ -60,7 +60,7 @@ export default [
}, },
{ {
name: "license", name: "license",
path: "/about/license", path: "/license",
component: License, component: License,
meta: { title: siteTitle, auth: false }, meta: { title: siteTitle, auth: false },
}, },
@@ -78,7 +78,7 @@ export default [
}, },
{ {
name: "login", name: "login",
path: "/auth/login", path: "/login",
component: Login, component: Login,
meta: { title: siteTitle, auth: false, hideNav: true }, meta: { title: siteTitle, auth: false, hideNav: true },
beforeEnter: (to, from, next) => { beforeEnter: (to, from, next) => {
@@ -265,20 +265,20 @@ export default [
}, },
{ {
name: "files", name: "files",
path: "/library/files*", path: "/index/files*",
component: Files, component: Browse,
meta: { title: $gettext("File Browser"), auth: true }, meta: { title: $gettext("File Browser"), auth: true },
}, },
{ {
name: "hidden", name: "hidden",
path: "/library/hidden", path: "/hidden",
component: Photos, component: Photos,
meta: { title: $gettext("Hidden Files"), auth: true }, meta: { title: $gettext("Hidden Files"), auth: true },
props: { staticFilter: { hidden: "true" } }, props: { staticFilter: { hidden: "true" } },
}, },
{ {
name: "errors", name: "errors",
path: "/library/errors", path: "/errors",
component: Errors, component: Errors,
meta: { title: $gettext("Errors"), auth: true }, meta: { title: $gettext("Errors"), auth: true },
}, },
@@ -319,25 +319,25 @@ export default [
meta: { title: $gettext("People"), auth: true, background: "application-light" }, meta: { title: $gettext("People"), auth: true, background: "application-light" },
}, },
{ {
name: "library", name: "index",
path: "/library", path: "/index",
component: Library, component: Files,
meta: { title: $gettext("Library"), auth: true, background: "application-light" }, meta: { title: $gettext("Library"), auth: true, background: "application-light" },
props: { tab: "library-index" }, props: { tab: "index" },
}, },
{ {
name: "library_import", name: "index_import",
path: "/library/import", path: "/import",
component: Library, component: Files,
meta: { title: $gettext("Library"), auth: true, background: "application-light" }, meta: { title: $gettext("Library"), auth: true, background: "application-light" },
props: { tab: "library-import" }, props: { tab: "import" },
}, },
{ {
name: "library_logs", name: "index_logs",
path: "/library/logs", path: "/logs",
component: Library, component: Files,
meta: { title: $gettext("Library"), auth: true, background: "application-light" }, meta: { title: $gettext("Library"), auth: true, background: "application-light" },
props: { tab: "library-logs" }, props: { tab: "logs" },
}, },
{ {
name: "settings", name: "settings",
@@ -352,40 +352,17 @@ export default [
props: { tab: "settings-general" }, props: { tab: "settings-general" },
}, },
{ {
name: "settings_library", name: "settings_files",
path: "/settings/library", path: "/settings/files",
component: Settings, component: Settings,
meta: { meta: {
title: $gettext("Settings"), title: $gettext("Settings"),
auth: true, auth: true,
admin: true,
settings: true, settings: true,
background: "application-light", background: "application-light",
}, },
props: { tab: "settings-library" }, props: { tab: "settings-files" },
},
{
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" },
}, },
{ {
name: "settings_advanced", name: "settings_advanced",
@@ -400,6 +377,30 @@ export default [
}, },
props: { tab: "settings-advanced" }, 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", name: "discover",
path: "/discover", path: "/discover",

View File

@@ -546,6 +546,10 @@ export default class Config {
return this.values && this.values.public; return this.values && this.values.public;
} }
isDemo() {
return this.values && this.values.demo;
}
isSponsor() { isSponsor() {
if (!this.values || !this.values.sponsor) { if (!this.values || !this.values.sponsor) {
return false; return false;

View File

@@ -220,10 +220,10 @@ export default class Session {
getHome() { getHome() {
if (this.loginRequired()) { if (this.loginRequired()) {
return "login"; return "login";
} else if (this.user.Role === "guest") { } else if (this.config.allow("photos", "access_library")) {
return "albums";
} else {
return "browse"; return "browse";
} else {
return "albums";
} }
} }

View File

@@ -227,7 +227,7 @@
<v-list-tile :to="{name: 'live'}" class="nav-live" @click.stop=""> <v-list-tile :to="{name: 'live'}" class="nav-live" @click.stop="">
<v-list-tile-content> <v-list-tile-content>
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`"> <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" <span v-show="config.count.live > 0"
:class="`nav-count ${rtl ? '--rtl' : ''}`">{{ config.count.live | abbreviateCount }}</span> :class="`nav-count ${rtl ? '--rtl' : ''}`">{{ config.count.live | abbreviateCount }}</span>
</v-list-tile-title> </v-list-tile-title>
@@ -387,7 +387,7 @@
</v-list-tile-content> </v-list-tile-content>
</v-list-tile> </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-list-tile-action :title="$gettext('Library')">
<v-icon>camera_roll</v-icon> <v-icon>camera_roll</v-icon>
</v-list-tile-action> </v-list-tile-action>
@@ -401,7 +401,7 @@
<v-list-group v-if="!isMini && $config.feature('library')" prepend-icon="camera_roll" no-action> <v-list-group v-if="!isMini && $config.feature('library')" prepend-icon="camera_roll" no-action>
<template #activator> <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-content>
<v-list-tile-title class="p-flex-menuitem"> <v-list-tile-title class="p-flex-menuitem">
<translate key="Library">Library</translate> <translate key="Library">Library</translate>
@@ -410,7 +410,7 @@
</v-list-tile> </v-list-tile>
</template> </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-content>
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`"> <v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`">
<translate key="Originals">Originals</translate> <translate key="Originals">Originals</translate>
@@ -420,7 +420,7 @@
</v-list-tile-content> </v-list-tile-content>
</v-list-tile> </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-content>
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`"> <v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`">
<translate key="Hidden">Hidden</translate> <translate key="Hidden">Hidden</translate>
@@ -430,7 +430,7 @@
</v-list-tile-content> </v-list-tile-content>
</v-list-tile> </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-content>
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`"> <v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`">
<translate key="Errors">Errors</translate> <translate key="Errors">Errors</translate>
@@ -440,7 +440,7 @@
</v-list-group> </v-list-group>
<template v-if="!config.disable.settings"> <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-list-tile-action :title="$gettext('Settings')">
<v-icon>settings</v-icon> <v-icon>settings</v-icon>
</v-list-tile-action> </v-list-tile-action>
@@ -454,7 +454,7 @@
<v-list-group v-else v-show="$config.feature('settings')" prepend-icon="settings" no-action> <v-list-group v-else v-show="$config.feature('settings')" prepend-icon="settings" no-action>
<template #activator> <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-content>
<v-list-tile-title> <v-list-tile-title>
<translate key="Settings">Settings</translate> <translate key="Settings">Settings</translate>
@@ -565,11 +565,11 @@
class="menu-action nav-login"> class="menu-action nav-login">
<v-icon>login</v-icon> <v-icon>login</v-icon>
</router-link> </router-link>
<router-link v-if="auth && !routeName('library') && $config.feature('library') && $config.feature('logs')" <router-link v-if="auth && !routeName('index') && $config.feature('library') && $config.feature('logs')"
:to="{ name: 'library_logs' }" :title="$gettext('Logs')" class="menu-action nav-logs"> :to="{ name: 'logs' }" :title="$gettext('Logs')" class="menu-action nav-logs">
<v-icon>notes</v-icon> <v-icon>grading</v-icon>
</router-link> </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"> :title="$gettext('Settings')" class="menu-action nav-settings">
<v-icon>settings</v-icon> <v-icon>settings</v-icon>
</router-link> </router-link>
@@ -605,13 +605,13 @@
</div> </div>
<div v-if="auth && !routeName('files') && $config.feature('files') && $config.feature('library')" <div v-if="auth && !routeName('files') && $config.feature('files') && $config.feature('library')"
class="menu-action nav-files"> class="menu-action nav-files">
<router-link to="/library/files"> <router-link to="/index/files">
<v-icon>folder</v-icon> <v-icon>folder</v-icon>
<translate>Files</translate> <translate>Files</translate>
</router-link> </router-link>
</div> </div>
<div v-if="auth && !routeName('library') && $config.feature('library')" class="menu-action nav-library"> <div v-if="auth && !routeName('index') && $config.feature('library')" class="menu-action nav-library">
<router-link :to="{ name: 'library' }"> <router-link :to="{ name: 'index' }">
<v-icon>camera_roll</v-icon> <v-icon>camera_roll</v-icon>
<translate>Index</translate> <translate>Index</translate>
</router-link> </router-link>

View File

@@ -6,6 +6,10 @@ nav .v-list__tile__title.title {
line-height: normal!important; line-height: normal!important;
} }
.v-navigation-drawer>.theme--dark.v-list .v-list__tile__sub-title {
color: #ffffff77;
}
#p-navigation .nav-title { #p-navigation .nav-title {
text-align: left; text-align: left;
/* font-weight: bold; */ /* font-weight: bold; */

View File

@@ -23,9 +23,9 @@ Additional information can be found in our Developer Guide:
*/ */
import PAccountAddDialog from "dialog/account/add.vue"; import PAccountAddDialog from "dialog/service/add.vue";
import PAccountRemoveDialog from "dialog/account/remove.vue"; import PAccountRemoveDialog from "dialog/service/remove.vue";
import PAccountEditDialog from "dialog/account/edit.vue"; import PAccountEditDialog from "dialog/service/edit.vue";
import PPhotoArchiveDialog from "dialog/photo/archive.vue"; import PPhotoArchiveDialog from "dialog/photo/archive.vue";
import PPhotoAlbumDialog from "dialog/photo/album.vue"; import PPhotoAlbumDialog from "dialog/photo/album.vue";
import PPhotoEditDialog from "dialog/photo/edit.vue"; import PPhotoEditDialog from "dialog/photo/edit.vue";

View File

@@ -66,7 +66,7 @@
</v-dialog> </v-dialog>
</template> </template>
<script> <script>
import Account from "model/account"; import Service from "model/service";
import * as options from "options/options"; import * as options from "options/options";
export default { export default {
@@ -80,7 +80,7 @@ export default {
showPassword: false, showPassword: false,
loading: false, loading: false,
search: null, search: null,
model: new Account(), model: new Service(false),
label: { label: {
cancel: this.$gettext("Cancel"), cancel: this.$gettext("Cancel"),
confirm: this.$gettext("Connect"), confirm: this.$gettext("Connect"),

View File

@@ -19,15 +19,15 @@
<v-layout row wrap> <v-layout row wrap>
<v-flex xs12 text-xs-left class="pt-2"> <v-flex xs12 text-xs-left class="pt-2">
<v-select <v-select
v-model="account" v-model="service"
color="secondary-dark" hide-details hide-no-data color="secondary-dark" hide-details hide-no-data
flat flat
:label="$gettext('Account')" :label="$gettext('Account')"
item-text="AccName" item-text="AccName"
item-value="ID" item-value="ID"
return-object return-object
:disabled="loading || noAccounts" :disabled="loading || noServices"
:items="accounts" :items="services"
@change="onChange"> @change="onChange">
</v-select> </v-select>
</v-flex> </v-flex>
@@ -41,7 +41,7 @@
:search-input.sync="search" :search-input.sync="search"
:items="pathItems" :items="pathItems"
:loading="loading" :loading="loading"
:disabled="loading || noAccounts" :disabled="loading || noServices"
item-text="abs" item-text="abs"
item-value="abs" item-value="abs"
:label="$gettext('Folder')" :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"> <v-btn depressed color="secondary-light" class="action-cancel ml-0 mt-0 mb-0 mr-2" @click.stop="cancel">
<translate>Cancel</translate> <translate>Cancel</translate>
</v-btn> </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"> class="action-setup ma-0" @click.stop="setup">
<translate>Setup</translate> <translate>Setup</translate>
</v-btn> </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"> class="action-upload ma-0" @click.stop="confirm">
<translate>Upload</translate> <translate>Upload</translate>
</v-btn> </v-btn>
@@ -67,7 +67,7 @@
</v-dialog> </v-dialog>
</template> </template>
<script> <script>
import Account from "model/account"; import Service from "model/service";
import Selection from "common/selection"; import Selection from "common/selection";
export default { export default {
@@ -87,11 +87,11 @@ export default {
return { return {
isDemo: this.$config.get("demo"), isDemo: this.$config.get("demo"),
isPublic: this.$config.get("public"), isPublic: this.$config.get("public"),
noAccounts: false, noServices: false,
loading: true, loading: true,
search: null, search: null,
account: {}, service: {},
accounts: [], services: [],
selection: new Selection({}), selection: new Selection({}),
path: "/", path: "/",
paths: [ paths: [
@@ -131,7 +131,7 @@ export default {
this.$router.push({name: "settings_sync"}); this.$router.push({name: "settings_sync"});
}, },
confirm() { confirm() {
if (this.noAccounts) { if (this.noServices) {
this.$notify.warn(this.$gettext('No servers configured.')); this.$notify.warn(this.$gettext('No servers configured.'));
return; return;
} else if (this.loading) { } else if (this.loading) {
@@ -140,7 +140,7 @@ export default {
} }
this.loading = true; this.loading = true;
this.account.Share(this.selection, this.path).then( this.service.Upload(this.selection, this.path).then(
(files) => { (files) => {
this.loading = false; this.loading = false;
@@ -150,7 +150,7 @@ export default {
this.$notify.success(this.$gettextInterpolate(this.$gettext("%{n} files uploaded"), {n: files.length})); 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); ).catch(() => this.loading = false);
}, },
@@ -158,13 +158,13 @@ export default {
this.paths = [{"abs": "/"}]; this.paths = [{"abs": "/"}];
this.loading = true; this.loading = true;
this.account.Folders().then(p => { this.service.Folders().then(p => {
for (let i = 0; i < p.length; i++) { for (let i = 0; i < p.length; i++) {
this.paths.push(p[i]); this.paths.push(p[i]);
} }
this.pathItems = [...this.paths]; this.pathItems = [...this.paths];
this.path = this.account.SharePath; this.path = this.service.SharePath;
}).finally(() => this.loading = false); }).finally(() => this.loading = false);
}, },
load() { load() {
@@ -188,13 +188,13 @@ export default {
offset: 0, offset: 0,
}; };
Account.search(params).then(response => { Service.search(params).then(response => {
if (!response.models.length) { if (!response.models.length) {
this.noAccounts = true; this.noServices = true;
this.loading = false; this.loading = false;
} else { } else {
this.account = response.models[0]; this.service = response.models[0];
this.accounts = response.models; this.services = response.models;
this.onChange(); this.onChange();
} }
}).catch(() => this.loading = false); }).catch(() => this.loading = false);

View File

@@ -28,7 +28,7 @@ import Api from "common/api";
import { $gettext } from "common/vm"; import { $gettext } from "common/vm";
import { config } from "app/session"; import { config } from "app/session";
export class Account extends RestModel { export class Service extends RestModel {
getDefaults() { getDefaults() {
return { return {
ID: 0, ID: 0,
@@ -76,7 +76,7 @@ export class Account extends RestModel {
); );
} }
Share(selection, folder) { Upload(selection, folder) {
if (!selection) { if (!selection) {
return; return;
} }
@@ -85,13 +85,13 @@ export class Account extends RestModel {
selection = { Photos: selection }; 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) Promise.resolve(response.data)
); );
} }
static getCollectionResource() { static getCollectionResource() {
return "accounts"; return "services";
} }
static getModelName() { static getModelName() {
@@ -99,4 +99,4 @@ export class Account extends RestModel {
} }
} }
export default Account; export default Service;

View File

@@ -112,28 +112,28 @@ export class User extends RestModel {
return this.Details.NickName; return this.Details.NickName;
} else if (this.Details && this.Details.GivenName) { } else if (this.Details && this.Details.GivenName) {
return this.Details.GivenName; return this.Details.GivenName;
} else if (this.Name) { } else if (this.Role) {
return Util.capitalize(this.Name); return T(Util.capitalize(this.Role));
} else if (this.Details && this.Details.JobTitle) { } else if (this.Details && this.Details.JobTitle) {
return this.Details.JobTitle; return this.Details.JobTitle;
} else if (this.Email) { } else if (this.Email) {
return this.Email; return this.Email;
} else if (this.Role) { } else if (this.Name) {
return T(Util.capitalize(this.Role)); return `@${this.Name}`;
} }
return $gettext("Unregistered"); return $gettext("Unregistered");
} }
getAccountInfo() { getAccountInfo() {
if (this.Email) { if (this.Name) {
return `@${this.Name}`;
} else if (this.Email) {
return this.Email; return this.Email;
} else if (this.Details && this.Details.JobTitle) { } else if (this.Details && this.Details.JobTitle) {
return this.Details.JobTitle; return this.Details.JobTitle;
} else if (this.Role) { } else if (this.Role) {
return T(Util.capitalize(this.Role)); return T(Util.capitalize(this.Role));
} else if (this.Name) {
return this.Name;
} }
return $gettext("Account"); return $gettext("Account");

View File

@@ -87,7 +87,7 @@
</p> </p>
<p class="text-xs-center pt-2 ma-0 pb-0"> <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%;"/> <img :src="$config.staticUri + '/img/badge-agpl.svg'" alt="License AGPL v3" style="max-width:100%;"/>
</router-link> </router-link>
<a target="_blank" href="https://docs.photoprism.app/"><img :src="$config.staticUri + '/img/badge-docs.svg'" <a target="_blank" href="https://docs.photoprism.app/"><img :src="$config.staticUri + '/img/badge-docs.svg'"

View File

@@ -8,7 +8,7 @@
slider-color="secondary-dark" slider-color="secondary-dark"
:height="$vuetify.breakpoint.smAndDown ? 48 : 64" :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)"> @click="changePath(item.path)">
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="item.label">{{ item.icon }}</v-icon> <v-icon v-if="$vuetify.breakpoint.smAndDown" :title="item.label">{{ item.icon }}</v-icon>
<template v-else> <template v-else>
@@ -17,7 +17,7 @@
</v-tab> </v-tab>
<v-tabs-items touchless> <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> <component :is="item.component"></component>
</v-tab-item> </v-tab-item>
</v-tabs-items> </v-tabs-items>
@@ -26,9 +26,9 @@
</template> </template>
<script> <script>
import Import from "pages/library/import.vue"; import Import from "pages/files/import.vue";
import Index from "pages/library/index.vue"; import Index from "pages/files/index.vue";
import Logs from "pages/library/logs.vue"; import Logs from "pages/files/logs.vue";
function initTabs(flag, tabs) { function initTabs(flag, tabs) {
let i = 0; let i = 0;
@@ -58,21 +58,21 @@ export default {
const tabs = [ const tabs = [
{ {
'name': 'library-index', 'name': 'index',
'component': Index, 'component': Index,
'label': this.$gettext('Index'), 'label': this.$gettext('Index'),
'class': '', 'class': '',
'path': '/library', 'path': '/index',
'icon': 'camera_roll', 'icon': 'camera_roll',
'readonly': true, 'readonly': true,
'demo': true, 'demo': true,
}, },
{ {
'name': 'library-import', 'name': 'import',
'component': Import, 'component': Import,
'label': this.$gettext('Import'), 'label': this.$gettext('Import'),
'class': '', 'class': '',
'path': '/library/import', 'path': '/import',
'icon': 'create_new_folder', 'icon': 'create_new_folder',
'readonly': false, 'readonly': false,
'demo': true, 'demo': true,
@@ -81,12 +81,12 @@ export default {
if(this.$config.feature('logs')) { if(this.$config.feature('logs')) {
tabs.push({ tabs.push({
'name': 'library-logs', 'name': 'logs',
'component': Logs, 'component': Logs,
'label': this.$gettext('Logs'), 'label': this.$gettext('Logs'),
'class': '', 'class': '',
'path': '/library/logs', 'path': '/logs',
'icon': 'notes', 'icon': 'grading',
'readonly': true, 'readonly': true,
'demo': true, 'demo': true,
}); });

View File

@@ -3,7 +3,7 @@
<v-form ref="form" class="p-files-search" lazy-validation dense @submit.prevent="updateQuery"> <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 flat color="secondary" :dense="$vuetify.breakpoint.smAndDown">
<v-toolbar-title> <v-toolbar-title>
<router-link to="/library/files"> <router-link to="/index/files">
<translate key="Originals">Originals</translate> <translate key="Originals">Originals</translate>
</router-link> </router-link>
@@ -189,7 +189,7 @@ export default {
methods: { methods: {
getBreadcrumbs() { getBreadcrumbs() {
let result = []; let result = [];
let path = "/library/files"; let path = "/index/files";
const crumbs = this.path.split("/"); const crumbs = this.path.split("/");
@@ -209,7 +209,7 @@ export default {
// Open Edit Dialog // Open Edit Dialog
Event.publish("dialog.edit", {selection: [model.PhotoUID], album: null, index: 0}); Event.publish("dialog.edit", {selection: [model.PhotoUID], album: null, index: 0});
} else { } else {
this.$router.push({path: '/library/files/' + model.Path}); this.$router.push({path: '/index/files/' + model.Path});
} }
}, },
downloadFile(index) { downloadFile(index) {

View File

@@ -29,9 +29,9 @@
<script> <script>
import General from "pages/settings/general.vue"; 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 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 Account from "pages/settings/account.vue";
import {config} from "app/session"; import {config} from "app/session";
@@ -55,8 +55,8 @@ export default {
}, },
}, },
data() { data() {
const isDemo = this.$config.get("demo"); const isDemo = this.$config.isDemo();
const isPublic = this.$config.get("public"); const isPublic = this.$config.isPublic();
const tabs = [ const tabs = [
{ {
@@ -72,16 +72,16 @@ export default {
'show': config.feature('settings'), 'show': config.feature('settings'),
}, },
{ {
'name': 'settings-library', 'name': 'settings-files',
'component': Library, 'component': Files,
'label': this.$gettext('Library'), 'label': this.$gettext('Library'),
'class': '', 'class': '',
'path': '/settings/library', 'path': '/settings/files',
'icon': 'camera_roll', 'icon': 'camera_roll',
'public': true, 'public': true,
'admin': true, 'admin': true,
'demo': true, 'demo': true,
'show': config.feature('advanced'), 'show': config.allow("config", "manage"),
}, },
{ {
'name': 'settings-advanced', 'name': 'settings-advanced',
@@ -93,19 +93,19 @@ export default {
'public': false, 'public': false,
'admin': true, 'admin': true,
'demo': true, 'demo': true,
'show': config.feature('advanced'), 'show': config.allow("config", "manage"),
}, },
{ {
'name': 'settings-sync', 'name': 'settings-services',
'component': Sync, 'component': Services,
'label': this.$gettext('Sync'), 'label': this.$gettext('Services'),
'class': '', 'class': '',
'path': '/settings/sync', 'path': '/settings/services',
'icon': 'sync_alt', 'icon': 'sync_alt',
'public': false, 'public': false,
'admin': true, 'admin': true,
'demo': true, 'demo': true,
'show': config.feature('sync'), 'show': config.feature('services') && config.allow("services", "manage"),
}, },
{ {
'name': 'settings-account', 'name': 'settings-account',
@@ -113,7 +113,7 @@ export default {
'label': this.$gettext('Account'), 'label': this.$gettext('Account'),
'class': '', 'class': '',
'path': '/settings/account', 'path': '/settings/account',
'icon': 'person', 'icon': 'admin_panel_settings',
'public': false, 'public': false,
'admin': true, 'admin': true,
'demo': true, 'demo': true,

View File

@@ -57,7 +57,7 @@
<v-flex xs12 class="pa-2"> <v-flex xs12 class="pa-2">
<p class="caption pa-0"> <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> </p>
</v-flex> </v-flex>

View File

@@ -46,141 +46,6 @@
<v-card v-if="isDemo || isAdmin" flat tile class="mt-0 px-1 application"> <v-card v-if="isDemo || isAdmin" flat tile class="mt-0 px-1 application">
<v-card-actions> <v-card-actions>
<v-layout wrap align-top> <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-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
<v-checkbox <v-checkbox
v-model="settings.features.people" v-model="settings.features.people"
@@ -226,6 +91,141 @@
</v-checkbox> </v-checkbox>
</v-flex> </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-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
<v-checkbox <v-checkbox
v-model="settings.features.library" v-model="settings.features.library"
@@ -233,7 +233,7 @@
class="ma-0 pa-0 input-library" class="ma-0 pa-0 input-library"
color="secondary-dark" color="secondary-dark"
:label="$gettext('Library')" :label="$gettext('Library')"
:hint="$gettext('Show Library in navigation menu.')" :hint="$gettext('Index and import files through the user interface.')"
prepend-icon="camera_roll" prepend-icon="camera_roll"
persistent-hint persistent-hint
@change="onChange" @change="onChange"
@@ -241,6 +241,21 @@
</v-checkbox> </v-checkbox>
</v-flex> </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-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
<v-checkbox <v-checkbox
v-model="settings.features.logs" v-model="settings.features.logs"
@@ -249,7 +264,22 @@
color="secondary-dark" color="secondary-dark"
:label="$gettext('Logs')" :label="$gettext('Logs')"
:hint="$gettext('Show server logs in Library.')" :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 persistent-hint
@change="onChange" @change="onChange"
> >

View File

@@ -91,7 +91,7 @@
<script> <script>
import Settings from "model/settings"; import Settings from "model/settings";
import Account from "model/account"; import Service from "model/service";
import {DateTime} from "luxon"; import {DateTime} from "luxon";
export default { export default {
@@ -150,7 +150,7 @@ export default {
return DateTime.fromISO(time).toLocaleString(DateTime.DATE_FULL); return DateTime.fromISO(time).toLocaleString(DateTime.DATE_FULL);
}, },
load() { load() {
Account.search({count: 2000}).then(r => this.results = r.models); Service.search({count: 2000}).then(r => this.results = r.models);
}, },
remove(model) { remove(model) {
this.model = model.clone(); this.model = model.clone();

View File

@@ -132,7 +132,7 @@ describe("common/session", () => {
}; };
session.setData(values2); session.setData(values2);
const result2 = session.getDisplayName(); const result2 = session.getDisplayName();
assert.equal(result2, "Bar"); assert.equal(result2, "Admin");
session.deleteData(); session.deleteData();
}); });

View File

@@ -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.onGet("api/v1/settings").reply(200, { download: true, language: "de" }, mockHeaders);
Mock.onPost("api/v1/settings").reply(200, { download: true, language: "en" }, 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.onGet("api/v1/services/123/folders").reply(200, { foo: "folders" }, mockHeaders);
Mock.onPost("api/v1/accounts/123/share").reply(200, { foo: "share" }, mockHeaders); Mock.onPost("api/v1/services/123/upload").reply(200, { foo: "upload" }, mockHeaders);
Mock.onGet("api/v1/folders/2011/10-Halloween", { Mock.onGet("api/v1/folders/2011/10-Halloween", {
params: { recursive: true, uncached: true }, params: { recursive: true, uncached: true },

View File

@@ -1,39 +1,39 @@
import "../fixtures"; import "../fixtures";
import Account from "model/account"; import Service from "model/service";
import Photo from "model/photo"; import Photo from "model/photo";
let chai = require("chai/chai"); let chai = require("chai/chai");
let assert = chai.assert; let assert = chai.assert;
describe("model/account", () => { describe("model/service", () => {
it("should get account defaults", () => { it("should get service defaults", () => {
const values = { ID: 5 }; const values = { ID: 5 };
const account = new Account(values); const service = new Service(values);
const result = account.getDefaults(); const result = service.getDefaults();
assert.equal(result.ID, 0); assert.equal(result.ID, 0);
assert.equal(result.AccShare, true); assert.equal(result.AccShare, true);
assert.equal(result.AccName, ""); assert.equal(result.AccName, "");
}); });
it("should get account entity name", () => { it("should get service entity name", () => {
const values = { ID: 5, AccName: "Test Name" }; const values = { ID: 5, AccName: "Test Name" };
const account = new Account(values); const service = new Service(values);
const result = account.getEntityName(); const result = service.getEntityName();
assert.equal(result, "Test Name"); assert.equal(result, "Test Name");
}); });
it("should get account id", () => { it("should get service id", () => {
const values = { ID: 5, AccName: "Test Name" }; const values = { ID: 5, AccName: "Test Name" };
const account = new Account(values); const service = new Service(values);
const result = account.getId(); const result = service.getId();
assert.equal(result, 5); assert.equal(result, 5);
}); });
it("should get folders", (done) => { it("should get folders", (done) => {
const values = { ID: 123, AccName: "Test Name" }; const values = { ID: 123, AccName: "Test Name" };
const account = new Account(values); const service = new Service(values);
account service
.Folders() .Folders()
.then((response) => { .then((response) => {
assert.equal(response.foo, "folders"); assert.equal(response.foo, "folders");
@@ -46,16 +46,16 @@ describe("model/account", () => {
it("should get share photos", (done) => { it("should get share photos", (done) => {
const values = { ID: 123, AccName: "Test Name" }; 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 values1 = { ID: 5, Title: "Crazy Cat", UID: 789 };
const photo = new Photo(values1); const photo = new Photo(values1);
const values2 = { ID: 6, Title: "Crazy Cat 2", UID: 783 }; const values2 = { ID: 6, Title: "Crazy Cat 2", UID: 783 };
const photo2 = new Photo(values2); const photo2 = new Photo(values2);
const Photos = [photo, photo2]; const Photos = [photo, photo2];
account service
.Share(Photos, "destination") .Upload(Photos, "destination")
.then((response) => { .then((response) => {
assert.equal(response.foo, "share"); assert.equal(response.foo, "upload");
done(); done();
}) })
.catch((error) => { .catch((error) => {
@@ -64,12 +64,12 @@ describe("model/account", () => {
}); });
it("should get collection resource", () => { it("should get collection resource", () => {
const result = Account.getCollectionResource(); const result = Service.getCollectionResource();
assert.equal(result, "accounts"); assert.equal(result, "services");
}); });
it("should get model name", () => { it("should get model name", () => {
const result = Account.getModelName(); const result = Service.getModelName();
assert.equal(result, "Account"); assert.equal(result, "Account");
}); });
}); });

View File

@@ -58,7 +58,7 @@ var Resources = ACL{
ResourceShares: Roles{ ResourceShares: Roles{
RoleAdmin: GrantFullAccess, RoleAdmin: GrantFullAccess,
}, },
ResourceAccounts: Roles{ ResourceServices: Roles{
RoleAdmin: GrantFullAccess, RoleAdmin: GrantFullAccess,
}, },
ResourceUsers: Roles{ ResourceUsers: Roles{

View File

@@ -18,7 +18,7 @@ const (
ResourceSettings Resource = "settings" ResourceSettings Resource = "settings"
ResourcePassword Resource = "password" ResourcePassword Resource = "password"
ResourceUsers Resource = "users" ResourceUsers Resource = "users"
ResourceAccounts Resource = "accounts" ResourceServices Resource = "services"
ResourceFiles Resource = "files" ResourceFiles Resource = "files"
ResourceFolders Resource = "folders" ResourceFolders Resource = "folders"
ResourceShares Resource = "shares" ResourceShares Resource = "shares"

View File

@@ -11,10 +11,10 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -22,7 +22,7 @@ var albumMutex = sync.Mutex{}
// SaveAlbumAsYaml saves album data as YAML file. // SaveAlbumAsYaml saves album data as YAML file.
func SaveAlbumAsYaml(a entity.Album) { func SaveAlbumAsYaml(a entity.Album) {
c := service.Config() c := get.Config()
// Write YAML sidecar file (optional). // Write YAML sidecar file (optional).
if !c.BackupYaml() { if !c.BackupYaml() {

View File

@@ -9,8 +9,8 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -36,7 +36,7 @@ func SearchAlbums(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
// Ignore private flag if feature is disabled. // Ignore private flag if feature is disabled.
if !conf.Settings().Features.Private { if !conf.Settings().Features.Private {

View File

@@ -6,12 +6,12 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/event" "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. // UpdateClientConfig publishes updated client configuration values over the websocket connections.
func UpdateClientConfig() { 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. // GetClientConfig returns the client configuration values as JSON.
@@ -20,7 +20,7 @@ func UpdateClientConfig() {
func GetClientConfig(router *gin.RouterGroup) { func GetClientConfig(router *gin.RouterGroup) {
router.GET("/config", func(c *gin.Context) { router.GET("/config", func(c *gin.Context) {
s := Session(SessionID(c)) s := Session(SessionID(c))
conf := service.Config() conf := get.Config()
if s == nil { if s == nil {
c.JSON(http.StatusOK, conf.ClientPublic()) c.JSON(http.StatusOK, conf.ClientPublic())

View File

@@ -12,7 +12,7 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@@ -21,7 +21,7 @@ func TestMain(m *testing.M) {
event.AuditLog = log event.AuditLog = log
c := config.TestConfig() c := config.TestConfig()
service.SetConfig(c) get.SetConfig(c)
code := m.Run() code := m.Run()
@@ -37,7 +37,7 @@ func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, conf *config.Config
app = gin.New() app = gin.New()
router = app.Group("/api/v1") router = app.Group("/api/v1")
return app, router, service.Config() return app, router, get.Config()
} }
// Executes an API request with an empty request body. // Executes an API request with an empty request body.

View File

@@ -14,7 +14,7 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/rnd"
) )
@@ -57,7 +57,7 @@ func WebSocket(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if conf == nil { if conf == nil {
return return

View File

@@ -9,9 +9,9 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/server/limiter" "github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -20,7 +20,7 @@ import (
// PUT /api/v1/users/:uid/password // PUT /api/v1/users/:uid/password
func ChangePassword(router *gin.RouterGroup) { func ChangePassword(router *gin.RouterGroup) {
router.PUT("/users/:uid/password", func(c *gin.Context) { 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. // You cannot change any passwords without authentication and settings enabled.
if conf.Public() || conf.DisableSettings() { if conf.Public() || conf.DisableSettings() {

View File

@@ -4,7 +4,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity" "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/internal/session"
"github.com/photoprism/photoprism/pkg/clean" "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. // Session finds the client session for the given ID or returns nil otherwise.
func Session(id string) *entity.Session { func Session(id string) *entity.Session {
// Return default session when public mode is enabled. // Return default session when public mode is enabled.
if service.Config().Public() { if get.Config().Public() {
return service.Session().Public() return get.Session().Public()
} else if id == "" { } else if id == "" {
return nil return nil
} }
// Find session or otherwise return nil. // Find session or otherwise return nil.
s, err := service.Session().Get(id) s, err := get.Session().Get(id)
if err != nil { if err != nil {
return nil return nil

View File

@@ -8,9 +8,9 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/server/limiter" "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. // 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 return
} }
conf := service.Config() conf := get.Config()
// Skip authentication if app is running in public mode. // Skip authentication if app is running in public mode.
if conf.Public() { if conf.Public() {
sess := service.Session().Public() sess := get.Session().Public()
data := gin.H{ data := gin.H{
"status": "ok", "status": "ok",
"id": sess.ID, "id": sess.ID,
@@ -57,7 +57,7 @@ func CreateSession(router *gin.RouterGroup) {
sess = s sess = s
} else { } else {
// Create new session. // Create new session.
sess = service.Session().New(c) sess = get.Session().New(c)
isNew = true isNew = true
} }
@@ -65,7 +65,7 @@ func CreateSession(router *gin.RouterGroup) {
if err := sess.LogIn(f, c); err != nil { if err := sess.LogIn(f, c); err != nil {
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return 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) event.AuditErr([]string{ClientIP(c), "%s"}, err)
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return return

View File

@@ -6,7 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -20,12 +20,12 @@ func DeleteSession(router *gin.RouterGroup) {
if id == "" { if id == "" {
AbortBadRequest(c) AbortBadRequest(c)
return return
} else if service.Config().Public() { } else if get.Config().Public() {
c.JSON(http.StatusOK, gin.H{"status": "authentication disabled", "id": id}) c.JSON(http.StatusOK, gin.H{"status": "authentication disabled", "id": id})
return 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) event.AuditErr([]string{ClientIP(c), "session %s"}, err)
} else { } else {
event.AuditDebug([]string{ClientIP(c), "session deleted"}) event.AuditDebug([]string{ClientIP(c), "session deleted"})

View File

@@ -5,7 +5,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -50,7 +50,7 @@ func GetSession(router *gin.RouterGroup) {
"id": sess.ID, "id": sess.ID,
"user": sess.User(), "user": sess.User(),
"data": sess.Data(), "data": sess.Data(),
"config": service.Config().ClientSession(sess), "config": get.Config().ClientSession(sess),
} }
c.JSON(http.StatusOK, data) c.JSON(http.StatusOK, data)

View File

@@ -8,8 +8,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -18,7 +18,7 @@ import (
// GET /s/:token/... // GET /s/:token/...
func Shares(router *gin.RouterGroup) { func Shares(router *gin.RouterGroup) {
router.GET("/:token", func(c *gin.Context) { router.GET("/:token", func(c *gin.Context) {
conf := service.Config() conf := get.Config()
token := clean.Token(c.Param("token")) token := clean.Token(c.Param("token"))
links := entity.FindValidLinks(token, "") links := entity.FindValidLinks(token, "")
@@ -37,7 +37,7 @@ func Shares(router *gin.RouterGroup) {
}) })
router.GET("/:token/:shared", func(c *gin.Context) { router.GET("/:token/:shared", func(c *gin.Context) {
conf := service.Config() conf := get.Config()
token := clean.Token(c.Param("token")) token := clean.Token(c.Param("token"))
shared := clean.Token(c.Param("shared")) shared := clean.Token(c.Param("shared"))

View File

@@ -13,10 +13,10 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -45,7 +45,7 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
log.Infof("photos: archiving %s", clean.Log(f.String())) log.Infof("photos: archiving %s", clean.Log(f.String()))
if service.Config().BackupYaml() { if get.Config().BackupYaml() {
// Fetch selection from index. // Fetch selection from index.
photos, err := query.SelectedPhotos(f) photos, err := query.SelectedPhotos(f)
@@ -108,7 +108,7 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
log.Infof("photos: restoring %s", clean.Log(f.String())) log.Infof("photos: restoring %s", clean.Log(f.String()))
if service.Config().BackupYaml() { if get.Config().BackupYaml() {
// Fetch selection from index. // Fetch selection from index.
photos, err := query.SelectedPhotos(f) photos, err := query.SelectedPhotos(f)
@@ -346,7 +346,7 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.ReadOnly() || !conf.Settings().Features.Delete { if conf.ReadOnly() || !conf.Settings().Features.Delete {
AbortFeatureDisabled(c) AbortFeatureDisabled(c)

View File

@@ -6,7 +6,7 @@ import (
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/thumb" "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. // RemoveFromFolderCache removes an item from the folder cache e.g. after indexing.
func RemoveFromFolderCache(rootName string) { func RemoveFromFolderCache(rootName string) {
cache := service.FolderCache() cache := get.FolderCache()
cacheKey := fmt.Sprintf("folder:%s:%t:%t", rootName, true, false) 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. // RemoveFromAlbumCoverCache removes covers by album UID e.g. after adding or removing photos.
func RemoveFromAlbumCoverCache(uid string) { func RemoveFromAlbumCoverCache(uid string) {
cache := service.CoverCache() cache := get.CoverCache()
for thumbName := range thumb.Sizes { for thumbName := range thumb.Sizes {
cacheKey := CacheKey(albumCover, uid, string(thumbName)) cacheKey := CacheKey(albumCover, uid, string(thumbName))
@@ -72,7 +72,7 @@ func RemoveFromAlbumCoverCache(uid string) {
// FlushCoverCache clears the complete cover cache. // FlushCoverCache clears the complete cover cache.
func FlushCoverCache() { func FlushCoverCache() {
service.CoverCache().Flush() get.CoverCache().Flush()
if err := query.UpdateCovers(); err != nil { if err := query.UpdateCovers(); err != nil {
log.Error(err) log.Error(err)

View File

@@ -10,8 +10,8 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
@@ -22,7 +22,7 @@ import (
func GetConfigOptions(router *gin.RouterGroup) { func GetConfigOptions(router *gin.RouterGroup) {
router.GET("/config/options", func(c *gin.Context) { router.GET("/config/options", func(c *gin.Context) {
s := Auth(c, acl.ResourceConfig, acl.AccessAll) s := Auth(c, acl.ResourceConfig, acl.AccessAll)
conf := service.Config() conf := get.Config()
// Abort if permission was not granted. // Abort if permission was not granted.
if s.Invalid() || conf.Public() || conf.DisableSettings() { if s.Invalid() || conf.Public() || conf.DisableSettings() {
@@ -40,7 +40,7 @@ func GetConfigOptions(router *gin.RouterGroup) {
func SaveConfigOptions(router *gin.RouterGroup) { func SaveConfigOptions(router *gin.RouterGroup) {
router.POST("/config/options", func(c *gin.Context) { router.POST("/config/options", func(c *gin.Context) {
s := Auth(c, acl.ResourceConfig, acl.ActionManage) s := Auth(c, acl.ResourceConfig, acl.ActionManage)
conf := service.Config() conf := get.Config()
if s.Invalid() || conf.Public() || conf.DisableSettings() { if s.Invalid() || conf.Public() || conf.DisableSettings() {
AbortForbidden(c) AbortForbidden(c)

View File

@@ -8,8 +8,8 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/customize" "github.com/photoprism/photoprism/internal/customize"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service"
) )
// GetSettings returns the user app settings as JSON. // GetSettings returns the user app settings as JSON.
@@ -24,7 +24,7 @@ func GetSettings(router *gin.RouterGroup) {
return return
} }
settings := service.Config().SessionSettings(s) settings := get.Config().SessionSettings(s)
if settings == nil { if settings == nil {
Abort(c, http.StatusNotFound, i18n.ErrNotFound) Abort(c, http.StatusNotFound, i18n.ErrNotFound)
@@ -47,7 +47,7 @@ func SaveSettings(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.DisableSettings() { if conf.DisableSettings() {
AbortForbidden(c) AbortForbidden(c)
@@ -101,6 +101,6 @@ func SaveSettings(router *gin.RouterGroup) {
event.InfoMsg(i18n.MsgSettingsSaved) event.InfoMsg(i18n.MsgSettingsSaved)
c.JSON(http.StatusOK, service.Config().SessionSettings(s)) c.JSON(http.StatusOK, get.Config().SessionSettings(s))
}) })
} }

View File

@@ -7,8 +7,8 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -39,7 +39,7 @@ func Connect(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.Public() { if conf.Public() {
Abort(c, http.StatusForbidden, i18n.ErrPublic) Abort(c, http.StatusForbidden, i18n.ErrPublic)

View File

@@ -7,9 +7,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
@@ -38,7 +38,7 @@ func AlbumCover(router *gin.RouterGroup) {
} }
start := time.Now() start := time.Now()
conf := service.Config() conf := get.Config()
thumbName := thumb.Name(clean.Token(c.Param("size"))) thumbName := thumb.Name(clean.Token(c.Param("size")))
uid := clean.UID(c.Param("uid")) uid := clean.UID(c.Param("uid"))
@@ -50,7 +50,7 @@ func AlbumCover(router *gin.RouterGroup) {
return return
} }
cache := service.CoverCache() cache := get.CoverCache()
cacheKey := CacheKey(albumCover, uid, string(thumbName)) cacheKey := CacheKey(albumCover, uid, string(thumbName))
if cacheData, ok := cache.Get(cacheKey); ok { if cacheData, ok := cache.Get(cacheKey); ok {
@@ -151,7 +151,7 @@ func LabelCover(router *gin.RouterGroup) {
} }
start := time.Now() start := time.Now()
conf := service.Config() conf := get.Config()
thumbName := thumb.Name(clean.Token(c.Param("size"))) thumbName := thumb.Name(clean.Token(c.Param("size")))
uid := clean.UID(c.Param("uid")) uid := clean.UID(c.Param("uid"))
@@ -163,7 +163,7 @@ func LabelCover(router *gin.RouterGroup) {
return return
} }
cache := service.CoverCache() cache := get.CoverCache()
cacheKey := CacheKey(labelCover, uid, string(thumbName)) cacheKey := CacheKey(labelCover, uid, string(thumbName))
if cacheData, ok := cache.Get(cacheKey); ok { if cacheData, ok := cache.Get(cacheKey); ok {

View File

@@ -8,11 +8,11 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
@@ -28,7 +28,7 @@ func DownloadAlbum(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if !conf.Settings().Features.Download { if !conf.Settings().Features.Download {
AbortFeatureDisabled(c) AbortFeatureDisabled(c)

View File

@@ -7,9 +7,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
@@ -29,7 +29,7 @@ func DownloadName(c *gin.Context) customize.DownloadName {
case "original": case "original":
return customize.DownloadNameOriginal return customize.DownloadNameOriginal
default: default:
return service.Config().Settings().Download.Name return get.Config().Settings().Download.Name
} }
} }

View File

@@ -6,9 +6,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -46,7 +46,7 @@ func GetErrors(router *gin.RouterGroup) {
// DELETE /api/v1/errors // DELETE /api/v1/errors
func DeleteErrors(router *gin.RouterGroup) { func DeleteErrors(router *gin.RouterGroup) {
router.DELETE("/errors", func(c *gin.Context) { router.DELETE("/errors", func(c *gin.Context) {
conf := service.Config() conf := get.Config()
// Disabled in public mode so that attackers cannot cover their tracks. // Disabled in public mode so that attackers cannot cover their tracks.
if conf.Public() { if conf.Public() {

View File

@@ -7,8 +7,8 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service"
) )
// SendFeedback sends a feedback message. // SendFeedback sends a feedback message.
@@ -16,7 +16,7 @@ import (
// POST /api/v1/feedback // POST /api/v1/feedback
func SendFeedback(router *gin.RouterGroup) { func SendFeedback(router *gin.RouterGroup) {
router.POST("/feedback", func(c *gin.Context) { router.POST("/feedback", func(c *gin.Context) {
conf := service.Config() conf := get.Config()
if conf.Public() { if conf.Public() {
Abort(c, http.StatusForbidden, i18n.ErrPublic) Abort(c, http.StatusForbidden, i18n.ErrPublic)

View File

@@ -9,10 +9,10 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
) )
// DeleteFile removes a file from storage. // DeleteFile removes a file from storage.
@@ -30,7 +30,7 @@ func DeleteFile(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.ReadOnly() || !conf.Settings().Features.Edit { if conf.ReadOnly() || !conf.Settings().Features.Edit {
Abort(c, http.StatusForbidden, i18n.ErrReadOnly) Abort(c, http.StatusForbidden, i18n.ErrReadOnly)

View File

@@ -7,9 +7,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
@@ -36,7 +36,7 @@ func FolderCover(router *gin.RouterGroup) {
} }
start := time.Now() start := time.Now()
conf := service.Config() conf := get.Config()
uid := c.Param("uid") uid := c.Param("uid")
thumbName := thumb.Name(clean.Token(c.Param("size"))) thumbName := thumb.Name(clean.Token(c.Param("size")))
download := c.Query("download") != "" 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)) cacheKey := CacheKey(folderCover, uid, string(thumbName))
if cacheData, ok := cache.Get(cacheKey); ok { if cacheData, ok := cache.Get(cacheKey); ok {

View File

@@ -12,8 +12,8 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -30,7 +30,7 @@ type FoldersResponse struct {
// //
// GET /api/v1/folders/originals // GET /api/v1/folders/originals
func SearchFoldersOriginals(router *gin.RouterGroup) { func SearchFoldersOriginals(router *gin.RouterGroup) {
conf := service.Config() conf := get.Config()
SearchFolders(router, "originals", entity.RootOriginals, conf.OriginalsPath()) SearchFolders(router, "originals", entity.RootOriginals, conf.OriginalsPath())
} }
@@ -38,7 +38,7 @@ func SearchFoldersOriginals(router *gin.RouterGroup) {
// //
// GET /api/v1/folders/import // GET /api/v1/folders/import
func SearchFoldersImport(router *gin.RouterGroup) { func SearchFoldersImport(router *gin.RouterGroup) {
conf := service.Config() conf := get.Config()
SearchFolders(router, "import", entity.RootImport, conf.ImportPath()) SearchFolders(router, "import", entity.RootImport, conf.ImportPath())
} }
@@ -62,7 +62,7 @@ func SearchFolders(router *gin.RouterGroup, urlPath, rootName, rootPath string)
return return
} }
cache := service.FolderCache() cache := get.FolderCache()
recursive := f.Recursive recursive := f.Recursive
listFiles := f.Files listFiles := f.Files
uncached := listFiles || f.Uncached uncached := listFiles || f.Uncached

View File

@@ -15,10 +15,10 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
@@ -38,7 +38,7 @@ func StartImport(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.ReadOnly() || !conf.Settings().Features.Import { if conf.ReadOnly() || !conf.Settings().Features.Import {
AbortFeatureDisabled(c) AbortFeatureDisabled(c)
@@ -76,7 +76,7 @@ func StartImport(router *gin.RouterGroup) {
importPath = path.Join(importPath, srcFolder) importPath = path.Join(importPath, srcFolder)
imp := service.Import() imp := get.Import()
RemoveFromFolderCache(entity.RootImport) RemoveFromFolderCache(entity.RootImport)
@@ -125,7 +125,7 @@ func StartImport(router *gin.RouterGroup) {
log.Infof("import: no new files found to import", clean.Log(importPath)) log.Infof("import: no new files found to import", clean.Log(importPath))
} else { } else {
log.Infof("import: imported %s", english.Plural(n, "file", "files")) 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") log.Warnf("import: moments service not set - possible bug")
} else if err := moments.Start(); err != nil { } else if err := moments.Start(); err != nil {
log.Warnf("moments: %s", err) log.Warnf("moments: %s", err)
@@ -168,14 +168,14 @@ func CancelImport(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.ReadOnly() || !conf.Settings().Features.Import { if conf.ReadOnly() || !conf.Settings().Features.Import {
AbortFeatureDisabled(c) AbortFeatureDisabled(c)
return return
} }
imp := service.Import() imp := get.Import()
imp.Cancel() imp.Cancel()

View File

@@ -11,9 +11,9 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -29,7 +29,7 @@ func StartIndexing(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
settings := conf.Settings() settings := conf.Settings()
if !settings.Features.Library { if !settings.Features.Library {
@@ -60,7 +60,7 @@ func StartIndexing(router *gin.RouterGroup) {
} }
// Start indexing. // Start indexing.
ind := service.Index() ind := get.Index()
indexed := ind.Start(indOpt) indexed := ind.Start(indOpt)
RemoveFromFolderCache(entity.RootOriginals) RemoveFromFolderCache(entity.RootOriginals)
@@ -72,7 +72,7 @@ func StartIndexing(router *gin.RouterGroup) {
} }
// Start purging. // Start purging.
prg := service.Purge() prg := get.Purge()
if files, photos, err := prg.Start(prgOpt); err != nil { if files, photos, err := prg.Start(prgOpt); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
@@ -85,7 +85,7 @@ func StartIndexing(router *gin.RouterGroup) {
"step": "moments", "step": "moments",
}) })
moments := service.Moments() moments := get.Moments()
if err := moments.Start(); err != nil { if err := moments.Start(); err != nil {
log.Warnf("moments: %s", err) log.Warnf("moments: %s", err)
@@ -115,14 +115,14 @@ func CancelIndexing(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if !conf.Settings().Features.Library { if !conf.Settings().Features.Library {
AbortFeatureDisabled(c) AbortFeatureDisabled(c)
return return
} }
ind := service.Index() ind := get.Index()
ind.Cancel() ind.Cancel()

View File

@@ -12,15 +12,15 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
) )
// Checks if background worker runs less than once per hour. // Checks if background worker runs less than once per hour.
func wakeupIntervalTooHigh(c *gin.Context) bool { func wakeupIntervalTooHigh(c *gin.Context) bool {
if conf := service.Config(); conf.Unsafe() { if conf := get.Config(); conf.Unsafe() {
return false return false
} else if i := conf.WakeupInterval(); i > time.Hour { } else if i := conf.WakeupInterval(); i > time.Hour {
Abort(c, http.StatusForbidden, i18n.ErrWakeupInterval, i.String()) 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. // Check feature flags.
conf := service.Config() conf := get.Config()
if !conf.Settings().Features.People { if !conf.Settings().Features.People {
AbortFeatureDisabled(c) AbortFeatureDisabled(c)
return nil, nil, fmt.Errorf("feature disabled") return nil, nil, fmt.Errorf("feature disabled")
@@ -119,7 +119,7 @@ func UpdateMarker(router *gin.RouterGroup) {
return return
} else if changed { } else if changed {
if marker.FaceID != "" && marker.SubjUID != "" && marker.SubjSrc == entity.SrcManual { 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) log.Errorf("faces: %s (optimize)", err)
} else if res.Merged > 0 { } else if res.Merged > 0 {
log.Infof("faces: merged %s", english.Plural(res.Merged, "cluster", "clusters")) log.Infof("faces: merged %s", english.Plural(res.Merged, "cluster", "clusters"))

View File

@@ -6,8 +6,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -22,7 +22,7 @@ func GetMomentsTime(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
result, err := query.MomentsTime(1, conf.Settings().Features.Private) result, err := query.MomentsTime(1, conf.Settings().Features.Private)

View File

@@ -10,10 +10,10 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -33,7 +33,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
fileUid := clean.UID(c.Param("file_uid")) fileUid := clean.UID(c.Param("file_uid"))
file, err := query.FileByUID(fileUid) file, err := query.FileByUID(fileUid)
@@ -170,7 +170,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
} }
} }
ind := service.Index() ind := get.Index()
// Index unstacked files. // Index unstacked files.
if res := ind.FileName(unstackFile.FileName(), photoprism.IndexOptionsSingle()); res.Failed() { if res := ind.FileName(unstackFile.FileName(), photoprism.IndexOptionsSingle()); res.Failed() {

View File

@@ -10,17 +10,17 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
// SavePhotoAsYaml saves photo data as YAML file. // SavePhotoAsYaml saves photo data as YAML file.
func SavePhotoAsYaml(p entity.Photo) { func SavePhotoAsYaml(p entity.Photo) {
c := service.Config() c := get.Config()
// Write YAML sidecar file (optional). // Write YAML sidecar file (optional).
if !c.BackupYaml() { if !c.BackupYaml() {

View File

@@ -10,9 +10,9 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service"
) )
// SearchPhotos searches the pictures index and returns the result as JSON. // SearchPhotos searches the pictures index and returns the result as JSON.
@@ -36,7 +36,7 @@ func SearchPhotos(router *gin.RouterGroup) {
return f, s, err return f, s, err
} }
settings := service.Config().Settings() settings := get.Config().Settings()
// Ignore private flag if feature is disabled. // Ignore private flag if feature is disabled.
if !settings.Features.Private { if !settings.Features.Private {
@@ -91,7 +91,7 @@ func SearchPhotos(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
result, count, err := search.UserPhotosViewerResults(f, s, conf.ContentUri(), conf.ApiUri(), s.PreviewToken, s.DownloadToken) result, count, err := search.UserPhotosViewerResults(f, s, conf.ContentUri(), conf.ApiUri(), s.PreviewToken, s.DownloadToken)

View File

@@ -9,8 +9,8 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -38,7 +38,7 @@ func SearchGeo(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
settings := conf.Settings() settings := conf.Settings()
// Ignore private flag if feature is disabled. // Ignore private flag if feature is disabled.

View File

@@ -6,8 +6,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/react" "github.com/photoprism/photoprism/pkg/react"
) )
@@ -31,7 +31,7 @@ func LikePhoto(router *gin.RouterGroup) {
return 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"))) logWarn("react", m.React(s.User(), react.Find("love")))
} }
@@ -71,7 +71,7 @@ func DislikePhoto(router *gin.RouterGroup) {
return 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())) logWarn("react", m.UnReact(s.User()))
} }

View File

@@ -3,8 +3,6 @@ package api
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"path"
"strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -13,9 +11,9 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/workers" "github.com/photoprism/photoprism/internal/workers"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
@@ -23,25 +21,21 @@ import (
// Namespaces for caching and logs. // Namespaces for caching and logs.
const ( const (
accountFolder = "account-folder" serviceFolder = "service-folder"
) )
// GetAccount returns an account as JSON. // GetService returns an account as JSON.
// //
// GET /api/v1/accounts/:id // GET /api/v1/services/:id
// func GetService(router *gin.RouterGroup) {
// Parameters: router.GET("/services/:id", func(c *gin.Context) {
// s := Auth(c, acl.ResourceServices, acl.ActionView)
// 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)
if s.Abort(c) { if s.Abort(c) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.Demo() || conf.DisableSettings() { if conf.Demo() || conf.DisableSettings() {
AbortForbidden(c) 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 // GET /api/v1/services/:id/folders
// func GetServiceFolders(router *gin.RouterGroup) {
// Parameters: router.GET("/services/:id/folders", func(c *gin.Context) {
// s := Auth(c, acl.ResourceServices, acl.ActionView)
// 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)
if s.Abort(c) { if s.Abort(c) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.Demo() || conf.DisableSettings() { if conf.Demo() || conf.DisableSettings() {
AbortForbidden(c) AbortForbidden(c)
@@ -82,8 +72,8 @@ func GetAccountFolders(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
id := clean.IdUint(c.Param("id")) id := clean.IdUint(c.Param("id"))
cache := service.FolderCache() cache := get.FolderCache()
cacheKey := fmt.Sprintf("%s:%d", accountFolder, id) cacheKey := fmt.Sprintf("%s:%d", serviceFolder, id)
if cacheData, ok := cache.Get(cacheKey); ok { if cacheData, ok := cache.Get(cacheKey); ok {
cached := cacheData.(fs.FileInfos) cached := cacheData.(fs.FileInfos)
@@ -104,7 +94,7 @@ func GetAccountFolders(router *gin.RouterGroup) {
list, err := m.Directories() list, err := m.Directories()
if err != nil { if err != nil {
log.Errorf("%s: %s", accountFolder, err.Error()) log.Errorf("%s: %s", serviceFolder, err.Error())
Abort(c, http.StatusBadRequest, i18n.ErrConnectionFailed) Abort(c, http.StatusBadRequest, i18n.ErrConnectionFailed)
return 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 // POST /api/v1/services
// func AddService(router *gin.RouterGroup) {
// Parameters: router.POST("/services", func(c *gin.Context) {
// s := Auth(c, acl.ResourceServices, acl.ActionCreate)
// 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)
if s.Abort(c) { if s.Abort(c) {
return return
} }
id := clean.IdUint(c.Param("id")) conf := get.Config()
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()
if conf.Demo() || conf.DisableSettings() { if conf.Demo() || conf.DisableSettings() {
AbortForbidden(c) AbortForbidden(c)
return return
} }
var f form.Account var f form.Service
if err := c.BindJSON(&f); err != nil { if err := c.BindJSON(&f); err != nil {
AbortBadRequest(c) AbortBadRequest(c)
return return
} }
if err := f.ServiceDiscovery(); err != nil { if err := f.Discovery(); err != nil {
log.Error(err) log.Error(err)
Abort(c, http.StatusBadRequest, i18n.ErrConnectionFailed) Abort(c, http.StatusBadRequest, i18n.ErrConnectionFailed)
return return
} }
m, err := entity.CreateAccount(f) m, err := entity.AddService(f)
if err != nil { if err != nil {
log.Error(err) 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 // PUT /api/v1/services/:id
// func UpdateService(router *gin.RouterGroup) {
// Parameters: router.PUT("/services/:id", func(c *gin.Context) {
// s := Auth(c, acl.ResourceServices, acl.ActionUpdate)
// 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)
if s.Abort(c) { if s.Abort(c) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.Demo() || conf.DisableSettings() { if conf.Demo() || conf.DisableSettings() {
AbortForbidden(c) AbortForbidden(c)
@@ -256,7 +179,7 @@ func UpdateAccount(router *gin.RouterGroup) {
} }
// 1) Init form with model values // 1) Init form with model values
f, err := form.NewAccount(m) f, err := form.NewService(m)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@@ -288,29 +211,25 @@ func UpdateAccount(router *gin.RouterGroup) {
} }
if m.AccSync { if m.AccSync {
workers.RunSync(service.Config()) workers.RunSync(get.Config())
} }
c.JSON(http.StatusOK, m) c.JSON(http.StatusOK, m)
}) })
} }
// DeleteAccount removes a remote account configuration. // DeleteService removes a remote account configuration.
// //
// DELETE /api/v1/accounts/:id // DELETE /api/v1/services/:id
// func DeleteService(router *gin.RouterGroup) {
// Parameters: router.DELETE("/services/:id", func(c *gin.Context) {
// s := Auth(c, acl.ResourceServices, acl.ActionDelete)
// 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)
if s.Abort(c) { if s.Abort(c) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.Demo() || conf.DisableSettings() { if conf.Demo() || conf.DisableSettings() {
AbortForbidden(c) AbortForbidden(c)

View File

@@ -9,29 +9,29 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/search" "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 // GET /api/v1/services
func SearchAccounts(router *gin.RouterGroup) { func SearchServices(router *gin.RouterGroup) {
router.GET("/accounts", func(c *gin.Context) { router.GET("/services", func(c *gin.Context) {
s := Auth(c, acl.ResourceAccounts, acl.ActionSearch) s := Auth(c, acl.ResourceServices, acl.ActionSearch)
if s.Abort(c) { if s.Abort(c) {
return return
} }
conf := service.Config() conf := get.Config()
if conf.Demo() || conf.DisableSettings() { if conf.Demo() || conf.DisableSettings() {
c.JSON(http.StatusOK, entity.Accounts{}) c.JSON(http.StatusOK, entity.Services{})
return return
} }
var f form.SearchAccounts var f form.SearchServices
err := c.MustBindWith(&f, binding.Form) err := c.MustBindWith(&f, binding.Form)

View File

@@ -9,12 +9,12 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestSearchAccounts(t *testing.T) { func TestSearchServices(t *testing.T) {
t.Run("successful request", func(t *testing.T) { t.Run("successful request", func(t *testing.T) {
app, router, _ := NewApiTest() app, router, _ := NewApiTest()
SearchAccounts(router) SearchServices(router)
sess := AuthenticateAdmin(app, 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") val := gjson.Get(r.Body.String(), "#(AccName=\"Test Account\").AccURL")
count := gjson.Get(r.Body.String(), "#") count := gjson.Get(r.Body.String(), "#")
assert.LessOrEqual(t, int64(1), count.Int()) assert.LessOrEqual(t, int64(1), count.Int())
@@ -23,8 +23,8 @@ func TestSearchAccounts(t *testing.T) {
}) })
t.Run("invalid request", func(t *testing.T) { t.Run("invalid request", func(t *testing.T) {
app, router, _ := NewApiTest() app, router, _ := NewApiTest()
SearchAccounts(router) SearchServices(router)
r := PerformRequest(app, "GET", "/api/v1/accounts?xxx=10") r := PerformRequest(app, "GET", "/api/v1/services?xxx=10")
assert.Equal(t, http.StatusBadRequest, r.Code) assert.Equal(t, http.StatusBadRequest, r.Code)
}) })
} }

View File

@@ -10,88 +10,69 @@ import (
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
) )
func TestGetAccount(t *testing.T) { func TestGetService(t *testing.T) {
t.Run("successful request", func(t *testing.T) { t.Run("Ok", func(t *testing.T) {
app, router, _ := NewApiTest() app, router, _ := NewApiTest()
GetAccount(router) GetService(router)
r := PerformRequest(app, "GET", "/api/v1/accounts/1000000") r := PerformRequest(app, "GET", "/api/v1/services/1000000")
val := gjson.Get(r.Body.String(), "AccName") val := gjson.Get(r.Body.String(), "AccName")
assert.Equal(t, "Test Account", val.String()) assert.Equal(t, "Test Account", val.String())
assert.Equal(t, http.StatusOK, r.Code) 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() app, router, _ := NewApiTest()
GetAccount(router) GetService(router)
r := PerformRequest(app, "GET", "/api/v1/accounts/999000") r := PerformRequest(app, "GET", "/api/v1/services/999000")
val := gjson.Get(r.Body.String(), "error") val := gjson.Get(r.Body.String(), "error")
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String()) assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
assert.Equal(t, http.StatusNotFound, r.Code) assert.Equal(t, http.StatusNotFound, r.Code)
}) })
} }
func TestGetAccountFolders(t *testing.T) { func TestGetServiceFolders(t *testing.T) {
t.Run("successful request", func(t *testing.T) { t.Run("Ok", func(t *testing.T) {
app, router, _ := NewApiTest() app, router, _ := NewApiTest()
GetAccountFolders(router) GetServiceFolders(router)
r := PerformRequest(app, "GET", "/api/v1/accounts/1000000/folders") r := PerformRequest(app, "GET", "/api/v1/services/1000000/folders")
count := gjson.Get(r.Body.String(), "#") count := gjson.Get(r.Body.String(), "#")
assert.LessOrEqual(t, int64(2), count.Int()) assert.LessOrEqual(t, int64(2), count.Int())
val := gjson.Get(r.Body.String(), "0.abs") val := gjson.Get(r.Body.String(), "0.abs")
assert.Equal(t, "/Photos", val.String()) assert.Equal(t, "/Photos", val.String())
assert.Equal(t, http.StatusOK, r.Code) 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() app, router, _ := NewApiTest()
GetAccountFolders(router) GetServiceFolders(router)
r := PerformRequest(app, "GET", "/api/v1/accounts/999000/folders") r := PerformRequest(app, "GET", "/api/v1/services/999000/folders")
val := gjson.Get(r.Body.String(), "error") val := gjson.Get(r.Body.String(), "error")
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String()) assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
assert.Equal(t, http.StatusNotFound, r.Code) assert.Equal(t, http.StatusNotFound, r.Code)
}) })
} }
func TestShareWithAccount(t *testing.T) { func TestCreateService(t *testing.T) {
t.Run("invalid request", func(t *testing.T) { t.Run("BadRequest", func(t *testing.T) {
app, router, _ := NewApiTest() app, router, _ := NewApiTest()
ShareWithAccount(router) AddService(router)
r := PerformRequest(app, "POST", "/api/v1/accounts/1000000/share") r := PerformRequest(app, "POST", "/api/v1/services")
val := gjson.Get(r.Body.String(), "error") val := gjson.Get(r.Body.String(), "error")
assert.Equal(t, i18n.Msg(i18n.ErrBadRequest), val.String()) assert.Equal(t, i18n.Msg(i18n.ErrBadRequest), val.String())
assert.Equal(t, http.StatusBadRequest, r.Code) 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() app, router, _ := NewApiTest()
ShareWithAccount(router) AddService(router)
r := PerformRequest(app, "POST", "/api/v1/accounts/999000/share") r := PerformRequestWithBody(app, "POST", "/api/v1/services", `{"AccName": "CreateTest1", "AccOwner": "Test", "AccUrl": "http://webdav123/", "AccType": "webdav",
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",
"AccKey": "123", "AccUser": "testuser", "AccPass": "testpasswd", "AccError": "", "AccShare": false, "AccSync": false, "RetryLimit": 3, "SharePath": "", "ShareSize": "", "ShareExpires": 0, "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}`) "SyncPath": "", "SyncInterval": 3, "SyncUpload": false, "SyncDownload": false, "SyncFilenames": false, "SyncRaw": false}`)
val := gjson.Get(r.Body.String(), "error") val := gjson.Get(r.Body.String(), "error")
assert.Equal(t, i18n.Msg(i18n.ErrConnectionFailed), val.String()) assert.Equal(t, i18n.Msg(i18n.ErrConnectionFailed), val.String())
assert.Equal(t, http.StatusBadRequest, r.Code) 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() app, router, _ := NewApiTest()
CreateAccount(router) AddService(router)
r := PerformRequestWithBody(app, "POST", "/api/v1/accounts", `{"AccName": "CreateTest", "AccOwner": "Test", "AccUrl": "http://dummy-webdav/", "AccType": "webdav", 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, "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}`) "SyncPath": "", "SyncInterval": 3, "SyncUpload": false, "SyncDownload": false, "SyncFilenames": false, "SyncRaw": false}`)
val := gjson.Get(r.Body.String(), "AccOwner") 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() app, router, _ := NewApiTest()
CreateAccount(router) AddService(router)
r := PerformRequestWithBody(app, "POST", "/api/v1/accounts", `{"AccName": "CreateTest3", "AccOwner": "TestUpdate", "AccUrl": "http://dummy-webdav/", "AccType": "webdav", 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, "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}`) "SyncPath": "", "SyncInterval": 5, "SyncUpload": false, "SyncDownload": false, "SyncFilenames": false, "SyncRaw": false}`)
val := gjson.Get(r.Body.String(), "AccOwner") val := gjson.Get(r.Body.String(), "AccOwner")
@@ -115,10 +96,10 @@ func TestUpdateAccount(t *testing.T) {
assert.Equal(t, http.StatusOK, r.Code) assert.Equal(t, http.StatusOK, r.Code)
id := gjson.Get(r.Body.String(), "ID").String() 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() app, router, _ := NewApiTest()
UpdateAccount(router) UpdateService(router)
r := PerformRequestWithBody(app, "PUT", "/api/v1/accounts/"+id, `{"AccName": "CreateTestUpdated", "AccOwner": "TestUpdated123", "SyncInterval": 9}`) r := PerformRequestWithBody(app, "PUT", "/api/v1/services/"+id, `{"AccName": "CreateTestUpdated", "AccOwner": "TestUpdated123", "SyncInterval": 9}`)
val := gjson.Get(r.Body.String(), "AccOwner") val := gjson.Get(r.Body.String(), "AccOwner")
assert.Equal(t, "TestUpdated123", val.String()) assert.Equal(t, "TestUpdated123", val.String())
val2 := gjson.Get(r.Body.String(), "SyncInterval") val2 := gjson.Get(r.Body.String(), "SyncInterval")
@@ -128,52 +109,52 @@ func TestUpdateAccount(t *testing.T) {
assert.Equal(t, http.StatusOK, r.Code) 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() app, router, _ := NewApiTest()
UpdateAccount(router) UpdateService(router)
r := PerformRequestWithBody(app, "PUT", "/api/v1/accounts/xxx", `{"AccName": "CreateTestUpdated", "AccOwner": "TestUpdated123", "SyncInterval": 9}`) r := PerformRequestWithBody(app, "PUT", "/api/v1/services/xxx", `{"AccName": "CreateTestUpdated", "AccOwner": "TestUpdated123", "SyncInterval": 9}`)
val := gjson.Get(r.Body.String(), "error") val := gjson.Get(r.Body.String(), "error")
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String()) assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
assert.Equal(t, http.StatusNotFound, r.Code) 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() app, router, _ := NewApiTest()
UpdateAccount(router) UpdateService(router)
r := PerformRequestWithBody(app, "PUT", "/api/v1/accounts/"+id, `{"AccName": 6, "AccOwner": "TestUpdated123", "SyncInterval": 9, "AccUrl": "https:xxx.com"}`) 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") val := gjson.Get(r.Body.String(), "error")
assert.Equal(t, i18n.Msg(i18n.ErrBadRequest), val.String()) assert.Equal(t, i18n.Msg(i18n.ErrBadRequest), val.String())
assert.Equal(t, http.StatusBadRequest, r.Code) assert.Equal(t, http.StatusBadRequest, r.Code)
}) })
} }
func TestDeleteAccount(t *testing.T) { func TestDeleteService(t *testing.T) {
app, router, _ := NewApiTest() app, router, _ := NewApiTest()
CreateAccount(router) AddService(router)
r := PerformRequestWithBody(app, "POST", "/api/v1/accounts", `{"AccName": "DeleteTest", "AccOwner": "TestDelete", "AccUrl": "http://dummy-webdav/", "AccType": "webdav", 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, "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}`) "SyncPath": "", "SyncInterval": 5, "SyncUpload": false, "SyncDownload": false, "SyncFilenames": false, "SyncRaw": false}`)
assert.Equal(t, http.StatusOK, r.Code) assert.Equal(t, http.StatusOK, r.Code)
id := gjson.Get(r.Body.String(), "ID").String() 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() app, router, _ := NewApiTest()
DeleteAccount(router) DeleteService(router)
r := PerformRequest(app, "DELETE", "/api/v1/accounts/"+id) r := PerformRequest(app, "DELETE", "/api/v1/services/"+id)
val := gjson.Get(r.Body.String(), "AccOwner") val := gjson.Get(r.Body.String(), "AccOwner")
assert.Equal(t, "TestDelete", val.String()) assert.Equal(t, "TestDelete", val.String())
assert.Equal(t, http.StatusOK, r.Code) assert.Equal(t, http.StatusOK, r.Code)
GetAccount(router) GetService(router)
r2 := PerformRequest(app, "GET", "/api/v1/accounts/"+id) r2 := PerformRequest(app, "GET", "/api/v1/services/"+id)
val2 := gjson.Get(r2.Body.String(), "error") val2 := gjson.Get(r2.Body.String(), "error")
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val2.String()) assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val2.String())
assert.Equal(t, http.StatusNotFound, r2.Code) 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() app, router, _ := NewApiTest()
DeleteAccount(router) DeleteService(router)
r := PerformRequest(app, "DELETE", "/api/v1/accounts/xxx") r := PerformRequest(app, "DELETE", "/api/v1/services/xxx")
val := gjson.Get(r.Body.String(), "error") val := gjson.Get(r.Body.String(), "error")
assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String()) assert.Equal(t, i18n.Msg(i18n.ErrAccountNotFound), val.String())
assert.Equal(t, http.StatusNotFound, r.Code) assert.Equal(t, http.StatusNotFound, r.Code)

View 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)
})
}

View 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)
})
}

View File

@@ -14,9 +14,9 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
@@ -28,7 +28,7 @@ import (
// TODO: Proof of concept, needs refactoring. // TODO: Proof of concept, needs refactoring.
func SharePreview(router *gin.RouterGroup) { func SharePreview(router *gin.RouterGroup) {
router.GET("/:token/:shared/preview", func(c *gin.Context) { router.GET("/:token/:shared/preview", func(c *gin.Context) {
conf := service.Config() conf := get.Config()
token := clean.Token(c.Param("token")) token := clean.Token(c.Param("token"))
shared := clean.Token(c.Param("shared")) shared := clean.Token(c.Param("shared"))

View File

@@ -8,9 +8,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/crop" "github.com/photoprism/photoprism/internal/crop"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
@@ -35,7 +35,7 @@ func GetThumb(router *gin.RouterGroup) {
logPrefix := "thumb" logPrefix := "thumb"
start := time.Now() start := time.Now()
conf := service.Config() conf := get.Config()
download := c.Query("download") != "" download := c.Query("download") != ""
fileHash, cropArea := crop.ParseThumb(clean.Token(c.Param("thumb"))) 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)) cacheKey := CacheKey("thumbs", fileHash, string(sizeName))
if cacheData, ok := cache.Get(cacheKey); ok { 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()) thumbName, err = size.FromCache(fileName, f.FileHash, conf.ThumbCachePath())
} }
// Failed? // RunFailed?
if err != nil { if err != nil {
log.Errorf("%s: %s", logPrefix, err) log.Errorf("%s: %s", logPrefix, err)
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg) c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)

View File

@@ -10,8 +10,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -20,7 +20,7 @@ import (
// POST /api/v1/upload/:path // POST /api/v1/upload/:path
func Upload(router *gin.RouterGroup) { func Upload(router *gin.RouterGroup) {
router.POST("/upload/:token", func(c *gin.Context) { router.POST("/upload/:token", func(c *gin.Context) {
conf := service.Config() conf := get.Config()
if conf.ReadOnly() || !conf.Settings().Features.Upload { if conf.ReadOnly() || !conf.Settings().Features.Upload {
Abort(c, http.StatusForbidden, i18n.ErrReadOnly) Abort(c, http.StatusForbidden, i18n.ErrReadOnly)
@@ -74,7 +74,7 @@ func Upload(router *gin.RouterGroup) {
} }
if !conf.UploadNSFW() { if !conf.UploadNSFW() {
nd := service.NsfwDetector() nd := get.NsfwDetector()
containsNSFW := false containsNSFW := false

View File

@@ -9,9 +9,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "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) supported := f.FileCodec != "" && f.FileCodec == string(format.Codec) || format.Codec == video.UnknownCodec && f.FileType == string(format.File)
// File bitrate too high (for streaming)? // File bitrate too high (for streaming)?
conf := service.Config() conf := get.Config()
transcode := !supported || conf.FFmpegEnabled() && conf.FFmpegBitrateExceeded(fileBitrate) transcode := !supported || conf.FFmpegEnabled() && conf.FFmpegBitrateExceeded(fileBitrate)
if mf, err := photoprism.NewMediaFile(fileName); err != nil { 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 error and default to 404.mp4
log.Errorf("video: file %s is missing", clean.Log(f.FileName)) 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) AddContentTypeHeader(c, ContentTypeAvc)
} else if transcode { } else if transcode {
if f.FileCodec != "" { 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) 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 error and default to 404.mp4
log.Errorf("video: transcoding %s failed", clean.Log(f.FileName)) 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 { } else {
fileName = avcFile.FileName() fileName = avcFile.FileName()
} }

View File

@@ -15,10 +15,10 @@ import (
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/rnd"
@@ -35,7 +35,7 @@ func ZipCreate(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
if !conf.Settings().Features.Download { if !conf.Settings().Features.Download {
AbortFeatureDisabled(c) AbortFeatureDisabled(c)
@@ -149,7 +149,7 @@ func ZipDownload(router *gin.RouterGroup) {
return return
} }
conf := service.Config() conf := get.Config()
zipBaseName := clean.FileName(filepath.Base(c.Param("filename"))) zipBaseName := clean.FileName(filepath.Base(c.Param("filename")))
zipPath := path.Join(conf.TempPath(), "zip") zipPath := path.Join(conf.TempPath(), "zip")
zipFileName := path.Join(zipPath, zipBaseName) zipFileName := path.Join(zipPath, zipBaseName)

View File

@@ -8,10 +8,10 @@ import (
"github.com/photoprism/photoprism/internal/api" "github.com/photoprism/photoprism/internal/api"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
) )
@@ -52,7 +52,7 @@ func Import() error {
return nil return nil
} }
conf := service.Config() conf := get.Config()
if conf.ReadOnly() || !conf.Settings().Features.Import { if conf.ReadOnly() || !conf.Settings().Features.Import {
return nil return nil
@@ -62,7 +62,7 @@ func Import() error {
path := filepath.Clean(conf.ImportPath()) path := filepath.Clean(conf.ImportPath())
imp := service.Import() imp := get.Import()
api.RemoveFromFolderCache(entity.RootImport) api.RemoveFromFolderCache(entity.RootImport)
@@ -82,7 +82,7 @@ func Import() error {
return nil return nil
} }
moments := service.Moments() moments := get.Moments()
if err := moments.Start(); err != nil { if err := moments.Start(); err != nil {
log.Warnf("moments: %s", err) log.Warnf("moments: %s", err)

View File

@@ -8,10 +8,10 @@ import (
"github.com/photoprism/photoprism/internal/api" "github.com/photoprism/photoprism/internal/api"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
) )
var autoIndex = time.Time{} var autoIndex = time.Time{}
@@ -51,14 +51,14 @@ func Index() error {
return nil return nil
} }
conf := service.Config() conf := get.Config()
settings := conf.Settings() settings := conf.Settings()
start := time.Now() start := time.Now()
path := conf.OriginalsPath() path := conf.OriginalsPath()
ind := service.Index() ind := get.Index()
convert := settings.Index.Convert && conf.SidecarWritable() convert := settings.Index.Convert && conf.SidecarWritable()
indOpt := photoprism.NewIndexOptions(entity.RootPath, false, convert, true, false, true) indOpt := photoprism.NewIndexOptions(entity.RootPath, false, convert, true, false, true)
@@ -71,7 +71,7 @@ func Index() error {
api.RemoveFromFolderCache(entity.RootOriginals) api.RemoveFromFolderCache(entity.RootOriginals)
prg := service.Purge() prg := get.Purge()
prgOpt := photoprism.PurgeOptions{ prgOpt := photoprism.PurgeOptions{
Path: filepath.Clean(entity.RootPath), Path: filepath.Clean(entity.RootPath),
@@ -88,7 +88,7 @@ func Index() error {
"step": "moments", "step": "moments",
}) })
moments := service.Moments() moments := get.Moments()
if err := moments.Start(); err != nil { if err := moments.Start(); err != nil {
log.Warnf("moments: %s", err) log.Warnf("moments: %s", err)

View File

@@ -7,8 +7,8 @@ import (
"github.com/dustin/go-humanize/english" "github.com/dustin/go-humanize/english"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
) )
// CleanUpCommand registers the cleanup command. // CleanUpCommand registers the cleanup command.
@@ -46,7 +46,7 @@ func cleanUpAction(ctx *cli.Context) error {
log.Infof("config: read-only mode enabled") log.Infof("config: read-only mode enabled")
} }
w := service.CleanUp() w := get.CleanUp()
opt := photoprism.CleanUpOptions{ opt := photoprism.CleanUpOptions{
Dry: ctx.Bool("dry"), Dry: ctx.Bool("dry"),

View File

@@ -76,14 +76,14 @@ func childAlreadyRunning(filePath string) (pid int, running bool) {
pid, err := daemon.ReadPidFile(filePath) pid, err := daemon.ReadPidFile(filePath)
// Failed? // RunFailed?
if err != nil { if err != nil {
return pid, false return pid, false
} }
process, err := os.FindProcess(pid) process, err := os.FindProcess(pid)
// Failed? // RunFailed?
if err != nil { if err != nil {
return pid, false return pid, false
} }

View File

@@ -9,7 +9,7 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@@ -18,7 +18,7 @@ func TestMain(m *testing.M) {
event.AuditLog = log event.AuditLog = log
c := config.NewTestConfig("commands") c := config.NewTestConfig("commands")
service.SetConfig(c) get.SetConfig(c)
InitConfig = func(ctx *cli.Context) (*config.Config, error) { InitConfig = func(ctx *cli.Context) (*config.Config, error) {
return c, c.Init() return c, c.Init()

View File

@@ -4,12 +4,12 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
) )
// InitConfig initializes the command config. // InitConfig initializes the command config.
var InitConfig = func(ctx *cli.Context) (*config.Config, error) { var InitConfig = func(ctx *cli.Context) (*config.Config, error) {
c := config.NewConfig(ctx) c := config.NewConfig(ctx)
service.SetConfig(c) get.SetConfig(c)
return c, c.Init() return c, c.Init()
} }

View File

@@ -9,7 +9,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/pkg/clean" "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)) log.Infof("converting originals in %s", clean.Log(convertPath))
w := service.Convert() w := get.Convert()
// Start file conversion. // Start file conversion.
if err := w.Start(convertPath, ctx.Bool("force")); err != nil { if err := w.Start(convertPath, ctx.Bool("force")); err != nil {

View File

@@ -10,8 +10,8 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "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)) 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) opt := photoprism.ImportOptionsCopy(sourcePath, destFolder)
w.Start(opt) w.Start(opt)

View File

@@ -11,9 +11,9 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
@@ -91,7 +91,7 @@ func facesStatsAction(ctx *cli.Context) error {
conf.InitDb() conf.InitDb()
defer conf.Shutdown() defer conf.Shutdown()
w := service.Faces() w := get.Faces()
if err := w.Stats(); err != nil { if err := w.Stats(); err != nil {
return err return err
@@ -109,7 +109,7 @@ func facesAuditAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf) get.SetConfig(conf)
_, cancel := context.WithCancel(context.Background()) _, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@@ -121,7 +121,7 @@ func facesAuditAction(ctx *cli.Context) error {
conf.InitDb() conf.InitDb()
defer conf.Shutdown() defer conf.Shutdown()
w := service.Faces() w := get.Faces()
if err := w.Audit(ctx.Bool("fix")); err != nil { if err := w.Audit(ctx.Bool("fix")); err != nil {
return err return err
@@ -152,7 +152,7 @@ func facesResetAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf) get.SetConfig(conf)
_, cancel := context.WithCancel(context.Background()) _, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@@ -164,7 +164,7 @@ func facesResetAction(ctx *cli.Context) error {
conf.InitDb() conf.InitDb()
defer conf.Shutdown() defer conf.Shutdown()
w := service.Faces() w := get.Faces()
if err := w.Reset(); err != nil { if err := w.Reset(); err != nil {
return err return err
@@ -191,7 +191,7 @@ func facesResetAllAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf) get.SetConfig(conf)
_, cancel := context.WithCancel(context.Background()) _, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@@ -219,7 +219,7 @@ func facesIndexAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf) get.SetConfig(conf)
_, cancel := context.WithCancel(context.Background()) _, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@@ -248,14 +248,14 @@ func facesIndexAction(ctx *cli.Context) error {
settings := conf.Settings() settings := conf.Settings()
if w := service.Index(); w != nil { if w := get.Index(); w != nil {
convert := settings.Index.Convert && conf.SidecarWritable() convert := settings.Index.Convert && conf.SidecarWritable()
opt := photoprism.NewIndexOptions(subPath, true, convert, true, true, true) opt := photoprism.NewIndexOptions(subPath, true, convert, true, true, true)
indexed = w.Start(opt) indexed = w.Start(opt)
} }
if w := service.Purge(); w != nil { if w := get.Purge(); w != nil {
opt := photoprism.PurgeOptions{ opt := photoprism.PurgeOptions{
Path: subPath, Path: subPath,
Ignore: indexed, Ignore: indexed,
@@ -280,7 +280,7 @@ func facesUpdateAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf) get.SetConfig(conf)
_, cancel := context.WithCancel(context.Background()) _, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@@ -296,7 +296,7 @@ func facesUpdateAction(ctx *cli.Context) error {
Force: ctx.Bool("force"), Force: ctx.Bool("force"),
} }
w := service.Faces() w := get.Faces()
if err := w.Start(opt); err != nil { if err := w.Start(opt); err != nil {
return err return err
@@ -314,7 +314,7 @@ func facesOptimizeAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf) get.SetConfig(conf)
_, cancel := context.WithCancel(context.Background()) _, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@@ -326,7 +326,7 @@ func facesOptimizeAction(ctx *cli.Context) error {
conf.InitDb() conf.InitDb()
defer conf.Shutdown() defer conf.Shutdown()
w := service.Faces() w := get.Faces()
if res, err := w.Optimize(); err != nil { if res, err := w.Optimize(); err != nil {
return err return err

View File

@@ -10,8 +10,8 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "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)) 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) opt := photoprism.ImportOptionsMove(sourcePath, destFolder)
w.Start(opt) w.Start(opt)

View File

@@ -9,8 +9,8 @@ import (
"github.com/dustin/go-humanize/english" "github.com/dustin/go-humanize/english"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
@@ -70,14 +70,14 @@ func indexAction(ctx *cli.Context) error {
var indexed fs.Done var indexed fs.Done
if w := service.Index(); w != nil { if w := get.Index(); w != nil {
convert := conf.Settings().Index.Convert && conf.SidecarWritable() convert := conf.Settings().Index.Convert && conf.SidecarWritable()
opt := photoprism.NewIndexOptions(subPath, ctx.Bool("force"), convert, true, false, !ctx.Bool("archived")) opt := photoprism.NewIndexOptions(subPath, ctx.Bool("force"), convert, true, false, !ctx.Bool("archived"))
indexed = w.Start(opt) indexed = w.Start(opt)
} }
if w := service.Purge(); w != nil { if w := get.Purge(); w != nil {
purgeStart := time.Now() purgeStart := time.Now()
opt := photoprism.PurgeOptions{ opt := photoprism.PurgeOptions{
Path: subPath, Path: subPath,
@@ -93,7 +93,7 @@ func indexAction(ctx *cli.Context) error {
if ctx.Bool("cleanup") { if ctx.Bool("cleanup") {
cleanupStart := time.Now() cleanupStart := time.Now()
w := service.CleanUp() w := get.CleanUp()
opt := photoprism.CleanUpOptions{ opt := photoprism.CleanUpOptions{
Dry: false, Dry: false,

View File

@@ -3,6 +3,8 @@ package commands
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"path/filepath"
"strings" "strings"
"time" "time"
@@ -17,7 +19,7 @@ import (
var MigrationsStatusCommand = cli.Command{ var MigrationsStatusCommand = cli.Command{
Name: "ls", Name: "ls",
Aliases: []string{"status", "show"}, Aliases: []string{"status", "show"},
Usage: "Lists the status of schema migrations", Usage: "Displays the status of schema migrations",
ArgsUsage: "[migrations...]", ArgsUsage: "[migrations...]",
Flags: report.CliFlags, Flags: report.CliFlags,
Action: migrationsStatusAction, Action: migrationsStatusAction,
@@ -81,13 +83,19 @@ func migrationsStatusAction(ctx *cli.Context) error {
} }
// Report columns. // Report columns.
cols := []string{"ID", "Dialect", "Started At", "Finished At", "Status"} cols := []string{"ID", "Dialect", "Stage", "Started At", "Finished At", "Status"}
// Report rows. // Report rows.
rows := make([][]string, 0, len(status)) rows := make([][]string, 0, len(status))
for _, m := range 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() { if m.StartedAt.IsZero() {
started = "-" started = "-"
@@ -113,7 +121,7 @@ func migrationsStatusAction(ctx *cli.Context) error {
info = "Running?" 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. // Display report.
@@ -130,6 +138,10 @@ func migrationsStatusAction(ctx *cli.Context) error {
// migrationsRunAction executes database schema migrations. // migrationsRunAction executes database schema migrations.
func migrationsRunAction(ctx *cli.Context) error { 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() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)

View File

@@ -6,7 +6,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
) )
// MomentsCommand registers the moments command. // MomentsCommand registers the moments command.
@@ -36,7 +36,7 @@ func momentsAction(ctx *cli.Context) error {
log.Infof("config: read-only mode enabled") log.Infof("config: read-only mode enabled")
} }
w := service.Moments() w := get.Moments()
if err := w.Start(); err != nil { if err := w.Start(); err != nil {
return err return err

View File

@@ -9,8 +9,8 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
) )
// PlacesCommand registers the places subcommands. // PlacesCommand registers the places subcommands.
@@ -68,7 +68,7 @@ func placesUpdateAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
// Run places worker. // Run places worker.
if w := service.Places(); w != nil { if w := get.Places(); w != nil {
_, err := w.Start() _, err := w.Start()
if err != nil { if err != nil {
@@ -77,7 +77,7 @@ func placesUpdateAction(ctx *cli.Context) error {
} }
// Run moments worker. // Run moments worker.
if w := service.Moments(); w != nil { if w := get.Moments(); w != nil {
err := w.Start() err := w.Start()
if err != nil { if err != nil {

View File

@@ -9,8 +9,8 @@ import (
"github.com/dustin/go-humanize/english" "github.com/dustin/go-humanize/english"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
@@ -63,7 +63,7 @@ func purgeAction(ctx *cli.Context) error {
log.Infof("config: read-only mode enabled") log.Infof("config: read-only mode enabled")
} }
w := service.Purge() w := get.Purge()
opt := photoprism.PurgeOptions{ opt := photoprism.PurgeOptions{
Path: subPath, Path: subPath,

View File

@@ -8,6 +8,8 @@ import (
"regexp" "regexp"
"time" "time"
"github.com/photoprism/photoprism/internal/migrate"
"github.com/manifoldco/promptui" "github.com/manifoldco/promptui"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@@ -175,7 +177,7 @@ func resetIndexDb(c *config.Config) {
tables.Drop(c.Db()) tables.Drop(c.Db())
log.Infoln("restoring default schema") log.Infoln("restoring default schema")
entity.InitDb(true, false, nil) entity.InitDb(migrate.Opt(false, nil))
// Reset admin account? // Reset admin account?
if c.AdminPassword() == "" { if c.AdminPassword() == "" {

View File

@@ -16,8 +16,8 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )
@@ -198,7 +198,7 @@ func restoreAction(ctx *cli.Context) error {
conf.InitDb() conf.InitDb()
if restoreAlbums { if restoreAlbums {
service.SetConfig(conf) get.SetConfig(conf)
if albumsPath == "" { if albumsPath == "" {
albumsPath = conf.AlbumsPath() albumsPath = conf.AlbumsPath()

View File

@@ -7,7 +7,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/pkg/report" "github.com/photoprism/photoprism/pkg/report"
) )
@@ -30,7 +30,7 @@ var ConfigReports = []Report{
func showConfigAction(ctx *cli.Context) error { func showConfigAction(ctx *cli.Context) error {
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
conf.SetLogLevel(logrus.FatalLevel) conf.SetLogLevel(logrus.FatalLevel)
service.SetConfig(conf) get.SetConfig(conf)
if err := conf.Init(); err != nil { if err := conf.Init(); err != nil {
log.Debug(err) log.Debug(err)

View File

@@ -6,7 +6,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/pkg/clean" "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())) 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 { if err := rs.Start(ctx.Bool("force"), ctx.Bool("originals")); err != nil {
log.Error(err) log.Error(err)

View File

@@ -95,8 +95,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
assert.Equal(t, adminFeatures, f) assert.Equal(t, adminFeatures, f)
expected := customize.FeatureSettings{ expected := customize.FeatureSettings{
Account: false, Account: true,
Advanced: false,
Albums: true, Albums: true,
Archive: true, Archive: true,
Delete: false, Delete: false,
@@ -120,7 +119,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
Search: true, Search: true,
Settings: true, Settings: true,
Share: true, Share: true,
Sync: true, Services: true,
Upload: true, Upload: true,
Videos: true, Videos: true,
} }
@@ -135,7 +134,6 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
expected := customize.FeatureSettings{ expected := customize.FeatureSettings{
Account: false, Account: false,
Advanced: false,
Albums: true, Albums: true,
Archive: false, Archive: false,
Delete: false, Delete: false,
@@ -159,7 +157,7 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
Search: false, Search: false,
Settings: false, Settings: false,
Share: false, Share: false,
Sync: false, Services: false,
Upload: false, Upload: false,
Videos: false, Videos: false,
} }
@@ -180,10 +178,9 @@ func TestConfig_ClientRoleConfig(t *testing.T) {
assert.False(t, f.Settings) assert.False(t, f.Settings)
assert.False(t, f.Edit) assert.False(t, f.Edit)
assert.False(t, f.Private) assert.False(t, f.Private)
assert.False(t, f.Advanced)
assert.False(t, f.Upload) assert.False(t, f.Upload)
assert.False(t, f.Download) assert.False(t, f.Download)
assert.False(t, f.Sync) assert.False(t, f.Services)
assert.False(t, f.Delete) assert.False(t, f.Delete)
assert.False(t, f.Import) assert.False(t, f.Import)
assert.False(t, f.Library) assert.False(t, f.Library)
@@ -223,10 +220,9 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.True(t, f.Settings) assert.True(t, f.Settings)
assert.True(t, f.Edit) assert.True(t, f.Edit)
assert.True(t, f.Private) assert.True(t, f.Private)
assert.False(t, f.Advanced)
assert.True(t, f.Upload) assert.True(t, f.Upload)
assert.True(t, f.Download) assert.True(t, f.Download)
assert.True(t, f.Sync) assert.True(t, f.Services)
assert.False(t, f.Delete) assert.False(t, f.Delete)
assert.True(t, f.Import) assert.True(t, f.Import)
assert.True(t, f.Library) assert.True(t, f.Library)
@@ -255,10 +251,9 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.False(t, f.Settings) assert.False(t, f.Settings)
assert.False(t, f.Edit) assert.False(t, f.Edit)
assert.False(t, f.Private) assert.False(t, f.Private)
assert.False(t, f.Advanced)
assert.False(t, f.Upload) assert.False(t, f.Upload)
assert.True(t, f.Download) assert.True(t, f.Download)
assert.False(t, f.Sync) assert.False(t, f.Services)
assert.False(t, f.Delete) assert.False(t, f.Delete)
assert.False(t, f.Import) assert.False(t, f.Import)
assert.False(t, f.Library) assert.False(t, f.Library)
@@ -288,10 +283,9 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.False(t, f.Settings) assert.False(t, f.Settings)
assert.False(t, f.Edit) assert.False(t, f.Edit)
assert.False(t, f.Private) assert.False(t, f.Private)
assert.False(t, f.Advanced)
assert.False(t, f.Upload) assert.False(t, f.Upload)
assert.False(t, f.Download) assert.False(t, f.Download)
assert.False(t, f.Sync) assert.False(t, f.Services)
assert.False(t, f.Delete) assert.False(t, f.Delete)
assert.False(t, f.Import) assert.False(t, f.Import)
assert.False(t, f.Library) assert.False(t, f.Library)
@@ -317,10 +311,9 @@ func TestConfig_ClientSessionConfig(t *testing.T) {
assert.True(t, f.Settings) assert.True(t, f.Settings)
assert.True(t, f.Edit) assert.True(t, f.Edit)
assert.True(t, f.Private) assert.True(t, f.Private)
assert.False(t, f.Advanced)
assert.True(t, f.Upload) assert.True(t, f.Upload)
assert.True(t, f.Download) assert.True(t, f.Download)
assert.True(t, f.Sync) assert.True(t, f.Services)
assert.False(t, f.Delete) assert.False(t, f.Delete)
assert.True(t, f.Import) assert.True(t, f.Import)
assert.True(t, f.Library) assert.True(t, f.Library)

View File

@@ -10,6 +10,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/photoprism/photoprism/internal/migrate"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql" _ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite" _ "github.com/jinzhu/gorm/dialects/sqlite"
@@ -281,7 +283,7 @@ func (c *Config) InitDb() {
// MigrateDb initializes the database and migrates the schema if needed. // MigrateDb initializes the database and migrates the schema if needed.
func (c *Config) MigrateDb(runFailed bool, ids []string) { func (c *Config) MigrateDb(runFailed bool, ids []string) {
entity.Admin.UserName = c.AdminUser() entity.Admin.UserName = c.AdminUser()
entity.InitDb(true, runFailed, ids) entity.InitDb(migrate.Opt(runFailed, ids))
// Init admin account? // Init admin account?
if c.AdminPassword() == "" { if c.AdminPassword() == "" {

Some files were not shown because too many files have changed in this diff Show More