Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
fc62279c0a
commit
03ec4b586d
150 changed files with 2499 additions and 1443 deletions
13
Makefile
13
Makefile
|
@ -14,7 +14,9 @@ all: dep build
|
|||
dep: dep-tensorflow dep-js dep-go
|
||||
build: generate build-js build-go
|
||||
install: install-bin install-assets
|
||||
test: reset-test-db test-js test-go
|
||||
test: test-js test-go
|
||||
test-go: reset-test-db run-test-go
|
||||
test-short: reset-test-db run-test-short
|
||||
acceptance-all: start acceptance acceptance-firefox stop
|
||||
test-all: test acceptance-all
|
||||
fmt: fmt-js fmt-go
|
||||
|
@ -91,8 +93,12 @@ acceptance-firefox:
|
|||
$(info Running JS acceptance tests in Firefox...)
|
||||
(cd frontend && npm run acceptance-firefox)
|
||||
reset-test-db:
|
||||
$(info Purging test database...)
|
||||
mysql < scripts/reset-test-db.sql
|
||||
test-go:
|
||||
run-test-short:
|
||||
$(info Running short Go unit tests in parallel mode...)
|
||||
$(GOTEST) -parallel 2 -count 1 -cpu 2 -short -timeout 5m ./pkg/... ./internal/...
|
||||
run-test-go:
|
||||
$(info Running all Go unit tests...)
|
||||
$(GOTEST) -parallel 1 -count 1 -cpu 1 -tags slow -timeout 20m ./pkg/... ./internal/...
|
||||
test-parallel:
|
||||
|
@ -101,9 +107,6 @@ test-parallel:
|
|||
test-verbose:
|
||||
$(info Running all Go unit tests in verbose mode...)
|
||||
$(GOTEST) -parallel 1 -count 1 -cpu 1 -tags slow -timeout 20m -v ./pkg/... ./internal/...
|
||||
test-short:
|
||||
$(info Running short Go unit tests in parallel mode...)
|
||||
$(GOTEST) -parallel 2 -count 1 -cpu 2 -short -timeout 5m ./pkg/... ./internal/...
|
||||
test-race:
|
||||
$(info Running all Go unit tests with race detection in verbose mode...)
|
||||
$(GOTEST) -tags slow -race -timeout 60m -v ./pkg/... ./internal/...
|
||||
|
|
|
@ -5,33 +5,33 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>{{ .clientConfig.title }}</title>
|
||||
<title>{{ .config.title }}</title>
|
||||
|
||||
<meta property="og:title" content="{{ .clientConfig.title }}: {{ .clientConfig.subtitle }}"/>
|
||||
<meta property="og:image" content="{{ .clientConfig.url }}api/v1/preview"/>
|
||||
<meta property="og:url" content="{{ .clientConfig.url }}"/>
|
||||
<meta property="og:description" content="{{ .clientConfig.description }}"/>
|
||||
<meta property="og:title" content="{{ .config.title }}: {{ .config.subtitle }}"/>
|
||||
<meta property="og:image" content="{{ .config.url }}api/v1/preview"/>
|
||||
<meta property="og:url" content="{{ .config.url }}"/>
|
||||
<meta property="og:description" content="{{ .config.description }}"/>
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:title" content="{{ .clientConfig.title }}: {{ .clientConfig.subtitle }}"/>
|
||||
<meta name="twitter:description" content="{{ .clientConfig.description }}"/>
|
||||
<meta name="twitter:image" content="{{ .clientConfig.url }}api/v1/preview"/>
|
||||
<meta name="twitter:title" content="{{ .config.title }}: {{ .config.subtitle }}"/>
|
||||
<meta name="twitter:description" content="{{ .config.description }}"/>
|
||||
<meta name="twitter:image" content="{{ .config.url }}api/v1/preview"/>
|
||||
|
||||
<meta name="author" content="{{ .clientConfig.author }}">
|
||||
<meta name="description" content="{{ .clientConfig.description }}"/>
|
||||
<meta name="author" content="{{ .config.author }}">
|
||||
<meta name="description" content="{{ .config.description }}"/>
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/static/favicons/favicon.png">
|
||||
<link rel="icon" type="image/png" href="/static/favicons/favicon.png"/>
|
||||
|
||||
<link rel="stylesheet" href="/static/build/app.css?{{ .clientConfig.cssHash }}">
|
||||
<link rel="stylesheet" href="/static/build/app.css?{{ .config.cssHash }}">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
|
||||
<script>
|
||||
window.clientConfig = {{ .clientConfig }};
|
||||
window.__CONFIG__ = {{ .config }};
|
||||
</script>
|
||||
</head>
|
||||
<body class="{{ .clientConfig.flags }}">
|
||||
<body class="{{ .config.flags }}">
|
||||
<!--[if lt IE 8]>
|
||||
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade
|
||||
your browser</a> to improve your experience.</p>
|
||||
|
@ -44,6 +44,6 @@
|
|||
|
||||
<div id="p-busy-overlay"></div>
|
||||
|
||||
<script src="/static/build/app.js?{{ .clientConfig.jsHash }}"></script>
|
||||
<script src="/static/build/app.js?{{ .config.jsHash }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,20 +5,20 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>{{ .clientConfig.title }}</title>
|
||||
<title>{{ .config.title }}</title>
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/static/favicons/favicon.png">
|
||||
<link rel="icon" type="image/png" href="/static/favicons/favicon.png"/>
|
||||
|
||||
<link rel="stylesheet" href="/static/build/app.css?{{ .clientConfig.cssHash }}">
|
||||
<link rel="stylesheet" href="/static/build/app.css?{{ .config.cssHash }}">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
|
||||
<script>
|
||||
window.clientConfig = {{ .clientConfig }};
|
||||
window.__CONFIG__ = {{ .config }};
|
||||
</script>
|
||||
</head>
|
||||
<body class="{{ .clientConfig.flags }}">
|
||||
<body class="{{ .config.flags }}">
|
||||
<!--[if lt IE 8]>
|
||||
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade
|
||||
your browser</a> to improve your experience.</p>
|
||||
|
@ -31,6 +31,6 @@
|
|||
|
||||
<div id="p-busy-overlay"></div>
|
||||
|
||||
<script src="/static/build/app.js?{{ .clientConfig.jsHash }}"></script>
|
||||
<script src="/static/build/app.js?{{ .config.jsHash }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,7 +2,7 @@ import Axios from "axios";
|
|||
import Notify from "common/notify";
|
||||
|
||||
const testConfig = {"jsHash": "test", "version": "test"};
|
||||
const config = window.clientConfig ? window.clientConfig : testConfig;
|
||||
const config = window.__CONFIG__ ? window.__CONFIG__ : testConfig;
|
||||
|
||||
const Api = Axios.create({
|
||||
baseURL: "/api/v1",
|
||||
|
|
|
@ -81,7 +81,7 @@ class Clipboard {
|
|||
return;
|
||||
}
|
||||
|
||||
let rangeStart = models.findIndex((photo) => photo.PhotoUUID === this.lastId);
|
||||
let rangeStart = models.findIndex((photo) => photo.UID === this.lastId);
|
||||
|
||||
if(rangeStart === -1) {
|
||||
this.toggle(models[rangeEnd]);
|
||||
|
|
|
@ -57,9 +57,18 @@ class Config {
|
|||
case "videos":
|
||||
this.values.count.videos += data.count;
|
||||
break;
|
||||
case "folders":
|
||||
this.values.count.folders += data.count;
|
||||
break;
|
||||
case "moments":
|
||||
this.values.count.moments += data.count;
|
||||
break;
|
||||
case "favorites":
|
||||
this.values.count.favorites += data.count;
|
||||
break;
|
||||
case "review":
|
||||
this.values.count.review += data.count;
|
||||
break;
|
||||
case "private":
|
||||
this.values.count.private += data.count;
|
||||
break;
|
||||
|
|
|
@ -136,9 +136,9 @@ export default class Session {
|
|||
sendClientInfo() {
|
||||
const clientInfo = {
|
||||
"session": this.getToken(),
|
||||
"js": window.clientConfig.jsHash,
|
||||
"css": window.clientConfig.cssHash,
|
||||
"version": window.clientConfig.version,
|
||||
"js": window.__CONFIG__.jsHash,
|
||||
"css": window.__CONFIG__.cssHash,
|
||||
"version": window.__CONFIG__.version,
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
@ -3,7 +3,7 @@ import PhotoSwipeUI_Default from "photoswipe/dist/photoswipe-ui-default.js";
|
|||
import Event from "pubsub-js";
|
||||
import stripHtml from "string-strip-html";
|
||||
|
||||
const thumbs = window.clientConfig.thumbnails;
|
||||
const thumbs = window.__CONFIG__.thumbnails;
|
||||
|
||||
class Viewer {
|
||||
constructor() {
|
||||
|
|
|
@ -9,6 +9,7 @@ import PPhotoList from "./p-photo-list.vue";
|
|||
import PPhotoSearch from "./p-photo-search.vue";
|
||||
import PPhotoClipboard from "./p-photo-clipboard.vue";
|
||||
import PLabelClipboard from "./p-label-clipboard.vue";
|
||||
import PFolderClipboard from "./p-folder-clipboard.vue";
|
||||
import PAlbumClipboard from "./p-album-clipboard.vue";
|
||||
import PAlbumToolbar from "./p-album-toolbar.vue";
|
||||
import PScrollTop from "./p-scroll-top.vue";
|
||||
|
@ -27,6 +28,7 @@ components.install = (Vue) => {
|
|||
Vue.component("p-photo-search", PPhotoSearch);
|
||||
Vue.component("p-photo-clipboard", PPhotoClipboard);
|
||||
Vue.component("p-label-clipboard", PLabelClipboard);
|
||||
Vue.component("p-folder-clipboard", PFolderClipboard);
|
||||
Vue.component("p-album-clipboard", PAlbumClipboard);
|
||||
Vue.component("p-album-toolbar", PAlbumToolbar);
|
||||
Vue.component("p-scroll-top", PScrollTop);
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
@submit.prevent="filterChange">
|
||||
<v-toolbar flat color="secondary">
|
||||
<v-edit-dialog
|
||||
:return-value.sync="album.AlbumName"
|
||||
:return-value.sync="album.Name"
|
||||
lazy
|
||||
@save="updateAlbum()"
|
||||
class="p-inline-edit">
|
||||
<v-toolbar-title>
|
||||
{{ album.AlbumName }}
|
||||
{{ album.Name }}
|
||||
</v-toolbar-title>
|
||||
<template v-slot:input>
|
||||
<v-text-field
|
||||
v-model="album.AlbumName"
|
||||
v-model="album.Name"
|
||||
:rules="[titleRule]"
|
||||
:label="labels.name"
|
||||
color="secondary-dark"
|
||||
|
@ -67,8 +67,8 @@
|
|||
:label="labels.country"
|
||||
flat solo hide-details
|
||||
color="secondary-dark"
|
||||
item-value="code"
|
||||
item-text="name"
|
||||
item-value="ID"
|
||||
item-text="Name"
|
||||
v-model="filter.country"
|
||||
:items="options.countries">
|
||||
</v-select>
|
||||
|
@ -79,7 +79,7 @@
|
|||
flat solo hide-details
|
||||
color="secondary-dark"
|
||||
item-value="ID"
|
||||
item-text="CameraModel"
|
||||
item-text="Model"
|
||||
v-model="filter.camera"
|
||||
:items="options.cameras">
|
||||
</v-select>
|
||||
|
@ -111,7 +111,7 @@
|
|||
:key="growDesc"
|
||||
color="secondary-dark"
|
||||
style="background-color: white"
|
||||
v-model="album.AlbumDescription"
|
||||
v-model="album.Description"
|
||||
@change="updateAlbum">
|
||||
</v-textarea>
|
||||
</v-flex>
|
||||
|
@ -133,11 +133,11 @@
|
|||
data() {
|
||||
const cameras = [{
|
||||
ID: 0,
|
||||
CameraModel: this.$gettext('All Cameras')
|
||||
Model: this.$gettext('All Cameras')
|
||||
}].concat(this.$config.get('cameras'));
|
||||
const countries = [{
|
||||
code: '',
|
||||
name: this.$gettext('All Countries')
|
||||
ID: '',
|
||||
Name: this.$gettext('All Countries')
|
||||
}].concat(this.$config.get('countries'));
|
||||
|
||||
return {
|
||||
|
|
121
frontend/src/component/p-folder-clipboard.vue
Normal file
121
frontend/src/component/p-folder-clipboard.vue
Normal file
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-container fluid class="pa-0" v-if="selection.length > 0">
|
||||
<v-speed-dial
|
||||
fixed
|
||||
bottom
|
||||
right
|
||||
direction="top"
|
||||
v-model="expanded"
|
||||
transition="slide-y-reverse-transition"
|
||||
class="p-clipboard p-folder-clipboard"
|
||||
id="t-clipboard"
|
||||
>
|
||||
<v-btn
|
||||
slot="activator"
|
||||
color="accent darken-2"
|
||||
dark
|
||||
fab
|
||||
class="p-folder-clipboard-menu"
|
||||
>
|
||||
<v-icon v-if="selection.length === 0">menu</v-icon>
|
||||
<span v-else class="t-clipboard-count">{{ selection.length }}</span>
|
||||
</v-btn>
|
||||
|
||||
<!-- v-btn
|
||||
fab
|
||||
dark
|
||||
small
|
||||
:title="labels.download"
|
||||
color="download"
|
||||
@click.stop="download()"
|
||||
class="p-label-clipboard-download"
|
||||
:disabled="selection.length !== 1"
|
||||
>
|
||||
<v-icon>cloud_download</v-icon>
|
||||
</v-btn -->
|
||||
<v-btn
|
||||
fab
|
||||
dark
|
||||
small
|
||||
:title="labels.addToAlbum"
|
||||
color="album"
|
||||
:disabled="selection.length === 0"
|
||||
@click.stop="dialog.album = true"
|
||||
class="p-photo-clipboard-album"
|
||||
>
|
||||
<v-icon>folder</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
fab
|
||||
dark
|
||||
small
|
||||
color="accent"
|
||||
@click.stop="clearClipboard()"
|
||||
class="p-folder-clipboard-clear"
|
||||
>
|
||||
<v-icon>clear</v-icon>
|
||||
</v-btn>
|
||||
</v-speed-dial>
|
||||
</v-container>
|
||||
<p-photo-album-dialog :show="dialog.album" @cancel="dialog.album = false"
|
||||
@confirm="addToAlbum"></p-photo-album-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Api from "common/api";
|
||||
import Notify from "common/notify";
|
||||
|
||||
export default {
|
||||
name: 'p-folder-clipboard',
|
||||
props: {
|
||||
selection: Array,
|
||||
refresh: Function,
|
||||
clearSelection: Function,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
dialog: {
|
||||
album: false,
|
||||
edit: false,
|
||||
},
|
||||
labels: {
|
||||
download: this.$gettext("Download"),
|
||||
addToAlbum: this.$gettext("Add to album"),
|
||||
removeFromAlbum: this.$gettext("Remove"),
|
||||
},
|
||||
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clearClipboard() {
|
||||
this.clearSelection();
|
||||
this.expanded = false;
|
||||
},
|
||||
addToAlbum(ppid) {
|
||||
this.dialog.album = false;
|
||||
|
||||
Api.post(`albums/${ppid}/photos`, {"folders": this.selection}).then(() => this.onAdded());
|
||||
},
|
||||
onAdded() {
|
||||
this.clearClipboard();
|
||||
},
|
||||
download() {
|
||||
if(this.selection.length !== 1) {
|
||||
Notify.error(this.$gettext("You can only download one folder"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.onDownload(`/api/v1/folders/${this.selection[0]}/download`);
|
||||
|
||||
this.expanded = false;
|
||||
},
|
||||
onDownload(path) {
|
||||
Notify.success(this.$gettext("Downloading..."));
|
||||
window.open(path, "_blank");
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -110,10 +110,10 @@
|
|||
this.clearSelection();
|
||||
this.expanded = false;
|
||||
},
|
||||
addToAlbum(albumUUID) {
|
||||
addToAlbum(ppid) {
|
||||
this.dialog.album = false;
|
||||
|
||||
Api.post(`albums/${albumUUID}/photos`, {"labels": this.selection}).then(() => this.onAdded());
|
||||
Api.post(`albums/${ppid}/photos`, {"labels": this.selection}).then(() => this.onAdded());
|
||||
},
|
||||
onAdded() {
|
||||
this.clearClipboard();
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>{{ $gettext('Photos') }}</v-list-tile-title>
|
||||
<v-list-tile-title><translate key="Photos">Photos</translate></v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
|
@ -63,7 +63,7 @@
|
|||
<v-list-tile slot="activator" to="/photos" @click.stop="" class="p-navigation-photos">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Photos') }}</span>
|
||||
<translate key="Photos">Photos</translate>
|
||||
<span v-if="config.count.photos > 0" class="p-navigation-count">{{ config.count.photos }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
|
@ -71,19 +71,22 @@
|
|||
|
||||
<v-list-tile :to="{name: 'photos', query: { q: 'mono:true quality:3 photo:true' }}" :exact="true" @click="">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>{{ $gettext('Monochrome') }}</v-list-tile-title>
|
||||
<v-list-tile-title><translate key="Monochrome">Monochrome</translate></v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/review" @click="" v-if="$config.feature('review')">
|
||||
<v-list-tile to="/review" @click="" v-if="$config.feature('review') && config.count.review > 0">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>{{ $gettext('Review') }}</v-list-tile-title>
|
||||
<v-list-tile-title>
|
||||
<translate key="Review">Review</translate>
|
||||
<span v-show="config.count.review > 0" class="p-navigation-count">{{ config.count.review }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/archive" @click="" class="p-navigation-archive" v-show="$config.feature('archive')">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>{{ $gettext('Archive') }}</v-list-tile-title>
|
||||
<v-list-tile-title><translate key="Archive">Archive</translate></v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</v-list-group>
|
||||
|
@ -95,7 +98,7 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Favorites') }}</span>
|
||||
<translate key="Favorites">Favorites</translate>
|
||||
<span v-show="config.count.favorites > 0" class="p-navigation-count">{{ config.count.favorites }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
|
@ -108,7 +111,7 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Private') }}</span>
|
||||
<translate key="Private">Private</translate>
|
||||
<span v-show="config.count.private > 0" class="p-navigation-count">{{ config.count.private }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
|
@ -121,7 +124,7 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Videos') }}</span>
|
||||
<translate key="Videos">Videos</translate>
|
||||
<span v-show="config.count.videos > 0" class="p-navigation-count">{{ config.count.videos }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
|
@ -134,45 +137,65 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Albums') }}</span>
|
||||
<translate key="Albums">Albums</translate>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-group v-if="!mini" prepend-icon="folder" no-action :append-icon="albumExpandIcon">
|
||||
<v-list-group v-if="!mini" prepend-icon="folder" no-action>
|
||||
<v-list-tile slot="activator" to="/albums" @click.stop="">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Albums') }}</span>
|
||||
<translate key="Albums">Albums</translate>
|
||||
<span v-if="config.count.albums > 0" class="p-navigation-count">{{ config.count.albums }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/folders" @click="" class="p-navigation-folders" v-show="$config.feature('folders')">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<translate key="Folders">Folders</translate>
|
||||
<span v-show="config.count.folders > 0" class="p-navigation-count">{{ config.count.folders }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile v-for="(album, index) in config.albums"
|
||||
:key="index"
|
||||
:to="{ name: 'album', params: { uuid: album.AlbumUUID, slug: album.AlbumSlug } }">
|
||||
:to="{ name: 'album', params: { uid: album.UID, slug: album.Slug } }">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title v-if="album.AlbumName">{{ album.AlbumName }}</v-list-tile-title>
|
||||
<v-list-tile-title v-if="album.Name">{{ album.Name }}</v-list-tile-title>
|
||||
<v-list-tile-title v-else>Untitled</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</v-list-group>
|
||||
|
||||
<v-list-tile to="/labels" @click="" class="p-navigation-labels" v-show="$config.feature('labels')">
|
||||
<v-list-tile :to="{ name: 'moments' }" @click="" class="p-navigation-moments"
|
||||
v-show="config.experimental && $config.feature('moments')">
|
||||
<v-list-tile-action>
|
||||
<v-icon>label</v-icon>
|
||||
<v-icon>star</v-icon>
|
||||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Labels') }}</span>
|
||||
<span v-show="config.count.labels > 0"
|
||||
class="p-navigation-count">{{ config.count.labels }}</span>
|
||||
<translate key="Moments">Moments</translate>
|
||||
<span v-show="config.count.moments > 0"
|
||||
class="p-navigation-count">{{ config.count.moments }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<!-- v-list-tile to="/events" @click="" class="p-navigation-events">
|
||||
<v-list-tile-action>
|
||||
<v-icon>date_range</v-icon>
|
||||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>Events</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile -->
|
||||
|
||||
<v-list-tile :to="{ name: 'places' }" @click="" class="p-navigation-places"
|
||||
v-show="$config.feature('places')">
|
||||
<v-list-tile-action>
|
||||
|
@ -181,7 +204,7 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Places') }}</span>
|
||||
<translate key="Places">Places</translate>
|
||||
<span v-show="config.count.places > 0"
|
||||
class="p-navigation-count">{{ config.count.places }}</span>
|
||||
</v-list-tile-title>
|
||||
|
@ -220,6 +243,34 @@
|
|||
</v-list-tile-content>
|
||||
</v-list-tile -->
|
||||
|
||||
|
||||
<!-- v-list-tile to="/folders" @click="" class="p-navigation-folders">
|
||||
<v-list-tile-action>
|
||||
<v-icon>sd_storage</v-icon>
|
||||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<translate key="Folders">Folders</translate>
|
||||
<span v-show="config.count.folders > 0" class="p-navigation-count">{{ config.count.folders }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile -->
|
||||
|
||||
<v-list-tile to="/labels" @click="" class="p-navigation-labels" v-show="$config.feature('labels')">
|
||||
<v-list-tile-action>
|
||||
<v-icon>label</v-icon>
|
||||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<translate key="Labels">Labels</translate>
|
||||
<span v-show="config.count.labels > 0"
|
||||
class="p-navigation-count">{{ config.count.labels }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/library" @click="" class="p-navigation-library">
|
||||
<v-list-tile-action>
|
||||
<v-icon>camera_roll</v-icon>
|
||||
|
@ -227,7 +278,7 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Library') }}</span>
|
||||
<translate key="Originals">Originals</translate>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
@ -239,7 +290,7 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Settings') }}</span>
|
||||
<translate key="Settings">Settings</translate>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
@ -251,7 +302,7 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Logout') }}</span>
|
||||
<translate key="Logout">Logout</translate>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
@ -263,7 +314,7 @@
|
|||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<span>{{ $gettext('Login') }}</span>
|
||||
<translate key="Login">Login</translate>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
@ -330,7 +381,7 @@
|
|||
},
|
||||
createAlbum() {
|
||||
let name = "New Album";
|
||||
const album = new Album({AlbumName: name, AlbumFavorite: true});
|
||||
const album = new Album({Name: name, Favorite: true});
|
||||
album.save();
|
||||
},
|
||||
logout() {
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
ma-0
|
||||
class="p-photo-live"
|
||||
style="overflow: hidden;"
|
||||
v-if="photo.PhotoType === 'live'"
|
||||
v-if="photo.Type === 'live'"
|
||||
v-show="hover"
|
||||
>
|
||||
<video width="500" height="500" autoplay loop muted playsinline>
|
||||
|
@ -62,7 +62,7 @@
|
|||
</video>
|
||||
</v-layout>
|
||||
|
||||
<v-btn v-if="hidePrivate && photo.PhotoPrivate" :ripple="false"
|
||||
<v-btn v-if="hidePrivate && photo.Private" :ripple="false"
|
||||
icon flat large absolute
|
||||
class="p-photo-private opacity-75">
|
||||
<v-icon color="white">lock</v-icon>
|
||||
|
@ -79,14 +79,14 @@
|
|||
</v-btn>
|
||||
|
||||
<v-btn icon flat large absolute :ripple="false"
|
||||
:class="photo.PhotoFavorite ? 'p-photo-like opacity-75' : 'p-photo-like opacity-50'"
|
||||
:class="photo.Favorite ? 'p-photo-like opacity-75' : 'p-photo-like opacity-50'"
|
||||
@click.stop.prevent="photo.toggleLike()">
|
||||
<v-icon v-if="photo.PhotoFavorite" color="white" class="t-like t-on">favorite</v-icon>
|
||||
<v-icon v-if="photo.Favorite" color="white" class="t-like t-on">favorite</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-like t-off">favorite_border</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<template v-if="photo.isPlayable()">
|
||||
<v-btn v-if="photo.PhotoType === 'live'" :ripple="false"
|
||||
<v-btn v-if="photo.Type === 'live'" :ripple="false"
|
||||
icon flat large absolute class="p-photo-live opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, true)" title="Live Photo">
|
||||
<v-icon color="white" class="action-play">adjust</v-icon>
|
||||
|
@ -97,12 +97,12 @@
|
|||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-btn v-else-if="photo.PhotoType === 'image' && photo.Files.length > 1" :ripple="false"
|
||||
<v-btn v-else-if="photo.Type === 'image' && photo.Files.length > 1" :ripple="false"
|
||||
icon flat large absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.PhotoType === 'raw'" :ripple="false"
|
||||
<v-btn v-else-if="photo.Type === 'raw'" :ripple="false"
|
||||
icon flat large absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, true)" title="RAW">
|
||||
<v-icon color="white" class="action-burst">photo_camera</v-icon>
|
||||
|
@ -111,22 +111,22 @@
|
|||
|
||||
<v-card-title primary-title class="pa-3 p-photo-desc" style="user-select: none;">
|
||||
<div>
|
||||
<h3 class="body-2 mb-2" :title="photo.PhotoTitle">
|
||||
<h3 class="body-2 mb-2" :title="photo.Title">
|
||||
<button @click.exact="editPhoto(index)">
|
||||
{{ photo.PhotoTitle | truncate(80) }}
|
||||
{{ photo.Title | truncate(80) }}
|
||||
</button>
|
||||
</h3>
|
||||
<div class="caption mb-2" v-if="photo.PhotoDescription" title="Description">
|
||||
{{ photo.PhotoDescription }}
|
||||
<div class="caption mb-2" v-if="photo.Description" title="Description">
|
||||
{{ photo.Description }}
|
||||
</div>
|
||||
<div class="caption">
|
||||
<button @click.exact="editPhoto(index)">
|
||||
<v-icon size="14" title="Taken">date_range</v-icon>
|
||||
{{ photo.getDateString() }}
|
||||
</button>
|
||||
<template v-if="!photo.PhotoDescription">
|
||||
<template v-if="!photo.Description">
|
||||
<br/>
|
||||
<button v-if="photo.PhotoType === 'video'" @click.exact="openPhoto(index, true)"
|
||||
<button v-if="photo.Type === 'video'" @click.exact="openPhoto(index, true)"
|
||||
title="Video">
|
||||
<v-icon size="14">movie</v-icon>
|
||||
{{ photo.getVideoInfo() }}
|
||||
|
@ -187,7 +187,7 @@
|
|||
downloadFile(index) {
|
||||
const photo = this.photos[index];
|
||||
const link = document.createElement('a')
|
||||
link.href = "/api/v1/download/" + photo.FileHash;
|
||||
link.href = "/api/v1/download/" + photo.Hash;
|
||||
link.download = photo.FileName;
|
||||
link.click()
|
||||
},
|
||||
|
|
|
@ -209,10 +209,10 @@
|
|||
Notify.success(this.$gettext("Photos restored"));
|
||||
this.clearClipboard();
|
||||
},
|
||||
addToAlbum(albumUUID) {
|
||||
addToAlbum(ppid) {
|
||||
this.dialog.album = false;
|
||||
|
||||
Api.post(`albums/${albumUUID}/photos`, {"photos": this.selection}).then(() => this.onAdded());
|
||||
Api.post(`albums/${ppid}/photos`, {"photos": this.selection}).then(() => this.onAdded());
|
||||
},
|
||||
onAdded() {
|
||||
this.clearClipboard();
|
||||
|
@ -223,11 +223,11 @@
|
|||
return
|
||||
}
|
||||
|
||||
const albumUUID = this.album.AlbumUUID;
|
||||
const uid = this.album.UID;
|
||||
|
||||
this.dialog.album = false;
|
||||
|
||||
Api.delete(`albums/${albumUUID}/photos`, {"data": {"photos": this.selection}}).then(() => this.onRemoved());
|
||||
Api.delete(`albums/${uid}/photos`, {"data": {"photos": this.selection}}).then(() => this.onRemoved());
|
||||
},
|
||||
onRemoved() {
|
||||
this.clearClipboard();
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
flat icon large absolute class="p-photo-select">
|
||||
<v-icon color="white" class="t-select t-on">check_circle</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="!selection.length && props.item.PhotoType === 'video' && props.item.isPlayable()" :ripple="false"
|
||||
<v-btn v-else-if="!selection.length && props.item.Type === 'video' && props.item.isPlayable()" :ripple="false"
|
||||
flat icon large absolute class="p-photo-play opacity-75"
|
||||
@click.stop.prevent="openPhoto(props.index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
|
@ -40,7 +40,7 @@
|
|||
</v-img>
|
||||
</td>
|
||||
<td class="p-photo-desc p-pointer" @click.exact="editPhoto(props.index)" style="user-select: none;">
|
||||
{{ props.item.PhotoTitle }}
|
||||
{{ props.item.Title }}
|
||||
</td>
|
||||
<td class="p-photo-desc hidden-xs-only" :title="props.item.getDateString()">
|
||||
<button @click.stop.prevent="editPhoto(props.index)" style="user-select: none;">
|
||||
|
@ -68,12 +68,12 @@
|
|||
<td class="text-xs-center">
|
||||
<v-btn v-if="hidePrivate" class="p-photo-private" icon small flat :ripple="false"
|
||||
@click.stop.prevent="props.item.togglePrivate()">
|
||||
<v-icon v-if="props.item.PhotoPrivate" color="secondary-dark">lock</v-icon>
|
||||
<v-icon v-if="props.item.Private" color="secondary-dark">lock</v-icon>
|
||||
<v-icon v-else color="accent lighten-3">lock_open</v-icon>
|
||||
</v-btn>
|
||||
<v-btn class="p-photo-like" icon small flat :ripple="false"
|
||||
@click.stop.prevent="props.item.toggleLike()">
|
||||
<v-icon v-if="props.item.PhotoFavorite" color="pink lighten-3">favorite</v-icon>
|
||||
<v-icon v-if="props.item.Favorite" color="pink lighten-3">favorite</v-icon>
|
||||
<v-icon v-else color="accent lighten-3">favorite_border</v-icon>
|
||||
</v-btn>
|
||||
</td>
|
||||
|
@ -106,7 +106,7 @@
|
|||
'selected': [],
|
||||
'listColumns': [
|
||||
{text: '', value: '', align: 'center', sortable: false, class: 'p-col-select'},
|
||||
{text: this.$gettext('Title'), value: 'PhotoTitle'},
|
||||
{text: this.$gettext('Title'), value: 'Title'},
|
||||
{text: this.$gettext('Taken'), class: 'hidden-xs-only', value: 'TakenAt'},
|
||||
{text: this.$gettext('Camera'), class: 'hidden-sm-and-down', value: 'CameraModel'},
|
||||
{text: showName ? this.$gettext('Name') : this.$gettext('Location'), class: 'hidden-xs-only', value: showName ? 'FileName' : 'LocLabel'},
|
||||
|
@ -139,7 +139,7 @@
|
|||
downloadFile(index) {
|
||||
const photo = this.photos[index];
|
||||
const link = document.createElement('a')
|
||||
link.href = "/api/v1/download/" + photo.FileHash;
|
||||
link.href = "/api/v1/download/" + photo.Hash;
|
||||
link.download = photo.FileName;
|
||||
link.click()
|
||||
},
|
||||
|
@ -166,7 +166,7 @@
|
|||
} else if(this.photos[index]) {
|
||||
let photo = this.photos[index];
|
||||
|
||||
if(photo.PhotoType === 'video' && photo.isPlayable()) {
|
||||
if(photo.Type === 'video' && photo.isPlayable()) {
|
||||
this.openPhoto(index, true);
|
||||
} else {
|
||||
this.openPhoto(index, false);
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<v-card tile slot-scope="{ hover }"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
:class="$clipboard.has(photo) ? 'elevation-10 ma-0' : 'elevation-0 ma-1'"
|
||||
:title="photo.PhotoTitle">
|
||||
:title="photo.Title">
|
||||
<v-img :src="photo.thumbnailUrl('tile_224')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2"
|
||||
|
@ -53,7 +53,7 @@
|
|||
ma-0
|
||||
class="p-photo-live"
|
||||
style="overflow: hidden;"
|
||||
v-if="photo.PhotoType === 'live'"
|
||||
v-if="photo.Type === 'live'"
|
||||
v-show="hover"
|
||||
>
|
||||
<video width="224" height="224" autoplay loop muted playsinline>
|
||||
|
@ -61,7 +61,7 @@
|
|||
</video>
|
||||
</v-layout>
|
||||
|
||||
<v-btn v-if="hidePrivate && photo.PhotoPrivate" :ripple="false"
|
||||
<v-btn v-if="hidePrivate && photo.Private" :ripple="false"
|
||||
icon flat small absolute
|
||||
class="p-photo-private opacity-75">
|
||||
<v-icon color="white">lock</v-icon>
|
||||
|
@ -78,14 +78,14 @@
|
|||
</v-btn>
|
||||
|
||||
<v-btn icon flat small absolute :ripple="false"
|
||||
:class="photo.PhotoFavorite ? 'p-photo-like opacity-75' : 'p-photo-like opacity-50'"
|
||||
:class="photo.Favorite ? 'p-photo-like opacity-75' : 'p-photo-like opacity-50'"
|
||||
@click.stop.prevent="photo.toggleLike()">
|
||||
<v-icon v-if="photo.PhotoFavorite" color="white" class="t-like t-on">favorite</v-icon>
|
||||
<v-icon v-if="photo.Favorite" color="white" class="t-like t-on">favorite</v-icon>
|
||||
<v-icon v-else color="accent lighten-3" class="t-like t-off">favorite_border</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<template v-if="photo.isPlayable()">
|
||||
<v-btn v-if="photo.PhotoType === 'live'" color="white"
|
||||
<v-btn v-if="photo.Type === 'live'" color="white"
|
||||
icon flat small absolute class="p-photo-live opacity-75" :depressed="false" :ripple="false"
|
||||
@click.stop.prevent="openPhoto(index, true)" title="Live Photo">
|
||||
<v-icon color="white" class="action-play">adjust</v-icon>
|
||||
|
@ -96,12 +96,12 @@
|
|||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-btn v-else-if="photo.PhotoType === 'image' && photo.Files.length > 1" :ripple="false"
|
||||
<v-btn v-else-if="photo.Type === 'image' && photo.Files.length > 1" :ripple="false"
|
||||
icon flat small absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
<v-icon color="white" class="action-burst">burst_mode</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else-if="photo.PhotoType === 'raw'" :ripple="false"
|
||||
<v-btn v-else-if="photo.Type === 'raw'" :ripple="false"
|
||||
icon flat small absolute class="p-photo-merged opacity-75"
|
||||
@click.stop.prevent="openPhoto(index, true)" title="RAW">
|
||||
<v-icon color="white" class="action-burst">photo_camera</v-icon>
|
||||
|
|
|
@ -52,8 +52,8 @@
|
|||
:label="labels.country"
|
||||
flat solo hide-details
|
||||
color="secondary-dark"
|
||||
item-value="code"
|
||||
item-text="name"
|
||||
item-value="ID"
|
||||
item-text="Name"
|
||||
v-model="filter.country"
|
||||
:items="countryOptions">
|
||||
</v-select>
|
||||
|
@ -63,8 +63,8 @@
|
|||
:label="labels.year"
|
||||
flat solo hide-details
|
||||
color="secondary-dark"
|
||||
item-value="year"
|
||||
item-text="label"
|
||||
item-value="Year"
|
||||
item-text="Name"
|
||||
v-model="filter.year"
|
||||
:items="yearOptions">
|
||||
</v-select>
|
||||
|
@ -94,7 +94,7 @@
|
|||
flat solo hide-details
|
||||
color="secondary-dark"
|
||||
item-value="ID"
|
||||
item-text="CameraModel"
|
||||
item-text="Model"
|
||||
v-model="filter.camera"
|
||||
:items="cameraOptions">
|
||||
</v-select>
|
||||
|
@ -105,7 +105,7 @@
|
|||
flat solo hide-details
|
||||
color="secondary-dark"
|
||||
item-value="ID"
|
||||
item-text="LensModel"
|
||||
item-text="Model"
|
||||
v-model="filter.lens"
|
||||
:items="lensOptions">
|
||||
</v-select>
|
||||
|
@ -115,8 +115,8 @@
|
|||
:label="labels.color"
|
||||
flat solo hide-details
|
||||
color="secondary-dark"
|
||||
item-value="name"
|
||||
item-text="label"
|
||||
item-value="Slug"
|
||||
item-text="Name"
|
||||
v-model="filter.color"
|
||||
:items="colorOptions">
|
||||
</v-select>
|
||||
|
@ -126,8 +126,8 @@
|
|||
:label="labels.category"
|
||||
flat solo hide-details
|
||||
color="secondary-dark"
|
||||
item-value="LabelName"
|
||||
item-text="Title"
|
||||
item-value="Slug"
|
||||
item-text="Name"
|
||||
v-model="filter.label"
|
||||
:items="categoryOptions">
|
||||
</v-select>
|
||||
|
@ -154,11 +154,11 @@
|
|||
config: this.$config.values,
|
||||
searchExpanded: false,
|
||||
all: {
|
||||
countries: [{code: "", name: this.$gettext("All Countries")}],
|
||||
cameras: [{ID: 0, CameraModel: this.$gettext("All Cameras")}],
|
||||
lenses: [{ID: 0, LensModel: this.$gettext("All Lenses")}],
|
||||
colors: [{label: this.$gettext("All Colors"), name: ""}],
|
||||
categories: [{LabelName: "", Title: this.$gettext("All Categories")}],
|
||||
countries: [{ID: "", Name: this.$gettext("All Countries")}],
|
||||
cameras: [{ID: 0, Model: this.$gettext("All Cameras")}],
|
||||
lenses: [{ID: 0, Model: this.$gettext("All Lenses")}],
|
||||
colors: [{Slug: "", Name: this.$gettext("All Colors")}],
|
||||
categories: [{Slug: "", Name: this.$gettext("All Categories")}],
|
||||
},
|
||||
options: {
|
||||
'views': [
|
||||
|
@ -208,16 +208,16 @@
|
|||
},
|
||||
yearOptions() {
|
||||
let result = [
|
||||
{"year": 0, "label": this.$gettext("All Years")},
|
||||
{"Year": 0, "Name": this.$gettext("All Years")},
|
||||
];
|
||||
|
||||
if (this.config.years) {
|
||||
for (let i = 0; i < this.config.years.length; i++) {
|
||||
result.push({"year": this.config.years[i], "label": this.config.years[i].toString()});
|
||||
result.push({"Year": this.config.years[i], "Name": this.config.years[i].toString()});
|
||||
}
|
||||
}
|
||||
|
||||
result.push({"year": -1, "label": this.$gettext("Unknown")});
|
||||
result.push({"Year": -1, "Name": this.$gettext("Unknown")});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
playVideo() {
|
||||
if(this.item && this.item.playable) {
|
||||
let photo = new Photo();
|
||||
photo.find(this.item.uuid).then((p) => {
|
||||
photo.find(this.item.uid).then((p) => {
|
||||
this.$modal.show('video', {video: p, album: null});
|
||||
});
|
||||
}
|
||||
|
@ -139,15 +139,15 @@
|
|||
|
||||
// remove duplicates
|
||||
let filtered = g.items.filter(function (p, i, s) {
|
||||
return !(i > 0 && p.uuid === s[i - 1].uuid);
|
||||
return !(i > 0 && p.uid === s[i - 1].uid);
|
||||
});
|
||||
|
||||
let selection = filtered.map((p, i) => {
|
||||
if (g.currItem.uuid === p.uuid) {
|
||||
if (g.currItem.uid === p.uid) {
|
||||
index = i;
|
||||
}
|
||||
|
||||
return p.uuid
|
||||
return p.uid
|
||||
});
|
||||
|
||||
let album = null;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
@import url("viewer.css");
|
||||
@import url("photos.css");
|
||||
@import url("labels.css");
|
||||
@import url("folders.css");
|
||||
|
||||
html,
|
||||
body {
|
||||
|
|
4
frontend/src/css/folders.css
Normal file
4
frontend/src/css/folders.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
#photoprism .p-folders-cards .p-folder-like {
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
}
|
|
@ -66,7 +66,8 @@
|
|||
}
|
||||
|
||||
#photoprism .p-albums-cards .p-album-select,
|
||||
#photoprism .p-labels-cards .p-label-select {
|
||||
#photoprism .p-labels-cards .p-label-select,
|
||||
#photoprism .p-folders-cards .p-folder-select{
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
|
||||
this.model.save().then((a) => {
|
||||
this.loading = false;
|
||||
this.$emit('confirm', a.AlbumUUID);
|
||||
this.$emit('confirm', a.UID);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
:loading="loading"
|
||||
hide-details
|
||||
hide-no-data
|
||||
item-text="AlbumName"
|
||||
item-value="AlbumUUID"
|
||||
item-text="Name"
|
||||
item-value="UID"
|
||||
:label="labels.select"
|
||||
color="secondary-dark"
|
||||
flat solo
|
||||
|
@ -73,7 +73,7 @@
|
|||
|
||||
this.newAlbum.save().then((a) => {
|
||||
this.loading = false;
|
||||
this.$emit('confirm', a.AlbumUUID);
|
||||
this.$emit('confirm', a.UID);
|
||||
});
|
||||
} else {
|
||||
this.$emit('confirm', this.album);
|
||||
|
@ -96,7 +96,7 @@
|
|||
this.loading = false;
|
||||
|
||||
if(response.models.length > 0 && !this.album) {
|
||||
this.album = response.models[0].AlbumUUID;
|
||||
this.album = response.models[0].UID;
|
||||
}
|
||||
|
||||
this.albums = response.models;
|
||||
|
@ -106,13 +106,13 @@
|
|||
},
|
||||
watch: {
|
||||
search (q) {
|
||||
const exists = this.albums.findIndex((album) => album.AlbumName === q);
|
||||
const exists = this.albums.findIndex((album) => album.Name === q);
|
||||
|
||||
if (exists !== -1 || !q) {
|
||||
this.items = this.albums;
|
||||
this.newAlbum = null;
|
||||
} else {
|
||||
this.newAlbum = new Album({AlbumName: q, AlbumUUID: "", AlbumFavorite: true});
|
||||
this.newAlbum = new Album({Name: q, UID: "", Favorite: true});
|
||||
this.items = this.albums.concat([this.newAlbum]);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -80,15 +80,15 @@
|
|||
},
|
||||
computed: {
|
||||
title: function () {
|
||||
if (this.model && this.model.PhotoTitle) {
|
||||
return this.model.PhotoTitle
|
||||
if (this.model && this.model.Title) {
|
||||
return this.model.Title
|
||||
}
|
||||
|
||||
this.$gettext("Edit Photo");
|
||||
},
|
||||
isPrivate: function () {
|
||||
if (this.model && this.model.PhotoPrivate && this.$config.settings().features.private) {
|
||||
return this.model.PhotoPrivate
|
||||
if (this.model && this.model.Private && this.$config.settings().features.private) {
|
||||
return this.model.Private
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -51,18 +51,18 @@
|
|||
let width = 0;
|
||||
let height = 0;
|
||||
|
||||
if (file.FileWidth > 0) {
|
||||
width = file.FileWidth;
|
||||
} else if (main && main.FileWidth > 0) {
|
||||
width = main.FileWidth;
|
||||
if (file.Width > 0) {
|
||||
width = file.Width;
|
||||
} else if (main && main.Width > 0) {
|
||||
width = main.Width;
|
||||
} else {
|
||||
width = this.defaultWidth;
|
||||
}
|
||||
|
||||
if (file.FileHeight > 0) {
|
||||
height = file.FileHeight;
|
||||
} else if (main && main.FileHeight > 0) {
|
||||
height = main.FileHeight;
|
||||
if (file.Height > 0) {
|
||||
height = file.Height;
|
||||
} else if (main && main.Height > 0) {
|
||||
height = main.Height;
|
||||
} else {
|
||||
height = this.defaultHeight;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
>
|
||||
<v-card tile
|
||||
class="ma-1 elevation-0"
|
||||
:title="model.PhotoTitle">
|
||||
:title="model.Title">
|
||||
<v-img :src="model.thumbnailUrl('tile_500')"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 elevation-0"
|
||||
|
@ -44,7 +44,7 @@
|
|||
placeholder=""
|
||||
color="secondary-dark"
|
||||
browser-autocomplete="off"
|
||||
v-model="model.PhotoTitle"
|
||||
v-model="model.Title"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
|
@ -123,8 +123,8 @@
|
|||
:label="labels.timezone"
|
||||
hide-details
|
||||
color="secondary-dark"
|
||||
item-value="code"
|
||||
item-text="name"
|
||||
item-value="ID"
|
||||
item-text="Name"
|
||||
v-model="model.TimeZone"
|
||||
:items="timeZones">
|
||||
</v-autocomplete>
|
||||
|
@ -138,7 +138,7 @@
|
|||
label="Latitude"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoLat"
|
||||
v-model="model.Lat"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
|
@ -150,7 +150,7 @@
|
|||
label="Longitude"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoLng"
|
||||
v-model="model.Lng"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
|
@ -162,7 +162,7 @@
|
|||
label="Altitude (m)"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoAltitude"
|
||||
v-model="model.Altitude"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
|
@ -175,7 +175,7 @@
|
|||
color="secondary-dark"
|
||||
item-value="Code"
|
||||
item-text="Name"
|
||||
v-model="model.PhotoCountry"
|
||||
v-model="model.Country"
|
||||
:items="countries">
|
||||
</v-select>
|
||||
</v-flex>
|
||||
|
@ -188,7 +188,7 @@
|
|||
hide-details
|
||||
color="secondary-dark"
|
||||
item-value="ID"
|
||||
item-text="CameraModel"
|
||||
item-text="Model"
|
||||
v-model="model.CameraID"
|
||||
:items="cameraOptions">
|
||||
</v-select>
|
||||
|
@ -202,7 +202,7 @@
|
|||
label="ISO"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoIso"
|
||||
v-model="model.Iso"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
|
@ -214,7 +214,7 @@
|
|||
label="Exposure"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoExposure"
|
||||
v-model="model.Exposure"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
|
@ -226,7 +226,7 @@
|
|||
hide-details
|
||||
color="secondary-dark"
|
||||
item-value="ID"
|
||||
item-text="LensModel"
|
||||
item-text="Model"
|
||||
v-model="model.LensID"
|
||||
:items="lensOptions">
|
||||
</v-select>
|
||||
|
@ -240,7 +240,7 @@
|
|||
label="F Number"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoFNumber"
|
||||
v-model="model.FNumber"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
|
@ -252,7 +252,7 @@
|
|||
label="Focal Length"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoFocalLength"
|
||||
v-model="model.FocalLength"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
|
@ -322,7 +322,7 @@
|
|||
placeholder=""
|
||||
:rows="1"
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoDescription"
|
||||
v-model="model.Description"
|
||||
></v-textarea>
|
||||
</v-flex>
|
||||
|
||||
|
@ -361,7 +361,7 @@
|
|||
</v-btn>
|
||||
<v-btn color="secondary-dark" depressed dark @click.stop="save(false)"
|
||||
class="p-photo-dialog-confirm">
|
||||
<span v-if="$config.feature('review') && model.PhotoQuality < 3">Approve</span>
|
||||
<span v-if="$config.feature('review') && model.Quality < 3">Approve</span>
|
||||
<span v-else>Apply</span>
|
||||
</v-btn>
|
||||
<v-btn color="secondary-dark" depressed dark @click.stop="save(true)"
|
||||
|
|
|
@ -12,19 +12,19 @@
|
|||
>
|
||||
<template slot="items" slot-scope="props" class="p-file">
|
||||
<td>
|
||||
<v-btn v-if="props.item.FileType === 'jpg'" flat :ripple="false" icon small
|
||||
<v-btn v-if="props.item.Type === 'jpg'" flat :ripple="false" icon small
|
||||
@click.stop.prevent="setPrimary(props.item)">
|
||||
<v-icon v-if="props.item.FilePrimary" color="secondary-dark">radio_button_checked</v-icon>
|
||||
<v-icon v-if="props.item.Primary" color="secondary-dark">radio_button_checked</v-icon>
|
||||
<v-icon v-else color="secondary-dark">radio_button_unchecked</v-icon>
|
||||
</v-btn>
|
||||
</td>
|
||||
<td>
|
||||
<a :href="'/api/v1/download/' + props.item.FileHash" class="secondary-dark--text" target="_blank"
|
||||
<a :href="'/api/v1/download/' + props.item.Hash" class="secondary-dark--text" target="_blank"
|
||||
v-if="$config.feature('download')">
|
||||
{{ props.item.FileName }}
|
||||
{{ props.item.Name }}
|
||||
</a>
|
||||
<span v-else>
|
||||
{{ props.item.FileName }}
|
||||
{{ props.item.Name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="hidden-sm-and-down">{{ fileDimensions(props.item) }}</td>
|
||||
|
@ -50,10 +50,10 @@
|
|||
readonly: this.$config.get("readonly"),
|
||||
selected: [],
|
||||
listColumns: [
|
||||
{text: this.$gettext('Primary'), value: 'FilePrimary', sortable: false, align: 'center', class: 'p-col-primary'},
|
||||
{text: this.$gettext('Name'), value: 'FileName', sortable: false, align: 'left'},
|
||||
{text: this.$gettext('Primary'), value: 'Primary', sortable: false, align: 'center', class: 'p-col-primary'},
|
||||
{text: this.$gettext('Name'), value: 'Name', sortable: false, align: 'left'},
|
||||
{text: this.$gettext('Dimensions'), value: '', sortable: false, class: 'hidden-sm-and-down'},
|
||||
{text: this.$gettext('Size'), value: 'FileSize', sortable: false, class: 'hidden-xs-only'},
|
||||
{text: this.$gettext('Size'), value: 'Size', sortable: false, class: 'hidden-xs-only'},
|
||||
{text: this.$gettext('Type'), value: '', sortable: false, align: 'left'},
|
||||
{text: this.$gettext('Status'), value: '', sortable: false, align: 'left'},
|
||||
],
|
||||
|
@ -65,38 +65,38 @@
|
|||
this.$viewer.show(Thumb.fromFiles([this.model]), 0)
|
||||
},
|
||||
setPrimary(file) {
|
||||
this.model.setPrimary(file.FileUUID);
|
||||
this.model.setPrimary(file.UID);
|
||||
},
|
||||
fileDimensions(file) {
|
||||
if (!file.FileWidth || !file.FileHeight) {
|
||||
if (!file.Width || !file.Height) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return file.FileWidth + " × " + file.FileHeight;
|
||||
return file.Width + " × " + file.Height;
|
||||
},
|
||||
fileSize(file) {
|
||||
if (!file.FileSize) {
|
||||
if (!file.Size) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const size = Number.parseFloat(file.FileSize) / 1048576;
|
||||
const size = Number.parseFloat(file.Size) / 1048576;
|
||||
|
||||
return size.toFixed(1) + " MB";
|
||||
},
|
||||
fileType(file) {
|
||||
if (file.FileVideo) {
|
||||
if (file.Video) {
|
||||
return this.$gettext("Video");
|
||||
} else if (file.FileSidecar) {
|
||||
} else if (file.Sidecar) {
|
||||
return this.$gettext("Sidecar");
|
||||
}
|
||||
|
||||
return file.FileType.toUpperCase();
|
||||
return file.Type.toUpperCase();
|
||||
},
|
||||
fileStatus(file) {
|
||||
if (file.FileMissing) {
|
||||
if (file.Missing) {
|
||||
return this.$gettext("Missing");
|
||||
} else if (file.FileError) {
|
||||
return file.FileError;
|
||||
} else if (file.Error) {
|
||||
return file.Error;
|
||||
} else if (file.Duplicate) {
|
||||
return this.$gettext("Duplicate");
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
<template v-slot:items="props" class="p-file">
|
||||
<td>
|
||||
<v-edit-dialog
|
||||
:return-value.sync="props.item.Label.LabelName"
|
||||
:return-value.sync="props.item.Label.Name"
|
||||
lazy
|
||||
@save="renameLabel(props.item.Label)"
|
||||
class="p-inline-edit"
|
||||
>
|
||||
{{ props.item.Label.LabelName | capitalize }}
|
||||
{{ props.item.Label.Name | capitalize }}
|
||||
<template v-slot:input>
|
||||
<v-text-field
|
||||
v-model="props.item.Label.LabelName"
|
||||
v-model="props.item.Label.Name"
|
||||
:rules="[nameRule]"
|
||||
:label="labels.name"
|
||||
color="secondary-dark"
|
||||
|
@ -123,7 +123,7 @@
|
|||
return
|
||||
}
|
||||
|
||||
const name = label.LabelName;
|
||||
const name = label.Name;
|
||||
|
||||
this.model.removeLabel(label.ID).then((m) => {
|
||||
this.$notify.success("removed " + name);
|
||||
|
@ -152,10 +152,10 @@
|
|||
return
|
||||
}
|
||||
|
||||
this.model.renameLabel(label.ID, label.LabelName);
|
||||
this.model.renameLabel(label.ID, label.Name);
|
||||
},
|
||||
searchLabel(label) {
|
||||
this.$router.push({name: 'photos', query: {q: 'label:' + label.LabelSlug}}).catch(err => {
|
||||
this.$router.push({name: 'photos', query: {q: 'label:' + label.Slug}}).catch(err => {
|
||||
});
|
||||
this.$emit('close');
|
||||
},
|
||||
|
|
|
@ -46,8 +46,8 @@ export class Account extends RestModel {
|
|||
return Api.get(this.getEntityResource() + "/dirs").then((response) => Promise.resolve(response.data));
|
||||
}
|
||||
|
||||
Share(UUIDs, dest) {
|
||||
const values = {Photos: UUIDs, Destination: dest};
|
||||
Share(photos, dest) {
|
||||
const values = {Photos: photos, Destination: dest};
|
||||
|
||||
return Api.post(this.getEntityResource() + "/share", values).then((response) => Promise.resolve(response.data));
|
||||
}
|
||||
|
|
|
@ -5,16 +5,19 @@ import {DateTime} from "luxon";
|
|||
export class Album extends RestModel {
|
||||
getDefaults() {
|
||||
return {
|
||||
ID: 0,
|
||||
CoverUUID: "",
|
||||
AlbumUUID: "",
|
||||
AlbumSlug: "",
|
||||
AlbumName: "",
|
||||
AlbumDescription: "",
|
||||
AlbumNotes: "",
|
||||
AlbumOrder: "",
|
||||
AlbumTemplate: "",
|
||||
AlbumFavorite: true,
|
||||
UID: "",
|
||||
Cover: "",
|
||||
Parent: "",
|
||||
Folder: "",
|
||||
Slug: "",
|
||||
Type: "",
|
||||
Name: "",
|
||||
Description: "",
|
||||
Notes: "",
|
||||
Order: "",
|
||||
Filter: "",
|
||||
Template: "",
|
||||
Favorite: true,
|
||||
Links: [],
|
||||
CreatedAt: "",
|
||||
UpdatedAt: "",
|
||||
|
@ -22,15 +25,15 @@ export class Album extends RestModel {
|
|||
}
|
||||
|
||||
getEntityName() {
|
||||
return this.AlbumSlug;
|
||||
return this.Slug;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.AlbumUUID;
|
||||
return this.UID;
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return this.AlbumName;
|
||||
return this.Name;
|
||||
}
|
||||
|
||||
thumbnailUrl(type) {
|
||||
|
@ -66,9 +69,9 @@ export class Album extends RestModel {
|
|||
}
|
||||
|
||||
toggleLike() {
|
||||
this.AlbumFavorite = !this.AlbumFavorite;
|
||||
this.Favorite = !this.Favorite;
|
||||
|
||||
if (this.AlbumFavorite) {
|
||||
if (this.Favorite) {
|
||||
return Api.post(this.getEntityResource() + "/like");
|
||||
} else {
|
||||
return Api.delete(this.getEntityResource() + "/like");
|
||||
|
@ -76,12 +79,12 @@ export class Album extends RestModel {
|
|||
}
|
||||
|
||||
like() {
|
||||
this.AlbumFavorite = true;
|
||||
this.Favorite = true;
|
||||
return Api.post(this.getEntityResource() + "/like");
|
||||
}
|
||||
|
||||
unlike() {
|
||||
this.AlbumFavorite = false;
|
||||
this.Favorite = false;
|
||||
return Api.delete(this.getEntityResource() + "/like");
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,15 @@ import RestModel from "model/rest";
|
|||
import Api from "common/api";
|
||||
import {DateTime} from "luxon";
|
||||
|
||||
export const FolderRootOriginals = "originals"
|
||||
export const FolderRootImport = "import"
|
||||
export const FolderRootOriginals = "originals";
|
||||
export const FolderRootImport = "import";
|
||||
|
||||
export class Folder extends RestModel {
|
||||
getDefaults() {
|
||||
return {
|
||||
Root: "",
|
||||
Path: "",
|
||||
PPID: "",
|
||||
UID: "",
|
||||
Title: "",
|
||||
Description: "",
|
||||
Type: "",
|
||||
|
@ -30,11 +30,11 @@ export class Folder extends RestModel {
|
|||
}
|
||||
|
||||
getId() {
|
||||
return this.PPID;
|
||||
return this.UID;
|
||||
}
|
||||
|
||||
thumbnailUrl(type) {
|
||||
return "/api/v1/folder/" + this.getId() + "/thumbnail/" + type;
|
||||
thumbnailUrl() {
|
||||
return "/api/v1/svg/folder";
|
||||
}
|
||||
|
||||
getDateString() {
|
||||
|
@ -62,7 +62,11 @@ export class Folder extends RestModel {
|
|||
}
|
||||
|
||||
static findAll(path) {
|
||||
return this.search(path, {recursive: true})
|
||||
return this.search(path, {recursive: true});
|
||||
}
|
||||
|
||||
static originals(path, params) {
|
||||
return this.search(FolderRootOriginals + "/" + path, params);
|
||||
}
|
||||
|
||||
static search(path, params) {
|
||||
|
@ -71,7 +75,7 @@ export class Folder extends RestModel {
|
|||
};
|
||||
|
||||
if (!path || path[0] !== "/") {
|
||||
path = "/" + path
|
||||
path = "/" + path;
|
||||
}
|
||||
|
||||
return Api.get(this.getCollectionResource() + path, options).then((response) => {
|
||||
|
|
|
@ -6,32 +6,32 @@ export class Label extends RestModel {
|
|||
getDefaults() {
|
||||
return {
|
||||
ID: 0,
|
||||
UID: "",
|
||||
Slug: "",
|
||||
CustomSlug: "",
|
||||
Name: "",
|
||||
Priority: 0,
|
||||
Favorite: false,
|
||||
Description: "",
|
||||
Notes: "",
|
||||
PhotoCount: 0,
|
||||
Links: [],
|
||||
CreatedAt: "",
|
||||
UpdatedAt: "",
|
||||
DeletedAt: "",
|
||||
LabelUUID: "",
|
||||
LabelSlug: "",
|
||||
CustomSlug: "",
|
||||
LabelName: "",
|
||||
LabelPriority: 0,
|
||||
LabelFavorite: false,
|
||||
LabelDescription: "",
|
||||
LabelNotes: "",
|
||||
PhotoCount: 0,
|
||||
Links: [],
|
||||
};
|
||||
}
|
||||
|
||||
getEntityName() {
|
||||
return this.LabelSlug;
|
||||
return this.Slug;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.LabelUUID;
|
||||
return this.UID;
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return this.LabelName;
|
||||
return this.Name;
|
||||
}
|
||||
|
||||
thumbnailUrl(type) {
|
||||
|
@ -67,9 +67,9 @@ export class Label extends RestModel {
|
|||
}
|
||||
|
||||
toggleLike() {
|
||||
this.LabelFavorite = !this.LabelFavorite;
|
||||
this.Favorite = !this.Favorite;
|
||||
|
||||
if (this.LabelFavorite) {
|
||||
if (this.Favorite) {
|
||||
return Api.post(this.getEntityResource() + "/like");
|
||||
} else {
|
||||
return Api.delete(this.getEntityResource() + "/like");
|
||||
|
@ -77,27 +77,15 @@ export class Label extends RestModel {
|
|||
}
|
||||
|
||||
like() {
|
||||
this.LabelFavorite = true;
|
||||
this.Favorite = true;
|
||||
return Api.post(this.getEntityResource() + "/like");
|
||||
}
|
||||
|
||||
unlike() {
|
||||
this.LabelFavorite = false;
|
||||
this.Favorite = false;
|
||||
return Api.delete(this.getEntityResource() + "/like");
|
||||
}
|
||||
|
||||
/* popularity(max) {
|
||||
if (!this.PhotoCount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.PhotoCount >= max) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
return Math.ceil(max / this.PhotoCount);
|
||||
} */
|
||||
|
||||
static getCollectionResource() {
|
||||
return "labels";
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ import RestModel from "model/rest";
|
|||
export class Link extends RestModel {
|
||||
getDefaults() {
|
||||
return {
|
||||
LinkToken: "",
|
||||
LinkPassword: "",
|
||||
LinkExpires: "",
|
||||
ShareUUID: "",
|
||||
Token: "",
|
||||
Password: "",
|
||||
Expires: "",
|
||||
ShareUID: "",
|
||||
CanComment: false,
|
||||
CanEdit: false,
|
||||
CreatedAt: "",
|
||||
|
@ -16,7 +16,7 @@ export class Link extends RestModel {
|
|||
}
|
||||
|
||||
getId() {
|
||||
return this.LinkToken;
|
||||
return this.Token;
|
||||
}
|
||||
|
||||
static getCollectionResource() {
|
||||
|
|
|
@ -7,38 +7,38 @@ export const SrcManual = "manual";
|
|||
export const CodecAvc1 = "avc1";
|
||||
export const TypeMP4 = "mp4";
|
||||
export const TypeJpeg = "jpg";
|
||||
export const TypeImage = "image";
|
||||
export const YearUnknown = -1;
|
||||
export const MonthUnknown = -1;
|
||||
|
||||
export class Photo extends RestModel {
|
||||
getDefaults() {
|
||||
return {
|
||||
ID: 0,
|
||||
PhotoUUID: "",
|
||||
PhotoType: "",
|
||||
PhotoFavorite: false,
|
||||
PhotoPrivate: false,
|
||||
UID: "",
|
||||
Type: TypeImage,
|
||||
Favorite: false,
|
||||
Private: false,
|
||||
TakenAt: "",
|
||||
TakenAtLocal: "",
|
||||
TakenSrc: "",
|
||||
TimeZone: "",
|
||||
PhotoPath: "",
|
||||
PhotoName: "",
|
||||
FileName: "",
|
||||
PhotoTitle: "",
|
||||
Path: "",
|
||||
Color: "",
|
||||
Name: "",
|
||||
Title: "",
|
||||
TitleSrc: "",
|
||||
PhotoDescription: "",
|
||||
Description: "",
|
||||
DescriptionSrc: "",
|
||||
PhotoResolution: 0,
|
||||
PhotoQuality: 0,
|
||||
PhotoLat: 0.0,
|
||||
PhotoLng: 0.0,
|
||||
PhotoAltitude: 0,
|
||||
PhotoIso: 0,
|
||||
PhotoFocalLength: 0,
|
||||
PhotoFNumber: 0.0,
|
||||
PhotoExposure: "",
|
||||
PhotoViews: 0,
|
||||
Resolution: 0,
|
||||
Quality: 0,
|
||||
Lat: 0.0,
|
||||
Lng: 0.0,
|
||||
Altitude: 0,
|
||||
Iso: 0,
|
||||
FocalLength: 0,
|
||||
FNumber: 0.0,
|
||||
Exposure: "",
|
||||
Views: 0,
|
||||
Camera: {},
|
||||
CameraID: 0,
|
||||
CameraSrc: "",
|
||||
|
@ -49,9 +49,9 @@ export class Photo extends RestModel {
|
|||
LocationSrc: "",
|
||||
Place: null,
|
||||
PlaceID: "",
|
||||
PhotoCountry: "",
|
||||
PhotoYear: YearUnknown,
|
||||
PhotoMonth: MonthUnknown,
|
||||
Country: "",
|
||||
Year: YearUnknown,
|
||||
Month: MonthUnknown,
|
||||
Details: {
|
||||
Keywords: "",
|
||||
Notes: "",
|
||||
|
@ -68,47 +68,45 @@ export class Photo extends RestModel {
|
|||
CreatedAt: "",
|
||||
UpdatedAt: "",
|
||||
DeletedAt: null,
|
||||
// Additional fields for result lists.
|
||||
LocLabel: "",
|
||||
LocCity: "",
|
||||
LocState: "",
|
||||
LocCountry: "",
|
||||
FileUID: "",
|
||||
FileName: "",
|
||||
Hash: "",
|
||||
Width: "",
|
||||
Height: "",
|
||||
};
|
||||
}
|
||||
|
||||
getEntityName() {
|
||||
return this.PhotoTitle;
|
||||
return this.Title;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.PhotoUUID;
|
||||
return this.UID;
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return this.PhotoTitle;
|
||||
}
|
||||
|
||||
getColor() {
|
||||
switch (this.PhotoColor) {
|
||||
case "brown":
|
||||
case "black":
|
||||
case "white":
|
||||
case "grey":
|
||||
return "grey lighten-2";
|
||||
default:
|
||||
return this.PhotoColor + " lighten-4";
|
||||
}
|
||||
return this.Title;
|
||||
}
|
||||
|
||||
getGoogleMapsLink() {
|
||||
return "https://www.google.com/maps/place/" + this.PhotoLat + "," + this.PhotoLng;
|
||||
return "https://www.google.com/maps/place/" + this.Lat + "," + this.Lng;
|
||||
}
|
||||
|
||||
refreshFileAttr() {
|
||||
const file = this.mainFile();
|
||||
|
||||
if (!file || !file.FileHash) {
|
||||
if (!file || !file.Hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.FileHash = file.FileHash;
|
||||
this.FileWidth = file.FileWidth;
|
||||
this.FileHeight = file.FileHeight;
|
||||
this.Hash = file.Hash;
|
||||
this.Width = file.Width;
|
||||
this.Height = file.Height;
|
||||
}
|
||||
|
||||
isPlayable() {
|
||||
|
@ -116,7 +114,7 @@ export class Photo extends RestModel {
|
|||
return false;
|
||||
}
|
||||
|
||||
return this.Files.findIndex(f => f.FileCodec === CodecAvc1) !== -1 || this.Files.findIndex(f => f.FileType === TypeMP4) !== -1;
|
||||
return this.Files.findIndex(f => f.Codec === CodecAvc1) !== -1 || this.Files.findIndex(f => f.Type === TypeMP4) !== -1;
|
||||
}
|
||||
|
||||
videoFile() {
|
||||
|
@ -124,14 +122,14 @@ export class Photo extends RestModel {
|
|||
return false;
|
||||
}
|
||||
|
||||
let file = this.Files.find(f => f.FileCodec === CodecAvc1);
|
||||
let file = this.Files.find(f => f.Codec === CodecAvc1);
|
||||
|
||||
if (!file) {
|
||||
file = this.Files.find(f => f.FileType === TypeMP4);
|
||||
file = this.Files.find(f => f.Type === TypeMP4);
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
file = this.Files.find(f => !!f.FileVideo);
|
||||
file = this.Files.find(f => !!f.Video);
|
||||
}
|
||||
|
||||
return file;
|
||||
|
@ -144,7 +142,7 @@ export class Photo extends RestModel {
|
|||
return "";
|
||||
}
|
||||
|
||||
return "/api/v1/videos/" + file.FileHash + "/" + TypeMP4;
|
||||
return "/api/v1/videos/" + file.Hash + "/" + TypeMP4;
|
||||
}
|
||||
|
||||
mainFile() {
|
||||
|
@ -152,10 +150,10 @@ export class Photo extends RestModel {
|
|||
return false;
|
||||
}
|
||||
|
||||
let file = this.Files.find(f => !!f.FilePrimary);
|
||||
let file = this.Files.find(f => !!f.Primary);
|
||||
|
||||
if (!file) {
|
||||
file = this.Files.find(f => f.FileType === TypeJpeg);
|
||||
file = this.Files.find(f => f.Type === TypeJpeg);
|
||||
}
|
||||
|
||||
return file;
|
||||
|
@ -165,11 +163,11 @@ export class Photo extends RestModel {
|
|||
if (this.Files) {
|
||||
let file = this.mainFile();
|
||||
|
||||
if (file && file.FileHash) {
|
||||
return file.FileHash;
|
||||
if (file && file.Hash) {
|
||||
return file.Hash;
|
||||
}
|
||||
} else if (this.FileHash) {
|
||||
return this.FileHash;
|
||||
} else if (this.Hash) {
|
||||
return this.Hash;
|
||||
}
|
||||
|
||||
return "";
|
||||
|
@ -181,8 +179,8 @@ export class Photo extends RestModel {
|
|||
if (!hash) {
|
||||
let video = this.videoFile();
|
||||
|
||||
if (video && video.FileHash) {
|
||||
return "/api/v1/thumbnails/" + video.FileHash + "/" + type;
|
||||
if (video && video.Hash) {
|
||||
return "/api/v1/thumbnails/" + video.Hash + "/" + type;
|
||||
}
|
||||
|
||||
return "/api/v1/svg/photo";
|
||||
|
@ -208,11 +206,11 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
calculateSize(width, height) {
|
||||
if (width >= this.FileWidth && height >= this.FileHeight) { // Smaller
|
||||
return {width: this.FileWidth, height: this.FileHeight};
|
||||
if (width >= this.Width && height >= this.Height) { // Smaller
|
||||
return {width: this.Width, height: this.Height};
|
||||
}
|
||||
|
||||
const srcAspectRatio = this.FileWidth / this.FileHeight;
|
||||
const srcAspectRatio = this.Width / this.Height;
|
||||
const maxAspectRatio = width / height;
|
||||
|
||||
let newW, newH;
|
||||
|
@ -242,7 +240,7 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
getDateString() {
|
||||
if (!this.TakenAt || this.PhotoYear === YearUnknown) {
|
||||
if (!this.TakenAt || this.Year === YearUnknown) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
|
@ -254,7 +252,7 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
shortDateString() {
|
||||
if (!this.TakenAt || this.PhotoYear === YearUnknown) {
|
||||
if (!this.TakenAt || this.Year === YearUnknown) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
|
@ -267,7 +265,7 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
hasLocation() {
|
||||
return this.PhotoLat !== 0 || this.PhotoLng !== 0;
|
||||
return this.Lat !== 0 || this.Lng !== 0;
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
|
@ -283,21 +281,21 @@ export class Photo extends RestModel {
|
|||
return;
|
||||
}
|
||||
|
||||
if (file.FileWidth && file.FileHeight) {
|
||||
info.push(file.FileWidth + " × " + file.FileHeight);
|
||||
} else if (!file.FilePrimary) {
|
||||
if (file.Width && file.Height) {
|
||||
info.push(file.Width + " × " + file.Height);
|
||||
} else if (!file.Primary) {
|
||||
let main = this.mainFile();
|
||||
if (main && main.FileWidth && main.FileHeight) {
|
||||
info.push(main.FileWidth + " × " + main.FileHeight);
|
||||
if (main && main.Width && main.Height) {
|
||||
info.push(main.Width + " × " + main.Height);
|
||||
}
|
||||
}
|
||||
|
||||
if (file.FileSize > 102400) {
|
||||
const size = Number.parseFloat(file.FileSize) / 1048576;
|
||||
if (file.Size > 102400) {
|
||||
const size = Number.parseFloat(file.Size) / 1048576;
|
||||
|
||||
info.push(size.toFixed(1) + " MB");
|
||||
} else if (file.FileSize) {
|
||||
const size = Number.parseFloat(file.FileSize) / 1024;
|
||||
} else if (file.Size) {
|
||||
const size = Number.parseFloat(file.Size) / 1024;
|
||||
|
||||
info.push(size.toFixed(1) + " KB");
|
||||
}
|
||||
|
@ -315,8 +313,8 @@ export class Photo extends RestModel {
|
|||
return "Video";
|
||||
}
|
||||
|
||||
if (file.FileDuration > 0) {
|
||||
info.push(Util.duration(file.FileDuration));
|
||||
if (file.Duration > 0) {
|
||||
info.push(Util.duration(file.Duration));
|
||||
}
|
||||
|
||||
this.addSizeInfo(file, info);
|
||||
|
@ -332,7 +330,7 @@ export class Photo extends RestModel {
|
|||
let info = [];
|
||||
|
||||
if (this.Camera) {
|
||||
info.push(this.Camera.CameraMake + " " + this.Camera.CameraModel);
|
||||
info.push(this.Camera.Make + " " + this.Camera.Model);
|
||||
} else if (this.CameraModel && this.CameraMake) {
|
||||
info.push(this.CameraMake + " " + this.CameraModel);
|
||||
}
|
||||
|
@ -350,7 +348,7 @@ export class Photo extends RestModel {
|
|||
|
||||
getCamera() {
|
||||
if (this.Camera) {
|
||||
return this.Camera.CameraMake + " " + this.Camera.CameraModel;
|
||||
return this.Camera.Make + " " + this.Camera.Model;
|
||||
} else if (this.CameraModel) {
|
||||
return this.CameraMake + " " + this.CameraModel;
|
||||
}
|
||||
|
@ -359,9 +357,9 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
toggleLike() {
|
||||
this.PhotoFavorite = !this.PhotoFavorite;
|
||||
this.Favorite = !this.Favorite;
|
||||
|
||||
if (this.PhotoFavorite) {
|
||||
if (this.Favorite) {
|
||||
return Api.post(this.getEntityResource() + "/like");
|
||||
} else {
|
||||
return Api.delete(this.getEntityResource() + "/like");
|
||||
|
@ -369,27 +367,27 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
togglePrivate() {
|
||||
this.PhotoPrivate = !this.PhotoPrivate;
|
||||
this.Private = !this.Private;
|
||||
|
||||
return Api.put(this.getEntityResource(), {PhotoPrivate: this.PhotoPrivate});
|
||||
return Api.put(this.getEntityResource(), {Private: this.Private});
|
||||
}
|
||||
|
||||
setPrimary(fileUUID) {
|
||||
return Api.post(this.getEntityResource() + "/primary/" + fileUUID).then((r) => Promise.resolve(this.setValues(r.data)));
|
||||
setPrimary(uid) {
|
||||
return Api.post(this.getEntityResource() + "/primary/" + uid).then((r) => Promise.resolve(this.setValues(r.data)));
|
||||
}
|
||||
|
||||
like() {
|
||||
this.PhotoFavorite = true;
|
||||
this.Favorite = true;
|
||||
return Api.post(this.getEntityResource() + "/like");
|
||||
}
|
||||
|
||||
unlike() {
|
||||
this.PhotoFavorite = false;
|
||||
this.Favorite = false;
|
||||
return Api.delete(this.getEntityResource() + "/like");
|
||||
}
|
||||
|
||||
addLabel(name) {
|
||||
return Api.post(this.getEntityResource() + "/label", {LabelName: name, LabelPriority: 10})
|
||||
return Api.post(this.getEntityResource() + "/label", {Name: name, Priority: 10})
|
||||
.then((r) => Promise.resolve(this.setValues(r.data)));
|
||||
}
|
||||
|
||||
|
@ -399,7 +397,7 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
renameLabel(id, name) {
|
||||
return Api.put(this.getEntityResource() + "/label/" + id, {Label: {LabelName: name}})
|
||||
return Api.put(this.getEntityResource() + "/label/" + id, {Label: {Name: name}})
|
||||
.then((r) => Promise.resolve(this.setValues(r.data)));
|
||||
}
|
||||
|
||||
|
@ -411,15 +409,15 @@ export class Photo extends RestModel {
|
|||
update() {
|
||||
const values = this.getValues(true);
|
||||
|
||||
if (values.PhotoTitle) {
|
||||
if (values.Title) {
|
||||
values.TitleSrc = SrcManual;
|
||||
}
|
||||
|
||||
if (values.PhotoDescription) {
|
||||
if (values.Description) {
|
||||
values.DescriptionSrc = SrcManual;
|
||||
}
|
||||
|
||||
if (values.PhotoLat || values.PhotoLng) {
|
||||
if (values.Lat || values.Lng) {
|
||||
values.LocationSrc = SrcManual;
|
||||
}
|
||||
|
||||
|
@ -427,7 +425,7 @@ export class Photo extends RestModel {
|
|||
values.TakenSrc = SrcManual;
|
||||
}
|
||||
|
||||
if (values.CameraID || values.LensID || values.PhotoFocalLength || values.PhotoFNumber || values.PhotoIso || values.PhotoExposure) {
|
||||
if (values.CameraID || values.LensID || values.FocalLength || values.FNumber || values.Iso || values.Exposure) {
|
||||
values.CameraSrc = SrcManual;
|
||||
}
|
||||
|
||||
|
@ -450,7 +448,7 @@ export class Photo extends RestModel {
|
|||
if (response.models.length > 0) {
|
||||
let i = results.length - 1;
|
||||
|
||||
if (results[i].PhotoUUID === response.models[0].PhotoUUID) {
|
||||
if (results[i].UID === response.models[0].UID) {
|
||||
const first = response.models.shift();
|
||||
results[i].Files = results[i].Files.concat(first.Files);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import Model from "./model";
|
||||
import Api from "../common/api";
|
||||
|
||||
const thumbs = window.clientConfig.thumbnails;
|
||||
const thumbs = window.__CONFIG__.thumbnails;
|
||||
|
||||
export class Thumb extends Model {
|
||||
getDefaults() {
|
||||
return {
|
||||
uuid: "",
|
||||
uid: "",
|
||||
title: "",
|
||||
taken: "",
|
||||
description: "",
|
||||
|
@ -22,15 +22,15 @@ export class Thumb extends Model {
|
|||
this.favorite = !this.favorite;
|
||||
|
||||
if (this.favorite) {
|
||||
return Api.post("photos/" + this.uuid + "/like");
|
||||
return Api.post("photos/" + this.uid + "/like");
|
||||
} else {
|
||||
return Api.delete("photos/" + this.uuid + "/like");
|
||||
return Api.delete("photos/" + this.uid + "/like");
|
||||
}
|
||||
}
|
||||
|
||||
static thumbNotFound() {
|
||||
const result = {
|
||||
uuid: "",
|
||||
uid: "",
|
||||
title: "Not Found",
|
||||
taken: "",
|
||||
description: "",
|
||||
|
@ -65,23 +65,23 @@ export class Thumb extends Model {
|
|||
|
||||
static fromPhoto(photo) {
|
||||
if (photo.Files) {
|
||||
return this.fromFile(photo, photo.Files.find(f => !!f.FilePrimary));
|
||||
return this.fromFile(photo, photo.Files.find(f => !!f.Primary));
|
||||
}
|
||||
|
||||
if (!photo || !photo.FileHash) {
|
||||
if (!photo || !photo.Hash) {
|
||||
return this.thumbNotFound();
|
||||
}
|
||||
|
||||
const result = {
|
||||
uuid: photo.PhotoUUID,
|
||||
title: photo.PhotoTitle,
|
||||
uid: photo.UID,
|
||||
title: photo.Title,
|
||||
taken: photo.getDateString(),
|
||||
description: photo.PhotoDescription,
|
||||
favorite: photo.PhotoFavorite,
|
||||
description: photo.Description,
|
||||
favorite: photo.Favorite,
|
||||
playable: photo.isPlayable(),
|
||||
download_url: this.downloadUrl(photo),
|
||||
original_w: photo.FileWidth,
|
||||
original_h: photo.FileHeight,
|
||||
original_w: photo.Width,
|
||||
original_h: photo.Height,
|
||||
};
|
||||
|
||||
for (let i = 0; i < thumbs.length; i++) {
|
||||
|
@ -98,20 +98,20 @@ export class Thumb extends Model {
|
|||
}
|
||||
|
||||
static fromFile(photo, file) {
|
||||
if (!photo || !file || !file.FileHash) {
|
||||
if (!photo || !file || !file.Hash) {
|
||||
return this.thumbNotFound();
|
||||
}
|
||||
|
||||
const result = {
|
||||
uuid: photo.PhotoUUID,
|
||||
title: photo.PhotoTitle,
|
||||
uid: photo.UID,
|
||||
title: photo.Title,
|
||||
taken: photo.getDateString(),
|
||||
description: photo.PhotoDescription,
|
||||
favorite: photo.PhotoFavorite,
|
||||
description: photo.Description,
|
||||
favorite: photo.Favorite,
|
||||
playable: photo.isPlayable(),
|
||||
download_url: this.downloadUrl(file),
|
||||
original_w: file.FileWidth,
|
||||
original_h: file.FileHeight,
|
||||
original_w: file.Width,
|
||||
original_h: file.Height,
|
||||
};
|
||||
|
||||
thumbs.forEach((t) => {
|
||||
|
@ -149,11 +149,11 @@ export class Thumb extends Model {
|
|||
}
|
||||
|
||||
static calculateSize(file, width, height) {
|
||||
if (width >= file.FileWidth && height >= file.FileHeight) { // Smaller
|
||||
return {width: file.FileWidth, height: file.FileHeight};
|
||||
if (width >= file.Width && height >= file.Height) { // Smaller
|
||||
return {width: file.Width, height: file.Height};
|
||||
}
|
||||
|
||||
const srcAspectRatio = file.FileWidth / file.FileHeight;
|
||||
const srcAspectRatio = file.Width / file.Height;
|
||||
const maxAspectRatio = width / height;
|
||||
|
||||
let newW, newH;
|
||||
|
@ -171,20 +171,20 @@ export class Thumb extends Model {
|
|||
}
|
||||
|
||||
static thumbnailUrl(file, type) {
|
||||
if (!file.FileHash) {
|
||||
if (!file.Hash) {
|
||||
return "/api/v1/svg/photo";
|
||||
|
||||
}
|
||||
|
||||
return "/api/v1/thumbnails/" + file.FileHash + "/" + type;
|
||||
return "/api/v1/thumbnails/" + file.Hash + "/" + type;
|
||||
}
|
||||
|
||||
static downloadUrl(file) {
|
||||
if (!file || !file.FileHash) {
|
||||
if (!file || !file.Hash) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "/api/v1/download/" + file.FileHash;
|
||||
return "/api/v1/download/" + file.Hash;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,8 +64,8 @@
|
|||
this.lastFilter = {};
|
||||
this.routeName = this.$route.name;
|
||||
|
||||
if (this.uuid !== this.$route.params.uuid) {
|
||||
this.uuid = this.$route.params.uuid;
|
||||
if (this.uid !== this.$route.params.uid) {
|
||||
this.uid = this.$route.params.uid;
|
||||
this.findAlbum().then(() => this.search());
|
||||
} else {
|
||||
this.search();
|
||||
|
@ -73,7 +73,7 @@
|
|||
}
|
||||
},
|
||||
data() {
|
||||
const uuid = this.$route.params.uuid;
|
||||
const uid = this.$route.params.uid;
|
||||
const query = this.$route.query;
|
||||
const routeName = this.$route.name;
|
||||
const order = query['order'] ? query['order'] : 'oldest';
|
||||
|
@ -89,7 +89,7 @@
|
|||
listen: false,
|
||||
dirty: false,
|
||||
model: new Album(),
|
||||
uuid: uuid,
|
||||
uid: uid,
|
||||
results: [],
|
||||
scrollDisabled: true,
|
||||
pageSize: 60,
|
||||
|
@ -144,7 +144,7 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
if (showMerged && (this.results[index].PhotoType === 'video' || this.results[index].PhotoType === 'live')) {
|
||||
if (showMerged && (this.results[index].Type === 'video' || this.results[index].Type === 'live')) {
|
||||
if(this.results[index].isPlayable()) {
|
||||
this.$modal.show('video', {video: this.results[index], album: this.album});
|
||||
} else {
|
||||
|
@ -170,7 +170,7 @@
|
|||
const params = {
|
||||
count: count,
|
||||
offset: offset,
|
||||
album: this.uuid,
|
||||
album: this.uid,
|
||||
merged: true,
|
||||
};
|
||||
|
||||
|
@ -183,7 +183,7 @@
|
|||
Photo.search(params).then(response => {
|
||||
this.results = Photo.mergeResponse(this.results, response);
|
||||
|
||||
this.scrollDisabled = (response.models.length < count);
|
||||
this.scrollDisabled = (response.count < count);
|
||||
|
||||
if (this.scrollDisabled) {
|
||||
this.offset = offset;
|
||||
|
@ -226,7 +226,7 @@
|
|||
const params = {
|
||||
count: this.pageSize,
|
||||
offset: this.offset,
|
||||
album: this.uuid,
|
||||
album: this.uid,
|
||||
merged: true,
|
||||
};
|
||||
|
||||
|
@ -269,7 +269,7 @@
|
|||
|
||||
this.results = response.models;
|
||||
|
||||
this.scrollDisabled = (response.models.length < this.pageSize);
|
||||
this.scrollDisabled = (response.count < this.pageSize);
|
||||
|
||||
if (this.scrollDisabled) {
|
||||
if (!this.results.length) {
|
||||
|
@ -291,11 +291,11 @@
|
|||
});
|
||||
},
|
||||
findAlbum() {
|
||||
return this.model.find(this.uuid).then(m => {
|
||||
return this.model.find(this.uid).then(m => {
|
||||
this.model = m;
|
||||
|
||||
this.filter.order = m.AlbumOrder;
|
||||
window.document.title = `PhotoPrism: ${this.model.AlbumName}`;
|
||||
window.document.title = `PhotoPrism: ${this.model.Name}`;
|
||||
|
||||
return Promise.resolve(this.model)
|
||||
});
|
||||
|
@ -308,7 +308,7 @@
|
|||
}
|
||||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
if (this.model.AlbumUUID === data.entities[i].AlbumUUID) {
|
||||
if (this.model.UID === data.entities[i].UID) {
|
||||
let values = data.entities[i];
|
||||
|
||||
for (let key in values) {
|
||||
|
@ -317,7 +317,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
window.document.title = `PhotoPrism: ${this.model.AlbumName}`
|
||||
window.document.title = `PhotoPrism: ${this.model.Name}`
|
||||
|
||||
this.dirty = true;
|
||||
this.scrollDisabled = false;
|
||||
|
@ -346,7 +346,7 @@
|
|||
case 'updated':
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const values = data.entities[i];
|
||||
const model = this.results.find((m) => m.PhotoUUID === values.PhotoUUID);
|
||||
const model = this.results.find((m) => m.UID === values.UID);
|
||||
|
||||
if (model) {
|
||||
for (let key in values) {
|
||||
|
@ -368,8 +368,8 @@
|
|||
this.dirty = true;
|
||||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const uuid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.PhotoUUID === uuid);
|
||||
const uid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.UID === uid);
|
||||
if (index >= 0) {
|
||||
this.results.splice(index, 1);
|
||||
}
|
||||
|
|
|
@ -62,9 +62,9 @@
|
|||
<v-card tile class="accent lighten-3"
|
||||
slot-scope="{ hover }"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
:dark="selection.includes(album.AlbumUUID)"
|
||||
:class="selection.includes(album.AlbumUUID) ? 'elevation-10 ma-0 accent darken-1 white--text' : 'elevation-0 ma-1 accent lighten-3'"
|
||||
:to="{name: 'album', params: {uuid: album.AlbumUUID, slug: album.AlbumSlug}}"
|
||||
:dark="selection.includes(album.UID)"
|
||||
:class="selection.includes(album.UID) ? 'elevation-10 ma-0 accent darken-1 white--text' : 'elevation-0 ma-1 accent lighten-3'"
|
||||
:to="{name: 'album', params: {uid: album.UID, slug: album.Slug}}"
|
||||
>
|
||||
<v-img
|
||||
:src="album.thumbnailUrl('tile_500')"
|
||||
|
@ -84,11 +84,11 @@
|
|||
color="accent lighten-5"></v-progress-circular>
|
||||
</v-layout>
|
||||
|
||||
<v-btn v-if="hover || selection.includes(album.AlbumUUID)" :flat="!hover" :ripple="false"
|
||||
<v-btn v-if="hover || selection.includes(album.UID)" :flat="!hover" :ripple="false"
|
||||
icon large absolute
|
||||
:class="selection.includes(album.AlbumUUID) ? 'p-album-select' : 'p-album-select opacity-50'"
|
||||
:class="selection.includes(album.UID) ? 'p-album-select' : 'p-album-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="selection.includes(album.AlbumUUID)" color="white">check_circle
|
||||
<v-icon v-if="selection.includes(album.UID)" color="white">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
|
@ -96,20 +96,20 @@
|
|||
|
||||
<v-card-actions @click.stop.prevent="">
|
||||
<v-edit-dialog
|
||||
:return-value.sync="album.AlbumName"
|
||||
:return-value.sync="album.Name"
|
||||
lazy
|
||||
@save="onSave(album)"
|
||||
class="p-inline-edit"
|
||||
>
|
||||
<span v-if="album.AlbumName">
|
||||
{{ album.AlbumName }}
|
||||
<span v-if="album.Name">
|
||||
{{ album.Name }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<v-icon>edit</v-icon>
|
||||
</span>
|
||||
<template v-slot:input>
|
||||
<v-text-field
|
||||
v-model="album.AlbumName"
|
||||
v-model="album.Name"
|
||||
:rules="[titleRule]"
|
||||
:label="labels.name"
|
||||
color="secondary-dark"
|
||||
|
@ -121,7 +121,7 @@
|
|||
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click.stop.prevent="album.toggleLike()">
|
||||
<v-icon v-if="album.AlbumFavorite" color="#FFD600">star
|
||||
<v-icon v-if="album.Favorite" color="#FFD600">star
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-2">star</v-icon>
|
||||
</v-btn>
|
||||
|
@ -386,39 +386,39 @@
|
|||
create() {
|
||||
let name = DateTime.local().toFormat("LLLL yyyy");
|
||||
|
||||
if (this.results.findIndex(a => a.AlbumName.startsWith(name)) !== -1) {
|
||||
const existing = this.results.filter(a => a.AlbumName.startsWith(name));
|
||||
if (this.results.findIndex(a => a.Name.startsWith(name)) !== -1) {
|
||||
const existing = this.results.filter(a => a.Name.startsWith(name));
|
||||
name = `${name} (${existing.length + 1})`
|
||||
}
|
||||
|
||||
const album = new Album({"AlbumName": name, "AlbumFavorite": true});
|
||||
const album = new Album({"Name": name, "Favorite": true});
|
||||
|
||||
album.save();
|
||||
},
|
||||
onSave(album) {
|
||||
album.update();
|
||||
},
|
||||
addSelection(uuid) {
|
||||
const pos = this.selection.indexOf(uuid);
|
||||
addSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos === -1) {
|
||||
this.selection.push(uuid)
|
||||
this.lastId = uuid;
|
||||
this.selection.push(uid)
|
||||
this.lastId = uid;
|
||||
}
|
||||
},
|
||||
toggleSelection(uuid) {
|
||||
const pos = this.selection.indexOf(uuid);
|
||||
toggleSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos !== -1) {
|
||||
this.selection.splice(pos, 1);
|
||||
this.lastId = "";
|
||||
} else {
|
||||
this.selection.push(uuid);
|
||||
this.lastId = uuid;
|
||||
this.selection.push(uid);
|
||||
this.lastId = uid;
|
||||
}
|
||||
},
|
||||
removeSelection(uuid) {
|
||||
const pos = this.selection.indexOf(uuid);
|
||||
removeSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos !== -1) {
|
||||
this.selection.splice(pos, 1);
|
||||
|
@ -442,7 +442,7 @@
|
|||
case 'updated':
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const values = data.entities[i];
|
||||
const model = this.results.find((m) => m.AlbumUUID === values.AlbumUUID);
|
||||
const model = this.results.find((m) => m.UID === values.UID);
|
||||
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
|
@ -455,14 +455,14 @@
|
|||
this.dirty = true;
|
||||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const uuid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.AlbumUUID === uuid);
|
||||
const uid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.UID === uid);
|
||||
|
||||
if (index >= 0) {
|
||||
this.results.splice(index, 1);
|
||||
}
|
||||
|
||||
this.removeSelection(uuid)
|
||||
this.removeSelection(uid)
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -471,7 +471,7 @@
|
|||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const values = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.AlbumUUID === values.AlbumUUID);
|
||||
const index = this.results.findIndex((m) => m.UID === values.UID);
|
||||
if (index === -1) {
|
||||
this.results.unshift(new Album(values));
|
||||
}
|
||||
|
|
483
frontend/src/pages/folders.vue
Normal file
483
frontend/src/pages/folders.vue
Normal file
|
@ -0,0 +1,483 @@
|
|||
<template>
|
||||
<div class="p-page p-page-folders" v-infinite-scroll="loadMore" :infinite-scroll-disabled="scrollDisabled"
|
||||
:infinite-scroll-distance="10" :infinite-scroll-listen-for-event="'scrollRefresh'">
|
||||
|
||||
<v-form ref="form" class="p-folders-search" lazy-validation @submit.prevent="updateQuery" dense>
|
||||
<v-toolbar flat color="secondary">
|
||||
<v-text-field class="pt-3 pr-3"
|
||||
single-line
|
||||
:label="labels.search"
|
||||
prepend-inner-icon="search"
|
||||
browser-autocomplete="off"
|
||||
clearable
|
||||
color="secondary-dark"
|
||||
@click:clear="clearQuery"
|
||||
v-model="filter.q"
|
||||
@keyup.enter.native="updateQuery"
|
||||
id="search"
|
||||
></v-text-field>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn icon @click.stop="refresh">
|
||||
<v-icon>refresh</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="!filter.all" icon @click.stop="showAll">
|
||||
<v-icon>visibility</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else icon @click.stop="showImportant">
|
||||
<v-icon>visibility_off</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
</v-form>
|
||||
|
||||
<v-container fluid class="pa-4" v-if="loading">
|
||||
<v-progress-linear color="secondary-dark" :indeterminate="true"></v-progress-linear>
|
||||
</v-container>
|
||||
<v-container fluid class="pa-0" v-else>
|
||||
<p-folder-clipboard :refresh="refresh" :selection="selection"
|
||||
:clear-selection="clearSelection"></p-folder-clipboard>
|
||||
|
||||
<p-scroll-top></p-scroll-top>
|
||||
|
||||
<v-container grid-list-xs fluid class="pa-2 p-folders p-folders-cards">
|
||||
<v-card v-if="results.length === 0" class="p-folders-empty secondary-light lighten-1 ma-1" flat>
|
||||
<v-card-title primary-title>
|
||||
<div>
|
||||
<h3 class="title mb-3">
|
||||
<translate>No folders matched your search</translate>
|
||||
</h3>
|
||||
<div>
|
||||
<translate>Try again using a related or otherwise similar term.</translate>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<v-layout row wrap class="p-folders-results">
|
||||
<v-flex
|
||||
v-for="(model, index) in results"
|
||||
:key="index"
|
||||
class="p-folder"
|
||||
xs6 sm4 md3 lg2 d-flex
|
||||
>
|
||||
<v-hover>
|
||||
<v-card tile class="accent lighten-3"
|
||||
slot-scope="{ hover }"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
:dark="selection.includes(model.UID)"
|
||||
:class="selection.includes(model.UID) ? 'elevation-10 ma-0 accent darken-1 white--text' : 'elevation-0 ma-1 accent lighten-3'"
|
||||
:to="{name: 'photos', query: {q: 'path:' + model.Path + '*' }}">
|
||||
<v-img
|
||||
:src="model.thumbnailUrl('tile_500')"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@click="onClick($event, index)"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2"
|
||||
>
|
||||
<v-layout
|
||||
slot="placeholder"
|
||||
fill-height
|
||||
align-center
|
||||
justify-center
|
||||
ma-0
|
||||
>
|
||||
<v-progress-circular indeterminate
|
||||
color="accent lighten-5"></v-progress-circular>
|
||||
</v-layout>
|
||||
|
||||
<v-btn v-if="hover || selection.includes(model.UID)" :flat="!hover" :ripple="false"
|
||||
icon large absolute
|
||||
:class="selection.includes(model.UID) ? 'p-folder-select' : 'p-folder-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="selection.includes(model.UID)" color="white">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
</v-img>
|
||||
|
||||
<v-card-actions @click.stop.prevent="">
|
||||
<v-edit-dialog
|
||||
:return-value.sync="model.Title"
|
||||
lazy
|
||||
@save="onSave(model)"
|
||||
class="p-inline-edit"
|
||||
>
|
||||
<span v-if="model.Title">
|
||||
{{ model.Title | capitalize }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<v-icon>edit</v-icon>
|
||||
</span>
|
||||
<template v-slot:input>
|
||||
<v-text-field
|
||||
v-model="model.Title"
|
||||
:rules="[titleRule]"
|
||||
:label="labels.name"
|
||||
color="secondary-dark"
|
||||
single-line
|
||||
autofocus
|
||||
></v-text-field>
|
||||
</template>
|
||||
</v-edit-dialog>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click.stop.prevent="model.toggleLike()">
|
||||
<v-icon v-if="model.Favorite" color="#FFD600">star
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-2">star</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Folder from "model/folder";
|
||||
import Event from "pubsub-js";
|
||||
import RestModel from "../model/rest";
|
||||
|
||||
export default {
|
||||
name: 'p-page-folders',
|
||||
props: {
|
||||
staticFilter: Object
|
||||
},
|
||||
watch: {
|
||||
'$route'() {
|
||||
const query = this.$route.query;
|
||||
|
||||
this.filter.q = query['q'] ? query['q'] : '';
|
||||
this.filter.all = query['all'] ? query['all'] : '';
|
||||
this.lastFilter = {};
|
||||
this.routeName = this.$route.name;
|
||||
this.search();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const query = this.$route.query;
|
||||
const routeName = this.$route.name;
|
||||
const q = query['q'] ? query['q'] : '';
|
||||
const all = query['all'] ? query['all'] : '';
|
||||
const filter = {q: q, all: all};
|
||||
const settings = {};
|
||||
|
||||
return {
|
||||
config: this.$config.values,
|
||||
subscriptions: [],
|
||||
listen: false,
|
||||
dirty: false,
|
||||
results: [],
|
||||
scrollDisabled: true,
|
||||
loading: true,
|
||||
pageSize: 24,
|
||||
offset: 0,
|
||||
page: 0,
|
||||
selection: [],
|
||||
settings: settings,
|
||||
filter: filter,
|
||||
lastFilter: {},
|
||||
routeName: routeName,
|
||||
labels: {
|
||||
search: this.$gettext("Search"),
|
||||
name: this.$gettext("Folder Name"),
|
||||
},
|
||||
titleRule: v => v.length <= this.$config.get('clip') || this.$gettext("Name too long"),
|
||||
mouseDown: {
|
||||
index: -1,
|
||||
timeStamp: -1,
|
||||
},
|
||||
lastId: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
selectRange(rangeEnd, models) {
|
||||
if (!models || !models[rangeEnd] || !(models[rangeEnd] instanceof RestModel)) {
|
||||
console.warn("selectRange() - invalid arguments:", rangeEnd, models);
|
||||
return;
|
||||
}
|
||||
|
||||
let rangeStart = models.findIndex((m) => m.getId() === this.lastId);
|
||||
|
||||
if (rangeStart === -1) {
|
||||
this.toggleSelection(models[rangeEnd].getId());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (rangeStart > rangeEnd) {
|
||||
const newEnd = rangeStart;
|
||||
rangeStart = rangeEnd;
|
||||
rangeEnd = newEnd;
|
||||
}
|
||||
|
||||
for (let i = rangeStart; i <= rangeEnd; i++) {
|
||||
this.addSelection(models[i].getId());
|
||||
}
|
||||
|
||||
return (rangeEnd - rangeStart) + 1;
|
||||
},
|
||||
onSelect(ev, index) {
|
||||
if (ev.shiftKey) {
|
||||
this.selectRange(index, this.results);
|
||||
} else {
|
||||
this.toggleSelection(this.results[index].getId());
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
this.mouseDown.index = index;
|
||||
this.mouseDown.timeStamp = ev.timeStamp;
|
||||
},
|
||||
onClick(ev, index) {
|
||||
let longClick = (this.mouseDown.index === index && ev.timeStamp - this.mouseDown.timeStamp > 400);
|
||||
|
||||
if (longClick || this.selection.length > 0) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if (longClick || ev.shiftKey) {
|
||||
this.selectRange(index, this.results);
|
||||
} else {
|
||||
this.toggleSelection(this.results[index].getId());
|
||||
}
|
||||
}
|
||||
},
|
||||
onContextMenu(ev, index) {
|
||||
if (this.$isMobile) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if(this.results[index]) {
|
||||
this.selectRange(index, this.results);
|
||||
}
|
||||
}
|
||||
},
|
||||
onSave(model) {
|
||||
model.update();
|
||||
},
|
||||
showAll() {
|
||||
this.filter.all = "true";
|
||||
this.updateQuery();
|
||||
},
|
||||
showImportant() {
|
||||
this.filter.all = "";
|
||||
this.updateQuery();
|
||||
},
|
||||
clearQuery() {
|
||||
this.filter.q = '';
|
||||
this.updateQuery();
|
||||
},
|
||||
addSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos === -1) {
|
||||
this.selection.push(uid)
|
||||
this.lastId = uid;
|
||||
}
|
||||
},
|
||||
toggleSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos !== -1) {
|
||||
this.selection.splice(pos, 1);
|
||||
this.lastId = "";
|
||||
} else {
|
||||
this.selection.push(uid);
|
||||
this.lastId = uid;
|
||||
}
|
||||
},
|
||||
removeSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos !== -1) {
|
||||
this.selection.splice(pos, 1);
|
||||
this.lastId = "";
|
||||
}
|
||||
},
|
||||
clearSelection() {
|
||||
this.selection.splice(0, this.selection.length);
|
||||
this.lastId = "";
|
||||
},
|
||||
loadMore() {
|
||||
if (this.scrollDisabled) return;
|
||||
|
||||
this.scrollDisabled = true;
|
||||
this.listen = false;
|
||||
|
||||
const count = this.dirty ? (this.page + 2) * this.pageSize : this.pageSize;
|
||||
const offset = this.dirty ? 0 : this.offset;
|
||||
|
||||
const params = {
|
||||
count: count,
|
||||
offset: offset,
|
||||
};
|
||||
|
||||
Object.assign(params, this.lastFilter);
|
||||
|
||||
if (this.staticFilter) {
|
||||
Object.assign(params, this.staticFilter);
|
||||
}
|
||||
|
||||
Folder.originals("", params).then(response => {
|
||||
this.results = this.dirty ? response.models : this.results.concat(response.models);
|
||||
|
||||
this.scrollDisabled = (response.models.length < count);
|
||||
|
||||
if (this.scrollDisabled) {
|
||||
this.offset = offset;
|
||||
if (this.results.length > 1) {
|
||||
this.$notify.info(this.$gettext('All ') + this.results.length + this.$gettext(' folders loaded'));
|
||||
}
|
||||
} else {
|
||||
this.offset = offset + count;
|
||||
this.page++;
|
||||
}
|
||||
}).catch(() => {
|
||||
this.scrollDisabled = false;
|
||||
}).finally(() => {
|
||||
this.dirty = false;
|
||||
this.loading = false;
|
||||
this.listen = true;
|
||||
});
|
||||
},
|
||||
updateQuery() {
|
||||
const query = {
|
||||
view: this.settings.view
|
||||
};
|
||||
|
||||
Object.assign(query, this.filter);
|
||||
|
||||
for (let key in query) {
|
||||
if (query[key] === undefined || !query[key]) {
|
||||
delete query[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(this.$route.query) === JSON.stringify(query)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.replace({query: query});
|
||||
},
|
||||
searchParams() {
|
||||
const params = {
|
||||
count: this.pageSize,
|
||||
offset: this.offset,
|
||||
};
|
||||
|
||||
Object.assign(params, this.filter);
|
||||
|
||||
if (this.staticFilter) {
|
||||
Object.assign(params, this.staticFilter);
|
||||
}
|
||||
|
||||
return params;
|
||||
},
|
||||
refresh() {
|
||||
if (this.loading) return;
|
||||
this.loading = true;
|
||||
this.page = 0;
|
||||
this.dirty = true;
|
||||
this.scrollDisabled = false;
|
||||
this.loadMore();
|
||||
},
|
||||
search() {
|
||||
this.scrollDisabled = true;
|
||||
|
||||
// Don't query the same data more than once
|
||||
if (JSON.stringify(this.lastFilter) === JSON.stringify(this.filter)) {
|
||||
this.$nextTick(() => this.$emit("scrollRefresh"));
|
||||
return;
|
||||
}
|
||||
|
||||
Object.assign(this.lastFilter, this.filter);
|
||||
|
||||
this.offset = 0;
|
||||
this.page = 0;
|
||||
this.loading = true;
|
||||
this.listen = false;
|
||||
|
||||
const params = this.searchParams();
|
||||
|
||||
Folder.originals("", params).then(response => {
|
||||
this.offset = this.pageSize;
|
||||
|
||||
this.results = response.models;
|
||||
|
||||
this.scrollDisabled = (response.models.length < this.pageSize);
|
||||
|
||||
if (this.scrollDisabled) {
|
||||
this.$notify.info(this.results.length + this.$gettext(' folders found'));
|
||||
} else {
|
||||
this.$notify.info(this.$gettext('More than 20 folders found'));
|
||||
|
||||
this.$nextTick(() => this.$emit("scrollRefresh"));
|
||||
}
|
||||
}).finally(() => {
|
||||
this.dirty = false;
|
||||
this.loading = false;
|
||||
this.listen = true;
|
||||
});
|
||||
},
|
||||
onUpdate(ev, data) {
|
||||
if (!this.listen) return;
|
||||
|
||||
if (!data || !data.entities) {
|
||||
return
|
||||
}
|
||||
|
||||
const type = ev.split('.')[1];
|
||||
|
||||
switch (type) {
|
||||
case 'updated':
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const values = data.entities[i];
|
||||
const model = this.results.find((m) => m.UID === values.UID);
|
||||
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
model[key] = values[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'deleted':
|
||||
this.dirty = true;
|
||||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const ppid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.UID === ppid);
|
||||
|
||||
if (index >= 0) {
|
||||
this.results.splice(index, 1);
|
||||
}
|
||||
|
||||
this.removeSelection(ppid)
|
||||
}
|
||||
|
||||
break;
|
||||
case 'created':
|
||||
this.dirty = true;
|
||||
break;
|
||||
default:
|
||||
console.warn("unexpected event type", ev);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.search();
|
||||
|
||||
this.subscriptions.push(Event.subscribe("folders", (ev, data) => this.onUpdate(ev, data)));
|
||||
|
||||
this.subscriptions.push(Event.subscribe("touchmove.top", () => this.refresh()));
|
||||
this.subscriptions.push(Event.subscribe("touchmove.bottom", () => this.loadMore()));
|
||||
},
|
||||
destroyed() {
|
||||
for (let i = 0; i < this.subscriptions.length; i++) {
|
||||
Event.unsubscribe(this.subscriptions[i]);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -65,9 +65,9 @@
|
|||
<v-card tile class="accent lighten-3"
|
||||
slot-scope="{ hover }"
|
||||
@contextmenu="onContextMenu($event, index)"
|
||||
:dark="selection.includes(label.LabelUUID)"
|
||||
:class="selection.includes(label.LabelUUID) ? 'elevation-10 ma-0 accent darken-1 white--text' : 'elevation-0 ma-1 accent lighten-3'"
|
||||
:to="{name: 'photos', query: {q: 'label:' + (label.CustomSlug ? label.CustomSlug : label.LabelSlug)}}">
|
||||
:dark="selection.includes(label.UID)"
|
||||
:class="selection.includes(label.UID) ? 'elevation-10 ma-0 accent darken-1 white--text' : 'elevation-0 ma-1 accent lighten-3'"
|
||||
:to="{name: 'photos', query: {q: 'label:' + (label.CustomSlug ? label.CustomSlug : label.Slug)}}">
|
||||
<v-img
|
||||
:src="label.thumbnailUrl('tile_500')"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
|
@ -96,11 +96,11 @@
|
|||
>
|
||||
</v-progress-circular -->
|
||||
|
||||
<v-btn v-if="hover || selection.includes(label.LabelUUID)" :flat="!hover" :ripple="false"
|
||||
<v-btn v-if="hover || selection.includes(label.UID)" :flat="!hover" :ripple="false"
|
||||
icon large absolute
|
||||
:class="selection.includes(label.LabelUUID) ? 'p-label-select' : 'p-label-select opacity-50'"
|
||||
:class="selection.includes(label.UID) ? 'p-label-select' : 'p-label-select opacity-50'"
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon v-if="selection.includes(label.LabelUUID)" color="white">check_circle
|
||||
<v-icon v-if="selection.includes(label.UID)" color="white">check_circle
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-3">radio_button_off</v-icon>
|
||||
</v-btn>
|
||||
|
@ -108,20 +108,20 @@
|
|||
|
||||
<v-card-actions @click.stop.prevent="">
|
||||
<v-edit-dialog
|
||||
:return-value.sync="label.LabelName"
|
||||
:return-value.sync="label.Name"
|
||||
lazy
|
||||
@save="onSave(label)"
|
||||
class="p-inline-edit"
|
||||
>
|
||||
<span v-if="label.LabelName">
|
||||
{{ label.LabelName | capitalize }}
|
||||
<span v-if="label.Name">
|
||||
{{ label.Name | capitalize }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<v-icon>edit</v-icon>
|
||||
</span>
|
||||
<template v-slot:input>
|
||||
<v-text-field
|
||||
v-model="label.LabelName"
|
||||
v-model="label.Name"
|
||||
:rules="[titleRule]"
|
||||
:label="labels.name"
|
||||
color="secondary-dark"
|
||||
|
@ -132,7 +132,7 @@
|
|||
</v-edit-dialog>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click.stop.prevent="label.toggleLike()">
|
||||
<v-icon v-if="label.LabelFavorite" color="#FFD600">star
|
||||
<v-icon v-if="label.Favorite" color="#FFD600">star
|
||||
</v-icon>
|
||||
<v-icon v-else color="accent lighten-2">star</v-icon>
|
||||
</v-btn>
|
||||
|
@ -279,27 +279,27 @@
|
|||
this.filter.q = '';
|
||||
this.updateQuery();
|
||||
},
|
||||
addSelection(uuid) {
|
||||
const pos = this.selection.indexOf(uuid);
|
||||
addSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos === -1) {
|
||||
this.selection.push(uuid)
|
||||
this.lastId = uuid;
|
||||
this.selection.push(uid)
|
||||
this.lastId = uid;
|
||||
}
|
||||
},
|
||||
toggleSelection(uuid) {
|
||||
const pos = this.selection.indexOf(uuid);
|
||||
toggleSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos !== -1) {
|
||||
this.selection.splice(pos, 1);
|
||||
this.lastId = "";
|
||||
} else {
|
||||
this.selection.push(uuid);
|
||||
this.lastId = uuid;
|
||||
this.selection.push(uid);
|
||||
this.lastId = uid;
|
||||
}
|
||||
},
|
||||
removeSelection(uuid) {
|
||||
const pos = this.selection.indexOf(uuid);
|
||||
removeSelection(uid) {
|
||||
const pos = this.selection.indexOf(uid);
|
||||
|
||||
if (pos !== -1) {
|
||||
this.selection.splice(pos, 1);
|
||||
|
@ -444,7 +444,7 @@
|
|||
case 'updated':
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const values = data.entities[i];
|
||||
const model = this.results.find((m) => m.LabelUUID === values.LabelUUID);
|
||||
const model = this.results.find((m) => m.UID === values.UID);
|
||||
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
|
@ -457,14 +457,14 @@
|
|||
this.dirty = true;
|
||||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const uuid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.LabelUUID === uuid);
|
||||
const uid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.UID === uid);
|
||||
|
||||
if (index >= 0) {
|
||||
this.results.splice(index, 1);
|
||||
}
|
||||
|
||||
this.removeSelection(uuid)
|
||||
this.removeSelection(uid)
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
@ -9,15 +9,15 @@
|
|||
height="64"
|
||||
>
|
||||
<v-tab id="tab-originals" ripple @click="changePath('/library')">
|
||||
<translate>Originals</translate>
|
||||
<translate key="Index">Index</translate>
|
||||
</v-tab>
|
||||
|
||||
<v-tab id="tab-import" :disabled="readonly || !$config.feature('import')" ripple @click="changePath('/library/import')">
|
||||
<translate>Import</translate>
|
||||
<translate key="Copy">Copy</translate>
|
||||
</v-tab>
|
||||
|
||||
<v-tab id="tab-logs" ripple @click="changePath('/library/logs')" v-if="$config.feature('logs')">
|
||||
<translate>Logs</translate>
|
||||
<translate key="Logs">Logs</translate>
|
||||
</v-tab>
|
||||
|
||||
<v-tabs-items touchless>
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
<v-container fluid>
|
||||
<p class="subheading">
|
||||
<span v-if="fileName">{{ $gettext('Importing') }} {{fileName}}...</span>
|
||||
<span v-else-if="busy">{{ $gettext('Importing files from import folder...') }}</span>
|
||||
<span v-else-if="busy">{{ $gettext('Importing files to originals...') }}</span>
|
||||
<span v-else-if="completed">{{ $gettext('Done.') }}</span>
|
||||
<span v-else>{{ $gettext('Press button to start importing...') }}</span>
|
||||
<span v-else>{{ $gettext('Press button to start copying to originals...') }}</span>
|
||||
</p>
|
||||
|
||||
<v-autocomplete
|
||||
|
@ -103,7 +103,7 @@
|
|||
export default {
|
||||
name: 'p-tab-import',
|
||||
data() {
|
||||
const root = {"path": "/", "name": this.$gettext("All files in import folder")}
|
||||
const root = {"path": "/", "name": this.$gettext("All files from import folder")}
|
||||
|
||||
return {
|
||||
settings: new Settings(this.$config.settings()),
|
||||
|
|
|
@ -181,7 +181,7 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
if (showMerged && (this.results[index].PhotoType === 'video' || this.results[index].PhotoType === 'live')) {
|
||||
if (showMerged && (this.results[index].Type === 'video' || this.results[index].Type === 'live')) {
|
||||
if(this.results[index].isPlayable()) {
|
||||
this.$modal.show('video', {video: this.results[index], album: null});
|
||||
} else {
|
||||
|
@ -341,7 +341,7 @@
|
|||
case 'updated':
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const values = data.entities[i];
|
||||
const model = this.results.find((m) => m.PhotoUUID === values.PhotoUUID);
|
||||
const model = this.results.find((m) => m.UID === values.UID);
|
||||
|
||||
if (model) {
|
||||
for (let key in values) {
|
||||
|
@ -358,8 +358,8 @@
|
|||
if (this.context !== "archive") break;
|
||||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const uuid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.PhotoUUID === uuid);
|
||||
const uid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.UID === uid);
|
||||
if (index >= 0) {
|
||||
this.results.splice(index, 1);
|
||||
}
|
||||
|
@ -372,8 +372,8 @@
|
|||
if (this.context === "archive") break;
|
||||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const uuid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.PhotoUUID === uuid);
|
||||
const uid = data.entities[i];
|
||||
const index = this.results.findIndex((m) => m.UID === uid);
|
||||
if (index >= 0) {
|
||||
this.results.splice(index, 1);
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
}
|
||||
|
||||
if (this.photos.length > 0) {
|
||||
const index = this.photos.findIndex((p) => p.PhotoUUID === id);
|
||||
const index = this.photos.findIndex((p) => p.UID === id);
|
||||
|
||||
this.$viewer.show(Thumb.fromPhotos(this.photos), index)
|
||||
} else {
|
||||
|
@ -169,14 +169,14 @@
|
|||
if (!marker) {
|
||||
let el = document.createElement('div');
|
||||
el.className = 'marker';
|
||||
el.title = props.PhotoTitle;
|
||||
el.title = props.Title;
|
||||
el.style.backgroundImage =
|
||||
'url(/api/v1/thumbnails/' +
|
||||
props.FileHash + '/tile_50)';
|
||||
props.Hash + '/tile_50)';
|
||||
el.style.width = '50px';
|
||||
el.style.height = '50px';
|
||||
|
||||
el.addEventListener('click', () => this.openPhoto(props.PhotoUUID));
|
||||
el.addEventListener('click', () => this.openPhoto(props.UID));
|
||||
marker = this.markers[id] = new mapboxgl.Marker({
|
||||
element: el
|
||||
}).setLngLat(coords);
|
||||
|
|
|
@ -202,6 +202,36 @@
|
|||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
@change="onChange"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0"
|
||||
v-model="settings.features.folders"
|
||||
color="secondary-dark"
|
||||
:label="labels.folders"
|
||||
:hint="hints.folders"
|
||||
prepend-icon="folder"
|
||||
persistent-hint
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
@change="onChange"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0"
|
||||
v-model="settings.features.moments"
|
||||
color="secondary-dark"
|
||||
:label="labels.moments"
|
||||
:hint="hints.moments"
|
||||
prepend-icon="star"
|
||||
persistent-hint
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
@change="onChange"
|
||||
|
@ -318,6 +348,7 @@
|
|||
data() {
|
||||
return {
|
||||
readonly: this.$config.get("readonly"),
|
||||
experimental: this.$config.get("experimental"),
|
||||
settings: new Settings(this.$config.settings()),
|
||||
options: options,
|
||||
labels: {
|
||||
|
@ -333,6 +364,8 @@
|
|||
private: this.$gettext("Hide Private"),
|
||||
review: this.$gettext("Quality Filter"),
|
||||
places: this.$gettext("Places"),
|
||||
folders: this.$gettext("Folders"),
|
||||
moments: this.$gettext("Moments"),
|
||||
labels: this.$gettext("Labels"),
|
||||
import: this.$gettext("Import"),
|
||||
upload: this.$gettext("Upload"),
|
||||
|
@ -348,6 +381,8 @@
|
|||
group: this.$gettext("Files with sequential names like 'IMG_1234 (2)' or 'IMG_1234 copy 2' belong to the same photo."),
|
||||
move: this.$gettext("Move files from import to originals to save storage. Unsupported file types will never be deleted, they remain in their current location."),
|
||||
places: this.$gettext("Search and display photos on a map."),
|
||||
folders: this.$gettext("Browse existing folders as albums."),
|
||||
moments: this.$gettext("Let PhotoPrism create albums from past events."),
|
||||
labels: this.$gettext("Browse and edit image classification labels."),
|
||||
import: this.$gettext("Imported files will be sorted by date and given a unique name."),
|
||||
archive: this.$gettext("Hide photos that have been moved to archive."),
|
||||
|
|
|
@ -2,8 +2,8 @@ import Photos from "pages/photos.vue";
|
|||
import Albums from "pages/albums.vue";
|
||||
import AlbumPhotos from "pages/album/photos.vue";
|
||||
import Places from "pages/places.vue";
|
||||
import Folders from "pages/folders.vue";
|
||||
import Labels from "pages/labels.vue";
|
||||
import Events from "pages/events.vue";
|
||||
import People from "pages/people.vue";
|
||||
import Library from "pages/library.vue";
|
||||
import Share from "pages/share.vue";
|
||||
|
@ -12,7 +12,7 @@ import Login from "pages/login.vue";
|
|||
import Discover from "pages/discover.vue";
|
||||
import Todo from "pages/todo.vue";
|
||||
|
||||
const c = window.clientConfig;
|
||||
const c = window.__CONFIG__;
|
||||
|
||||
export default [
|
||||
{
|
||||
|
@ -41,7 +41,7 @@ export default [
|
|||
},
|
||||
{
|
||||
name: "album",
|
||||
path: "/albums/:uuid",
|
||||
path: "/albums/:uid",
|
||||
component: AlbumPhotos,
|
||||
meta: {title: "Album", auth: true},
|
||||
},
|
||||
|
@ -92,6 +92,12 @@ export default [
|
|||
component: Places,
|
||||
meta: {title: "Places", auth: true},
|
||||
},
|
||||
{
|
||||
name: "folders",
|
||||
path: "/folders",
|
||||
component: Folders,
|
||||
meta: {title: "Folders", auth: true},
|
||||
},
|
||||
{
|
||||
name: "labels",
|
||||
path: "/labels",
|
||||
|
@ -99,10 +105,11 @@ export default [
|
|||
meta: {title: "Labels", auth: true},
|
||||
},
|
||||
{
|
||||
name: "events",
|
||||
path: "/events",
|
||||
component: Events,
|
||||
meta: {title: "Events", auth: true},
|
||||
name: "moments",
|
||||
path: "/moments",
|
||||
component: Photos,
|
||||
meta: {title: "Moments", auth: true},
|
||||
props: {},
|
||||
},
|
||||
{
|
||||
name: "people",
|
||||
|
@ -134,7 +141,7 @@ export default [
|
|||
name: "library",
|
||||
path: "/library",
|
||||
component: Library,
|
||||
meta: {title: "Photo Library", auth: true, background: "application-light"},
|
||||
meta: {title: "Originals", auth: true, background: "application-light"},
|
||||
props: {tab: 0},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Config from "common/config";
|
||||
import Session from "common/session";
|
||||
|
||||
export const config = new Config(window.localStorage, window.clientConfig);
|
||||
export const config = new Config(window.localStorage, window.__CONFIG__);
|
||||
export const session = new Session(window.localStorage, config);
|
||||
|
||||
export default session;
|
||||
|
|
|
@ -28,11 +28,11 @@ describe("common/clipboard", () => {
|
|||
assert.equal(clipboard.storageKey, "clipboard");
|
||||
assert.equal(clipboard.selection, "");
|
||||
|
||||
const values = {ID: 5, PhotoUUID: "ABC123", PhotoTitle: "Crazy Cat", PhotoColor: "brown"};
|
||||
const values = {ID: 5, UID: "ABC123", Title: "Crazy Cat"};
|
||||
const photo = new Photo(values);
|
||||
clipboard.toggle(photo);
|
||||
assert.equal(clipboard.selection[0], "ABC123");
|
||||
const values2 = {ID: 8, PhotoUUID: "ABC124", PhotoTitle: "Crazy Cat", PhotoColor: "brown"};
|
||||
const values2 = {ID: 8, UID: "ABC124", Title: "Crazy Cat"};
|
||||
const photo2 = new Photo(values2);
|
||||
clipboard.toggle(photo2);
|
||||
assert.equal(clipboard.selection[0], "ABC123");
|
||||
|
@ -66,7 +66,7 @@ describe("common/clipboard", () => {
|
|||
assert.equal(clipboard.selection, "");
|
||||
assert(spy.calledWith("Clipboard::add() - not a model:"));
|
||||
|
||||
const values = {ID: 5, PhotoUUID: "ABC124", PhotoTitle: "Crazy Cat", PhotoColor: "brown"};
|
||||
const values = {ID: 5, UID: "ABC124", Title: "Crazy Cat"};
|
||||
const photo = new Photo(values);
|
||||
clipboard.add(photo);
|
||||
assert.equal(clipboard.selection[0], "ABC124");
|
||||
|
@ -97,13 +97,13 @@ describe("common/clipboard", () => {
|
|||
assert.equal(clipboard.selection, "");
|
||||
assert(spy.calledWith("Clipboard::has() - not a model:"));
|
||||
|
||||
const values = {ID: 5, PhotoUUID: "ABC124", PhotoTitle: "Crazy Cat", PhotoColor: "brown"};
|
||||
const values = {ID: 5, UID: "ABC124", Title: "Crazy Cat"};
|
||||
const photo = new Photo(values);
|
||||
clipboard.add(photo);
|
||||
assert.equal(clipboard.selection[0], "ABC124");
|
||||
const result = clipboard.has(photo);
|
||||
assert.equal(result, true);
|
||||
const values2 = {ID: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
const values2 = {ID: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values2);
|
||||
const result2 = clipboard.has(album);
|
||||
assert.equal(result2, false);
|
||||
|
@ -133,14 +133,14 @@ describe("common/clipboard", () => {
|
|||
assert.equal(clipboard.selection, "");
|
||||
assert(spy.calledWith("Clipboard::remove() - not a model:"));
|
||||
|
||||
const values = {ID: 5, PhotoUUID: "ABC123", PhotoTitle: "Crazy Cat", PhotoColor: "brown"};
|
||||
const values = {ID: 5, UID: "ABC123", Title: "Crazy Cat"};
|
||||
const photo = new Photo(values);
|
||||
clipboard.add(photo);
|
||||
assert.equal(clipboard.selection[0], "ABC123");
|
||||
|
||||
clipboard.remove(photo);
|
||||
assert.equal(clipboard.selection, "");
|
||||
const values2 = {ID: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
const values2 = {ID: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values2);
|
||||
clipboard.remove(album);
|
||||
assert.equal(clipboard.selection, "");
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -13,49 +13,49 @@ mock
|
|||
|
||||
describe("model/album", () => {
|
||||
it("should get album entity name", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019"};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019"};
|
||||
const album = new Album(values);
|
||||
const result = album.getEntityName();
|
||||
assert.equal(result, "christmas-2019");
|
||||
});
|
||||
|
||||
it("should get album id", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
const result = album.getId();
|
||||
assert.equal(result, "66");
|
||||
});
|
||||
|
||||
it("should get album title", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019"};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019"};
|
||||
const album = new Album(values);
|
||||
const result = album.getTitle();
|
||||
assert.equal(result, "Christmas 2019");
|
||||
});
|
||||
|
||||
it("should get thumbnail url", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
const result = album.thumbnailUrl("xyz");
|
||||
assert.equal(result, "/api/v1/albums/66/thumbnail/xyz");
|
||||
});
|
||||
|
||||
it("should get thumbnail src set", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
const result = album.thumbnailSrcset("");
|
||||
assert.equal(result, "/api/v1/albums/66/thumbnail/fit_720 720w, /api/v1/albums/66/thumbnail/fit_1280 1280w, /api/v1/albums/66/thumbnail/fit_1920 1920w, /api/v1/albums/66/thumbnail/fit_2560 2560w, /api/v1/albums/66/thumbnail/fit_3840 3840w");
|
||||
});
|
||||
|
||||
it("should get thumbnail sizes", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", CreatedAt: "2012-07-08T14:45:39Z"};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", CreatedAt: "2012-07-08T14:45:39Z"};
|
||||
const album = new Album(values);
|
||||
const result = album.thumbnailSizes();
|
||||
assert.equal(result, "(min-width: 2560px) 3840px, (min-width: 1920px) 2560px, (min-width: 1280px) 1920px, (min-width: 720px) 1280px, 720px");
|
||||
});
|
||||
|
||||
it("should get date string", () => {
|
||||
const values = {ID: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", CreatedAt: "2012-07-08T14:45:39Z"};
|
||||
const values = {ID: 5, Name: "Christmas 2019", Slug: "christmas-2019", CreatedAt: "2012-07-08T14:45:39Z"};
|
||||
const album = new Album(values);
|
||||
const result = album.getDateString();
|
||||
assert.equal(result, "Jul 8, 2012, 2:45 PM");
|
||||
|
@ -72,28 +72,28 @@ describe("model/album", () => {
|
|||
});
|
||||
|
||||
it("should like album", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumFavorite: false};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", Favorite: false};
|
||||
const album = new Album(values);
|
||||
assert.equal(album.AlbumFavorite, false);
|
||||
assert.equal(album.Favorite, false);
|
||||
album.like();
|
||||
assert.equal(album.AlbumFavorite, true);
|
||||
assert.equal(album.Favorite, true);
|
||||
});
|
||||
|
||||
it("should unlike album", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumFavorite: true};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", Favorite: true};
|
||||
const album = new Album(values);
|
||||
assert.equal(album.AlbumFavorite, true);
|
||||
assert.equal(album.Favorite, true);
|
||||
album.unlike();
|
||||
assert.equal(album.AlbumFavorite, false);
|
||||
assert.equal(album.Favorite, false);
|
||||
});
|
||||
|
||||
it("should toggle like", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumFavorite: true};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", Favorite: true};
|
||||
const album = new Album(values);
|
||||
assert.equal(album.AlbumFavorite, true);
|
||||
assert.equal(album.Favorite, true);
|
||||
album.toggleLike();
|
||||
assert.equal(album.AlbumFavorite, false);
|
||||
assert.equal(album.Favorite, false);
|
||||
album.toggleLike();
|
||||
assert.equal(album.AlbumFavorite, true);
|
||||
assert.equal(album.Favorite, true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,49 +13,49 @@ mock
|
|||
|
||||
describe("model/label", () => {
|
||||
it("should get label entity name", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat"};
|
||||
const label = new Label(values);
|
||||
const result = label.getEntityName();
|
||||
assert.equal(result, "black-cat");
|
||||
});
|
||||
|
||||
it("should get label id", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat"};
|
||||
const label = new Label(values);
|
||||
const result = label.getId();
|
||||
assert.equal(result, "ABC123");
|
||||
});
|
||||
|
||||
it("should get label title", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat"};
|
||||
const label = new Label(values);
|
||||
const result = label.getTitle();
|
||||
assert.equal(result, "Black Cat");
|
||||
});
|
||||
|
||||
it("should get thumbnail url", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat"};
|
||||
const label = new Label(values);
|
||||
const result = label.thumbnailUrl("xyz");
|
||||
assert.equal(result, "/api/v1/labels/ABC123/thumbnail/xyz");
|
||||
});
|
||||
|
||||
it("should get thumbnail src set", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat"};
|
||||
const label = new Label(values);
|
||||
const result = label.thumbnailSrcset("");
|
||||
assert.equal(result, "/api/v1/labels/ABC123/thumbnail/fit_720 720w, /api/v1/labels/ABC123/thumbnail/fit_1280 1280w, /api/v1/labels/ABC123/thumbnail/fit_1920 1920w, /api/v1/labels/ABC123/thumbnail/fit_2560 2560w, /api/v1/labels/ABC123/thumbnail/fit_3840 3840w");
|
||||
});
|
||||
|
||||
it("should get thumbnail sizes", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat"};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat"};
|
||||
const label = new Label(values);
|
||||
const result = label.thumbnailSizes();
|
||||
assert.equal(result, "(min-width: 2560px) 3840px, (min-width: 1920px) 2560px, (min-width: 1280px) 1920px, (min-width: 720px) 1280px, 720px");
|
||||
});
|
||||
|
||||
it("should get date string", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat", CreatedAt: "2012-07-08T14:45:39Z"};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat", CreatedAt: "2012-07-08T14:45:39Z"};
|
||||
const label = new Label(values);
|
||||
const result = label.getDateString();
|
||||
assert.equal(result, "Jul 8, 2012, 2:45 PM");
|
||||
|
@ -72,28 +72,28 @@ describe("model/label", () => {
|
|||
});
|
||||
|
||||
it("should like label", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: false};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat", Favorite: false};
|
||||
const label = new Label(values);
|
||||
assert.equal(label.LabelFavorite, false);
|
||||
assert.equal(label.Favorite, false);
|
||||
label.like();
|
||||
assert.equal(label.LabelFavorite, true);
|
||||
assert.equal(label.Favorite, true);
|
||||
});
|
||||
|
||||
it("should unlike label", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123",LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: true};
|
||||
const values = {ID: 5, UID: "ABC123",Name: "Black Cat", Slug: "black-cat", Favorite: true};
|
||||
const label = new Label(values);
|
||||
assert.equal(label.LabelFavorite, true);
|
||||
assert.equal(label.Favorite, true);
|
||||
label.unlike();
|
||||
assert.equal(label.LabelFavorite, false);
|
||||
assert.equal(label.Favorite, false);
|
||||
});
|
||||
|
||||
it("should toggle like", () => {
|
||||
const values = {ID: 5, LabelUUID: "ABC123", LabelName: "Black Cat", LabelSlug: "black-cat", LabelFavorite: true};
|
||||
const values = {ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat", Favorite: true};
|
||||
const label = new Label(values);
|
||||
assert.equal(label.LabelFavorite, true);
|
||||
assert.equal(label.Favorite, true);
|
||||
label.toggleLike();
|
||||
assert.equal(label.LabelFavorite, false);
|
||||
assert.equal(label.Favorite, false);
|
||||
label.toggleLike();
|
||||
assert.equal(label.LabelFavorite, true);
|
||||
assert.equal(label.Favorite, true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,77 +13,56 @@ mock
|
|||
|
||||
describe("model/photo", () => {
|
||||
it("should get photo entity name", () => {
|
||||
const values = {id: 5, PhotoTitle: "Crazy Cat"};
|
||||
const values = {UID: 5, Title: "Crazy Cat"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getEntityName();
|
||||
assert.equal(result, "Crazy Cat");
|
||||
});
|
||||
|
||||
it("should get photo uuid", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", PhotoUUID: 789};
|
||||
const values = {ID: 5, Title: "Crazy Cat", UID: 789};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getId();
|
||||
assert.equal(result, 789);
|
||||
});
|
||||
|
||||
it("should get photo title", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", PhotoUUID: 789};
|
||||
const values = {ID: 5, Title: "Crazy Cat", UID: 789};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getTitle();
|
||||
assert.equal(result, "Crazy Cat");
|
||||
});
|
||||
|
||||
it("should get photo color brown", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", PhotoColor: "brown"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getColor();
|
||||
assert.equal(result, "grey lighten-2");
|
||||
});
|
||||
|
||||
it("should get photo color grey", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", PhotoColor: "grey"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getColor();
|
||||
assert.equal(result, "grey lighten-2");
|
||||
});
|
||||
|
||||
it("should get photo color pink", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", PhotoColor: "pink"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getColor();
|
||||
assert.equal(result, "pink lighten-4");
|
||||
});
|
||||
|
||||
it("should get photo maps link", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", PhotoLat: 36.442881666666665, PhotoLng: 28.229493333333334};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Lat: 36.442881666666665, Lng: 28.229493333333334};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getGoogleMapsLink();
|
||||
assert.equal(result, "https://www.google.com/maps/place/36.442881666666665,28.229493333333334");
|
||||
});
|
||||
|
||||
it("should get photo thumbnail url", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", FileHash: 345982};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Hash: 345982};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.thumbnailUrl("tile500");
|
||||
assert.equal(result, "/api/v1/thumbnails/345982/tile500");
|
||||
});
|
||||
|
||||
it("should get photo download url", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", FileHash: 345982};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Hash: 345982};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getDownloadUrl();
|
||||
assert.equal(result, "/api/v1/download/345982");
|
||||
});
|
||||
|
||||
it("should get photo thumbnail src set", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", FileHash: 345982};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Hash: 345982};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.thumbnailSrcset();
|
||||
assert.equal(result, "/api/v1/thumbnails/345982/fit_720 720w, /api/v1/thumbnails/345982/fit_1280 1280w, /api/v1/thumbnails/345982/fit_1920 1920w, /api/v1/thumbnails/345982/fit_2560 2560w, /api/v1/thumbnails/345982/fit_3840 3840w");
|
||||
});
|
||||
|
||||
it("should calculate photo size", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", FileWidth: 500, FileHeight: 200};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Width: 500,Height: 200};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.calculateSize(500, 200);
|
||||
assert.equal(result.width, 500);
|
||||
|
@ -91,7 +70,7 @@ describe("model/photo", () => {
|
|||
});
|
||||
|
||||
it("should calculate photo size with srcAspectRatio < maxAspectRatio", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", FileWidth: 500, FileHeight: 200};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Width: 500, Height: 200};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.calculateSize(300, 50);
|
||||
assert.equal(result.width, 125);
|
||||
|
@ -99,7 +78,7 @@ describe("model/photo", () => {
|
|||
});
|
||||
|
||||
it("should calculate photo size with srcAspectRatio > maxAspectRatio", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", FileWidth: 500, FileHeight: 200};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Width: 500, Height: 200};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.calculateSize(400, 300);
|
||||
assert.equal(result.width, 400);
|
||||
|
@ -107,70 +86,70 @@ describe("model/photo", () => {
|
|||
});
|
||||
|
||||
it("should get thumbnail sizes", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", FileWidth: 500, FileHeight: 200};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Width: 500, Height: 200};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.thumbnailSizes();
|
||||
assert.equal(result, "(min-width: 2560px) 3840px, (min-width: 1920px) 2560px, (min-width: 1280px) 1920px, (min-width: 720px) 1280px, 720px");
|
||||
});
|
||||
|
||||
it("should get date string", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", TakenAt: "2012-07-08T14:45:39Z", TimeZone: "UTC"};
|
||||
const values = {ID: 5, Title: "Crazy Cat", TakenAt: "2012-07-08T14:45:39Z", TimeZone: "UTC"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getDateString();
|
||||
assert.equal(result, "July 8, 2012, 2:45 PM UTC");
|
||||
});
|
||||
|
||||
it("should test whether photo has location", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", PhotoLat: 36.442881666666665, PhotoLng: 28.229493333333334};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Lat: 36.442881666666665, Lng: 28.229493333333334};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.hasLocation();
|
||||
assert.equal(result, true);
|
||||
});
|
||||
|
||||
it("should test whether photo has location", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", PhotoLat: 0, PhotoLng: 0};
|
||||
const values = {ID: 5, Title: "Crazy Cat", Lat: 0, Lng: 0};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.hasLocation();
|
||||
assert.equal(result, false);
|
||||
});
|
||||
|
||||
it("should get location", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", LocationID: 6, LocType: "viewpoint", LocLabel: "Cape Point, South Africa", LocCountry: "South Africa"};
|
||||
const values = {ID: 5, Title: "Crazy Cat", LocationID: 6, LocType: "viewpoint", LocLabel: "Cape Point, South Africa", LocCountry: "South Africa"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getLocation();
|
||||
assert.equal(result, "Cape Point, South Africa");
|
||||
});
|
||||
|
||||
it("should get location", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", LocationID: 6, LocType: "viewpoint", LocLabel: "Cape Point, State, South Africa", LocCountry: "South Africa", LocCity: "Cape Town", LocCounty: "County", LocState: "State"};
|
||||
const values = {ID: 5, Title: "Crazy Cat", LocationID: 6, LocType: "viewpoint", LocLabel: "Cape Point, State, South Africa", LocCountry: "South Africa", LocCity: "Cape Town", LocCounty: "County", LocState: "State"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getLocation();
|
||||
assert.equal(result, "Cape Point, State, South Africa");
|
||||
});
|
||||
|
||||
it("should get location", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", LocType: "viewpoint", LocName: "Cape Point", LocCountry: "Africa", LocCity: "Cape Town", LocCounty: "County", LocState: "State"};
|
||||
const values = {ID: 5, Title: "Crazy Cat", LocType: "viewpoint", LocName: "Cape Point", LocCountry: "Africa", LocCity: "Cape Town", LocCounty: "County", LocState: "State"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getLocation();
|
||||
assert.equal(result, "Unknown");
|
||||
});
|
||||
|
||||
it("should get location", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", CountryName: "Africa", LocCity: "Cape Town"};
|
||||
const values = {ID: 5, Title: "Crazy Cat", CountryName: "Africa", LocCity: "Cape Town"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getLocation();
|
||||
assert.equal(result, "Unknown");
|
||||
});
|
||||
|
||||
it("should get camera", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", CameraModel: "EOSD10", CameraMake: "Canon"};
|
||||
const values = {ID: 5, Title: "Crazy Cat", CameraModel: "EOSD10", CameraMake: "Canon"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getCamera();
|
||||
assert.equal(result, "Canon EOSD10");
|
||||
});
|
||||
|
||||
it("should get camera", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat"};
|
||||
const values = {ID: 5, Title: "Crazy Cat"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getCamera();
|
||||
assert.equal(result, "Unknown");
|
||||
|
@ -187,29 +166,29 @@ describe("model/photo", () => {
|
|||
});
|
||||
|
||||
it("should like photo", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", CountryName: "Africa", PhotoFavorite: false};
|
||||
const values = {ID: 5, Title: "Crazy Cat", CountryName: "Africa", Favorite: false};
|
||||
const photo = new Photo(values);
|
||||
assert.equal(photo.PhotoFavorite, false);
|
||||
assert.equal(photo.Favorite, false);
|
||||
photo.like();
|
||||
assert.equal(photo.PhotoFavorite, true);
|
||||
assert.equal(photo.Favorite, true);
|
||||
});
|
||||
|
||||
it("should unlike photo", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", CountryName: "Africa", PhotoFavorite: true};
|
||||
const values = {ID: 5, Title: "Crazy Cat", CountryName: "Africa", Favorite: true};
|
||||
const photo = new Photo(values);
|
||||
assert.equal(photo.PhotoFavorite, true);
|
||||
assert.equal(photo.Favorite, true);
|
||||
photo.unlike();
|
||||
assert.equal(photo.PhotoFavorite, false);
|
||||
assert.equal(photo.Favorite, false);
|
||||
});
|
||||
|
||||
it("should toggle like", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", CountryName: "Africa", PhotoFavorite: true};
|
||||
const values = {ID: 5, Title: "Crazy Cat", CountryName: "Africa", Favorite: true};
|
||||
const photo = new Photo(values);
|
||||
assert.equal(photo.PhotoFavorite, true);
|
||||
assert.equal(photo.Favorite, true);
|
||||
photo.toggleLike();
|
||||
assert.equal(photo.PhotoFavorite, false);
|
||||
assert.equal(photo.Favorite, false);
|
||||
photo.toggleLike();
|
||||
assert.equal(photo.PhotoFavorite, true);
|
||||
assert.equal(photo.Favorite, true);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -10,36 +10,36 @@ let assert = chai.assert;
|
|||
describe("model/abstract", () => {
|
||||
const mock = new MockAdapter(Api);
|
||||
it("should set values", () => {
|
||||
const values = {id: 5, LabelName: "Black Cat", LabelSlug: "black-cat"};
|
||||
const values = {id: 5, Name: "Black Cat", Slug: "black-cat"};
|
||||
const label = new Label(values);
|
||||
assert.equal(label.LabelName, "Black Cat");
|
||||
assert.equal(label.LabelSlug, "black-cat");
|
||||
assert.equal(label.Name, "Black Cat");
|
||||
assert.equal(label.Slug, "black-cat");
|
||||
label.setValues();
|
||||
assert.equal(label.LabelName, "Black Cat");
|
||||
assert.equal(label.LabelSlug, "black-cat");
|
||||
const values2 = {id: 6, LabelName: "White Cat", LabelSlug: "white-cat"};
|
||||
assert.equal(label.Name, "Black Cat");
|
||||
assert.equal(label.Slug, "black-cat");
|
||||
const values2 = {id: 6, Name: "White Cat", Slug: "white-cat"};
|
||||
label.setValues(values2);
|
||||
assert.equal(label.LabelName, "White Cat");
|
||||
assert.equal(label.LabelSlug, "white-cat");
|
||||
assert.equal(label.Name, "White Cat");
|
||||
assert.equal(label.Slug, "white-cat");
|
||||
});
|
||||
|
||||
it("should get values", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
const result = album.getValues();
|
||||
assert.equal(result.AlbumName, "Christmas 2019");
|
||||
assert.equal(result.AlbumUUID, 66);
|
||||
assert.equal(result.Name, "Christmas 2019");
|
||||
assert.equal(result.UID, 66);
|
||||
});
|
||||
|
||||
it("should get id", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
const result = album.getId();
|
||||
assert.equal(result, 66);
|
||||
});
|
||||
|
||||
it("should test if id exists", () => {
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
const result = album.hasId();
|
||||
assert.equal(result, true);
|
||||
|
@ -51,47 +51,47 @@ describe("model/abstract", () => {
|
|||
});
|
||||
|
||||
it("should update album", async() => {
|
||||
mock.onPut().reply(200, {AlbumDescription: "Test description"});
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
mock.onPut().reply(200, {Description: "Test description"});
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
assert.equal(album.AlbumDescription, undefined);
|
||||
assert.equal(album.Description, undefined);
|
||||
await album.update();
|
||||
assert.equal(album.AlbumDescription, "Test description");
|
||||
assert.equal(album.Description, "Test description");
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
it("should save album", async() => {
|
||||
mock.onPut().reply(200, {AlbumDescription: "Test description"});
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019", AlbumUUID: 66};
|
||||
mock.onPut().reply(200, {Description: "Test description"});
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
assert.equal(album.AlbumDescription, undefined);
|
||||
assert.equal(album.Description, undefined);
|
||||
await album.save();
|
||||
assert.equal(album.AlbumDescription, "Test description");
|
||||
assert.equal(album.Description, "Test description");
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
it("should save album", async() => {
|
||||
mock.onPost().reply(200, {AlbumDescription: "Test description"});
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019"};
|
||||
mock.onPost().reply(200, {Description: "Test description"});
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019"};
|
||||
const album = new Album(values);
|
||||
assert.equal(album.AlbumDescription, undefined);
|
||||
assert.equal(album.Description, undefined);
|
||||
await album.save();
|
||||
assert.equal(album.AlbumDescription, "Test description");
|
||||
assert.equal(album.Description, "Test description");
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
it("should remove album", async() => {
|
||||
mock.onDelete().reply(200);
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019"};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019"};
|
||||
const album = new Album(values);
|
||||
assert.equal(album.AlbumName, "Christmas 2019");
|
||||
assert.equal(album.Name, "Christmas 2019");
|
||||
await album.remove();
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
it("should get edit form", async() => {
|
||||
mock.onAny().reply(200, "editForm");
|
||||
const values = {id: 5, AlbumName: "Christmas 2019", AlbumSlug: "christmas-2019"};
|
||||
const values = {id: 5, Name: "Christmas 2019", Slug: "christmas-2019"};
|
||||
const album = new Album(values);
|
||||
const result = await album.getEditForm();
|
||||
assert.equal(result.definition, "editForm");
|
||||
|
@ -113,10 +113,10 @@ describe("model/abstract", () => {
|
|||
});
|
||||
|
||||
it("should search label", async() => {
|
||||
mock.onAny().reply(200, {"ID":51,"CreatedAt":"2019-07-03T18:48:07Z","UpdatedAt":"2019-07-25T01:04:44Z","DeletedAt":"0001-01-01T00:00:00Z","LabelSlug":"tabby-cat","LabelName":"tabby cat","LabelPriority":5,"LabelCount":9,"LabelFavorite":false,"LabelDescription":"","LabelNotes":""});
|
||||
mock.onAny().reply(200, {"ID":51,"CreatedAt":"2019-07-03T18:48:07Z","UpdatedAt":"2019-07-25T01:04:44Z","DeletedAt":"0001-01-01T00:00:00Z","Slug":"tabby-cat","Name":"tabby cat","Priority":5,"LabelCount":9,"Favorite":false,"Description":"","Notes":""});
|
||||
const result = await Album.search();
|
||||
assert.equal(result.data.ID, 51);
|
||||
assert.equal(result.data.LabelName, "tabby cat");
|
||||
assert.equal(result.data.Name, "tabby cat");
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ func ShareWithAccount(router *gin.RouterGroup, conf *config.Config) {
|
|||
}
|
||||
|
||||
dst := f.Destination
|
||||
files, err := query.FilesByUUID(f.Photos, 1000, 0)
|
||||
files, err := query.FilesByUID(f.Photos, 1000, 0)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
||||
|
|
|
@ -57,11 +57,11 @@ func GetAlbums(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/albums/:uuid
|
||||
// GET /api/v1/albums/:uid
|
||||
func GetAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/albums/:uuid", func(c *gin.Context) {
|
||||
id := c.Param("uuid")
|
||||
m, err := query.AlbumByUUID(id)
|
||||
router.GET("/albums/:uid", func(c *gin.Context) {
|
||||
id := c.Param("uid")
|
||||
m, err := query.AlbumByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
|
@ -87,7 +87,7 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
m := entity.NewAlbum(f.AlbumName)
|
||||
m := entity.NewAlbum(f.AlbumName, entity.TypeDefault)
|
||||
m.AlbumFavorite = f.AlbumFavorite
|
||||
|
||||
log.Debugf("create album: %+v %+v", f, m)
|
||||
|
@ -102,22 +102,22 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
|
||||
PublishAlbumEvent(EntityCreated, m.AlbumUUID, c)
|
||||
PublishAlbumEvent(EntityCreated, m.AlbumUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
})
|
||||
}
|
||||
|
||||
// PUT /api/v1/albums/:uuid
|
||||
// PUT /api/v1/albums/:uid
|
||||
func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.PUT("/albums/:uuid", func(c *gin.Context) {
|
||||
router.PUT("/albums/:uid", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
uuid := c.Param("uuid")
|
||||
m, err := query.AlbumByUUID(uuid)
|
||||
uid := c.Param("uid")
|
||||
m, err := query.AlbumByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
|
@ -147,23 +147,23 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
event.Success("album saved")
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, uuid, c)
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/albums/:uuid
|
||||
// DELETE /api/v1/albums/:uid
|
||||
func DeleteAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.DELETE("/albums/:uuid", func(c *gin.Context) {
|
||||
router.DELETE("/albums/:uid", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("uuid")
|
||||
id := c.Param("uid")
|
||||
|
||||
m, err := query.AlbumByUUID(id)
|
||||
m, err := query.AlbumByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
|
@ -181,19 +181,19 @@ func DeleteAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/albums/:uuid/like
|
||||
// POST /api/v1/albums/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string Album UUID
|
||||
// uid: string Album UID
|
||||
func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/albums/:uuid/like", func(c *gin.Context) {
|
||||
router.POST("/albums/:uid/like", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("uuid")
|
||||
album, err := query.AlbumByUUID(id)
|
||||
id := c.Param("uid")
|
||||
album, err := query.AlbumByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
|
@ -210,19 +210,19 @@ func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/albums/:uuid/like
|
||||
// DELETE /api/v1/albums/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string Album UUID
|
||||
// uid: string Album UID
|
||||
func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.DELETE("/albums/:uuid/like", func(c *gin.Context) {
|
||||
router.DELETE("/albums/:uid/like", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("uuid")
|
||||
album, err := query.AlbumByUUID(id)
|
||||
id := c.Param("uid")
|
||||
album, err := query.AlbumByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
|
@ -239,9 +239,9 @@ func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/albums/:uuid/photos
|
||||
// POST /api/v1/albums/:uid/photos
|
||||
func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/albums/:uuid/photos", func(c *gin.Context) {
|
||||
router.POST("/albums/:uid/photos", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
|
@ -254,8 +254,8 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
uuid := c.Param("uuid")
|
||||
a, err := query.AlbumByUUID(uuid)
|
||||
uid := c.Param("uid")
|
||||
a, err := query.AlbumByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
|
@ -273,24 +273,24 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
var added []*entity.PhotoAlbum
|
||||
|
||||
for _, p := range photos {
|
||||
added = append(added, entity.NewPhotoAlbum(p.PhotoUUID, a.AlbumUUID).FirstOrCreate())
|
||||
added = append(added, entity.NewPhotoAlbum(p.PhotoUID, a.AlbumUID).FirstOrCreate())
|
||||
}
|
||||
|
||||
if len(added) == 1 {
|
||||
event.Success(fmt.Sprintf("one photo added to %s", a.AlbumName))
|
||||
event.Success(fmt.Sprintf("one photo added to %s", txt.Quote(a.AlbumName)))
|
||||
} else {
|
||||
event.Success(fmt.Sprintf("%d photos added to %s", len(added), a.AlbumName))
|
||||
event.Success(fmt.Sprintf("%d photos added to %s", len(added), txt.Quote(a.AlbumName)))
|
||||
}
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUUID, c)
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "photos added to album", "album": a, "added": added})
|
||||
})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/albums/:uuid/photos
|
||||
// DELETE /api/v1/albums/:uid/photos
|
||||
func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.DELETE("/albums/:uuid/photos", func(c *gin.Context) {
|
||||
router.DELETE("/albums/:uid/photos", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
|
@ -309,29 +309,29 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
a, err := query.AlbumByUUID(c.Param("uuid"))
|
||||
a, err := query.AlbumByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
entity.Db().Where("album_uuid = ? AND photo_uuid IN (?)", a.AlbumUUID, f.Photos).Delete(&entity.PhotoAlbum{})
|
||||
entity.Db().Where("album_uid = ? AND photo_uid IN (?)", a.AlbumUID, f.Photos).Delete(&entity.PhotoAlbum{})
|
||||
|
||||
event.Success(fmt.Sprintf("photos removed from %s", a.AlbumName))
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUUID, c)
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "photos removed from album", "album": a, "photos": f.Photos})
|
||||
})
|
||||
}
|
||||
|
||||
// GET /albums/:uuid/download
|
||||
// GET /albums/:uid/download
|
||||
func DownloadAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/albums/:uuid/download", func(c *gin.Context) {
|
||||
router.GET("/albums/:uid/download", func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
|
||||
a, err := query.AlbumByUUID(c.Param("uuid"))
|
||||
a, err := query.AlbumByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
|
@ -339,7 +339,7 @@ func DownloadAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
}
|
||||
|
||||
p, _, err := query.Photos(form.PhotoSearch{
|
||||
Album: a.AlbumUUID,
|
||||
Album: a.AlbumUID,
|
||||
Count: 10000,
|
||||
Offset: 0,
|
||||
})
|
||||
|
@ -411,15 +411,15 @@ func DownloadAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/albums/:uuid/thumbnail/:type
|
||||
// GET /api/v1/albums/:uid/thumbnail/:type
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string Album UUID
|
||||
// uid: string Album UID
|
||||
// type: string Thumbnail type, see photoprism.ThumbnailTypes
|
||||
func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/albums/:uuid/thumbnail/:type", func(c *gin.Context) {
|
||||
router.GET("/albums/:uid/thumbnail/:type", func(c *gin.Context) {
|
||||
typeName := c.Param("type")
|
||||
uuid := c.Param("uuid")
|
||||
uid := c.Param("uid")
|
||||
start := time.Now()
|
||||
|
||||
thumbType, ok := thumb.Types[typeName]
|
||||
|
@ -431,18 +431,18 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
}
|
||||
|
||||
gc := service.Cache()
|
||||
cacheKey := fmt.Sprintf("album-thumbnail:%s:%s", uuid, typeName)
|
||||
cacheKey := fmt.Sprintf("album-thumbnail:%s:%s", uid, typeName)
|
||||
|
||||
if cacheData, ok := gc.Get(cacheKey); ok {
|
||||
log.Debugf("%s cache hit [%s]", cacheKey, time.Since(start))
|
||||
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
c.Data(http.StatusOK, "image/jpeg", cacheData.([]byte))
|
||||
return
|
||||
}
|
||||
|
||||
f, err := query.AlbumThumbByUUID(uuid)
|
||||
f, err := query.AlbumThumbByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("album: no photos yet, using generic image for %s", uuid)
|
||||
log.Debugf("album: no photos yet, using generic image for %s", uid)
|
||||
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
|
||||
return
|
||||
}
|
||||
|
@ -494,7 +494,7 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
gc.Set(cacheKey, thumbData, time.Hour)
|
||||
|
||||
log.Debugf("%s cached [%s]", cacheKey, time.Since(start))
|
||||
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
|
||||
|
||||
c.Data(http.StatusOK, "image/jpeg", thumbData)
|
||||
})
|
||||
|
|
|
@ -31,7 +31,7 @@ func TestGetAlbum(t *testing.T) {
|
|||
app, router, conf := NewApiTest()
|
||||
GetAlbum(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8")
|
||||
val := gjson.Get(r.Body.String(), "AlbumSlug")
|
||||
val := gjson.Get(r.Body.String(), "Slug")
|
||||
assert.Equal(t, "holiday-2030", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
@ -49,34 +49,34 @@ func TestCreateAlbum(t *testing.T) {
|
|||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
CreateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"AlbumName": "New created album", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
val := gjson.Get(r.Body.String(), "AlbumSlug")
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"Name": "New created album", "Notes": "", "Favorite": true}`)
|
||||
val := gjson.Get(r.Body.String(), "Slug")
|
||||
assert.Equal(t, "new-created-album", val.String())
|
||||
val2 := gjson.Get(r.Body.String(), "AlbumFavorite")
|
||||
val2 := gjson.Get(r.Body.String(), "Favorite")
|
||||
assert.Equal(t, "true", val2.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
CreateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"AlbumName": 333, "AlbumDescription": "Created via unit test", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"Name": 333, "Description": "Created via unit test", "Notes": "", "Favorite": true}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
||||
func TestUpdateAlbum(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
CreateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"AlbumName": "Update", "AlbumDescription": "To be updated", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"Name": "Update", "Description": "To be updated", "Notes": "", "Favorite": true}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
uuid := gjson.Get(r.Body.String(), "AlbumUUID").String()
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
UpdateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/albums/"+uuid, `{"AlbumName": "Updated01", "AlbumNotes": "", "AlbumFavorite": false}`)
|
||||
val := gjson.Get(r.Body.String(), "AlbumSlug")
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/albums/"+uid, `{"Name": "Updated01", "Notes": "", "Favorite": false}`)
|
||||
val := gjson.Get(r.Body.String(), "Slug")
|
||||
assert.Equal(t, "updated01", val.String())
|
||||
val2 := gjson.Get(r.Body.String(), "AlbumFavorite")
|
||||
val2 := gjson.Get(r.Body.String(), "Favorite")
|
||||
assert.Equal(t, "false", val2.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
@ -84,14 +84,14 @@ func TestUpdateAlbum(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
UpdateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/albums"+uuid, `{"AlbumName": 333, "AlbumDescription": "Created via unit test", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/albums"+uid, `{"Name": 333, "Description": "Created via unit test", "Notes": "", "Favorite": true}`)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
UpdateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/albums/xxx", `{"AlbumName": "Update03", "AlbumDescription": "Created via unit test", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/albums/xxx", `{"Name": "Update03", "Description": "Created via unit test", "Notes": "", "Favorite": true}`)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "Album not found", val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
|
@ -100,19 +100,19 @@ func TestUpdateAlbum(t *testing.T) {
|
|||
func TestDeleteAlbum(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
CreateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"AlbumName": "Delete", "AlbumDescription": "To be deleted", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"Name": "Delete", "Description": "To be deleted", "Notes": "", "Favorite": true}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
uuid := gjson.Get(r.Body.String(), "AlbumUUID").String()
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
|
||||
t.Run("delete existing album", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
DeleteAlbum(router, conf)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/albums/"+uuid)
|
||||
r := PerformRequest(app, "DELETE", "/api/v1/albums/"+uid)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "AlbumSlug")
|
||||
val := gjson.Get(r.Body.String(), "Slug")
|
||||
assert.Equal(t, "delete", val.String())
|
||||
GetAlbums(router, conf)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/albums/"+uuid)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/albums/"+uid)
|
||||
assert.Equal(t, http.StatusNotFound, r2.Code)
|
||||
})
|
||||
t.Run("delete not existing album", func(t *testing.T) {
|
||||
|
@ -142,7 +142,7 @@ func TestLikeAlbum(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
GetAlbum(router, ctx)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba7")
|
||||
val := gjson.Get(r2.Body.String(), "AlbumFavorite")
|
||||
val := gjson.Get(r2.Body.String(), "Favorite")
|
||||
assert.Equal(t, "true", val.String())
|
||||
})
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ func TestDislikeAlbum(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
GetAlbum(router, conf)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8")
|
||||
val := gjson.Get(r2.Body.String(), "AlbumFavorite")
|
||||
val := gjson.Get(r2.Body.String(), "Favorite")
|
||||
assert.Equal(t, "false", val.String())
|
||||
})
|
||||
}
|
||||
|
@ -173,14 +173,14 @@ func TestDislikeAlbum(t *testing.T) {
|
|||
func TestAddPhotosToAlbum(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
CreateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"AlbumName": "Add photos", "AlbumDescription": "", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"Name": "Add photos", "Description": "", "Notes": "", "Favorite": true}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
uuid := gjson.Get(r.Body.String(), "AlbumUUID").String()
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
AddPhotosToAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/"+uuid+"/photos", `{"photos": ["pt9jtdre2lvl0y12", "pt9jtdre2lvl0y11"]}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/"+uid+"/photos", `{"photos": ["pt9jtdre2lvl0y12", "pt9jtdre2lvl0y11"]}`)
|
||||
val := gjson.Get(r.Body.String(), "message")
|
||||
assert.Equal(t, "photos added to album", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
|
@ -188,7 +188,7 @@ func TestAddPhotosToAlbum(t *testing.T) {
|
|||
t.Run("add one photo to album", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
AddPhotosToAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/"+uuid+"/photos", `{"photos": ["pt9jtdre2lvl0y12"]}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/"+uid+"/photos", `{"photos": ["pt9jtdre2lvl0y12"]}`)
|
||||
val := gjson.Get(r.Body.String(), "message")
|
||||
assert.Equal(t, "photos added to album", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
|
@ -196,7 +196,7 @@ func TestAddPhotosToAlbum(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
AddPhotosToAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/"+uuid+"/photos", `{"photos": [123, "pt9jtdre2lvl0yxx"]}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/"+uid+"/photos", `{"photos": [123, "pt9jtdre2lvl0yxx"]}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
|
@ -210,17 +210,17 @@ func TestAddPhotosToAlbum(t *testing.T) {
|
|||
func TestRemovePhotosFromAlbum(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
CreateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"AlbumName": "Remove photos", "AlbumDescription": "", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"Name": "Remove photos", "Description": "", "Notes": "", "Favorite": true}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
uuid := gjson.Get(r.Body.String(), "AlbumUUID").String()
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
AddPhotosToAlbum(router, conf)
|
||||
r2 := PerformRequestWithBody(app, "POST", "/api/v1/albums/"+uuid+"/photos", `{"photos": ["pt9jtdre2lvl0y12", "pt9jtdre2lvl0y11"]}`)
|
||||
r2 := PerformRequestWithBody(app, "POST", "/api/v1/albums/"+uid+"/photos", `{"photos": ["pt9jtdre2lvl0y12", "pt9jtdre2lvl0y11"]}`)
|
||||
assert.Equal(t, http.StatusOK, r2.Code)
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
RemovePhotosFromAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "DELETE", "/api/v1/albums/"+uuid+"/photos", `{"photos": ["pt9jtdre2lvl0y12", "pt9jtdre2lvl0y11"]}`)
|
||||
r := PerformRequestWithBody(app, "DELETE", "/api/v1/albums/"+uid+"/photos", `{"photos": ["pt9jtdre2lvl0y12", "pt9jtdre2lvl0y11"]}`)
|
||||
val := gjson.Get(r.Body.String(), "message")
|
||||
assert.Equal(t, "photos removed from album", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
|
@ -236,7 +236,7 @@ func TestRemovePhotosFromAlbum(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
RemovePhotosFromAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "DELETE", "/api/v1/albums/"+uuid+"/photos", `{"photos": [123, "pt9jtdre2lvl0yxx"]}`)
|
||||
r := PerformRequestWithBody(app, "DELETE", "/api/v1/albums/"+uid+"/photos", `{"photos": [123, "pt9jtdre2lvl0yxx"]}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
t.Run("album not found", func(t *testing.T) {
|
||||
|
|
|
@ -41,7 +41,7 @@ func BatchPhotosArchive(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
log.Infof("photos: archiving %#v", f.Photos)
|
||||
|
||||
err := entity.Db().Where("photo_uuid IN (?)", f.Photos).Delete(&entity.Photo{}).Error
|
||||
err := entity.Db().Where("photo_uid IN (?)", f.Photos).Delete(&entity.Photo{}).Error
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
|
||||
|
@ -87,7 +87,7 @@ func BatchPhotosRestore(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
log.Infof("restoring photos: %#v", f.Photos)
|
||||
|
||||
err := entity.Db().Unscoped().Model(&entity.Photo{}).Where("photo_uuid IN (?)", f.Photos).
|
||||
err := entity.Db().Unscoped().Model(&entity.Photo{}).Where("photo_uid IN (?)", f.Photos).
|
||||
UpdateColumn("deleted_at", gorm.Expr("NULL")).Error
|
||||
|
||||
if err != nil {
|
||||
|
@ -132,8 +132,8 @@ func BatchAlbumsDelete(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
log.Infof("albums: deleting %#v", f.Albums)
|
||||
|
||||
entity.Db().Where("album_uuid IN (?)", f.Albums).Delete(&entity.Album{})
|
||||
entity.Db().Where("album_uuid IN (?)", f.Albums).Delete(&entity.PhotoAlbum{})
|
||||
entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.Album{})
|
||||
entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.PhotoAlbum{})
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
|
||||
|
@ -168,7 +168,7 @@ func BatchPhotosPrivate(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
log.Infof("marking photos as private: %#v", f.Photos)
|
||||
|
||||
err := entity.Db().Model(entity.Photo{}).Where("photo_uuid IN (?)", f.Photos).UpdateColumn("photo_private", gorm.Expr("IF (`photo_private`, 0, 1)")).Error
|
||||
err := entity.Db().Model(entity.Photo{}).Where("photo_uid IN (?)", f.Photos).UpdateColumn("photo_private", gorm.Expr("IF (`photo_private`, 0, 1)")).Error
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
|
||||
|
@ -214,7 +214,7 @@ func BatchLabelsDelete(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
log.Infof("labels: deleting %#v", f.Labels)
|
||||
|
||||
entity.Db().Where("label_uuid IN (?)", f.Labels).Delete(&entity.Label{})
|
||||
entity.Db().Where("label_uid IN (?)", f.Labels).Delete(&entity.Label{})
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
|
||||
|
|
|
@ -90,25 +90,25 @@ func TestBatchPhotosRestore(t *testing.T) {
|
|||
func TestBatchAlbumsDelete(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
CreateAlbum(router, conf)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"AlbumName": "BatchDelete", "AlbumDescription": "To be deleted", "AlbumNotes": "", "AlbumFavorite": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums", `{"Name": "BatchDelete", "Description": "To be deleted", "Notes": "", "Favorite": true}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
uuid := gjson.Get(r.Body.String(), "AlbumUUID").String()
|
||||
uid := gjson.Get(r.Body.String(), "UID").String()
|
||||
|
||||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
|
||||
GetAlbum(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/"+uuid)
|
||||
val := gjson.Get(r.Body.String(), "AlbumSlug")
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/"+uid)
|
||||
val := gjson.Get(r.Body.String(), "Slug")
|
||||
assert.Equal(t, "batchdelete", val.String())
|
||||
|
||||
BatchAlbumsDelete(router, conf)
|
||||
r2 := PerformRequestWithBody(app, "POST", "/api/v1/batch/albums/delete", fmt.Sprintf(`{"albums": ["%s", "pt9jtdre2lvl0ycc"]}`, uuid))
|
||||
r2 := PerformRequestWithBody(app, "POST", "/api/v1/batch/albums/delete", fmt.Sprintf(`{"albums": ["%s", "pt9jtdre2lvl0ycc"]}`, uid))
|
||||
val2 := gjson.Get(r2.Body.String(), "message")
|
||||
assert.Contains(t, val2.String(), "albums deleted")
|
||||
assert.Equal(t, http.StatusOK, r2.Code)
|
||||
|
||||
r3 := PerformRequest(app, "GET", "/api/v1/albums/"+uuid)
|
||||
r3 := PerformRequest(app, "GET", "/api/v1/albums/"+uid)
|
||||
val3 := gjson.Get(r3.Body.String(), "error")
|
||||
assert.Equal(t, "Album not found", val3.String())
|
||||
assert.Equal(t, http.StatusNotFound, r3.Code)
|
||||
|
@ -135,7 +135,7 @@ func TestBatchPhotosPrivate(t *testing.T) {
|
|||
GetPhoto(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/photos/pt9jtdre2lvl0yh8")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "PhotoPrivate")
|
||||
val := gjson.Get(r.Body.String(), "Private")
|
||||
assert.Equal(t, "false", val.String())
|
||||
|
||||
BatchPhotosPrivate(router, conf)
|
||||
|
@ -146,7 +146,7 @@ func TestBatchPhotosPrivate(t *testing.T) {
|
|||
|
||||
r3 := PerformRequest(app, "GET", "/api/v1/photos/pt9jtdre2lvl0yh8")
|
||||
assert.Equal(t, http.StatusOK, r3.Code)
|
||||
val3 := gjson.Get(r3.Body.String(), "PhotoPrivate")
|
||||
val3 := gjson.Get(r3.Body.String(), "Private")
|
||||
assert.Equal(t, "true", val3.String())
|
||||
})
|
||||
t.Run("no photos selected", func(t *testing.T) {
|
||||
|
@ -170,7 +170,7 @@ func TestBatchLabelsDelete(t *testing.T) {
|
|||
app, router, conf := NewApiTest()
|
||||
GetLabels(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels?count=15")
|
||||
val := gjson.Get(r.Body.String(), `#(LabelName=="BatchDelete").LabelSlug`)
|
||||
val := gjson.Get(r.Body.String(), `#(Name=="BatchDelete").Slug`)
|
||||
assert.Equal(t, val.String(), "batchdelete")
|
||||
|
||||
BatchLabelsDelete(router, conf)
|
||||
|
@ -180,7 +180,7 @@ func TestBatchLabelsDelete(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, r2.Code)
|
||||
|
||||
r3 := PerformRequest(app, "GET", "/api/v1/labels?count=15")
|
||||
val3 := gjson.Get(r3.Body.String(), `#(LabelName=="BatchDelete").LabelSlug`)
|
||||
val3 := gjson.Get(r3.Body.String(), `#(Name=="BatchDelete").Slug`)
|
||||
assert.Equal(t, val3.String(), "")
|
||||
})
|
||||
t.Run("no labels selected", func(t *testing.T) {
|
||||
|
|
|
@ -13,8 +13,8 @@ import (
|
|||
)
|
||||
|
||||
// TODO: GET /api/v1/dl/file/:hash
|
||||
// TODO: GET /api/v1/dl/photo/:uuid
|
||||
// TODO: GET /api/v1/dl/album/:uuid
|
||||
// TODO: GET /api/v1/dl/photo/:uid
|
||||
// TODO: GET /api/v1/dl/album/:uid
|
||||
|
||||
// GET /api/v1/download/:hash
|
||||
//
|
||||
|
|
|
@ -17,8 +17,8 @@ const (
|
|||
EntityDeleted EntityEvent = "deleted"
|
||||
)
|
||||
|
||||
func PublishPhotoEvent(e EntityEvent, uuid string, c *gin.Context) {
|
||||
f := form.PhotoSearch{ID: uuid, Merged: true}
|
||||
func PublishPhotoEvent(e EntityEvent, uid string, c *gin.Context) {
|
||||
f := form.PhotoSearch{ID: uid, Merged: true}
|
||||
result, _, err := query.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
|
@ -30,8 +30,8 @@ func PublishPhotoEvent(e EntityEvent, uuid string, c *gin.Context) {
|
|||
event.PublishEntities("photos", string(e), result)
|
||||
}
|
||||
|
||||
func PublishAlbumEvent(e EntityEvent, uuid string, c *gin.Context) {
|
||||
f := form.AlbumSearch{ID: uuid}
|
||||
func PublishAlbumEvent(e EntityEvent, uid string, c *gin.Context) {
|
||||
f := form.AlbumSearch{ID: uid}
|
||||
result, err := query.Albums(f)
|
||||
|
||||
if err != nil {
|
||||
|
@ -43,8 +43,8 @@ func PublishAlbumEvent(e EntityEvent, uuid string, c *gin.Context) {
|
|||
event.PublishEntities("albums", string(e), result)
|
||||
}
|
||||
|
||||
func PublishLabelEvent(e EntityEvent, uuid string, c *gin.Context) {
|
||||
f := form.LabelSearch{ID: uuid}
|
||||
func PublishLabelEvent(e EntityEvent, uid string, c *gin.Context) {
|
||||
f := form.LabelSearch{ID: uid}
|
||||
result, err := query.Labels(f)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -33,18 +33,18 @@ func GetFile(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/files/:uuid/link
|
||||
// POST /api/v1/files/:uid/link
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string SHA-1 hash of the file
|
||||
// uid: string SHA-1 hash of the file
|
||||
func LinkFile(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/files/:uuid/link", func(c *gin.Context) {
|
||||
router.POST("/files/:uid/link", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := query.FileByUUID(c.Param("uuid"))
|
||||
m, err := query.FileByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrFileNotFound)
|
||||
|
|
|
@ -17,7 +17,7 @@ func TestGetFile(t *testing.T) {
|
|||
r := PerformRequest(app, "GET", "/api/v1/files/2cad9168fa6acc5c5c2965ddf6ec465ca42fd818")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
|
||||
val := gjson.Get(r.Body.String(), "FileName")
|
||||
val := gjson.Get(r.Body.String(), "Name")
|
||||
assert.Equal(t, "exampleFileName.jpg", val.String())
|
||||
})
|
||||
t.Run("search for not existing file", func(t *testing.T) {
|
||||
|
@ -32,7 +32,7 @@ func TestLinkFile(t *testing.T) {
|
|||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkFile(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/files/ft9es39w45bnlqdw/link", `{"password": "foobar123", "expires": 0, "edit": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/files/ft9es39w45bnlqdw/link", `{"Password": "foobar123", "Expires": 0, "CanEdit": true}`)
|
||||
|
||||
var label entity.Label
|
||||
|
||||
|
@ -54,7 +54,7 @@ func TestLinkFile(t *testing.T) {
|
|||
t.Run("file not found", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkFile(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/files/xxx/link", `{"password": "foobar", "expires": 0, "edit": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/files/xxx/link", `{"Password": "foobar", "Expires": 0, "CanEdit": true}`)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "File not found", val.String())
|
||||
|
@ -62,7 +62,7 @@ func TestLinkFile(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkFile(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/files/ft9es39w45bnlqdw/link", `{"xxx": 123, "expires": 0, "edit": "xxx"}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/files/ft9es39w45bnlqdw/link", `{"xxx": 123, "Expires": 0, "CanEdit": "xxx"}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
)
|
||||
|
||||
// GetFolders is a reusable request handler for directory listings (GET /api/v1/folders/*).
|
||||
func GetFolders(router *gin.RouterGroup, conf *config.Config, root, pathName string) {
|
||||
router.GET("/folders/" + root, func(c *gin.Context) {
|
||||
func GetFolders(router *gin.RouterGroup, conf *config.Config, root, pathName string) {
|
||||
router.GET("/folders/"+root, func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
|
@ -26,7 +26,7 @@ func GetFolders(router *gin.RouterGroup, conf *config.Config, root, pathName str
|
|||
cacheKey := fmt.Sprintf("folders:%s:%t", pathName, recursive)
|
||||
|
||||
if cacheData, ok := gc.Get(cacheKey); ok {
|
||||
log.Debugf("%s cache hit [%s]", cacheKey, time.Since(start))
|
||||
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
c.JSON(http.StatusOK, cacheData.([]entity.Folder))
|
||||
return
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func GetFolders(router *gin.RouterGroup, conf *config.Config, root, pathName str
|
|||
} else {
|
||||
gc.Set(cacheKey, folders, time.Minute*5)
|
||||
|
||||
log.Debugf("%s cached [%s]", cacheKey, time.Since(start))
|
||||
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, folders)
|
||||
|
|
|
@ -29,7 +29,7 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(folders) != len(expected){
|
||||
if len(folders) != len(expected) {
|
||||
t.Fatalf("response contains %d folders", len(folders))
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
assert.Equal(t, entity.TypeDefault, folder.FolderType)
|
||||
assert.Equal(t, entity.SortOrderName, folder.FolderOrder)
|
||||
assert.Equal(t, entity.FolderRootOriginals, folder.Root)
|
||||
assert.Equal(t, "", folder.FolderUUID)
|
||||
assert.IsType(t, "", folder.FolderUID)
|
||||
assert.Equal(t, false, folder.FolderFavorite)
|
||||
assert.Equal(t, false, folder.FolderHidden)
|
||||
assert.Equal(t, false, folder.FolderIgnore)
|
||||
|
@ -69,7 +69,7 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(folders) != len(expected){
|
||||
if len(folders) != len(expected) {
|
||||
t.Fatalf("response contains %d folders", len(folders))
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
assert.Equal(t, entity.TypeDefault, folder.FolderType)
|
||||
assert.Equal(t, entity.SortOrderName, folder.FolderOrder)
|
||||
assert.Equal(t, entity.FolderRootOriginals, folder.Root)
|
||||
assert.Equal(t, "", folder.FolderUUID)
|
||||
assert.IsType(t, "", folder.FolderUID)
|
||||
assert.Equal(t, false, folder.FolderFavorite)
|
||||
assert.Equal(t, false, folder.FolderHidden)
|
||||
assert.Equal(t, false, folder.FolderIgnore)
|
||||
|
@ -108,7 +108,7 @@ func TestGetFoldersImport(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(folders) != len(expected){
|
||||
if len(folders) != len(expected) {
|
||||
t.Fatalf("response contains %d folders", len(folders))
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ func TestGetFoldersImport(t *testing.T) {
|
|||
assert.Equal(t, entity.TypeDefault, folder.FolderType)
|
||||
assert.Equal(t, entity.SortOrderName, folder.FolderOrder)
|
||||
assert.Equal(t, entity.FolderRootImport, folder.Root)
|
||||
assert.Equal(t, "", folder.FolderUUID)
|
||||
assert.IsType(t, "", folder.FolderUID)
|
||||
assert.Equal(t, false, folder.FolderFavorite)
|
||||
assert.Equal(t, false, folder.FolderHidden)
|
||||
assert.Equal(t, false, folder.FolderIgnore)
|
||||
|
@ -140,6 +140,7 @@ func TestGetFoldersImport(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
GetFoldersImport(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/folders/import?recursive=true")
|
||||
var folders []entity.Folder
|
||||
|
@ -149,7 +150,7 @@ func TestGetFoldersImport(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(folders) != len(expected){
|
||||
if len(folders) != len(expected) {
|
||||
t.Fatalf("response contains %d folders", len(folders))
|
||||
}
|
||||
|
||||
|
@ -158,7 +159,7 @@ func TestGetFoldersImport(t *testing.T) {
|
|||
assert.Equal(t, entity.TypeDefault, folder.FolderType)
|
||||
assert.Equal(t, entity.SortOrderName, folder.FolderOrder)
|
||||
assert.Equal(t, entity.FolderRootImport, folder.Root)
|
||||
assert.Equal(t, "", folder.FolderUUID)
|
||||
assert.IsType(t, "", folder.FolderUID)
|
||||
assert.Equal(t, false, folder.FolderFavorite)
|
||||
assert.Equal(t, false, folder.FolderHidden)
|
||||
assert.Equal(t, false, folder.FolderIgnore)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
||||
|
@ -60,17 +61,30 @@ func GetGeo(router *gin.RouterGroup, conf *config.Config) {
|
|||
bboxMax(2, p.Lng())
|
||||
bboxMax(3, p.Lat())
|
||||
|
||||
props := gin.H{
|
||||
"UID": p.PhotoUID,
|
||||
"Hash": p.FileHash,
|
||||
"Width": p.FileWidth,
|
||||
"Height": p.FileHeight,
|
||||
"TakenAt": p.TakenAt,
|
||||
"Title": p.PhotoTitle,
|
||||
}
|
||||
|
||||
if p.PhotoDescription != "" {
|
||||
props["Description"] = p.PhotoDescription
|
||||
}
|
||||
|
||||
if p.PhotoType != entity.TypeImage && p.PhotoType != entity.TypeDefault {
|
||||
props["Type"] = p.PhotoType
|
||||
}
|
||||
|
||||
if p.PhotoFavorite {
|
||||
props["Favorite"] = true
|
||||
}
|
||||
|
||||
feat := geojson.NewPointFeature([]float64{p.Lng(), p.Lat()})
|
||||
feat.ID = p.ID
|
||||
feat.Properties = gin.H{
|
||||
"PhotoUUID": p.PhotoUUID,
|
||||
"PhotoTitle": p.PhotoTitle,
|
||||
"PhotoFavorite": p.PhotoFavorite,
|
||||
"FileHash": p.FileHash,
|
||||
"FileWidth": p.FileWidth,
|
||||
"FileHeight": p.FileHeight,
|
||||
"TakenAt": p.TakenAt,
|
||||
}
|
||||
feat.Properties = props
|
||||
fc.AddFeature(feat)
|
||||
}
|
||||
|
||||
|
|
|
@ -53,9 +53,9 @@ func GetLabels(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// PUT /api/v1/labels/:uuid
|
||||
// PUT /api/v1/labels/:uid
|
||||
func UpdateLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.PUT("/labels/:uuid", func(c *gin.Context) {
|
||||
router.PUT("/labels/:uid", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
|
@ -68,8 +68,8 @@ func UpdateLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
id := c.Param("uuid")
|
||||
m, err := query.LabelByUUID(id)
|
||||
id := c.Param("uid")
|
||||
m, err := query.LabelByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrLabelNotFound)
|
||||
|
@ -87,19 +87,19 @@ func UpdateLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/labels/:uuid/like
|
||||
// POST /api/v1/labels/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string Label UUID
|
||||
// uid: string Label UID
|
||||
func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/labels/:uuid/like", func(c *gin.Context) {
|
||||
router.POST("/labels/:uid/like", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("uuid")
|
||||
label, err := query.LabelByUUID(id)
|
||||
id := c.Param("uid")
|
||||
label, err := query.LabelByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(404, gin.H{"error": txt.UcFirst(err.Error())})
|
||||
|
@ -121,19 +121,19 @@ func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/labels/:uuid/like
|
||||
// DELETE /api/v1/labels/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string Label UUID
|
||||
// uid: string Label UID
|
||||
func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.DELETE("/labels/:uuid/like", func(c *gin.Context) {
|
||||
router.DELETE("/labels/:uid/like", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("uuid")
|
||||
label, err := query.LabelByUUID(id)
|
||||
id := c.Param("uid")
|
||||
label, err := query.LabelByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(404, gin.H{"error": txt.UcFirst(err.Error())})
|
||||
|
@ -155,17 +155,17 @@ func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/labels/:uuid/thumbnail/:type
|
||||
// GET /api/v1/labels/:uid/thumbnail/:type
|
||||
//
|
||||
// Example: /api/v1/labels/cheetah/thumbnail/tile_500
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string Label UUID
|
||||
// uid: string Label UID
|
||||
// type: string Thumbnail type, see photoprism.ThumbnailTypes
|
||||
func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/labels/:uuid/thumbnail/:type", func(c *gin.Context) {
|
||||
router.GET("/labels/:uid/thumbnail/:type", func(c *gin.Context) {
|
||||
typeName := c.Param("type")
|
||||
labelUUID := c.Param("uuid")
|
||||
labelUID := c.Param("uid")
|
||||
start := time.Now()
|
||||
|
||||
thumbType, ok := thumb.Types[typeName]
|
||||
|
@ -177,15 +177,15 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
}
|
||||
|
||||
gc := service.Cache()
|
||||
cacheKey := fmt.Sprintf("label-thumbnail:%s:%s", labelUUID, typeName)
|
||||
cacheKey := fmt.Sprintf("label-thumbnail:%s:%s", labelUID, typeName)
|
||||
|
||||
if cacheData, ok := gc.Get(cacheKey); ok {
|
||||
log.Debugf("%s cache hit [%s]", cacheKey, time.Since(start))
|
||||
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
c.Data(http.StatusOK, "image/jpeg", cacheData.([]byte))
|
||||
return
|
||||
}
|
||||
|
||||
f, err := query.LabelThumbByUUID(labelUUID)
|
||||
f, err := query.LabelThumbByUID(labelUID)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
|
@ -238,7 +238,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
gc.Set(cacheKey, thumbData, time.Hour*4)
|
||||
|
||||
log.Debugf("%s cached [%s]", cacheKey, time.Since(start))
|
||||
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
|
||||
|
||||
c.Data(http.StatusOK, "image/jpeg", thumbData)
|
||||
})
|
||||
|
|
|
@ -29,8 +29,8 @@ func TestUpdateLabel(t *testing.T) {
|
|||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
UpdateLabel(router, conf)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/lt9k3pw1wowuy3c7", `{"LabelName": "Updated01", "LabelPriority": 2}`)
|
||||
val := gjson.Get(r.Body.String(), "LabelName")
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/lt9k3pw1wowuy3c7", `{"Name": "Updated01", "Priority": 2}`)
|
||||
val := gjson.Get(r.Body.String(), "Name")
|
||||
assert.Equal(t, "Updated01", val.String())
|
||||
val2 := gjson.Get(r.Body.String(), "CustomSlug")
|
||||
assert.Equal(t, "updated01", val2.String())
|
||||
|
@ -40,7 +40,7 @@ func TestUpdateLabel(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
UpdateLabel(router, conf)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/lt9k3pw1wowuy3c7", `{"LabelName": 123, "LabelPriority": 4, "Uncertainty": 80}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/labels/lt9k3pw1wowuy3c7", `{"Name": 123, "Priority": 4, "Uncertainty": 80}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
|
||||
|
@ -66,7 +66,7 @@ func TestLikeLabel(t *testing.T) {
|
|||
GetLabels(router, ctx)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/labels?count=1&q=likeLabel")
|
||||
t.Log(r2.Body.String())
|
||||
val := gjson.Get(r2.Body.String(), `#(LabelSlug=="likeLabel").LabelFavorite`)
|
||||
val := gjson.Get(r2.Body.String(), `#(Slug=="likeLabel").Favorite`)
|
||||
assert.Equal(t, "false", val.String())
|
||||
LikeLabel(router, ctx)
|
||||
r := PerformRequest(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c9/like")
|
||||
|
@ -74,7 +74,7 @@ func TestLikeLabel(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
r3 := PerformRequest(app, "GET", "/api/v1/labels?count=1&q=likeLabel")
|
||||
t.Log(r3.Body.String())
|
||||
val2 := gjson.Get(r3.Body.String(), `#(LabelSlug=="likeLabel").LabelFavorite`)
|
||||
val2 := gjson.Get(r3.Body.String(), `#(Slug=="likeLabel").Favorite`)
|
||||
assert.Equal(t, "true", val2.String())
|
||||
})
|
||||
|
||||
|
@ -93,7 +93,7 @@ func TestDislikeLabel(t *testing.T) {
|
|||
app, router, ctx := NewApiTest()
|
||||
GetLabels(router, ctx)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/labels?count=1&q=landscape")
|
||||
val := gjson.Get(r2.Body.String(), `#(LabelSlug=="landscape").LabelFavorite`)
|
||||
val := gjson.Get(r2.Body.String(), `#(Slug=="landscape").Favorite`)
|
||||
assert.Equal(t, "true", val.String())
|
||||
|
||||
DislikeLabel(router, ctx)
|
||||
|
@ -102,7 +102,7 @@ func TestDislikeLabel(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
|
||||
r3 := PerformRequest(app, "GET", "/api/v1/labels?count=1&q=landscape")
|
||||
val2 := gjson.Get(r3.Body.String(), `#(LabelSlug=="landscape").LabelFavorite`)
|
||||
val2 := gjson.Get(r3.Body.String(), `#(Slug=="landscape").Favorite`)
|
||||
assert.Equal(t, "false", val2.String())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -31,15 +31,15 @@ func newLink(c *gin.Context) (link entity.Link, err error) {
|
|||
return link, nil
|
||||
}
|
||||
|
||||
// POST /api/v1/albums/:uuid/link
|
||||
// POST /api/v1/albums/:uid/link
|
||||
func LinkAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/albums/:uuid/link", func(c *gin.Context) {
|
||||
router.POST("/albums/:uid/link", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := query.AlbumByUUID(c.Param("uuid"))
|
||||
m, err := query.AlbumByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
|
||||
|
@ -59,15 +59,15 @@ func LinkAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/photos/:uuid/link
|
||||
// POST /api/v1/photos/:uid/link
|
||||
func LinkPhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/photos/:uuid/link", func(c *gin.Context) {
|
||||
router.POST("/photos/:uid/link", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := query.PhotoByUUID(c.Param("uuid"))
|
||||
m, err := query.PhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -87,15 +87,15 @@ func LinkPhoto(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/labels/:uuid/link
|
||||
// POST /api/v1/labels/:uid/link
|
||||
func LinkLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/labels/:uuid/link", func(c *gin.Context) {
|
||||
router.POST("/labels/:uid/link", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := query.LabelByUUID(c.Param("uuid"))
|
||||
m, err := query.LabelByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrLabelNotFound)
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestLinkAlbum(t *testing.T) {
|
|||
|
||||
LinkAlbum(router, ctx)
|
||||
|
||||
result1 := PerformRequestWithBody(app, "POST", "/api/v1/albums/at9lxuqxpogaaba7/link", `{"password": "foobar", "expires": 0, "edit": true}`)
|
||||
result1 := PerformRequestWithBody(app, "POST", "/api/v1/albums/at9lxuqxpogaaba7/link", `{"Password": "foobar", "Expires": 0, "CanEdit": true}`)
|
||||
assert.Equal(t, http.StatusOK, result1.Code)
|
||||
|
||||
if err := json.Unmarshal(result1.Body.Bytes(), &album); err != nil {
|
||||
|
@ -31,12 +31,12 @@ func TestLinkAlbum(t *testing.T) {
|
|||
|
||||
link := album.Links[0]
|
||||
|
||||
assert.Equal(t, "foobar", link.LinkPassword)
|
||||
assert.Equal(t, true, link.CanEdit)
|
||||
assert.Nil(t, link.LinkExpires)
|
||||
assert.False(t, link.CanComment)
|
||||
assert.True(t, link.CanEdit)
|
||||
|
||||
result2 := PerformRequestWithBody(app, "POST", "/api/v1/albums/at9lxuqxpogaaba7/link", `{"password": "", "expires": 3600}`)
|
||||
result2 := PerformRequestWithBody(app, "POST", "/api/v1/albums/at9lxuqxpogaaba7/link", `{"Password": "", "Expires": 3600}`)
|
||||
|
||||
assert.Equal(t, http.StatusOK, result2.Code)
|
||||
|
||||
|
@ -54,7 +54,7 @@ func TestLinkAlbum(t *testing.T) {
|
|||
t.Run("album not found", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkAlbum(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/xxx/link", `{"password": "foobar", "expires": 0, "edit": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/xxx/link", `{"Password": "foobar", "Expires": 0, "CanEdit": true}`)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "Album not found", val.String())
|
||||
|
@ -62,7 +62,7 @@ func TestLinkAlbum(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkAlbum(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/at9lxuqxpogaaba7/link", `{"xxx": 123, "expires": 0, "edit": "xxx"}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/albums/at9lxuqxpogaaba7/link", `{"xxx": 123, "Expires": 0, "CanEdit": "xxx"}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func TestLinkPhoto(t *testing.T) {
|
|||
|
||||
LinkPhoto(router, ctx)
|
||||
|
||||
result1 := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh7/link", `{"password": "foobar", "expires": 0, "edit": true}`)
|
||||
result1 := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh7/link", `{"Password": "foobar", "Expires": 0, "CanEdit": true}`)
|
||||
assert.Equal(t, http.StatusOK, result1.Code)
|
||||
|
||||
if err := json.Unmarshal(result1.Body.Bytes(), &photo); err != nil {
|
||||
|
@ -93,7 +93,7 @@ func TestLinkPhoto(t *testing.T) {
|
|||
assert.False(t, link.CanComment)
|
||||
assert.True(t, link.CanEdit)
|
||||
|
||||
result2 := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh7/link", `{"password": "", "expires": 3600}`)
|
||||
result2 := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh7/link", `{"Password": "", "Expires": 3600}`)
|
||||
|
||||
assert.Equal(t, http.StatusOK, result2.Code)
|
||||
|
||||
|
@ -108,7 +108,7 @@ func TestLinkPhoto(t *testing.T) {
|
|||
t.Run("photo not found", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkPhoto(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/xxx/link", `{"password": "foobar", "expires": 0, "edit": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/xxx/link", `{"Password": "foobar", "Expires": 0, "CanEdit": true}`)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "Photo not found", val.String())
|
||||
|
@ -116,7 +116,7 @@ func TestLinkPhoto(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkPhoto(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh7/link", `{"xxx": 123, "expires": 0, "edit": "xxx"}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh7/link", `{"xxx": 123, "Expires": 0, "CanEdit": "xxx"}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ func TestLinkLabel(t *testing.T) {
|
|||
|
||||
LinkLabel(router, ctx)
|
||||
|
||||
result1 := PerformRequestWithBody(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c2/link", `{"password": "foobar", "expires": 0, "edit": true}`)
|
||||
result1 := PerformRequestWithBody(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c2/link", `{"Password": "foobar", "Expires": 0, "CanEdit": true}`)
|
||||
assert.Equal(t, http.StatusOK, result1.Code)
|
||||
|
||||
if err := json.Unmarshal(result1.Body.Bytes(), &label); err != nil {
|
||||
|
@ -147,7 +147,7 @@ func TestLinkLabel(t *testing.T) {
|
|||
assert.False(t, link.CanComment)
|
||||
assert.True(t, link.CanEdit)
|
||||
|
||||
result2 := PerformRequestWithBody(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c2/link", `{"password": "", "expires": 3600}`)
|
||||
result2 := PerformRequestWithBody(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c2/link", `{"Password": "", "Expires": 3600}`)
|
||||
|
||||
assert.Equal(t, http.StatusOK, result2.Code)
|
||||
|
||||
|
@ -162,7 +162,7 @@ func TestLinkLabel(t *testing.T) {
|
|||
t.Run("label not found", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/labels/xxx/link", `{"password": "foobar", "expires": 0, "edit": true}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/labels/xxx/link", `{"Password": "foobar", "Expires": 0, "CanEdit": true}`)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "Label not found", val.String())
|
||||
|
@ -170,7 +170,7 @@ func TestLinkLabel(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LinkLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c2/link", `{"xxx": 123, "expires": 0, "edit": "xxx"}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c2/link", `{"xxx": 123, "Expires": 0, "CanEdit": "xxx"}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -29,18 +29,18 @@ func SavePhotoAsYaml(p entity.Photo, conf *config.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// GET /api/v1/photos/:uuid
|
||||
// GET /api/v1/photos/:uid
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func GetPhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/photos/:uuid", func(c *gin.Context) {
|
||||
router.GET("/photos/:uid", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := query.PreloadPhotoByUUID(c.Param("uuid"))
|
||||
p, err := query.PreloadPhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -51,16 +51,16 @@ func GetPhoto(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// PUT /api/v1/photos/:uuid
|
||||
// PUT /api/v1/photos/:uid
|
||||
func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.PUT("/photos/:uuid", func(c *gin.Context) {
|
||||
router.PUT("/photos/:uid", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
uuid := c.Param("uuid")
|
||||
m, err := query.PhotoByUUID(uuid)
|
||||
uid := c.Param("uid")
|
||||
m, err := query.PhotoByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -91,11 +91,11 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, uuid, c)
|
||||
PublishPhotoEvent(EntityUpdated, uid, c)
|
||||
|
||||
event.Success("photo saved")
|
||||
|
||||
p, err := query.PreloadPhotoByUUID(uuid)
|
||||
p, err := query.PreloadPhotoByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -108,13 +108,13 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/photos/:uuid/download
|
||||
// GET /api/v1/photos/:uid/download
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/photos/:uuid/download", func(c *gin.Context) {
|
||||
f, err := query.FileByPhotoUUID(c.Param("uuid"))
|
||||
router.GET("/photos/:uid/download", func(c *gin.Context) {
|
||||
f, err := query.FileByPhotoUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -124,7 +124,7 @@ func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
|
|||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("could not find original: %s", c.Param("uuid"))
|
||||
log.Errorf("could not find original: %s", c.Param("uid"))
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
|
||||
// Set missing flag so that the file doesn't show up in search results anymore
|
||||
|
@ -141,18 +141,18 @@ func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/photos/:uuid/yaml
|
||||
// GET /api/v1/photos/:uid/yaml
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func GetPhotoYaml(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/photos/:uuid/yaml", func(c *gin.Context) {
|
||||
router.GET("/photos/:uid/yaml", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := query.PreloadPhotoByUUID(c.Param("uuid"))
|
||||
p, err := query.PreloadPhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
|
@ -167,26 +167,26 @@ func GetPhotoYaml(router *gin.RouterGroup, conf *config.Config) {
|
|||
}
|
||||
|
||||
if c.Query("download") != "" {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", c.Param("uuid")+fs.YamlExt))
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", c.Param("uid")+fs.YamlExt))
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, "text/x-yaml; charset=utf-8", data)
|
||||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/photos/:uuid/like
|
||||
// POST /api/v1/photos/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/photos/:uuid/like", func(c *gin.Context) {
|
||||
router.POST("/photos/:uid/like", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("uuid")
|
||||
m, err := query.PhotoByUUID(id)
|
||||
id := c.Param("uid")
|
||||
m, err := query.PhotoByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -209,19 +209,19 @@ func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/photos/:uuid/like
|
||||
// DELETE /api/v1/photos/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func DislikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.DELETE("/photos/:uuid/like", func(c *gin.Context) {
|
||||
router.DELETE("/photos/:uid/like", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("uuid")
|
||||
m, err := query.PhotoByUUID(id)
|
||||
id := c.Param("uid")
|
||||
m, err := query.PhotoByUID(id)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -244,31 +244,31 @@ func DislikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// POST /api/v1/photos/:uuid/primary/:file_uuid
|
||||
// POST /api/v1/photos/:uid/primary/:file_uid
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func SetPhotoPrimary(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/photos/:uuid/primary/:file_uuid", func(c *gin.Context) {
|
||||
router.POST("/photos/:uid/primary/:file_uid", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
uuid := c.Param("uuid")
|
||||
fileUUID := c.Param("file_uuid")
|
||||
err := query.SetPhotoPrimary(uuid, fileUUID)
|
||||
uid := c.Param("uid")
|
||||
fileUID := c.Param("file_uid")
|
||||
err := query.SetPhotoPrimary(uid, fileUID)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, uuid, c)
|
||||
PublishPhotoEvent(EntityUpdated, uid, c)
|
||||
|
||||
event.Success("photo saved")
|
||||
|
||||
p, err := query.PreloadPhotoByUUID(uuid)
|
||||
p, err := query.PreloadPhotoByUID(uid)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
|
|
@ -13,18 +13,18 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// POST /api/v1/photos/:uuid/label
|
||||
// POST /api/v1/photos/:uid/label
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func AddPhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/photos/:uuid/label", func(c *gin.Context) {
|
||||
router.POST("/photos/:uid/label", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := query.PhotoByUUID(c.Param("uuid"))
|
||||
m, err := query.PhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -59,7 +59,7 @@ func AddPhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
entity.Db().Save(&lm)
|
||||
|
||||
p, err := query.PreloadPhotoByUUID(c.Param("uuid"))
|
||||
p, err := query.PreloadPhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -71,7 +71,7 @@ func AddPhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, c.Param("uuid"), c)
|
||||
PublishPhotoEvent(EntityUpdated, c.Param("uid"), c)
|
||||
|
||||
event.Success("label updated")
|
||||
|
||||
|
@ -79,19 +79,19 @@ func AddPhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/photos/:uuid/label/:id
|
||||
// DELETE /api/v1/photos/:uid/label/:id
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
// id: int LabelId as returned by the API
|
||||
func RemovePhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.DELETE("/photos/:uuid/label/:id", func(c *gin.Context) {
|
||||
router.DELETE("/photos/:uid/label/:id", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := query.PhotoByUUID(c.Param("uuid"))
|
||||
m, err := query.PhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -119,7 +119,7 @@ func RemovePhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
entity.Db().Save(&label)
|
||||
}
|
||||
|
||||
p, err := query.PreloadPhotoByUUID(c.Param("uuid"))
|
||||
p, err := query.PreloadPhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -131,7 +131,7 @@ func RemovePhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, c.Param("uuid"), c)
|
||||
PublishPhotoEvent(EntityUpdated, c.Param("uid"), c)
|
||||
|
||||
event.Success("label removed")
|
||||
|
||||
|
@ -139,13 +139,13 @@ func RemovePhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// PUT /api/v1/photos/:uuid/label/:id
|
||||
// PUT /api/v1/photos/:uid/label/:id
|
||||
//
|
||||
// Parameters:
|
||||
// uuid: string PhotoUUID as returned by the API
|
||||
// uid: string PhotoUID as returned by the API
|
||||
// id: int LabelId as returned by the API
|
||||
func UpdatePhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.PUT("/photos/:uuid/label/:id", func(c *gin.Context) {
|
||||
router.PUT("/photos/:uid/label/:id", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
|
@ -153,7 +153,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
// TODO: Code clean-up, simplify
|
||||
|
||||
m, err := query.PhotoByUUID(c.Param("uuid"))
|
||||
m, err := query.PhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -184,7 +184,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
p, err := query.PreloadPhotoByUUID(c.Param("uuid"))
|
||||
p, err := query.PreloadPhotoByUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, ErrPhotoNotFound)
|
||||
|
@ -196,7 +196,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, c.Param("uuid"), c)
|
||||
PublishPhotoEvent(EntityUpdated, c.Param("uid"), c)
|
||||
|
||||
event.Success("label saved")
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@ func TestAddPhotoLabel(t *testing.T) {
|
|||
t.Run("add new label", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
AddPhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh8/label", `{"LabelName": "testAddLabel", "Uncertainty": 95, "LabelPriority": 2}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh8/label", `{"Name": "testAddLabel", "Uncertainty": 95, "Priority": 2}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
assert.Contains(t, r.Body.String(), "TestAddLabel")
|
||||
})
|
||||
t.Run("add existing label", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
AddPhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh8/label", `{"LabelName": "Flower", "Uncertainty": 10, "LabelPriority": 2}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh8/label", `{"Name": "Flower", "Uncertainty": 10, "Priority": 2}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "Labels.#(LabelID==1000001).Uncertainty")
|
||||
assert.Equal(t, "10", val.String())
|
||||
|
@ -27,7 +27,7 @@ func TestAddPhotoLabel(t *testing.T) {
|
|||
t.Run("not found", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
AddPhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/xxx/label", `{"LabelName": "Flower", "Uncertainty": 10, "LabelPriority": 2}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/xxx/label", `{"Name": "Flower", "Uncertainty": 10, "Priority": 2}`)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "Photo not found", val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
|
@ -35,7 +35,7 @@ func TestAddPhotoLabel(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
AddPhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh8/label", `{"LabelName": 123, "Uncertainty": 10, "LabelPriority": 2}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh8/label", `{"Name": 123, "Uncertainty": 10, "Priority": 2}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
|
||||
|
@ -94,15 +94,15 @@ func TestUpdatePhotoLabel(t *testing.T) {
|
|||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
UpdatePhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0yh0/label/1000006", `{"Label": {"LabelName": "NewLabelName"}}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0yh0/label/1000006", `{"Label": {"Name": "NewLabelName"}}`)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "PhotoTitle")
|
||||
val := gjson.Get(r.Body.String(), "Title")
|
||||
assert.Contains(t, val.String(), "NewLabelName")
|
||||
})
|
||||
t.Run("photo not found", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
UpdatePhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/xxx/label/1000006", `{"Label": {"LabelName": "NewLabelName"}}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/xxx/label/1000006", `{"Label": {"Name": "NewLabelName"}}`)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "Photo not found", val.String())
|
||||
|
@ -110,19 +110,19 @@ func TestUpdatePhotoLabel(t *testing.T) {
|
|||
t.Run("label not existing", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
UpdatePhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0yh0/label/9000006", `{"Label": {"LabelName": "NewLabelName"}}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0yh0/label/9000006", `{"Label": {"Name": "NewLabelName"}}`)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
t.Run("label not linked to photo", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
UpdatePhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0yh0/label/1000005", `{"Label": {"LabelName": "NewLabelName"}}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0yh0/label/1000005", `{"Label": {"Name": "NewLabelName"}}`)
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
t.Run("bad request", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
UpdatePhotoLabel(router, ctx)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0yh0/label/1000006", `{"Label": {"LabelName": 123}}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0yh0/label/1000006", `{"Label": {"Name": 123}}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ func TestGetPhoto(t *testing.T) {
|
|||
GetPhoto(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/photos/pt9jtdre2lvl0yh7")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
val := gjson.Get(r.Body.String(), "PhotoLat")
|
||||
val := gjson.Get(r.Body.String(), "Lat")
|
||||
assert.Equal(t, "48.519234", val.String())
|
||||
})
|
||||
t.Run("search for not existing photo", func(t *testing.T) {
|
||||
|
@ -29,10 +29,10 @@ func TestUpdatePhoto(t *testing.T) {
|
|||
t.Run("successful request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
UpdatePhoto(router, conf)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0y13", `{"PhotoTitle": "Updated01", "PhotoCountry": "de"}`)
|
||||
val := gjson.Get(r.Body.String(), "PhotoTitle")
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0y13", `{"Title": "Updated01", "Country": "de"}`)
|
||||
val := gjson.Get(r.Body.String(), "Title")
|
||||
assert.Equal(t, "Updated01", val.String())
|
||||
val2 := gjson.Get(r.Body.String(), "PhotoCountry")
|
||||
val2 := gjson.Get(r.Body.String(), "Country")
|
||||
assert.Equal(t, "de", val2.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
@ -40,14 +40,14 @@ func TestUpdatePhoto(t *testing.T) {
|
|||
t.Run("invalid request", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
UpdatePhoto(router, conf)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0y13", `{"PhotoName": "Updated01", "PhotoCountry": 123}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/pt9jtdre2lvl0y13", `{"Name": "Updated01", "Country": 123}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
UpdatePhoto(router, conf)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/xxx", `{"PhotoName": "Updated01", "PhotoCountry": "de"}`)
|
||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/photos/xxx", `{"Name": "Updated01", "Country": "de"}`)
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "Photo not found", val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
|
@ -77,7 +77,7 @@ func TestLikePhoto(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
GetPhoto(router, ctx)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/photos/pt9jtdre2lvl0yh9")
|
||||
val := gjson.Get(r2.Body.String(), "PhotoFavorite")
|
||||
val := gjson.Get(r2.Body.String(), "Favorite")
|
||||
assert.Equal(t, "true", val.String())
|
||||
})
|
||||
t.Run("not existing photo", func(t *testing.T) {
|
||||
|
@ -96,7 +96,7 @@ func TestDislikePhoto(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
GetPhoto(router, ctx)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/photos/pt9jtdre2lvl0yh8")
|
||||
val := gjson.Get(r2.Body.String(), "PhotoFavorite")
|
||||
val := gjson.Get(r2.Body.String(), "Favorite")
|
||||
assert.Equal(t, "false", val.String())
|
||||
})
|
||||
t.Run("not existing photo", func(t *testing.T) {
|
||||
|
@ -115,13 +115,13 @@ func TestSetPhotoPrimary(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
GetFile(router, ctx)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/files/ocad9168fa6acc5c5c2965ddf6ec465ca42fd818")
|
||||
val := gjson.Get(r2.Body.String(), "FilePrimary")
|
||||
val := gjson.Get(r2.Body.String(), "Primary")
|
||||
assert.Equal(t, "true", val.String())
|
||||
r3 := PerformRequest(app, "GET", "/api/v1/files/3cad9168fa6acc5c5c2965ddf6ec465ca42fd818")
|
||||
val2 := gjson.Get(r3.Body.String(), "FilePrimary")
|
||||
val2 := gjson.Get(r3.Body.String(), "Primary")
|
||||
assert.Equal(t, "false", val2.String())
|
||||
})
|
||||
t.Run("wrong photo uuid", func(t *testing.T) {
|
||||
t.Run("wrong photo uid", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
SetPhotoPrimary(router, ctx)
|
||||
r := PerformRequest(app, "POST", "/api/v1/photos/xxx/primary/ft1es39w45bnlqdw")
|
||||
|
|
|
@ -38,7 +38,7 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
}
|
||||
|
||||
if f.FileVideo {
|
||||
f, err = query.FileByPhotoUUID(f.PhotoUUID)
|
||||
f, err = query.FileByPhotoUID(f.PhotoUID)
|
||||
|
||||
if err != nil {
|
||||
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
|
||||
|
|
|
@ -48,6 +48,10 @@ func GetSvg(router *gin.RouterGroup) {
|
|||
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
|
||||
})
|
||||
|
||||
router.GET("/svg/folder", func(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
|
||||
})
|
||||
|
||||
router.GET("/svg/broken", func(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
||||
})
|
||||
|
|
|
@ -40,7 +40,7 @@ func GetVideo(router *gin.RouterGroup, conf *config.Config) {
|
|||
}
|
||||
|
||||
if !f.FileVideo {
|
||||
f, err = query.VideoByPhotoUUID(f.PhotoUUID)
|
||||
f, err = query.VideoByPhotoUID(f.PhotoUID)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("video: %s", err.Error())
|
||||
|
|
|
@ -47,7 +47,7 @@ func CreateZip(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
files, err := query.FilesByUUID(f.Photos, 1000, 0)
|
||||
files, err := query.FilesByUID(f.Photos, 1000, 0)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
||||
|
|
|
@ -50,7 +50,7 @@ func (c *Config) PublicClientConfig() ClientConfig {
|
|||
configFlags := c.Flags()
|
||||
|
||||
var noPos = struct {
|
||||
PhotoUUID string `json:"photo"`
|
||||
PhotoUID string `json:"photo"`
|
||||
LocationID string `json:"location"`
|
||||
TakenAt time.Time `json:"utc"`
|
||||
PhotoLat float64 `json:"lat"`
|
||||
|
@ -63,9 +63,12 @@ func (c *Config) PublicClientConfig() ClientConfig {
|
|||
Hidden uint `json:"hidden"`
|
||||
Favorites uint `json:"favorites"`
|
||||
Private uint `json:"private"`
|
||||
Review uint `json:"review"`
|
||||
Stories uint `json:"stories"`
|
||||
Labels uint `json:"labels"`
|
||||
Albums uint `json:"albums"`
|
||||
Folders uint `json:"folders"`
|
||||
Moments uint `json:"moments"`
|
||||
Countries uint `json:"countries"`
|
||||
Places uint `json:"places"`
|
||||
}{}
|
||||
|
@ -112,12 +115,13 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
|
||||
db := c.Db()
|
||||
|
||||
var cameras []*entity.Camera
|
||||
var lenses []*entity.Lens
|
||||
var albums []*entity.Album
|
||||
var cameras []entity.Camera
|
||||
var lenses []entity.Lens
|
||||
var albums []entity.Album
|
||||
var countries []entity.Country
|
||||
|
||||
var position struct {
|
||||
PhotoUUID string `json:"photo"`
|
||||
PhotoUID string `json:"photo"`
|
||||
LocationID string `json:"location"`
|
||||
TakenAt time.Time `json:"utc"`
|
||||
PhotoLat float64 `json:"lat"`
|
||||
|
@ -125,7 +129,7 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
}
|
||||
|
||||
db.Table("photos").
|
||||
Select("photo_uuid, location_id, photo_lat, photo_lng, taken_at").
|
||||
Select("photo_uid, location_id, photo_lat, photo_lng, taken_at").
|
||||
Where("deleted_at IS NULL AND photo_lat != 0 AND photo_lng != 0").
|
||||
Order("taken_at DESC").
|
||||
Limit(1).Offset(0).
|
||||
|
@ -137,7 +141,10 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
Hidden uint `json:"hidden"`
|
||||
Favorites uint `json:"favorites"`
|
||||
Private uint `json:"private"`
|
||||
Review uint `json:"review"`
|
||||
Albums uint `json:"albums"`
|
||||
Folders uint `json:"folders"`
|
||||
Moments uint `json:"moments"`
|
||||
Countries uint `json:"countries"`
|
||||
Places uint `json:"places"`
|
||||
Labels uint `json:"labels"`
|
||||
|
@ -145,7 +152,7 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
}{}
|
||||
|
||||
db.Table("photos").
|
||||
Select("SUM(photo_type = 'video' AND photo_quality >= 0 AND photo_private = 0) AS videos, SUM(photo_quality = -1) AS hidden, SUM(photo_type IN ('image','raw','live') AND photo_private = 0 AND photo_quality >= 0) AS photos, SUM(photo_favorite = 1 AND photo_quality >= 0) AS favorites, SUM(photo_private = 1 AND photo_quality >= 0) AS private").
|
||||
Select("SUM(photo_type = 'video' AND photo_quality >= 0 AND photo_private = 0) AS videos, SUM(photo_type IN ('image','raw','live') AND photo_quality < 3 AND photo_quality >= 0 AND photo_private = 0) AS review, SUM(photo_quality = -1) AS hidden, SUM(photo_type IN ('image','raw','live') AND photo_private = 0 AND photo_quality >= 0) AS photos, SUM(photo_favorite = 1 AND photo_quality >= 0) AS favorites, SUM(photo_private = 1 AND photo_quality >= 0) AS private").
|
||||
Where("photos.id NOT IN (SELECT photo_id FROM files WHERE file_primary = 1 AND (file_missing = 1 OR file_error <> ''))").
|
||||
Where("deleted_at IS NULL").
|
||||
Take(&count)
|
||||
|
@ -158,7 +165,13 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
Take(&count)
|
||||
|
||||
db.Table("albums").
|
||||
Select("COUNT(*) AS albums").
|
||||
Select("SUM(album_type = '') AS albums, SUM(album_type = 'moment') AS moments, SUM(album_type = 'folder') AS folders").
|
||||
Where("deleted_at IS NULL").
|
||||
Take(&count)
|
||||
|
||||
db.Table("folders").
|
||||
Select("COUNT(*) AS folders").
|
||||
Where("folder_hidden = 0").
|
||||
Where("deleted_at IS NULL").
|
||||
Take(&count)
|
||||
|
||||
|
@ -171,17 +184,8 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
Where("id != 'zz'").
|
||||
Take(&count)
|
||||
|
||||
type country struct {
|
||||
ID string `json:"code"`
|
||||
CountryName string `json:"name"`
|
||||
}
|
||||
|
||||
var countries []country
|
||||
|
||||
db.Model(&entity.Country{}).
|
||||
Select("id, country_name").
|
||||
Order("country_slug").
|
||||
Scan(&countries)
|
||||
db.Order("country_slug").
|
||||
Find(&countries)
|
||||
|
||||
db.Where("deleted_at IS NULL").
|
||||
Limit(10000).Order("camera_slug").
|
||||
|
@ -203,24 +207,22 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
Pluck("DISTINCT photo_year", &years)
|
||||
|
||||
type CategoryLabel struct {
|
||||
LabelName string
|
||||
Title string
|
||||
LabelUID string `json:"UID"`
|
||||
CustomSlug string `json:"Slug"`
|
||||
LabelName string `json:"Name"`
|
||||
}
|
||||
|
||||
var categories []CategoryLabel
|
||||
|
||||
db.Table("categories").
|
||||
Select("l.label_name").
|
||||
Select("l.label_uid, l.custom_slug, l.label_name").
|
||||
Joins("JOIN labels l ON categories.category_id = l.id").
|
||||
Group("l.label_name").
|
||||
Order("l.label_name").
|
||||
Where("l.deleted_at IS NULL").
|
||||
Group("l.custom_slug").
|
||||
Order("l.custom_slug").
|
||||
Limit(1000).Offset(0).
|
||||
Scan(&categories)
|
||||
|
||||
for i, l := range categories {
|
||||
categories[i].Title = strings.Title(l.LabelName)
|
||||
}
|
||||
|
||||
jsHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.js")
|
||||
cssHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.css")
|
||||
configFlags := c.Flags()
|
||||
|
|
|
@ -150,17 +150,17 @@ func (c *Params) Load(fileName string) error {
|
|||
func (c *Params) SetContext(ctx *cli.Context) error {
|
||||
v := reflect.ValueOf(c).Elem()
|
||||
|
||||
// Iterate through all config fields
|
||||
// Iterate through all config fields.
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fieldValue := v.Field(i)
|
||||
|
||||
tagValue := v.Type().Field(i).Tag.Get("flag")
|
||||
|
||||
// Automatically assign values to fields with "flag" tag
|
||||
// Automatically assign values to fields with "flag" tag.
|
||||
if tagValue != "" {
|
||||
switch t := fieldValue.Interface().(type) {
|
||||
case int, int64:
|
||||
// Only if explicitly set or current value is empty (use default)
|
||||
// Only if explicitly set or current value is empty (use default).
|
||||
if ctx.IsSet(tagValue) {
|
||||
f := ctx.Int64(tagValue)
|
||||
fieldValue.SetInt(f)
|
||||
|
@ -169,7 +169,7 @@ func (c *Params) SetContext(ctx *cli.Context) error {
|
|||
fieldValue.SetInt(f)
|
||||
}
|
||||
case uint, uint64:
|
||||
// Only if explicitly set or current value is empty (use default)
|
||||
// Only if explicitly set or current value is empty (use default).
|
||||
if ctx.IsSet(tagValue) {
|
||||
f := ctx.Uint64(tagValue)
|
||||
fieldValue.SetUint(f)
|
||||
|
|
|
@ -42,6 +42,8 @@ type FeatureSettings struct {
|
|||
Review bool `json:"review" yaml:"review"`
|
||||
Upload bool `json:"upload" yaml:"upload"`
|
||||
Import bool `json:"import" yaml:"import"`
|
||||
Folders bool `json:"folders" yaml:"folders"`
|
||||
Moments bool `json:"moments" yaml:"moments"`
|
||||
Labels bool `json:"labels" yaml:"labels"`
|
||||
Places bool `json:"places" yaml:"places"`
|
||||
Download bool `json:"download" yaml:"download"`
|
||||
|
@ -79,6 +81,8 @@ func NewSettings() *Settings {
|
|||
Private: true,
|
||||
Upload: true,
|
||||
Import: true,
|
||||
Folders: true,
|
||||
Moments: true,
|
||||
Labels: true,
|
||||
Places: true,
|
||||
Download: true,
|
||||
|
|
2
internal/config/testdata/configEmpty.yml
vendored
2
internal/config/testdata/configEmpty.yml
vendored
|
@ -11,6 +11,8 @@ features:
|
|||
review: true
|
||||
upload: true
|
||||
import: true
|
||||
folders: true
|
||||
moments: true
|
||||
labels: true
|
||||
places: true
|
||||
download: true
|
||||
|
|
|
@ -14,43 +14,48 @@ import (
|
|||
|
||||
// Album represents a photo album
|
||||
type Album struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CoverUUID string `gorm:"type:varbinary(36);"`
|
||||
AlbumUUID string `gorm:"type:varbinary(36);unique_index;"`
|
||||
AlbumSlug string `gorm:"type:varbinary(255);index;"`
|
||||
AlbumName string `gorm:"type:varchar(255);"`
|
||||
AlbumDescription string `gorm:"type:text;"`
|
||||
AlbumNotes string `gorm:"type:text;"`
|
||||
AlbumOrder string `gorm:"type:varbinary(32);"`
|
||||
AlbumTemplate string `gorm:"type:varbinary(255);"`
|
||||
AlbumFavorite bool
|
||||
Links []Link `gorm:"foreignkey:ShareUUID;association_foreignkey:AlbumUUID"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time `sql:"index"`
|
||||
ID uint `gorm:"primary_key" json:"ID" yaml:"-"`
|
||||
AlbumUID string `gorm:"type:varbinary(36);unique_index;" json:"UID" yaml:"UID"`
|
||||
CoverUID string `gorm:"type:varbinary(36);" json:"CoverUID" yaml:"CoverUID,omitempty"`
|
||||
ParentUID string `gorm:"type:varbinary(36);index;" json:"ParentUID" yaml:"ParentUID,omitempty"`
|
||||
FolderUID string `gorm:"type:varbinary(36);index;" json:"FolderUID" yaml:"FolderUID,omitempty"`
|
||||
AlbumSlug string `gorm:"type:varbinary(255);index;" json:"Slug" yaml:"Slug"`
|
||||
AlbumName string `gorm:"type:varchar(255);" json:"Name" yaml:"Name"`
|
||||
AlbumType string `gorm:"type:varbinary(8);default:'';" json:"Type" yaml:"Type"`
|
||||
AlbumFilter string `gorm:"type:varchar(1024);" json:"Filter" yaml:"Filter,omitempty"`
|
||||
AlbumDescription string `gorm:"type:text;" json:"Description" yaml:"Description,omitempty"`
|
||||
AlbumNotes string `gorm:"type:text;" json:"Notes" yaml:"Notes,omitempty"`
|
||||
AlbumOrder string `gorm:"type:varbinary(32);" json:"Order" yaml:"Order,omitempty"`
|
||||
AlbumTemplate string `gorm:"type:varbinary(255);" json:"Template" yaml:"Template,omitempty"`
|
||||
AlbumFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
||||
Links []Link `gorm:"foreignkey:ShareUID;association_foreignkey:AlbumUID" json:"Links" yaml:"-"`
|
||||
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UUID if needed before inserting a new row to the database.
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *Album) BeforeCreate(scope *gorm.Scope) error {
|
||||
if rnd.IsPPID(m.AlbumUUID, 'a') {
|
||||
if rnd.IsPPID(m.AlbumUID, 'a') {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scope.SetColumn("AlbumUUID", rnd.PPID('a'))
|
||||
return scope.SetColumn("AlbumUID", rnd.PPID('a'))
|
||||
}
|
||||
|
||||
// NewAlbum creates a new album; default name is current month and year
|
||||
func NewAlbum(name string) *Album {
|
||||
func NewAlbum(albumName, albumType string) *Album {
|
||||
now := time.Now().UTC()
|
||||
|
||||
result := &Album{
|
||||
AlbumUUID: rnd.PPID('a'),
|
||||
AlbumUID: rnd.PPID('a'),
|
||||
AlbumOrder: SortOrderOldest,
|
||||
AlbumType: albumType,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
result.SetName(name)
|
||||
result.SetName(albumName)
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -68,7 +73,7 @@ func (m *Album) SetName(name string) {
|
|||
if len(m.AlbumName) < txt.ClipSlug {
|
||||
m.AlbumSlug = slug.Make(m.AlbumName)
|
||||
} else {
|
||||
m.AlbumSlug = slug.Make(txt.Clip(m.AlbumName, txt.ClipSlug)) + "-" + m.AlbumUUID
|
||||
m.AlbumSlug = slug.Make(txt.Clip(m.AlbumName, txt.ClipSlug)) + "-" + m.AlbumUID
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ func (m AlbumMap) Get(name string) Album {
|
|||
return result
|
||||
}
|
||||
|
||||
return *NewAlbum(name)
|
||||
return *NewAlbum(name, TypeDefault)
|
||||
}
|
||||
|
||||
func (m AlbumMap) Pointer(name string) *Album {
|
||||
|
@ -19,15 +19,16 @@ func (m AlbumMap) Pointer(name string) *Album {
|
|||
return &result
|
||||
}
|
||||
|
||||
return NewAlbum(name)
|
||||
return NewAlbum(name, TypeDefault)
|
||||
}
|
||||
|
||||
var AlbumFixtures = AlbumMap{
|
||||
"christmas2030": {
|
||||
ID: 1000000,
|
||||
CoverUUID: "",
|
||||
AlbumUUID: "at9lxuqxpogaaba7",
|
||||
CoverUID: "",
|
||||
AlbumUID: "at9lxuqxpogaaba7",
|
||||
AlbumSlug: "christmas2030",
|
||||
AlbumType: TypeDefault,
|
||||
AlbumName: "Christmas2030",
|
||||
AlbumDescription: "Wonderful christmas",
|
||||
AlbumNotes: "",
|
||||
|
@ -41,9 +42,10 @@ var AlbumFixtures = AlbumMap{
|
|||
},
|
||||
"holiday-2030": {
|
||||
ID: 1000001,
|
||||
CoverUUID: "",
|
||||
AlbumUUID: "at9lxuqxpogaaba8",
|
||||
CoverUID: "",
|
||||
AlbumUID: "at9lxuqxpogaaba8",
|
||||
AlbumSlug: "holiday-2030",
|
||||
AlbumType: TypeDefault,
|
||||
AlbumName: "Holiday2030",
|
||||
AlbumDescription: "Wonderful christmas",
|
||||
AlbumNotes: "",
|
||||
|
@ -57,9 +59,10 @@ var AlbumFixtures = AlbumMap{
|
|||
},
|
||||
"berlin-2019": {
|
||||
ID: 1000002,
|
||||
CoverUUID: "",
|
||||
AlbumUUID: "at9lxuqxpogaaba9",
|
||||
CoverUID: "",
|
||||
AlbumUID: "at9lxuqxpogaaba9",
|
||||
AlbumSlug: "berlin-2019",
|
||||
AlbumType: TypeDefault,
|
||||
AlbumName: "Berlin2019",
|
||||
AlbumDescription: "Wonderful christmas",
|
||||
AlbumNotes: "",
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
func TestAlbumMap_Get(t *testing.T) {
|
||||
t.Run("get existing album", func(t *testing.T) {
|
||||
r := AlbumFixtures.Get("christmas2030")
|
||||
assert.Equal(t, "at9lxuqxpogaaba7", r.AlbumUUID)
|
||||
assert.Equal(t, "at9lxuqxpogaaba7", r.AlbumUID)
|
||||
assert.Equal(t, "christmas2030", r.AlbumSlug)
|
||||
assert.IsType(t, Album{}, r)
|
||||
})
|
||||
|
@ -22,7 +22,7 @@ func TestAlbumMap_Get(t *testing.T) {
|
|||
func TestAlbumMap_Pointer(t *testing.T) {
|
||||
t.Run("get existing album pointer", func(t *testing.T) {
|
||||
r := AlbumFixtures.Pointer("christmas2030")
|
||||
assert.Equal(t, "at9lxuqxpogaaba7", r.AlbumUUID)
|
||||
assert.Equal(t, "at9lxuqxpogaaba7", r.AlbumUID)
|
||||
assert.Equal(t, "christmas2030", r.AlbumSlug)
|
||||
assert.IsType(t, &Album{}, r)
|
||||
})
|
||||
|
|
|
@ -12,12 +12,12 @@ import (
|
|||
|
||||
func TestNewAlbum(t *testing.T) {
|
||||
t.Run("name Christmas 2018", func(t *testing.T) {
|
||||
album := NewAlbum("Christmas 2018")
|
||||
album := NewAlbum("Christmas 2018", TypeDefault)
|
||||
assert.Equal(t, "Christmas 2018", album.AlbumName)
|
||||
assert.Equal(t, "christmas-2018", album.AlbumSlug)
|
||||
})
|
||||
t.Run("name empty", func(t *testing.T) {
|
||||
album := NewAlbum("")
|
||||
album := NewAlbum("", TypeDefault)
|
||||
|
||||
defaultName := time.Now().Format("January 2006")
|
||||
defaultSlug := slug.Make(defaultName)
|
||||
|
@ -29,7 +29,7 @@ func TestNewAlbum(t *testing.T) {
|
|||
|
||||
func TestAlbum_SetName(t *testing.T) {
|
||||
t.Run("valid name", func(t *testing.T) {
|
||||
album := NewAlbum("initial name")
|
||||
album := NewAlbum("initial name", TypeDefault)
|
||||
assert.Equal(t, "initial name", album.AlbumName)
|
||||
assert.Equal(t, "initial-name", album.AlbumSlug)
|
||||
album.SetName("New Album Name")
|
||||
|
@ -37,7 +37,7 @@ func TestAlbum_SetName(t *testing.T) {
|
|||
assert.Equal(t, "new-album-name", album.AlbumSlug)
|
||||
})
|
||||
t.Run("empty name", func(t *testing.T) {
|
||||
album := NewAlbum("initial name")
|
||||
album := NewAlbum("initial name", TypeDefault)
|
||||
assert.Equal(t, "initial name", album.AlbumName)
|
||||
assert.Equal(t, "initial-name", album.AlbumSlug)
|
||||
|
||||
|
@ -57,7 +57,7 @@ The discrepancy of 1 second meridian arc length between equator and pole is abou
|
|||
is an oblate spheroid.`
|
||||
expected := txt.Clip(longName, txt.ClipDefault)
|
||||
slugExpected := txt.Clip(longName, txt.ClipSlug)
|
||||
album := NewAlbum(longName)
|
||||
album := NewAlbum(longName, TypeDefault)
|
||||
assert.Equal(t, expected, album.AlbumName)
|
||||
assert.Contains(t, album.AlbumSlug, slug.Make(slugExpected))
|
||||
})
|
||||
|
@ -65,7 +65,7 @@ is an oblate spheroid.`
|
|||
|
||||
func TestAlbum_Save(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
album := NewAlbum("Old Name")
|
||||
album := NewAlbum("Old Name", TypeDefault)
|
||||
|
||||
assert.Equal(t, "Old Name", album.AlbumName)
|
||||
assert.Equal(t, "old-name", album.AlbumSlug)
|
||||
|
|
|
@ -11,16 +11,16 @@ import (
|
|||
|
||||
// Camera model and make (as extracted from UpdateExif metadata)
|
||||
type Camera struct {
|
||||
ID uint `gorm:"primary_key" yaml:"CameraID"`
|
||||
CameraSlug string `gorm:"type:varbinary(255);unique_index;" yaml:"Slug"`
|
||||
CameraModel string `gorm:"type:varchar(255);" yaml:"Model"`
|
||||
CameraMake string `gorm:"type:varchar(255);" yaml:"Make"`
|
||||
CameraType string `gorm:"type:varchar(255);" yaml:"Type,omitempty"`
|
||||
CameraDescription string `gorm:"type:text;" yaml:"Description,omitempty"`
|
||||
CameraNotes string `gorm:"type:text;" yaml:"Notes,omitempty"`
|
||||
CreatedAt time.Time `yaml:"-"`
|
||||
UpdatedAt time.Time `yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" yaml:"-"`
|
||||
ID uint `gorm:"primary_key" json:"ID" yaml:"ID"`
|
||||
CameraSlug string `gorm:"type:varbinary(255);unique_index;" json:"Slug" yaml:"-"`
|
||||
CameraModel string `gorm:"type:varchar(255);" json:"Model" yaml:"Model"`
|
||||
CameraMake string `gorm:"type:varchar(255);" json:"Make" yaml:"Make"`
|
||||
CameraType string `gorm:"type:varchar(255);" json:"Type,omitempty" yaml:"Type,omitempty"`
|
||||
CameraDescription string `gorm:"type:text;" json:"Description,omitempty" yaml:"Description,omitempty"`
|
||||
CameraNotes string `gorm:"type:text;" json:"Notes,omitempty" yaml:"Notes,omitempty"`
|
||||
CreatedAt time.Time `json:"-" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"-" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
var UnknownCamera = Camera{
|
||||
|
|
|
@ -27,6 +27,8 @@ const (
|
|||
TitleUnknown = "Unknown"
|
||||
|
||||
TypeDefault = ""
|
||||
TypeFolder = "folder"
|
||||
TypeMoment = "moment"
|
||||
TypeImage = "image"
|
||||
TypeLive = "live"
|
||||
TypeVideo = "video"
|
||||
|
|
|
@ -15,14 +15,14 @@ var altCountryNames = map[string]string{
|
|||
|
||||
// Country represents a country location, used for labeling photos.
|
||||
type Country struct {
|
||||
ID string `gorm:"type:varbinary(2);primary_key"`
|
||||
CountrySlug string `gorm:"type:varbinary(255);unique_index;"`
|
||||
CountryName string
|
||||
CountryDescription string `gorm:"type:text;"`
|
||||
CountryNotes string `gorm:"type:text;"`
|
||||
CountryPhoto *Photo
|
||||
CountryPhotoID uint
|
||||
New bool `gorm:"-"`
|
||||
ID string `gorm:"type:varbinary(2);primary_key" json:"ID" yaml:"ID"`
|
||||
CountrySlug string `gorm:"type:varbinary(255);unique_index;" json:"Slug" yaml:"-"`
|
||||
CountryName string `json:"Name" yaml:"Name,omitempty"`
|
||||
CountryDescription string `gorm:"type:text;" json:"Description,omitempty" yaml:"Description,omitempty"`
|
||||
CountryNotes string `gorm:"type:text;" json:"Notes,omitempty" yaml:"Notes,omitempty"`
|
||||
CountryPhoto *Photo `json:"-" yaml:"-"`
|
||||
CountryPhotoID uint `json:"-" yaml:"-"`
|
||||
New bool `gorm:"-" json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// UnknownCountry is defined here to use it as a default
|
||||
|
|
|
@ -13,45 +13,45 @@ import (
|
|||
|
||||
// File represents an image or sidecar file that belongs to a photo
|
||||
type File struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Photo *Photo
|
||||
PhotoID uint `gorm:"index;"`
|
||||
PhotoUUID string `gorm:"type:varbinary(36);index;"`
|
||||
FileUUID string `gorm:"type:varbinary(36);unique_index;"`
|
||||
FileName string `gorm:"type:varbinary(768);unique_index"`
|
||||
OriginalName string `gorm:"type:varbinary(768);"`
|
||||
FileHash string `gorm:"type:varbinary(128);index"`
|
||||
FileModified time.Time
|
||||
FileSize int64
|
||||
FileCodec string `gorm:"type:varbinary(32)"`
|
||||
FileType string `gorm:"type:varbinary(32)"`
|
||||
FileMime string `gorm:"type:varbinary(64)"`
|
||||
FilePrimary bool
|
||||
FileSidecar bool
|
||||
FileMissing bool
|
||||
FileDuplicate bool
|
||||
FilePortrait bool
|
||||
FileVideo bool
|
||||
FileDuration time.Duration
|
||||
FileWidth int
|
||||
FileHeight int
|
||||
FileOrientation int
|
||||
FileAspectRatio float32 `gorm:"type:FLOAT;"`
|
||||
FileMainColor string `gorm:"type:varbinary(16);index;"`
|
||||
FileColors string `gorm:"type:varbinary(9);"`
|
||||
FileLuminance string `gorm:"type:varbinary(9);"`
|
||||
FileDiff uint32
|
||||
FileChroma uint8
|
||||
FileNotes string `gorm:"type:text"`
|
||||
FileError string `gorm:"type:varbinary(512)"`
|
||||
Share []FileShare
|
||||
Sync []FileSync
|
||||
Links []Link `gorm:"foreignkey:ShareUUID;association_foreignkey:FileUUID"`
|
||||
CreatedAt time.Time
|
||||
CreatedIn int64
|
||||
UpdatedAt time.Time
|
||||
UpdatedIn int64
|
||||
DeletedAt *time.Time `sql:"index"`
|
||||
ID uint `gorm:"primary_key" json:"-" yaml:"-"`
|
||||
Photo *Photo `json:"-" yaml:"-"`
|
||||
PhotoID uint `gorm:"index;" json:"-" yaml:"-"`
|
||||
PhotoUID string `gorm:"type:varbinary(36);index;" json:"PhotoUID" yaml:"PhotoUID"`
|
||||
FileUID string `gorm:"type:varbinary(36);unique_index;" json:"UID" yaml:"UID"`
|
||||
FileName string `gorm:"type:varbinary(768);unique_index" json:"Name" yaml:"Name"`
|
||||
OriginalName string `gorm:"type:varbinary(768);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
||||
FileHash string `gorm:"type:varbinary(128);index" json:"Hash" yaml:"Hash,omitempty"`
|
||||
FileModified time.Time `json:"Modified" yaml:"Modified,omitempty"`
|
||||
FileSize int64 `json:"Size" yaml:"Size,omitempty"`
|
||||
FileCodec string `gorm:"type:varbinary(32)" json:"Codec" yaml:"Codec,omitempty"`
|
||||
FileType string `gorm:"type:varbinary(32)" json:"Type" yaml:"Type,omitempty"`
|
||||
FileMime string `gorm:"type:varbinary(64)" json:"Mime" yaml:"Mime,omitempty"`
|
||||
FilePrimary bool `json:"Primary" yaml:"Primary,omitempty"`
|
||||
FileSidecar bool `json:"Sidecar" yaml:"Sidecar,omitempty"`
|
||||
FileMissing bool `json:"Missing" yaml:"Missing,omitempty"`
|
||||
FileDuplicate bool `json:"Duplicate" yaml:"Duplicate,omitempty"`
|
||||
FilePortrait bool `json:"Portrait" yaml:"Portrait,omitempty"`
|
||||
FileVideo bool `json:"Video" yaml:"Video,omitempty"`
|
||||
FileDuration time.Duration `json:"Duration" yaml:"Duration,omitempty"`
|
||||
FileWidth int `json:"Width" yaml:"Width,omitempty"`
|
||||
FileHeight int `json:"Height" yaml:"Height,omitempty"`
|
||||
FileOrientation int `json:"Orientation" yaml:"Orientation,omitempty"`
|
||||
FileAspectRatio float32 `gorm:"type:FLOAT;" json:"AspectRatio" yaml:"AspectRatio,omitempty"`
|
||||
FileMainColor string `gorm:"type:varbinary(16);index;" json:"eMainColor" yaml:"eMainColor,omitempty"`
|
||||
FileColors string `gorm:"type:varbinary(9);" json:"Colors" yaml:"Colors,omitempty"`
|
||||
FileLuminance string `gorm:"type:varbinary(9);" json:"Luminance" yaml:"Luminance,omitempty"`
|
||||
FileDiff uint32 `json:"Diff" yaml:"Diff,omitempty"`
|
||||
FileChroma uint8 `json:"Chroma" yaml:"Chroma,omitempty"`
|
||||
FileNotes string `gorm:"type:text" json:"Notes" yaml:"Notes,omitempty"`
|
||||
FileError string `gorm:"type:varbinary(512)" json:"Error" yaml:"Error,omitempty"`
|
||||
Share []FileShare `json:"-" yaml:"-"`
|
||||
Sync []FileSync `json:"-" yaml:"-"`
|
||||
Links []Link `gorm:"foreignkey:ShareUID;association_foreignkey:FileUID" json:"Links" yaml:"-"`
|
||||
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
||||
CreatedIn int64 `json:"CreatedIn" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
||||
UpdatedIn int64 `json:"UpdatedIn" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
|
||||
}
|
||||
|
||||
type FileInfos struct {
|
||||
|
@ -75,13 +75,13 @@ func FirstFileByHash(fileHash string) (File, error) {
|
|||
return file, q.Error
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UUID if needed before inserting a new row to the database.
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *File) BeforeCreate(scope *gorm.Scope) error {
|
||||
if rnd.IsPPID(m.FileUUID, 'f') {
|
||||
if rnd.IsPPID(m.FileUID, 'f') {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scope.SetColumn("FileUUID", rnd.PPID('f'))
|
||||
return scope.SetColumn("FileUID", rnd.PPID('f'))
|
||||
}
|
||||
|
||||
// ShareFileName returns a meaningful file name useful for sharing.
|
||||
|
@ -95,7 +95,7 @@ func (m *File) ShareFileName() string {
|
|||
if m.Photo.PhotoTitle != "" {
|
||||
name = strings.Title(slug.MakeLang(m.Photo.PhotoTitle, "en"))
|
||||
} else {
|
||||
name = m.PhotoUUID
|
||||
name = m.PhotoUID
|
||||
}
|
||||
|
||||
taken := m.Photo.TakenAtLocal.Format("20060102-150405")
|
||||
|
@ -144,7 +144,7 @@ func (m *File) AllFilesMissing() bool {
|
|||
// Saves the file in the database.
|
||||
func (m *File) Save() error {
|
||||
if m.PhotoID == 0 {
|
||||
return fmt.Errorf("file: photo id is empty (%s)", m.FileUUID)
|
||||
return fmt.Errorf("file: photo id is empty (%s)", m.FileUID)
|
||||
}
|
||||
|
||||
if err := Db().Save(m).Error; err != nil {
|
||||
|
|
|
@ -9,8 +9,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000000,
|
||||
Photo: PhotoFixtures.Pointer("19800101_000002_D640C559"),
|
||||
PhotoID: 1000000,
|
||||
PhotoUUID: "pt9jtdre2lvl0yh7",
|
||||
FileUUID: "ft8es39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0yh7",
|
||||
FileUID: "ft8es39w45bnlqdw",
|
||||
FileName: "exampleFileName.jpg",
|
||||
OriginalName: "exampleFileNameOriginal.jpg",
|
||||
FileHash: "2cad9168fa6acc5c5c2965ddf6ec465ca42fd818",
|
||||
|
@ -51,8 +51,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000001,
|
||||
Photo: PhotoFixtures.Pointer("Photo01"),
|
||||
PhotoID: 1000001,
|
||||
PhotoUUID: "pt9jtdre2lvl0yh8",
|
||||
FileUUID: "ft9es39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0yh8",
|
||||
FileUID: "ft9es39w45bnlqdw",
|
||||
FileName: "exampleDNGFile.dng",
|
||||
OriginalName: "exampleDNGFile.dng",
|
||||
FileHash: "3cad9168fa6acc5c5c2965ddf6ec465ca42fd818",
|
||||
|
@ -90,8 +90,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000002,
|
||||
Photo: PhotoFixtures.Pointer("Photo01"),
|
||||
PhotoID: 1000001,
|
||||
PhotoUUID: "pt9jtdre2lvl0yh8",
|
||||
FileUUID: "ft1es39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0yh8",
|
||||
FileUID: "ft1es39w45bnlqdw",
|
||||
FileName: "exampleXmpFile.xmp",
|
||||
OriginalName: "exampleXmpFile.xmp",
|
||||
FileHash: "ocad9168fa6acc5c5c2965ddf6ec465ca42fd818",
|
||||
|
@ -129,8 +129,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000003,
|
||||
Photo: PhotoFixtures.Pointer("Photo04"),
|
||||
PhotoID: 1000004,
|
||||
PhotoUUID: "pt9jtdre2lvl0y11",
|
||||
FileUUID: "ft2es39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0y11",
|
||||
FileUID: "ft2es39w45bnlqdw",
|
||||
FileName: "bridge.jpg",
|
||||
OriginalName: "bridgeOriginal.jpg",
|
||||
FileHash: "pcad9168fa6acc5c5c2965ddf6ec465ca42fd818",
|
||||
|
@ -168,8 +168,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000004,
|
||||
Photo: PhotoFixtures.Pointer("Photo05"),
|
||||
PhotoID: 1000005,
|
||||
PhotoUUID: "pt9jtdre2lvl0y12",
|
||||
FileUUID: "ft3es39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0y12",
|
||||
FileUID: "ft3es39w45bnlqdw",
|
||||
FileName: "reunion.jpg",
|
||||
OriginalName: "reunionOriginal.jpg",
|
||||
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd818",
|
||||
|
@ -207,8 +207,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000005,
|
||||
Photo: PhotoFixtures.Pointer("Photo17"),
|
||||
PhotoID: 1000017,
|
||||
PhotoUUID: "pt9jtdre2lvl0y24",
|
||||
FileUUID: "ft4es39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0y24",
|
||||
FileUID: "ft4es39w45bnlqdw",
|
||||
FileName: "Quality1FavoriteTrue.jpg",
|
||||
OriginalName: "Quality1FavoriteTrue.jpg",
|
||||
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd819",
|
||||
|
@ -246,8 +246,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000006,
|
||||
Photo: PhotoFixtures.Pointer("Photo15"),
|
||||
PhotoID: 1000015,
|
||||
PhotoUUID: "pt9jtdre2lvl0y22",
|
||||
FileUUID: "ft5es39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0y22",
|
||||
FileUID: "ft5es39w45bnlqdw",
|
||||
FileName: "missing.jpg",
|
||||
OriginalName: "missing.jpg",
|
||||
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd819",
|
||||
|
@ -285,8 +285,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000007,
|
||||
Photo: nil, // no pointer here because related photo is deleted
|
||||
PhotoID: 1000018,
|
||||
PhotoUUID: "pt9jtdre2lvl0y25",
|
||||
FileUUID: "ft6es39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0y25",
|
||||
FileUID: "ft6es39w45bnlqdw",
|
||||
FileName: "Photo18.jpg",
|
||||
OriginalName: "Photo18.jpg",
|
||||
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd820",
|
||||
|
@ -324,8 +324,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000008,
|
||||
Photo: PhotoFixtures.Pointer("Photo10"),
|
||||
PhotoID: 1000010,
|
||||
PhotoUUID: "pt9jtdre2lvl0y17",
|
||||
FileUUID: "ft71s39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0y17",
|
||||
FileUID: "ft71s39w45bnlqdw",
|
||||
FileName: "Video.mp4",
|
||||
OriginalName: "Video.mp4",
|
||||
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd831",
|
||||
|
@ -363,8 +363,8 @@ var FileFixtures = map[string]File{
|
|||
ID: 1000009,
|
||||
Photo: PhotoFixtures.Pointer("Photo10"),
|
||||
PhotoID: 1000010,
|
||||
PhotoUUID: "pt9jtdre2lvl0y17",
|
||||
FileUUID: "ft72s39w45bnlqdw",
|
||||
PhotoUID: "pt9jtdre2lvl0y17",
|
||||
FileUID: "ft72s39w45bnlqdw",
|
||||
FileName: "VideoError.mp4",
|
||||
OriginalName: "VideoError.mp4",
|
||||
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd832",
|
||||
|
|
|
@ -26,7 +26,7 @@ func TestFirstFileByHash(t *testing.T) {
|
|||
func TestFile_DownloadFileName(t *testing.T) {
|
||||
t.Run("photo with title", func(t *testing.T) {
|
||||
photo := &Photo{TakenAtLocal: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), PhotoTitle: "Berlin / Morning Mood"}
|
||||
file := &File{Photo: photo, FileType: "jpg", FileUUID: "foobar345678765"}
|
||||
file := &File{Photo: photo, FileType: "jpg", FileUID: "foobar345678765"}
|
||||
|
||||
filename := file.ShareFileName()
|
||||
|
||||
|
@ -35,7 +35,7 @@ func TestFile_DownloadFileName(t *testing.T) {
|
|||
})
|
||||
t.Run("photo without title", func(t *testing.T) {
|
||||
photo := &Photo{TakenAtLocal: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), PhotoTitle: ""}
|
||||
file := &File{Photo: photo, FileType: "jpg", PhotoUUID: "123", FileUUID: "foobar345678765"}
|
||||
file := &File{Photo: photo, FileType: "jpg", PhotoUID: "123", FileUID: "foobar345678765"}
|
||||
|
||||
filename := file.ShareFileName()
|
||||
|
||||
|
@ -43,7 +43,7 @@ func TestFile_DownloadFileName(t *testing.T) {
|
|||
assert.Contains(t, filename, fs.JpegExt)
|
||||
})
|
||||
t.Run("photo without photo", func(t *testing.T) {
|
||||
file := &File{Photo: nil, FileType: "jpg", FileHash: "123Hash", FileUUID: "foobar345678765"}
|
||||
file := &File{Photo: nil, FileType: "jpg", FileHash: "123Hash", FileUID: "foobar345678765"}
|
||||
|
||||
filename := file.ShareFileName()
|
||||
|
||||
|
@ -85,8 +85,8 @@ func TestFile_Purge(t *testing.T) {
|
|||
func TestFile_AllFilesMissing(t *testing.T) {
|
||||
t.Run("true", func(t *testing.T) {
|
||||
photo := &Photo{TakenAtLocal: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), PhotoTitle: ""}
|
||||
file := &File{Photo: photo, FileType: "jpg", PhotoUUID: "123", FileUUID: "123", FileMissing: true}
|
||||
file2 := &File{Photo: photo, FileType: "jpg", PhotoUUID: "123", FileUUID: "456", FileMissing: true}
|
||||
file := &File{Photo: photo, FileType: "jpg", PhotoUID: "123", FileUID: "123", FileMissing: true}
|
||||
file2 := &File{Photo: photo, FileType: "jpg", PhotoUID: "123", FileUID: "456", FileMissing: true}
|
||||
assert.True(t, file.AllFilesMissing())
|
||||
assert.NotEmpty(t, file2)
|
||||
})
|
||||
|
@ -100,7 +100,7 @@ func TestFile_AllFilesMissing(t *testing.T) {
|
|||
|
||||
func TestFile_Save(t *testing.T) {
|
||||
t.Run("save without photo", func(t *testing.T) {
|
||||
file := &File{Photo: nil, FileType: "jpg", PhotoUUID: "123", FileUUID: "123"}
|
||||
file := &File{Photo: nil, FileType: "jpg", PhotoUID: "123", FileUID: "123"}
|
||||
err := file.Save()
|
||||
|
||||
if err == nil {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
|
@ -25,7 +26,7 @@ const (
|
|||
type Folder struct {
|
||||
Root string `gorm:"type:varbinary(255);unique_index:idx_folders_root_path;" json:"Root" yaml:"Root"`
|
||||
Path string `gorm:"type:varbinary(1024);unique_index:idx_folders_root_path;" json:"Path" yaml:"Path"`
|
||||
FolderUUID string `gorm:"type:varbinary(36);primary_key;" json:"PPID,omitempty" yaml:"PPID,omitempty"`
|
||||
FolderUID string `gorm:"type:varbinary(36);primary_key;" json:"UID,omitempty" yaml:"UID,omitempty"`
|
||||
FolderTitle string `gorm:"type:varchar(255);" json:"Title" yaml:"Title,omitempty"`
|
||||
FolderDescription string `gorm:"type:text;" json:"Description,omitempty" yaml:"Description,omitempty"`
|
||||
FolderType string `gorm:"type:varbinary(16);" json:"Type" yaml:"Type,omitempty"`
|
||||
|
@ -34,20 +35,20 @@ type Folder struct {
|
|||
FolderIgnore bool `json:"Ignore" yaml:"Ignore"`
|
||||
FolderHidden bool `json:"Hidden" yaml:"Hidden"`
|
||||
FolderWatch bool `json:"Watch" yaml:"Watch"`
|
||||
Links []Link `gorm:"foreignkey:ShareUUID;association_foreignkey:FolderUUID" json:"-" yaml:"-"`
|
||||
Links []Link `gorm:"foreignkey:ShareUID;association_foreignkey:FolderUID" json:"Links" json:"-" yaml:"-"`
|
||||
CreatedAt time.Time `json:"-" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"-" yaml:"-"`
|
||||
ModifiedAt *time.Time `json:"ModifiedAt,omitempty" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"-"`
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UUID if needed before inserting a new row to the database.
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *Folder) BeforeCreate(scope *gorm.Scope) error {
|
||||
if rnd.IsPPID(m.FolderUUID, 'd') {
|
||||
if rnd.IsPPID(m.FolderUID, 'd') {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scope.SetColumn("FolderUUID", rnd.PPID('d'))
|
||||
return scope.SetColumn("FolderUID", rnd.PPID('d'))
|
||||
}
|
||||
|
||||
// NewFolder creates a new file system directory entity.
|
||||
|
@ -61,6 +62,7 @@ func NewFolder(root, pathName string, modTime *time.Time) Folder {
|
|||
}
|
||||
|
||||
result := Folder{
|
||||
FolderUID: rnd.PPID('d'),
|
||||
Root: root,
|
||||
Path: pathName,
|
||||
FolderType: TypeDefault,
|
||||
|
@ -106,7 +108,15 @@ func (m *Folder) SetTitleFromPath() {
|
|||
|
||||
// Saves the complete entity in the database.
|
||||
func (m *Folder) Create() error {
|
||||
return Db().Create(m).Error
|
||||
if err := Db().Create(m).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
event.Publish("count.folders", event.Data{
|
||||
"count": 1,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Updates selected properties in the database.
|
||||
|
|
|
@ -15,7 +15,7 @@ func TestNewFolder(t *testing.T) {
|
|||
assert.Equal(t, "", folder.FolderDescription)
|
||||
assert.Equal(t, "", folder.FolderType)
|
||||
assert.Equal(t, SortOrderName, folder.FolderOrder)
|
||||
assert.Equal(t, "", folder.FolderUUID)
|
||||
assert.IsType(t, "", folder.FolderUID)
|
||||
assert.Equal(t, false, folder.FolderFavorite)
|
||||
assert.Equal(t, false, folder.FolderHidden)
|
||||
assert.Equal(t, false, folder.FolderIgnore)
|
||||
|
|
|
@ -12,31 +12,31 @@ import (
|
|||
|
||||
// Label is used for photo, album and location categorization
|
||||
type Label struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
LabelUUID string `gorm:"type:varbinary(36);unique_index;"`
|
||||
LabelSlug string `gorm:"type:varbinary(255);unique_index;"`
|
||||
CustomSlug string `gorm:"type:varbinary(255);index;"`
|
||||
LabelName string `gorm:"type:varchar(255);"`
|
||||
LabelPriority int
|
||||
LabelFavorite bool
|
||||
LabelDescription string `gorm:"type:text;"`
|
||||
LabelNotes string `gorm:"type:text;"`
|
||||
LabelCategories []*Label `gorm:"many2many:categories;association_jointable_foreignkey:category_id"`
|
||||
Links []Link `gorm:"foreignkey:ShareUUID;association_foreignkey:LabelUUID"`
|
||||
PhotoCount int `gorm:"default:1"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time `sql:"index"`
|
||||
New bool `gorm:"-"`
|
||||
ID uint `gorm:"primary_key" json:"ID" yaml:"-"`
|
||||
LabelUID string `gorm:"type:varbinary(36);unique_index;" json:"UID" yaml:"UID"`
|
||||
LabelSlug string `gorm:"type:varbinary(255);unique_index;" json:"Slug" yaml:"-"`
|
||||
CustomSlug string `gorm:"type:varbinary(255);index;" json:"CustomSlug" yaml:"-"`
|
||||
LabelName string `gorm:"type:varchar(255);" json:"Name" yaml:"Name"`
|
||||
LabelPriority int `gorm:"type:varchar(255);" json:"Priority" yaml:"Priority,omitempty"`
|
||||
LabelFavorite bool `gorm:"type:varchar(255);" json:"Favorite" yaml:"Favorite,omitempty"`
|
||||
LabelDescription string `gorm:"type:text;" json:"Description" yaml:"Description,omitempty"`
|
||||
LabelNotes string `gorm:"type:text;" json:"Notes" yaml:"Notes,omitempty"`
|
||||
LabelCategories []*Label `gorm:"many2many:categories;association_jointable_foreignkey:category_id" json:"-" yaml:"-"`
|
||||
Links []Link `gorm:"foreignkey:ShareUID;association_foreignkey:LabelUID" json:"Links" yaml:"-"`
|
||||
PhotoCount int `gorm:"default:1" json:"PhotoCount" yaml:"-"`
|
||||
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
|
||||
New bool `gorm:"-" json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UUID if needed before inserting a new row to the database.
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *Label) BeforeCreate(scope *gorm.Scope) error {
|
||||
if rnd.IsPPID(m.LabelUUID, 'l') {
|
||||
if rnd.IsPPID(m.LabelUID, 'l') {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scope.SetColumn("LabelUUID", rnd.PPID('l'))
|
||||
return scope.SetColumn("LabelUID", rnd.PPID('l'))
|
||||
}
|
||||
|
||||
// NewLabel creates a label in database with a given name and priority
|
||||
|
|
|
@ -34,7 +34,7 @@ func (m LabelMap) PhotoLabel(photoId uint, labelName string, uncertainty int, so
|
|||
var LabelFixtures = LabelMap{
|
||||
"landscape": {
|
||||
ID: 1000000,
|
||||
LabelUUID: "lt9k3pw1wowuy3c2",
|
||||
LabelUID: "lt9k3pw1wowuy3c2",
|
||||
LabelSlug: "landscape",
|
||||
CustomSlug: "landscape",
|
||||
LabelName: "Landscape",
|
||||
|
@ -52,7 +52,7 @@ var LabelFixtures = LabelMap{
|
|||
},
|
||||
"flower": {
|
||||
ID: 1000001,
|
||||
LabelUUID: "lt9k3pw1wowuy3c3",
|
||||
LabelUID: "lt9k3pw1wowuy3c3",
|
||||
LabelSlug: "flower",
|
||||
CustomSlug: "flower",
|
||||
LabelName: "Flower",
|
||||
|
@ -70,7 +70,7 @@ var LabelFixtures = LabelMap{
|
|||
},
|
||||
"cake": {
|
||||
ID: 1000002,
|
||||
LabelUUID: "lt9k3pw1wowuy3c4",
|
||||
LabelUID: "lt9k3pw1wowuy3c4",
|
||||
LabelSlug: "cake",
|
||||
CustomSlug: "kuchen",
|
||||
LabelName: "Cake",
|
||||
|
@ -88,7 +88,7 @@ var LabelFixtures = LabelMap{
|
|||
},
|
||||
"cow": {
|
||||
ID: 1000003,
|
||||
LabelUUID: "lt9k3pw1wowuy3c5",
|
||||
LabelUID: "lt9k3pw1wowuy3c5",
|
||||
LabelSlug: "cow",
|
||||
CustomSlug: "kuh",
|
||||
LabelName: "COW",
|
||||
|
@ -106,7 +106,7 @@ var LabelFixtures = LabelMap{
|
|||
},
|
||||
"batchdelete": {
|
||||
ID: 1000004,
|
||||
LabelUUID: "lt9k3pw1wowuy3c6",
|
||||
LabelUID: "lt9k3pw1wowuy3c6",
|
||||
LabelSlug: "batchdelete",
|
||||
CustomSlug: "batchDelete",
|
||||
LabelName: "BatchDelete",
|
||||
|
@ -124,7 +124,7 @@ var LabelFixtures = LabelMap{
|
|||
},
|
||||
"updateLabel": {
|
||||
ID: 1000005,
|
||||
LabelUUID: "lt9k3pw1wowuy3c7",
|
||||
LabelUID: "lt9k3pw1wowuy3c7",
|
||||
LabelSlug: "updatelabel",
|
||||
CustomSlug: "updateLabel",
|
||||
LabelName: "updateLabel",
|
||||
|
@ -142,7 +142,7 @@ var LabelFixtures = LabelMap{
|
|||
},
|
||||
"updatePhotoLabel": {
|
||||
ID: 1000006,
|
||||
LabelUUID: "lt9k3pw1wowuy3c8",
|
||||
LabelUID: "lt9k3pw1wowuy3c8",
|
||||
LabelSlug: "updatephotolabel",
|
||||
CustomSlug: "updateLabelPhoto",
|
||||
LabelName: "updatePhotoLabel",
|
||||
|
@ -160,7 +160,7 @@ var LabelFixtures = LabelMap{
|
|||
},
|
||||
"likeLabel": {
|
||||
ID: 1000007,
|
||||
LabelUUID: "lt9k3pw1wowuy3c9",
|
||||
LabelUID: "lt9k3pw1wowuy3c9",
|
||||
LabelSlug: "likeLabel",
|
||||
CustomSlug: "likeLabel",
|
||||
LabelName: "likeLabel",
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
func TestLabelMap_Get(t *testing.T) {
|
||||
t.Run("get existing label", func(t *testing.T) {
|
||||
r := LabelFixtures.Get("landscape")
|
||||
assert.Equal(t, "lt9k3pw1wowuy3c2", r.LabelUUID)
|
||||
assert.Equal(t, "lt9k3pw1wowuy3c2", r.LabelUID)
|
||||
assert.Equal(t, "landscape", r.LabelSlug)
|
||||
assert.IsType(t, Label{}, r)
|
||||
})
|
||||
|
@ -22,7 +22,7 @@ func TestLabelMap_Get(t *testing.T) {
|
|||
func TestLabelMap_Pointer(t *testing.T) {
|
||||
t.Run("get existing label pointer", func(t *testing.T) {
|
||||
r := LabelFixtures.Pointer("landscape")
|
||||
assert.Equal(t, "lt9k3pw1wowuy3c2", r.LabelUUID)
|
||||
assert.Equal(t, "lt9k3pw1wowuy3c2", r.LabelUID)
|
||||
assert.Equal(t, "landscape", r.LabelSlug)
|
||||
assert.IsType(t, &Label{}, r)
|
||||
})
|
||||
|
|
|
@ -9,16 +9,16 @@ import (
|
|||
|
||||
// Lens represents camera lens (as extracted from UpdateExif metadata)
|
||||
type Lens struct {
|
||||
ID uint `gorm:"primary_key" yaml:"LensID"`
|
||||
LensSlug string `gorm:"type:varbinary(255);unique_index;" yaml:"Slug"`
|
||||
LensModel string `yaml:"Model"`
|
||||
LensMake string `yaml:"Make"`
|
||||
LensType string `yaml:"Type,omitempty"`
|
||||
LensDescription string `gorm:"type:text;" yaml:"Description,omitempty"`
|
||||
LensNotes string `gorm:"type:text;" yaml:"Notes,omitempty"`
|
||||
CreatedAt time.Time `yaml:"-"`
|
||||
UpdatedAt time.Time `yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" yaml:"-"`
|
||||
ID uint `gorm:"primary_key" json:"ID" yaml:"ID"`
|
||||
LensSlug string `gorm:"type:varbinary(255);unique_index;" json:"Slug" yaml:"Slug,omitempty"`
|
||||
LensModel string `json:"Model" yaml:"Model"`
|
||||
LensMake string `json:"Make" yaml:"Make"`
|
||||
LensType string `json:"Type" yaml:"Type,omitempty"`
|
||||
LensDescription string `gorm:"type:text;" json:"Description,omitempty" yaml:"Description,omitempty"`
|
||||
LensNotes string `gorm:"type:text;" json:"Notes,omitempty" yaml:"Notes,omitempty"`
|
||||
CreatedAt time.Time `json:"-" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"-" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
var UnknownLens = Lens{
|
||||
|
|
|
@ -9,18 +9,18 @@ import (
|
|||
|
||||
// Link represents a sharing link.
|
||||
type Link struct {
|
||||
LinkToken string `gorm:"type:varbinary(255);primary_key;"`
|
||||
LinkPassword string `gorm:"type:varbinary(255);"`
|
||||
LinkExpires *time.Time `gorm:"type:datetime;"`
|
||||
ShareUUID string `gorm:"type:varbinary(36);index;"`
|
||||
CanComment bool
|
||||
CanEdit bool
|
||||
CreatedAt time.Time `deepcopier:"skip"`
|
||||
UpdatedAt time.Time `deepcopier:"skip"`
|
||||
DeletedAt *time.Time `deepcopier:"skip" sql:"index"`
|
||||
LinkToken string `gorm:"type:varbinary(255);primary_key;" json:"Token"`
|
||||
LinkPassword string `gorm:"type:varbinary(255);" json:"Password"`
|
||||
LinkExpires *time.Time `gorm:"type:datetime;" json:"Expires"`
|
||||
ShareUID string `gorm:"type:varbinary(36);index;" json:"ShareUID"`
|
||||
CanComment bool `json:"CanComment"`
|
||||
CanEdit bool `json:"CanEdit"`
|
||||
CreatedAt time.Time `deepcopier:"skip" json:"CreatedAt"`
|
||||
UpdatedAt time.Time `deepcopier:"skip" json:"UpdatedAt"`
|
||||
DeletedAt *time.Time `deepcopier:"skip" sql:"index" json:"DeletedAt,omitempty"`
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UUID if needed before inserting a new row to the database.
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *Link) BeforeCreate(scope *gorm.Scope) error {
|
||||
if err := scope.SetColumn("LinkToken", rnd.Token(10)); err != nil {
|
||||
return err
|
||||
|
|
|
@ -11,7 +11,7 @@ var LinkFixtures = LinkMap{
|
|||
LinkToken: "1jxf3jfn2k",
|
||||
LinkPassword: "somepassword",
|
||||
LinkExpires: &date,
|
||||
ShareUUID: "4",
|
||||
ShareUID: "4",
|
||||
CanComment: true,
|
||||
CanEdit: false,
|
||||
CreatedAt: time.Date(2020, 3, 6, 2, 6, 51, 0, time.UTC),
|
||||
|
|
|
@ -18,33 +18,33 @@ import (
|
|||
// Photo represents a photo, all its properties, and link to all its images and sidecar files.
|
||||
type Photo struct {
|
||||
ID uint `gorm:"primary_key" yaml:"-"`
|
||||
TakenAt time.Time `gorm:"type:datetime;index:idx_photos_taken_uuid;" json:"TakenAt" yaml:"Taken"`
|
||||
TakenAt time.Time `gorm:"type:datetime;index:idx_photos_taken_uid;" json:"TakenAt" yaml:"TakenAt"`
|
||||
TakenAtLocal time.Time `gorm:"type:datetime;" yaml:"-"`
|
||||
TakenSrc string `gorm:"type:varbinary(8);" json:"TakenSrc" yaml:"TakenSrc,omitempty"`
|
||||
PhotoUUID string `gorm:"type:varbinary(36);unique_index;index:idx_photos_taken_uuid;" yaml:"PPID"`
|
||||
PhotoType string `gorm:"type:varbinary(8);default:'image';" json:"PhotoType" yaml:"Type"`
|
||||
PhotoTitle string `gorm:"type:varchar(255);" json:"PhotoTitle" yaml:"Title"`
|
||||
PhotoUID string `gorm:"type:varbinary(36);unique_index;index:idx_photos_taken_uid;" json:"UID" yaml:"UID"`
|
||||
PhotoType string `gorm:"type:varbinary(8);default:'image';" json:"Type" yaml:"Type"`
|
||||
PhotoTitle string `gorm:"type:varchar(255);" json:"Title" yaml:"Title"`
|
||||
TitleSrc string `gorm:"type:varbinary(8);" json:"TitleSrc" yaml:"TitleSrc,omitempty"`
|
||||
PhotoDescription string `gorm:"type:text;" json:"PhotoDescription" yaml:"Description,omitempty"`
|
||||
PhotoDescription string `gorm:"type:text;" json:"Description" yaml:"Description,omitempty"`
|
||||
DescriptionSrc string `gorm:"type:varbinary(8);" json:"DescriptionSrc" yaml:"DescriptionSrc,omitempty"`
|
||||
Details Details `json:"Details" yaml:"Details"`
|
||||
PhotoPath string `gorm:"type:varbinary(768);index;" yaml:"-"`
|
||||
PhotoName string `gorm:"type:varbinary(255);" yaml:"-"`
|
||||
PhotoFavorite bool `json:"PhotoFavorite" yaml:"Favorite,omitempty"`
|
||||
PhotoPrivate bool `json:"PhotoPrivate" yaml:"Private,omitempty"`
|
||||
PhotoFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
||||
PhotoPrivate bool `json:"Private" yaml:"Private,omitempty"`
|
||||
TimeZone string `gorm:"type:varbinary(64);" json:"TimeZone" yaml:"-"`
|
||||
PhotoLat float32 `gorm:"type:FLOAT;index;" json:"PhotoLat" yaml:"Lat,omitempty"`
|
||||
PhotoLng float32 `gorm:"type:FLOAT;index;" json:"PhotoLng" yaml:"Lng,omitempty"`
|
||||
PhotoAltitude int `json:"PhotoAltitude" yaml:"Altitude,omitempty"`
|
||||
PhotoCountry string `gorm:"type:varbinary(2);index:idx_photos_country_year_month;default:'zz'" json:"PhotoCountry" yaml:"-"`
|
||||
PhotoLat float32 `gorm:"type:FLOAT;index;" json:"Lat" yaml:"Lat,omitempty"`
|
||||
PhotoLng float32 `gorm:"type:FLOAT;index;" json:"Lng" yaml:"Lng,omitempty"`
|
||||
PhotoAltitude int `json:"Altitude" yaml:"Altitude,omitempty"`
|
||||
PhotoCountry string `gorm:"type:varbinary(2);index:idx_photos_country_year_month;default:'zz'" json:"Country" yaml:"-"`
|
||||
PhotoYear int `gorm:"index:idx_photos_country_year_month;" yaml:"-"`
|
||||
PhotoMonth int `gorm:"index:idx_photos_country_year_month;" yaml:"-"`
|
||||
PhotoIso int `json:"PhotoIso" yaml:"ISO,omitempty"`
|
||||
PhotoExposure string `gorm:"type:varbinary(64);" json:"PhotoExposure" yaml:"Exposure,omitempty"`
|
||||
PhotoFNumber float32 `gorm:"type:FLOAT;" json:"PhotoFNumber" yaml:"FNumber,omitempty"`
|
||||
PhotoFocalLength int `json:"PhotoFocalLength" yaml:"FocalLength,omitempty"`
|
||||
PhotoQuality int `gorm:"type:SMALLINT" json:"PhotoQuality" yaml:"-"`
|
||||
PhotoResolution int `gorm:"type:SMALLINT" json:"PhotoResolution" yaml:"-"`
|
||||
PhotoIso int `json:"Iso" yaml:"ISO,omitempty"`
|
||||
PhotoExposure string `gorm:"type:varbinary(64);" json:"Exposure" yaml:"Exposure,omitempty"`
|
||||
PhotoFNumber float32 `gorm:"type:FLOAT;" json:"FNumber" yaml:"FNumber,omitempty"`
|
||||
PhotoFocalLength int `json:"FocalLength" yaml:"FocalLength,omitempty"`
|
||||
PhotoQuality int `gorm:"type:SMALLINT" json:"Quality" yaml:"-"`
|
||||
PhotoResolution int `gorm:"type:SMALLINT" json:"Resolution" yaml:"-"`
|
||||
CameraID uint `gorm:"index:idx_photos_camera_lens;" json:"CameraID" yaml:"-"`
|
||||
CameraSerial string `gorm:"type:varbinary(255);" json:"CameraSerial" yaml:"CameraSerial,omitempty"`
|
||||
CameraSrc string `gorm:"type:varbinary(8);" json:"CameraSrc" yaml:"-"`
|
||||
|
@ -56,15 +56,15 @@ type Photo struct {
|
|||
Lens *Lens `json:"Lens" yaml:"-"`
|
||||
Location *Location `json:"Location" yaml:"-"`
|
||||
Place *Place `json:"-" yaml:"-"`
|
||||
Links []Link `gorm:"foreignkey:ShareUUID;association_foreignkey:PhotoUUID" yaml:"-"`
|
||||
Links []Link `gorm:"foreignkey:ShareUID;association_foreignkey:PhotoUID" json:"Links" yaml:"-"`
|
||||
Keywords []Keyword `json:"-" yaml:"-"`
|
||||
Albums []Album `json:"-" yaml:"-"`
|
||||
Files []File `yaml:"-"`
|
||||
Labels []PhotoLabel `yaml:"-"`
|
||||
CreatedAt time.Time `yaml:"Created"`
|
||||
UpdatedAt time.Time `yaml:"Updated"`
|
||||
EditedAt *time.Time `yaml:"Edited,omitempty"`
|
||||
DeletedAt *time.Time `sql:"index" yaml:"Deleted,omitempty"`
|
||||
CreatedAt time.Time `yaml:"CreatedAt,omitempty"`
|
||||
UpdatedAt time.Time `yaml:"UpdatedAt,omitempty"`
|
||||
EditedAt *time.Time `yaml:"EditedAt,omitempty"`
|
||||
DeletedAt *time.Time `sql:"index" yaml:"DeletedAt,omitempty"`
|
||||
}
|
||||
|
||||
// SavePhotoForm saves a model in the database using form data.
|
||||
|
@ -102,7 +102,7 @@ func SavePhotoForm(model Photo, form form.Photo, geoApi string) error {
|
|||
}
|
||||
|
||||
if err := model.UpdateTitle(model.ClassifyLabels()); err != nil {
|
||||
log.Warnf("%s (%s)", err.Error(), model.PhotoUUID)
|
||||
log.Warnf("%s (%s)", err.Error(), model.PhotoUID)
|
||||
}
|
||||
|
||||
if err := model.IndexKeywords(); err != nil {
|
||||
|
@ -136,7 +136,7 @@ func (m *Photo) Save() error {
|
|||
m.UpdateYearMonth()
|
||||
|
||||
if err := m.UpdateTitle(labels); err != nil {
|
||||
log.Warnf("%s (%s)", err.Error(), m.PhotoUUID)
|
||||
log.Warnf("%s (%s)", err.Error(), m.PhotoUID)
|
||||
}
|
||||
|
||||
if m.DetailsLoaded() {
|
||||
|
@ -178,7 +178,7 @@ func (m *Photo) ClassifyLabels() classify.Labels {
|
|||
return result
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UUID if needed before inserting a new row to the database.
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *Photo) BeforeCreate(scope *gorm.Scope) error {
|
||||
if m.TakenAt.IsZero() || m.TakenAtLocal.IsZero() {
|
||||
now := time.Now()
|
||||
|
@ -192,11 +192,11 @@ func (m *Photo) BeforeCreate(scope *gorm.Scope) error {
|
|||
}
|
||||
}
|
||||
|
||||
if rnd.IsPPID(m.PhotoUUID, 'p') {
|
||||
if rnd.IsPPID(m.PhotoUID, 'p') {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scope.SetColumn("PhotoUUID", rnd.PPID('p'))
|
||||
return scope.SetColumn("PhotoUID", rnd.PPID('p'))
|
||||
}
|
||||
|
||||
// BeforeSave ensures the existence of TakenAt properties before indexing or updating a photo
|
||||
|
@ -219,7 +219,7 @@ func (m *Photo) BeforeSave(scope *gorm.Scope) error {
|
|||
// IndexKeywords adds given keywords to the photo entry
|
||||
func (m *Photo) IndexKeywords() error {
|
||||
if !m.DetailsLoaded() {
|
||||
return fmt.Errorf("photo: can't index keywords, details not loaded (%s)", m.PhotoUUID)
|
||||
return fmt.Errorf("photo: can't index keywords, details not loaded (%s)", m.PhotoUID)
|
||||
}
|
||||
|
||||
db := Db()
|
||||
|
@ -289,7 +289,7 @@ func (m *Photo) PreloadAlbums() {
|
|||
q := Db().NewScope(nil).DB().
|
||||
Table("albums").
|
||||
Select(`albums.*`).
|
||||
Joins("JOIN photos_albums ON photos_albums.album_uuid = albums.album_uuid AND photos_albums.photo_uuid = ?", m.PhotoUUID).
|
||||
Joins("JOIN photos_albums ON photos_albums.album_uid = albums.album_uid AND photos_albums.photo_uid = ?", m.PhotoUID).
|
||||
Where("albums.deleted_at IS NULL").
|
||||
Order("albums.album_name ASC")
|
||||
|
||||
|
@ -304,9 +304,9 @@ func (m *Photo) PreloadMany() {
|
|||
m.PreloadAlbums()
|
||||
}
|
||||
|
||||
// HasID checks if the photo has a database id and uuid.
|
||||
// HasID checks if the photo has a database id and uid.
|
||||
func (m *Photo) HasID() bool {
|
||||
return m.ID > 0 && m.PhotoUUID != ""
|
||||
return m.ID > 0 && m.PhotoUID != ""
|
||||
}
|
||||
|
||||
// NoLocation checks if the photo has no location
|
||||
|
@ -576,7 +576,7 @@ func (m *Photo) DeletePermanently() error {
|
|||
Db().Unscoped().Delete(File{}, "photo_id = ?", m.ID)
|
||||
Db().Unscoped().Delete(PhotoKeyword{}, "photo_id = ?", m.ID)
|
||||
Db().Unscoped().Delete(PhotoLabel{}, "photo_id = ?", m.ID)
|
||||
Db().Unscoped().Delete(PhotoAlbum{}, "photo_uuid = ?", m.PhotoUUID)
|
||||
Db().Unscoped().Delete(PhotoAlbum{}, "photo_uid = ?", m.PhotoUID)
|
||||
|
||||
return Db().Unscoped().Delete(m).Error
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue