mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 08:44:04 +01:00
Refactor download urls and client config
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
@@ -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>{{ .config.title }}</title>
|
||||
<title>{{ .config.Title }}</title>
|
||||
|
||||
<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 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="{{ .config.title }}: {{ .config.subtitle }}"/>
|
||||
<meta name="twitter:description" content="{{ .config.description }}"/>
|
||||
<meta name="twitter:image" content="{{ .config.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="{{ .config.author }}">
|
||||
<meta name="description" content="{{ .config.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?{{ .config.cssHash }}">
|
||||
<link rel="stylesheet" href="/static/build/app.css?{{ .config.CSSHash }}">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
|
||||
<script>
|
||||
window.__CONFIG__ = {{ .config }};
|
||||
</script>
|
||||
</head>
|
||||
<body class="{{ .config.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?{{ .config.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>{{ .config.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?{{ .config.cssHash }}">
|
||||
<link rel="stylesheet" href="/static/build/app.css?{{ .config.CSSHash }}">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
|
||||
<script>
|
||||
window.__CONFIG__ = {{ .config }};
|
||||
</script>
|
||||
</head>
|
||||
<body class="{{ .config.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?{{ .config.jsHash }}"></script>
|
||||
<script src="/static/build/app.js?{{ .config.JSHash }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,34 +5,34 @@
|
||||
<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="twitter:site" content="@browseyourlife"/>
|
||||
|
||||
<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>
|
||||
@@ -45,6 +45,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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Axios from "axios";
|
||||
import Notify from "common/notify";
|
||||
|
||||
const testConfig = {"jsHash": "test", "version": "test"};
|
||||
const testConfig = {"jsHash":"48019917", "cssHash":"2b327230", "version": "test"};
|
||||
const config = window.__CONFIG__ ? window.__CONFIG__ : testConfig;
|
||||
|
||||
const Api = Axios.create({
|
||||
|
||||
@@ -11,16 +11,23 @@ class Config {
|
||||
this.storage = storage;
|
||||
this.storage_key = "config";
|
||||
|
||||
this.$vuetify = null;
|
||||
this.translations = translations;
|
||||
this.values = values;
|
||||
this.debug = !!values.debug;
|
||||
this.page = {
|
||||
title: "PhotoPrism",
|
||||
};
|
||||
|
||||
this.$vuetify = null;
|
||||
if(!values) {
|
||||
console.warn("config: values are empty");
|
||||
this.debug = true;
|
||||
this.values = {};
|
||||
return;
|
||||
}
|
||||
|
||||
Event.subscribe("config.updated", (ev, data) => this.setValues(data));
|
||||
this.values = values;
|
||||
this.debug = !!values.debug;
|
||||
|
||||
Event.subscribe("config.updated", (ev, data) => this.setValues(data.config));
|
||||
Event.subscribe("count", (ev, data) => this.onCount(ev, data));
|
||||
|
||||
if (this.has("settings")) {
|
||||
@@ -140,6 +147,14 @@ class Config {
|
||||
settings() {
|
||||
return this.values.settings;
|
||||
}
|
||||
|
||||
downloadToken() {
|
||||
return this.values["downloadToken"];
|
||||
}
|
||||
|
||||
thumbToken() {
|
||||
return this.values["thumbToken"];
|
||||
}
|
||||
}
|
||||
|
||||
export default Config;
|
||||
|
||||
@@ -54,22 +54,6 @@ class Viewer {
|
||||
counterEl: false,
|
||||
arrowEl: true,
|
||||
preloaderEl: true,
|
||||
getImageURLForShare: function (button) {
|
||||
const item = gallery.currItem;
|
||||
|
||||
if (!item.original_w) {
|
||||
button.label = button.template.replace("size", "not available");
|
||||
return item.download_url;
|
||||
}
|
||||
|
||||
if(button.id === "original") {
|
||||
button.label = button.template.replace("size", item.original_w + " × " + item.original_h);
|
||||
return item.download_url;
|
||||
} else {
|
||||
button.label = button.template.replace("size", item[button.id].w + " × " + item[button.id].h);
|
||||
return item[button.id].src + "?download=1";
|
||||
}
|
||||
},
|
||||
addCaptionHTMLFn: function(item, captionEl /*, isFake */) {
|
||||
// item - slide object
|
||||
// captionEl - caption DOM element
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
this.onDownload(`/api/v1/albums/${this.selection[0]}/download`);
|
||||
this.onDownload(`/api/v1/albums/${this.selection[0]}/dl?t=${this.$config.downloadToken()}`);
|
||||
|
||||
this.expanded = false;
|
||||
},
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
},
|
||||
download() {
|
||||
Api.post("zip", {"files": this.selection}).then(r => {
|
||||
this.onDownload("/api/v1/zip/" + r.data.filename);
|
||||
this.onDownload("/api/v1/zip/" + r.data.filename + "?t=" + this.$config.downloadToken());
|
||||
});
|
||||
|
||||
this.expanded = false;
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
this.onDownload(`/api/v1/labels/${this.selection[0]}/download`);
|
||||
this.onDownload(`/api/v1/labels/${this.selection[0]}/dl?t=${this.$config.downloadToken()}`);
|
||||
|
||||
this.expanded = false;
|
||||
},
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
downloadFile(index) {
|
||||
const photo = this.photos[index];
|
||||
const link = document.createElement('a')
|
||||
link.href = "/api/v1/download/" + photo.Hash;
|
||||
link.href = `/api/v1/dl/${photo.Hash}?t=${this.$config.downloadToken()}`;
|
||||
link.download = photo.FileName;
|
||||
link.click();
|
||||
},
|
||||
|
||||
@@ -234,10 +234,10 @@
|
||||
},
|
||||
download() {
|
||||
if (this.selection.length === 1) {
|
||||
this.onDownload(`/api/v1/photos/${this.selection[0]}/download`);
|
||||
this.onDownload(`/api/v1/photos/${this.selection[0]}/dl?t=${this.$config.downloadToken()}`);
|
||||
} else {
|
||||
Api.post("zip", {"photos": this.selection}).then(r => {
|
||||
this.onDownload("/api/v1/zip/" + r.data.filename);
|
||||
this.onDownload(`/api/v1/zip/${r.data.filename}?t=${this.$config.downloadToken()}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
downloadFile(index) {
|
||||
const photo = this.photos[index];
|
||||
const link = document.createElement('a')
|
||||
link.href = "/api/v1/download/" + photo.Hash;
|
||||
link.href = `/api/v1/dl/${photo.Hash}?t=${this.$config.downloadToken()}`;
|
||||
link.download = photo.FileName;
|
||||
link.click()
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</v-btn>
|
||||
</td>
|
||||
<td>
|
||||
<a :href="'/api/v1/download/' + props.item.Hash" class="secondary-dark--text" target="_blank"
|
||||
<a :href="'/api/v1/dl/' + props.item.Hash + '?t=' + $config.downloadToken()" class="secondary-dark--text" target="_blank"
|
||||
v-if="$config.feature('download')">
|
||||
{{ props.item.Name }}
|
||||
</a>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import RestModel from "model/rest";
|
||||
import Api from "common/api";
|
||||
import {DateTime} from "luxon";
|
||||
import {config} from "../session";
|
||||
|
||||
export class Album extends RestModel {
|
||||
getDefaults() {
|
||||
@@ -44,7 +45,7 @@ export class Album extends RestModel {
|
||||
}
|
||||
|
||||
thumbnailUrl(type) {
|
||||
return "/api/v1/albums/" + this.getId() + "/thumbnail/" + type;
|
||||
return `/api/v1/albums/${this.getId()}/t/${config.thumbToken()}/${type}`;
|
||||
}
|
||||
|
||||
thumbnailSrcset() {
|
||||
|
||||
@@ -2,6 +2,7 @@ import RestModel from "model/rest";
|
||||
import Api from "common/api";
|
||||
import {DateTime} from "luxon";
|
||||
import Util from "common/util";
|
||||
import {config} from "../session";
|
||||
|
||||
export class File extends RestModel {
|
||||
getDefaults() {
|
||||
@@ -79,11 +80,11 @@ export class File extends RestModel {
|
||||
return "/api/v1/svg/raw";
|
||||
}
|
||||
|
||||
return "/api/v1/thumbnails/" + this.Hash + "/" + type;
|
||||
return `/api/v1/t/${this.Hash}/t/${config.thumbToken()}/${type}`;
|
||||
}
|
||||
|
||||
getDownloadUrl() {
|
||||
return "/api/v1/download/" + this.Hash;
|
||||
return "/api/v1/dl/" + this.Hash + "?t=" + config.downloadToken();
|
||||
}
|
||||
|
||||
thumbnailSrcset() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import RestModel from "model/rest";
|
||||
import Api from "common/api";
|
||||
import {DateTime} from "luxon";
|
||||
import {config} from "../session";
|
||||
|
||||
export class Label extends RestModel {
|
||||
getDefaults() {
|
||||
@@ -35,7 +36,7 @@ export class Label extends RestModel {
|
||||
}
|
||||
|
||||
thumbnailUrl(type) {
|
||||
return "/api/v1/labels/" + this.getId() + "/thumbnail/" + type;
|
||||
return `/api/v1/labels/${this.getId()}/t/${config.thumbToken()}/${type}`;
|
||||
}
|
||||
|
||||
thumbnailSrcset() {
|
||||
|
||||
@@ -2,6 +2,7 @@ import RestModel from "model/rest";
|
||||
import Api from "common/api";
|
||||
import {DateTime} from "luxon";
|
||||
import Util from "common/util";
|
||||
import {config} from "../session";
|
||||
|
||||
export const SrcManual = "manual";
|
||||
export const CodecAvc1 = "avc1";
|
||||
@@ -166,7 +167,7 @@ export class Photo extends RestModel {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "/api/v1/videos/" + file.Hash + "/" + TypeMP4;
|
||||
return `/api/v1/videos/${file.Hash}/${config.thumbToken()}/${TypeMP4}`;
|
||||
}
|
||||
|
||||
mainFile() {
|
||||
@@ -204,23 +205,23 @@ export class Photo extends RestModel {
|
||||
let video = this.videoFile();
|
||||
|
||||
if (video && video.Hash) {
|
||||
return "/api/v1/thumbnails/" + video.Hash + "/" + type;
|
||||
return `/api/v1/t/${video.Hash}/${config.thumbToken()}/${type}`;
|
||||
}
|
||||
|
||||
return "/api/v1/svg/photo";
|
||||
}
|
||||
|
||||
return "/api/v1/thumbnails/" + hash + "/" + type;
|
||||
return `/api/v1/t/${hash}/${config.thumbToken()}/${type}`;
|
||||
}
|
||||
|
||||
getDownloadUrl() {
|
||||
return "/api/v1/download/" + this.mainFileHash();
|
||||
return `/api/v1/dl/${this.mainFileHash()}?t=${config.downloadToken()}`;
|
||||
}
|
||||
|
||||
downloadAll() {
|
||||
if (!this.Files) {
|
||||
let link = document.createElement('a')
|
||||
link.href = "/api/v1/download/" + this.mainFileHash();
|
||||
let link = document.createElement("a");
|
||||
link.href = `/api/v1/dl/${this.mainFileHash()}?t=${config.downloadToken()}`;
|
||||
link.download = this.baseName(false);
|
||||
link.click();
|
||||
return;
|
||||
@@ -228,12 +229,12 @@ export class Photo extends RestModel {
|
||||
|
||||
this.Files.forEach((file) => {
|
||||
if (!file || !file.Hash) {
|
||||
console.warn("no file hash found for download", file)
|
||||
return
|
||||
console.warn("no file hash found for download", file);
|
||||
return;
|
||||
}
|
||||
|
||||
let link = document.createElement('a')
|
||||
link.href = "/api/v1/download/" + file.Hash;
|
||||
let link = document.createElement("a");
|
||||
link.href = `/api/v1/dl/${file.Hash}?t=${config.downloadToken()}`;
|
||||
link.download = this.fileBase(file.Name);
|
||||
link.click();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Model from "./model";
|
||||
import Api from "../common/api";
|
||||
import {config} from "../session";
|
||||
|
||||
const thumbs = window.__CONFIG__.thumbnails;
|
||||
|
||||
@@ -176,7 +177,7 @@ export class Thumb extends Model {
|
||||
|
||||
}
|
||||
|
||||
return "/api/v1/thumbnails/" + file.Hash + "/" + type;
|
||||
return `/api/v1/t/${file.Hash}/${config.thumbToken()}/${type}`;
|
||||
}
|
||||
|
||||
static downloadUrl(file) {
|
||||
@@ -184,7 +185,7 @@ export class Thumb extends Model {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "/api/v1/download/" + file.Hash;
|
||||
return `/api/v1/dl/${file.Hash}?t=${config.downloadToken()}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
uid: uid,
|
||||
results: [],
|
||||
scrollDisabled: true,
|
||||
pageSize: 480,
|
||||
pageSize: 120,
|
||||
offset: 0,
|
||||
page: 0,
|
||||
selection: this.$clipboard.selection,
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
downloadFile(index) {
|
||||
const model = this.results[index];
|
||||
const link = document.createElement('a')
|
||||
link.href = "/api/v1/download/" + model.Hash;
|
||||
link.href = `/api/v1/dl/${model.Hash}?t=${this.$config.downloadToken()}`;
|
||||
link.download = model.Name;
|
||||
link.click()
|
||||
},
|
||||
|
||||
@@ -90,11 +90,11 @@
|
||||
|
||||
const settings = this.$config.settings();
|
||||
|
||||
if (settings.features.private) {
|
||||
if (settings && settings.features.private) {
|
||||
filter.public = true;
|
||||
}
|
||||
|
||||
if (settings.features.review && !("quality" in this.staticFilter)) {
|
||||
if (settings && settings.features.review && (!this.staticFilter || !("quality" in this.staticFilter))) {
|
||||
filter.quality = 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -166,13 +166,12 @@
|
||||
let id = features[i].id;
|
||||
|
||||
let marker = this.markers[id];
|
||||
let token = this.$config.thumbToken();
|
||||
if (!marker) {
|
||||
let el = document.createElement('div');
|
||||
el.className = 'marker';
|
||||
el.title = props.Title;
|
||||
el.style.backgroundImage =
|
||||
'url(/api/v1/thumbnails/' +
|
||||
props.Hash + '/tile_50)';
|
||||
el.style.backgroundImage = `url(/api/v1/t/${props.Hash}/${token}/tile_50)`;
|
||||
el.style.width = '50px';
|
||||
el.style.height = '50px';
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -37,14 +37,14 @@ describe("model/album", () => {
|
||||
const values = {id: 5, Title: "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");
|
||||
assert.equal(result, "/api/v1/albums/66/t/static/xyz");
|
||||
});
|
||||
|
||||
it("should get thumbnail src set", () => {
|
||||
const values = {id: 5, Title: "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");
|
||||
assert.equal(result, "/api/v1/albums/66/t/static/fit_720 720w, /api/v1/albums/66/t/static/fit_1280 1280w, /api/v1/albums/66/t/static/fit_1920 1920w, /api/v1/albums/66/t/static/fit_2560 2560w, /api/v1/albums/66/t/static/fit_3840 3840w");
|
||||
});
|
||||
|
||||
it("should get thumbnail sizes", () => {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -100,7 +100,7 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
event.Success("album created")
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
PublishAlbumEvent(EntityCreated, m.AlbumUID, c)
|
||||
|
||||
@@ -144,7 +144,8 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
event.Success("album saved")
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
@@ -174,7 +175,7 @@ func DeleteAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
conf.Db().Delete(&m)
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
event.Success(fmt.Sprintf("album %s deleted", txt.Quote(m.AlbumTitle)))
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
@@ -203,7 +204,7 @@ func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
album.AlbumFavorite = true
|
||||
conf.Db().Save(&album)
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
PublishAlbumEvent(EntityUpdated, id, c)
|
||||
|
||||
c.JSON(http.StatusOK, http.Response{})
|
||||
@@ -232,7 +233,7 @@ func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
album.AlbumFavorite = false
|
||||
conf.Db().Save(&album)
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
PublishAlbumEvent(EntityUpdated, id, c)
|
||||
|
||||
c.JSON(http.StatusOK, http.Response{})
|
||||
@@ -330,9 +331,14 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
})
|
||||
}
|
||||
|
||||
// GET /albums/:uid/download
|
||||
// GET /albums/:uid/dl
|
||||
func DownloadAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/albums/:uid/download", func(c *gin.Context) {
|
||||
router.GET("/albums/:uid/dl", func(c *gin.Context) {
|
||||
if InvalidDownloadToken(c, conf) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
a, err := query.AlbumByUID(c.Param("uid"))
|
||||
@@ -413,13 +419,18 @@ func DownloadAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/albums/:uid/thumbnail/:type
|
||||
// GET /api/v1/albums/:uid/t/:token/:type
|
||||
//
|
||||
// Parameters:
|
||||
// uid: string Album UID
|
||||
// type: string Thumbnail type, see photoprism.ThumbnailTypes
|
||||
func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/albums/:uid/thumbnail/:type", func(c *gin.Context) {
|
||||
router.GET("/albums/:uid/t/:token/:type", func(c *gin.Context) {
|
||||
if InvalidToken(c, conf) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
typeName := c.Param("type")
|
||||
uid := c.Param("uid")
|
||||
start := time.Now()
|
||||
|
||||
@@ -253,7 +253,7 @@ func TestDownloadAlbum(t *testing.T) {
|
||||
|
||||
DownloadAlbum(router, conf)
|
||||
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/5678/download")
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/5678/dl?t="+conf.DownloadToken())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
t.Run("download existing album", func(t *testing.T) {
|
||||
@@ -261,29 +261,29 @@ func TestDownloadAlbum(t *testing.T) {
|
||||
|
||||
DownloadAlbum(router, conf)
|
||||
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8/download")
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8/dl?t="+conf.DownloadToken())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlbumThumbnail(t *testing.T) {
|
||||
t.Run("invalid type", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
AlbumThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba7/thumbnail/xxx")
|
||||
app, router, conf := NewApiTest()
|
||||
AlbumThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba7/t/"+conf.ThumbToken()+"/xxx")
|
||||
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("album has no photo (because is not existing)", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
AlbumThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/987-986435/thumbnail/tile_500")
|
||||
app, router, conf := NewApiTest()
|
||||
AlbumThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/987-986435/t/"+conf.ThumbToken()+"/tile_500")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("album: could not find original", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
AlbumThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8/thumbnail/tile_500")
|
||||
app, router, conf := NewApiTest()
|
||||
AlbumThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8/t/"+conf.ThumbToken()+"/tile_500")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/photoprism/photoprism/wiki
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
||||
@@ -18,3 +19,7 @@ func report(prefix string, err error) {
|
||||
log.Errorf("%s: %s", prefix, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateClientConfig(conf *config.Config) {
|
||||
event.Publish("config.updated", event.Data{"config": conf.ClientConfig()})
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func BatchPhotosArchive(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
elapsed := int(time.Since(start).Seconds())
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
event.EntitiesArchived("photos", f.Photos)
|
||||
|
||||
@@ -101,7 +101,7 @@ func BatchPhotosRestore(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
elapsed := int(time.Since(start).Seconds())
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
event.EntitiesRestored("photos", f.Photos)
|
||||
|
||||
@@ -135,7 +135,7 @@ func BatchAlbumsDelete(router *gin.RouterGroup, conf *config.Config) {
|
||||
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()))
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
event.EntitiesDeleted("albums", f.Albums)
|
||||
|
||||
@@ -183,7 +183,7 @@ func BatchPhotosPrivate(router *gin.RouterGroup, conf *config.Config) {
|
||||
event.EntitiesUpdated("photos", entities)
|
||||
}
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
@@ -216,7 +216,7 @@ func BatchLabelsDelete(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
entity.Db().Where("label_uid IN (?)", f.Labels).Delete(&entity.Label{})
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
event.EntitiesDeleted("labels", f.Labels)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
@@ -16,12 +17,17 @@ import (
|
||||
// TODO: GET /api/v1/dl/photo/:uid
|
||||
// TODO: GET /api/v1/dl/album/:uid
|
||||
|
||||
// GET /api/v1/download/:hash
|
||||
// GET /api/v1/dl/:hash
|
||||
//
|
||||
// Parameters:
|
||||
// hash: string The file hash as returned by the search API
|
||||
func GetDownload(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/download/:hash", func(c *gin.Context) {
|
||||
router.GET("/dl/:hash", func(c *gin.Context) {
|
||||
if InvalidDownloadToken(c, conf) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
fileHash := c.Param("hash")
|
||||
|
||||
f, err := query.FileByHash(fileHash)
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestGetDownload(t *testing.T) {
|
||||
|
||||
GetDownload(router, conf)
|
||||
|
||||
r := PerformRequest(app, "GET", "/api/v1/download/123xxx")
|
||||
r := PerformRequest(app, "GET", "/api/v1/dl/123xxx?t="+conf.DownloadToken())
|
||||
val := gjson.Get(r.Body.String(), "error")
|
||||
assert.Equal(t, "record not found", val.String())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
@@ -22,7 +22,7 @@ func TestGetDownload(t *testing.T) {
|
||||
t.Run("could not find original", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
GetDownload(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/download/3cad9168fa6acc5c5c2965ddf6ec465ca42fd818")
|
||||
r := PerformRequest(app, "GET", "/api/v1/dl/3cad9168fa6acc5c5c2965ddf6ec465ca42fd818?t="+conf.DownloadToken())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ func StartImport(router *gin.RouterGroup, conf *config.Config) {
|
||||
event.Success(fmt.Sprintf("import completed in %d s", elapsed))
|
||||
event.Publish("import.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
event.Publish("index.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("import completed in %d s", elapsed)})
|
||||
})
|
||||
|
||||
@@ -68,7 +68,8 @@ func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
event.Success(fmt.Sprintf("indexing completed in %d s", elapsed))
|
||||
event.Publish("index.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("indexing completed in %d s", elapsed)})
|
||||
})
|
||||
|
||||
@@ -155,15 +155,18 @@ func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/labels/:uid/thumbnail/:type
|
||||
//
|
||||
// Example: /api/v1/labels/cheetah/thumbnail/tile_500
|
||||
// GET /api/v1/labels/:uid/t/:token/:type
|
||||
//
|
||||
// Parameters:
|
||||
// uid: string Label UID
|
||||
// type: string Thumbnail type, see photoprism.ThumbnailTypes
|
||||
func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/labels/:uid/thumbnail/:type", func(c *gin.Context) {
|
||||
router.GET("/labels/:uid/t/:token/:type", func(c *gin.Context) {
|
||||
if InvalidToken(c, conf) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
typeName := c.Param("type")
|
||||
labelUID := c.Param("uid")
|
||||
start := time.Now()
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/tidwall/gjson"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -109,22 +110,22 @@ func TestDislikeLabel(t *testing.T) {
|
||||
|
||||
func TestLabelThumbnail(t *testing.T) {
|
||||
t.Run("invalid type", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LabelThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels/lt9k3pw1wowuy3c2/thumbnail/xxx")
|
||||
app, router, conf := NewApiTest()
|
||||
LabelThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels/lt9k3pw1wowuy3c2/t/"+conf.ThumbToken()+"/xxx")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("invalid label", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LabelThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels/xxx/thumbnail/tile_500")
|
||||
app, router, conf := NewApiTest()
|
||||
LabelThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels/xxx/t/"+conf.ThumbToken()+"/tile_500")
|
||||
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("could not find original", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LabelThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels/lt9k3pw1wowuy3c3/thumbnail/tile_500")
|
||||
app, router, conf := NewApiTest()
|
||||
LabelThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/labels/lt9k3pw1wowuy3c3/t/"+conf.ThumbToken()+"/tile_500")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,12 +108,17 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/photos/:uid/download
|
||||
// GET /api/v1/photos/:uid/dl
|
||||
//
|
||||
// Parameters:
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/photos/:uid/download", func(c *gin.Context) {
|
||||
router.GET("/photos/:uid/dl", func(c *gin.Context) {
|
||||
if InvalidDownloadToken(c, conf) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := query.FileByPhotoUID(c.Param("uid"))
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -56,33 +56,33 @@ func TestUpdatePhoto(t *testing.T) {
|
||||
|
||||
func TestGetPhotoDownload(t *testing.T) {
|
||||
t.Run("could not find original", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
GetPhotoDownload(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/photos/pt9jtdre2lvl0yh7/download")
|
||||
app, router, conf := NewApiTest()
|
||||
GetPhotoDownload(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/photos/pt9jtdre2lvl0yh7/dl?t="+conf.DownloadToken())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
t.Run("not existing photo", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
GetPhotoDownload(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/photos/xxx/download")
|
||||
app, router, conf := NewApiTest()
|
||||
GetPhotoDownload(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/photos/xxx/dl?t="+conf.DownloadToken())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLikePhoto(t *testing.T) {
|
||||
t.Run("existing photo", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LikePhoto(router, ctx)
|
||||
app, router, conf := NewApiTest()
|
||||
LikePhoto(router, conf)
|
||||
r := PerformRequest(app, "POST", "/api/v1/photos/pt9jtdre2lvl0yh9/like")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
GetPhoto(router, ctx)
|
||||
GetPhoto(router, conf)
|
||||
r2 := PerformRequest(app, "GET", "/api/v1/photos/pt9jtdre2lvl0yh9")
|
||||
val := gjson.Get(r2.Body.String(), "Favorite")
|
||||
assert.Equal(t, "true", val.String())
|
||||
})
|
||||
t.Run("not existing photo", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
LikePhoto(router, ctx)
|
||||
app, router, conf := NewApiTest()
|
||||
LikePhoto(router, conf)
|
||||
r := PerformRequest(app, "POST", "/api/v1/photos/xxx/like")
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
|
||||
@@ -13,13 +13,18 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// GET /api/v1/thumbnails/:hash/:type
|
||||
// GET /api/v1/t/:hash/:token/:type
|
||||
//
|
||||
// Parameters:
|
||||
// hash: string The file hash as returned by the search API
|
||||
// type: string Thumbnail type, see photoprism.ThumbnailTypes
|
||||
func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/thumbnails/:hash/:type", func(c *gin.Context) {
|
||||
router.GET("/t/:hash/:token/:type", func(c *gin.Context) {
|
||||
if InvalidToken(c, conf) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
fileHash := c.Param("hash")
|
||||
typeName := c.Param("type")
|
||||
|
||||
|
||||
@@ -9,23 +9,23 @@ import (
|
||||
|
||||
func TestGetThumbnail(t *testing.T) {
|
||||
t.Run("invalid type", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
GetThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/thumbnails/1/xxx")
|
||||
app, router, conf := NewApiTest()
|
||||
GetThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/t/1/"+conf.ThumbToken()+"/xxx")
|
||||
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("invalid hash", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
GetThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/thumbnails/1/tile_500")
|
||||
app, router, conf := NewApiTest()
|
||||
GetThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/t/1/"+conf.ThumbToken()+"/tile_500")
|
||||
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("could not find original", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
GetThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/thumbnails/2cad9168fa6acc5c5c2965ddf6ec465ca42fd818/tile_500")
|
||||
app, router, conf := NewApiTest()
|
||||
GetThumbnail(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/t/2cad9168fa6acc5c5c2965ddf6ec465ca42fd818/"+conf.ThumbToken()+"/tile_500")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,3 +61,19 @@ func Unauthorized(c *gin.Context, conf *config.Config) bool {
|
||||
// Check if session token is valid
|
||||
return !service.Session().Exists(token)
|
||||
}
|
||||
|
||||
// InvalidToken returns true if the token is invalid.
|
||||
func InvalidToken(c *gin.Context, conf *config.Config) bool {
|
||||
token := c.Param("token")
|
||||
|
||||
if token == "" {
|
||||
token = c.Query("t")
|
||||
}
|
||||
|
||||
return conf.InvalidToken(token)
|
||||
}
|
||||
|
||||
// InvalidDownloadToken returns true if the token is invalid.
|
||||
func InvalidDownloadToken(c *gin.Context, conf *config.Config) bool {
|
||||
return conf.InvalidDownloadToken(c.Query("t"))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
@@ -43,7 +42,8 @@ func SaveSettings(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
log.Infof("settings saved")
|
||||
|
||||
c.JSON(http.StatusOK, s)
|
||||
|
||||
@@ -12,13 +12,18 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// GET /api/v1/videos/:hash/:type
|
||||
// GET /api/v1/videos/:hash/:token/:type
|
||||
//
|
||||
// Parameters:
|
||||
// hash: string The photo or video file hash as returned by the search API
|
||||
// type: string Video type
|
||||
func GetVideo(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/videos/:hash/:type", func(c *gin.Context) {
|
||||
router.GET("/videos/:hash/:token/:type", func(c *gin.Context) {
|
||||
if InvalidToken(c, conf) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
fileHash := c.Param("hash")
|
||||
typeName := c.Param("type")
|
||||
|
||||
|
||||
@@ -10,25 +10,25 @@ func TestGetVideo(t *testing.T) {
|
||||
t.Run("invalid hash", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
GetVideo(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/videos/xxx/mp4")
|
||||
r := PerformRequest(app, "GET", "/api/v1/videos/xxx/"+conf.ThumbToken()+"/mp4")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("invalid type", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
GetVideo(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd831/xxx")
|
||||
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd831/"+conf.ThumbToken()+"/xxx")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("file for video not found", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
GetVideo(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd831/mp4")
|
||||
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd831/"+conf.ThumbToken()+"/mp4")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("file with error", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
GetVideo(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd832/mp4")
|
||||
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd832/"+conf.ThumbToken()+"/mp4")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func wsReader(ws *websocket.Conn, writeMutex *sync.Mutex, connId string, conf *c
|
||||
writeMutex.Lock()
|
||||
ws.SetWriteDeadline(time.Now().Add(30 * time.Second))
|
||||
|
||||
if err := ws.WriteJSON(gin.H{"event": "config.updated", "data": event.Data(conf.ClientConfig())}); err != nil {
|
||||
if err := ws.WriteJSON(gin.H{"event": "config.updated", "data": event.Data{"config": conf.ClientConfig()}}); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
writeMutex.Unlock()
|
||||
|
||||
@@ -109,6 +109,11 @@ func CreateZip(router *gin.RouterGroup, conf *config.Config) {
|
||||
// GET /api/v1/zip/:filename
|
||||
func DownloadZip(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/zip/:filename", func(c *gin.Context) {
|
||||
if InvalidDownloadToken(c, conf) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
zipBaseName := filepath.Base(c.Param("filename"))
|
||||
zipPath := path.Join(conf.TempPath(), "zip")
|
||||
zipFileName := path.Join(zipPath, zipBaseName)
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestDownloadZip(t *testing.T) {
|
||||
t.Run("zip not existing", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
DownloadZip(router, conf)
|
||||
r := PerformRequest(app, "GET", "/api/v1/zip/xxx")
|
||||
r := PerformRequest(app, "GET", "/api/v1/zip/xxx?t="+conf.DownloadToken())
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -101,6 +101,8 @@ func configAction(ctx *cli.Context) error {
|
||||
fmt.Printf("%-25s %s\n", "geocoding-api", conf.GeoCodingApi())
|
||||
|
||||
// Thumbnails
|
||||
fmt.Printf("%-25s %s\n", "download-token", conf.DownloadToken())
|
||||
fmt.Printf("%-25s %s\n", "thumb-token", conf.ThumbToken())
|
||||
fmt.Printf("%-25s %s\n", "thumb-filter", conf.ThumbFilter())
|
||||
fmt.Printf("%-25s %t\n", "thumb-uncached", conf.ThumbUncached())
|
||||
fmt.Printf("%-25s %d\n", "thumb-size", conf.ThumbSize())
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -25,3 +26,31 @@ func (c *Config) CheckPassword(p string) bool {
|
||||
|
||||
return ap == p
|
||||
}
|
||||
|
||||
// InvalidDownloadToken returns true if the token is invalid.
|
||||
func (c *Config) InvalidDownloadToken(t string) bool {
|
||||
return c.DownloadToken() != t
|
||||
}
|
||||
|
||||
// DownloadToken returns the DOWNLOAD api token (you can optionally use a static value for permanent caching).
|
||||
func (c *Config) DownloadToken() string {
|
||||
if c.params.DownloadToken == "" {
|
||||
c.params.DownloadToken = rnd.Token(8)
|
||||
}
|
||||
|
||||
return c.params.DownloadToken
|
||||
}
|
||||
|
||||
// InvalidToken returns true if the token is invalid.
|
||||
func (c *Config) InvalidToken(t string) bool {
|
||||
return c.ThumbToken() != t && c.DownloadToken() != t
|
||||
}
|
||||
|
||||
// ThumbToken returns the THUMBNAILS api token (you can optionally use a static value for permanent caching).
|
||||
func (c *Config) ThumbToken() string {
|
||||
if c.params.ThumbToken == "" {
|
||||
c.params.ThumbToken = rnd.Token(8)
|
||||
}
|
||||
|
||||
return c.params.ThumbToken
|
||||
}
|
||||
|
||||
@@ -12,7 +12,73 @@ import (
|
||||
)
|
||||
|
||||
// ClientConfig contains HTTP client / Web UI config values
|
||||
type ClientConfig map[string]interface{}
|
||||
|
||||
type ClientConfig struct {
|
||||
Flags string `json:"flags"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Title string `json:"title"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
Version string `json:"version"`
|
||||
Copyright string `json:"copyright"`
|
||||
Debug bool `json:"debug"`
|
||||
ReadOnly bool `json:"readonly"`
|
||||
UploadNSFW bool `json:"uploadNSFW"`
|
||||
Public bool `json:"public"`
|
||||
Experimental bool `json:"experimental"`
|
||||
DisableSettings bool `json:"disableSettings"`
|
||||
Albums []entity.Album `json:"albums"`
|
||||
Cameras []entity.Camera `json:"cameras"`
|
||||
Lenses []entity.Lens `json:"lenses"`
|
||||
Countries []entity.Country `json:"countries"`
|
||||
Thumbnails []Thumbnail `json:"thumbnails"`
|
||||
DownloadToken string `json:"downloadToken"`
|
||||
ThumbToken string `json:"thumbToken"`
|
||||
JSHash string `json:"jsHash"`
|
||||
CSSHash string `json:"cssHash"`
|
||||
Settings Settings `json:"settings"`
|
||||
Count ClientCounts `json:"count"`
|
||||
Pos ClientPosition `json:"pos"`
|
||||
Years []int `json:"years"`
|
||||
Colors []map[string]string `json:"colors"`
|
||||
Categories []CategoryLabel `json:"categories"`
|
||||
Clip int `json:"clip"`
|
||||
Server RuntimeInfo `json:"server"`
|
||||
}
|
||||
|
||||
type ClientCounts struct {
|
||||
Photos uint `json:"photos"`
|
||||
Videos uint `json:"videos"`
|
||||
Hidden uint `json:"hidden"`
|
||||
Favorites uint `json:"favorites"`
|
||||
Private uint `json:"private"`
|
||||
Review uint `json:"review"`
|
||||
Stories uint `json:"stories"`
|
||||
Albums uint `json:"albums"`
|
||||
Folders uint `json:"folders"`
|
||||
Files uint `json:"files"`
|
||||
Moments uint `json:"moments"`
|
||||
Countries uint `json:"countries"`
|
||||
Places uint `json:"places"`
|
||||
Labels uint `json:"labels"`
|
||||
LabelMaxPhotos uint `json:"labelMaxPhotos"`
|
||||
}
|
||||
|
||||
type CategoryLabel struct {
|
||||
LabelUID string `json:"UID"`
|
||||
CustomSlug string `json:"Slug"`
|
||||
LabelName string `json:"Name"`
|
||||
}
|
||||
|
||||
type ClientPosition struct {
|
||||
PhotoUID string `json:"uid"`
|
||||
LocUID string `json:"loc"`
|
||||
TakenAt time.Time `json:"utc"`
|
||||
PhotoLat float64 `json:"lat"`
|
||||
PhotoLng float64 `json:"lng"`
|
||||
}
|
||||
|
||||
// Flags returns config flags as string slice.
|
||||
func (c *Config) Flags() (flags []string) {
|
||||
@@ -45,67 +111,30 @@ func (c *Config) PublicClientConfig() ClientConfig {
|
||||
return c.ClientConfig()
|
||||
}
|
||||
|
||||
jsHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.js")
|
||||
cssHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.css")
|
||||
configFlags := c.Flags()
|
||||
|
||||
var noPos = struct {
|
||||
PhotoUID string `json:"photo"`
|
||||
LocUID string `json:"location"`
|
||||
TakenAt time.Time `json:"utc"`
|
||||
PhotoLat float64 `json:"lat"`
|
||||
PhotoLng float64 `json:"lng"`
|
||||
}{}
|
||||
|
||||
var count = struct {
|
||||
Photos uint `json:"photos"`
|
||||
Videos uint `json:"videos"`
|
||||
Hidden uint `json:"hidden"`
|
||||
Favorites uint `json:"favorites"`
|
||||
Private uint `json:"private"`
|
||||
Review uint `json:"review"`
|
||||
Stories uint `json:"stories"`
|
||||
Albums uint `json:"albums"`
|
||||
Folders uint `json:"folders"`
|
||||
Files uint `json:"files"`
|
||||
Moments uint `json:"moments"`
|
||||
Countries uint `json:"countries"`
|
||||
Places uint `json:"places"`
|
||||
Labels uint `json:"labels"`
|
||||
LabelMaxPhotos uint `json:"labelMaxPhotos"`
|
||||
}{}
|
||||
settings := c.Settings()
|
||||
|
||||
result := ClientConfig{
|
||||
"settings": c.Settings(),
|
||||
"flags": strings.Join(configFlags, " "),
|
||||
"name": c.Name(),
|
||||
"url": c.Url(),
|
||||
"title": c.Title(),
|
||||
"subtitle": c.Subtitle(),
|
||||
"description": c.Description(),
|
||||
"author": c.Author(),
|
||||
"version": c.Version(),
|
||||
"copyright": c.Copyright(),
|
||||
"debug": c.Debug(),
|
||||
"readonly": c.ReadOnly(),
|
||||
"uploadNSFW": c.UploadNSFW(),
|
||||
"public": c.Public(),
|
||||
"experimental": c.Experimental(),
|
||||
"disableSettings": c.DisableSettings(),
|
||||
"albums": []string{},
|
||||
"cameras": []string{},
|
||||
"lenses": []string{},
|
||||
"countries": []string{},
|
||||
"thumbnails": Thumbnails,
|
||||
"jsHash": jsHash,
|
||||
"cssHash": cssHash,
|
||||
"count": count,
|
||||
"pos": noPos,
|
||||
"years": []int{},
|
||||
"colors": colors.All.List(),
|
||||
"categories": []string{},
|
||||
"clip": txt.ClipDefault,
|
||||
"server": RuntimeInfo{},
|
||||
Settings: Settings{Language: settings.Language, Theme: settings.Theme},
|
||||
Flags: strings.Join(c.Flags(), " "),
|
||||
Name: c.Name(),
|
||||
URL: c.Url(),
|
||||
Title: c.Title(),
|
||||
Subtitle: c.Subtitle(),
|
||||
Description: c.Description(),
|
||||
Author: c.Author(),
|
||||
Version: c.Version(),
|
||||
Copyright: c.Copyright(),
|
||||
Debug: c.Debug(),
|
||||
ReadOnly: c.ReadOnly(),
|
||||
Public: c.Public(),
|
||||
Experimental: c.Experimental(),
|
||||
Thumbnails: Thumbnails,
|
||||
Colors: colors.All.List(),
|
||||
JSHash: fs.Checksum(c.HttpStaticBuildPath() + "/app.js"),
|
||||
CSSHash: fs.Checksum(c.HttpStaticBuildPath() + "/app.css"),
|
||||
Clip: txt.ClipDefault,
|
||||
ThumbToken: "public",
|
||||
DownloadToken: "public",
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -115,27 +144,41 @@ func (c *Config) PublicClientConfig() ClientConfig {
|
||||
func (c *Config) ClientConfig() ClientConfig {
|
||||
defer log.Debug(capture.Time(time.Now(), "config: client config created"))
|
||||
|
||||
db := c.Db()
|
||||
|
||||
var cameras []entity.Camera
|
||||
var lenses []entity.Lens
|
||||
var albums []entity.Album
|
||||
var countries []entity.Country
|
||||
|
||||
var position struct {
|
||||
PhotoUID string `json:"uid"`
|
||||
LocUID string `json:"loc"`
|
||||
TakenAt time.Time `json:"utc"`
|
||||
PhotoLat float64 `json:"lat"`
|
||||
PhotoLng float64 `json:"lng"`
|
||||
result := ClientConfig{
|
||||
Settings: *c.Settings(),
|
||||
Flags: strings.Join(c.Flags(), " "),
|
||||
Name: c.Name(),
|
||||
URL: c.Url(),
|
||||
Title: c.Title(),
|
||||
Subtitle: c.Subtitle(),
|
||||
Description: c.Description(),
|
||||
Author: c.Author(),
|
||||
Version: c.Version(),
|
||||
Copyright: c.Copyright(),
|
||||
Debug: c.Debug(),
|
||||
ReadOnly: c.ReadOnly(),
|
||||
UploadNSFW: c.UploadNSFW(),
|
||||
DisableSettings: c.DisableSettings(),
|
||||
Public: c.Public(),
|
||||
Experimental: c.Experimental(),
|
||||
Colors: colors.All.List(),
|
||||
Thumbnails: Thumbnails,
|
||||
DownloadToken: c.DownloadToken(),
|
||||
ThumbToken: c.ThumbToken(),
|
||||
JSHash: fs.Checksum(c.HttpStaticBuildPath() + "/app.js"),
|
||||
CSSHash: fs.Checksum(c.HttpStaticBuildPath() + "/app.css"),
|
||||
Clip: txt.ClipDefault,
|
||||
Server: NewRuntimeInfo(),
|
||||
}
|
||||
|
||||
db := c.Db()
|
||||
|
||||
db.Table("photos").
|
||||
Select("photo_uid, loc_uid, 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).
|
||||
Take(&position)
|
||||
Take(&result.Pos)
|
||||
|
||||
var count = struct {
|
||||
Photos uint `json:"photos"`
|
||||
@@ -158,35 +201,35 @@ func (c *Config) ClientConfig() ClientConfig {
|
||||
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)
|
||||
Take(&result.Count)
|
||||
|
||||
db.Table("labels").
|
||||
Select("MAX(photo_count) as label_max_photos, COUNT(*) AS labels").
|
||||
Where("photo_count > 0").
|
||||
Where("deleted_at IS NULL").
|
||||
Where("(label_priority >= 0 || label_favorite = 1)").
|
||||
Take(&count)
|
||||
Take(&result.Count)
|
||||
|
||||
db.Table("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)
|
||||
Take(&result.Count)
|
||||
|
||||
db.Table("folders").
|
||||
Select("COUNT(*) AS folders").
|
||||
Where("folder_ignore = 0").
|
||||
Where("deleted_at IS NULL").
|
||||
Take(&count)
|
||||
Take(&result.Count)
|
||||
|
||||
db.Table("files").
|
||||
Select("COUNT(*) AS files").
|
||||
Where("file_missing = 0").
|
||||
Where("deleted_at IS NULL").
|
||||
Take(&count)
|
||||
Take(&result.Count)
|
||||
|
||||
db.Table("countries").
|
||||
Select("(COUNT(*) - 1) AS countries").
|
||||
Take(&count)
|
||||
Take(&result.Count)
|
||||
|
||||
db.Table("places").
|
||||
Select("SUM(photo_count > 0) AS places").
|
||||
@@ -194,34 +237,24 @@ func (c *Config) ClientConfig() ClientConfig {
|
||||
Take(&count)
|
||||
|
||||
db.Order("country_slug").
|
||||
Find(&countries)
|
||||
Find(&result.Countries)
|
||||
|
||||
db.Where("deleted_at IS NULL").
|
||||
Limit(10000).Order("camera_slug").
|
||||
Find(&cameras)
|
||||
Find(&result.Cameras)
|
||||
|
||||
db.Where("deleted_at IS NULL").
|
||||
Limit(10000).Order("lens_slug").
|
||||
Find(&lenses)
|
||||
Find(&result.Lenses)
|
||||
|
||||
db.Where("deleted_at IS NULL AND album_favorite = 1").
|
||||
Limit(20).Order("album_title").
|
||||
Find(&albums)
|
||||
|
||||
var years []int
|
||||
Find(&result.Albums)
|
||||
|
||||
db.Table("photos").
|
||||
Where("photo_year > 0").
|
||||
Order("photo_year DESC").
|
||||
Pluck("DISTINCT photo_year", &years)
|
||||
|
||||
type CategoryLabel struct {
|
||||
LabelUID string `json:"UID"`
|
||||
CustomSlug string `json:"Slug"`
|
||||
LabelName string `json:"Name"`
|
||||
}
|
||||
|
||||
var categories []CategoryLabel
|
||||
Pluck("DISTINCT photo_year", &result.Years)
|
||||
|
||||
db.Table("categories").
|
||||
Select("l.label_uid, l.custom_slug, l.label_name").
|
||||
@@ -230,44 +263,7 @@ func (c *Config) ClientConfig() ClientConfig {
|
||||
Group("l.custom_slug").
|
||||
Order("l.custom_slug").
|
||||
Limit(1000).Offset(0).
|
||||
Scan(&categories)
|
||||
|
||||
jsHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.js")
|
||||
cssHash := fs.Checksum(c.HttpStaticBuildPath() + "/app.css")
|
||||
configFlags := c.Flags()
|
||||
|
||||
result := ClientConfig{
|
||||
"flags": strings.Join(configFlags, " "),
|
||||
"name": c.Name(),
|
||||
"url": c.Url(),
|
||||
"title": c.Title(),
|
||||
"subtitle": c.Subtitle(),
|
||||
"description": c.Description(),
|
||||
"author": c.Author(),
|
||||
"version": c.Version(),
|
||||
"copyright": c.Copyright(),
|
||||
"debug": c.Debug(),
|
||||
"readonly": c.ReadOnly(),
|
||||
"uploadNSFW": c.UploadNSFW(),
|
||||
"public": c.Public(),
|
||||
"experimental": c.Experimental(),
|
||||
"disableSettings": c.DisableSettings(),
|
||||
"albums": albums,
|
||||
"cameras": cameras,
|
||||
"lenses": lenses,
|
||||
"countries": countries,
|
||||
"thumbnails": Thumbnails,
|
||||
"jsHash": jsHash,
|
||||
"cssHash": cssHash,
|
||||
"settings": c.Settings(),
|
||||
"count": count,
|
||||
"pos": position,
|
||||
"years": years,
|
||||
"colors": colors.All.List(),
|
||||
"categories": categories,
|
||||
"clip": txt.ClipDefault,
|
||||
"server": NewRuntimeInfo(),
|
||||
}
|
||||
Scan(&result.Categories)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@@ -27,6 +28,7 @@ type Config struct {
|
||||
cache *gc.Cache
|
||||
params *Params
|
||||
settings *Settings
|
||||
token string
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -60,6 +62,7 @@ func NewConfig(ctx *cli.Context) *Config {
|
||||
|
||||
c := &Config{
|
||||
params: NewParams(ctx),
|
||||
token: rnd.Token(8),
|
||||
}
|
||||
|
||||
c.initSettings()
|
||||
|
||||
@@ -355,17 +355,16 @@ func TestConfig_ClientConfig(t *testing.T) {
|
||||
c := TestConfig()
|
||||
|
||||
cc := c.ClientConfig()
|
||||
assert.NotEmpty(t, cc)
|
||||
assert.Contains(t, cc, "name")
|
||||
assert.Contains(t, cc, "version")
|
||||
assert.Contains(t, cc, "copyright")
|
||||
assert.Contains(t, cc, "debug")
|
||||
assert.Contains(t, cc, "readonly")
|
||||
assert.Contains(t, cc, "cameras")
|
||||
assert.Contains(t, cc, "countries")
|
||||
assert.Contains(t, cc, "thumbnails")
|
||||
assert.Contains(t, cc, "jsHash")
|
||||
assert.Contains(t, cc, "cssHash")
|
||||
|
||||
assert.IsType(t, ClientConfig{}, cc)
|
||||
assert.NotEmpty(t, cc.Name)
|
||||
assert.NotEmpty(t, cc.Version)
|
||||
assert.NotEmpty(t, cc.Copyright)
|
||||
assert.NotEmpty(t, cc.Thumbnails)
|
||||
assert.NotEmpty(t, cc.JSHash)
|
||||
assert.NotEmpty(t, cc.CSSHash)
|
||||
assert.Equal(t, true, cc.Debug)
|
||||
assert.Equal(t, false, cc.ReadOnly)
|
||||
}
|
||||
|
||||
func TestConfig_Workers(t *testing.T) {
|
||||
|
||||
@@ -259,6 +259,18 @@ var GlobalFlags = []cli.Flag{
|
||||
Value: "places",
|
||||
EnvVar: "PHOTOPRISM_GEOCODING_API",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "download-token",
|
||||
Usage: "url `TOKEN` for file downloads",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_DOWNLOAD_TOKEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "thumb-token",
|
||||
Usage: "url `TOKEN` for thumbnails and video streaming",
|
||||
Value: "static",
|
||||
EnvVar: "PHOTOPRISM_THUMB_TOKEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "thumb-filter, f",
|
||||
Usage: "resample filter (best to worst: blackman, lanczos, cubic, linear)",
|
||||
|
||||
@@ -78,12 +78,14 @@ type Params struct {
|
||||
DetectNSFW bool `yaml:"detect-nsfw" flag:"detect-nsfw"`
|
||||
UploadNSFW bool `yaml:"upload-nsfw" flag:"upload-nsfw"`
|
||||
GeoCodingApi string `yaml:"geocoding-api" flag:"geocoding-api"`
|
||||
JpegHidden bool `yaml:"jpeg-hidden" flag:"jpeg-hidden"`
|
||||
JpegQuality int `yaml:"jpeg-quality" flag:"jpeg-quality"`
|
||||
DownloadToken string `yaml:"download-token" flag:"download-token"`
|
||||
ThumbToken string `yaml:"thumb-token" flag:"thumb-token"`
|
||||
ThumbFilter string `yaml:"thumb-filter" flag:"thumb-filter"`
|
||||
ThumbUncached bool `yaml:"thumb-uncached" flag:"thumb-uncached"`
|
||||
ThumbSize int `yaml:"thumb-size" flag:"thumb-size"`
|
||||
ThumbLimit int `yaml:"thumb-limit" flag:"thumb-limit"`
|
||||
JpegHidden bool `yaml:"jpeg-hidden" flag:"jpeg-hidden"`
|
||||
JpegQuality int `yaml:"jpeg-quality" flag:"jpeg-quality"`
|
||||
DisableTensorFlow bool `yaml:"disable-tf" flag:"disable-tf"`
|
||||
DisableSettings bool `yaml:"disable-settings" flag:"disable-settings"`
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/capture"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -38,6 +39,9 @@ func NewTestParams() *Params {
|
||||
testDataPath := testDataPath(assetsPath)
|
||||
|
||||
c := &Params{
|
||||
Name: "PhotoPrism",
|
||||
Version: "0.0.0",
|
||||
Copyright: "(c) 2018-2020 PhotoPrism.org",
|
||||
Debug: true,
|
||||
Public: true,
|
||||
ReadOnly: false,
|
||||
@@ -96,7 +100,11 @@ func NewTestConfig() *Config {
|
||||
testConfigMutex.Lock()
|
||||
defer testConfigMutex.Unlock()
|
||||
|
||||
c := &Config{params: NewTestParams()}
|
||||
c := &Config{
|
||||
params: NewTestParams(),
|
||||
token: rnd.Token(8),
|
||||
}
|
||||
|
||||
c.initSettings()
|
||||
|
||||
if err := c.Init(context.Background()); err != nil {
|
||||
|
||||
@@ -43,4 +43,3 @@ func TestIsUID(t *testing.T) {
|
||||
assert.False(t, IsUID("_", '_'))
|
||||
assert.False(t, IsUID("", '_'))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user