Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
6e74f16a77
commit
4c516cac38
38 changed files with 302 additions and 240 deletions
100
frontend/package-lock.json
generated
100
frontend/package-lock.json
generated
|
@ -2472,9 +2472,9 @@
|
|||
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
|
||||
},
|
||||
"node_modules/@vvo/tzdb": {
|
||||
"version": "6.68.0",
|
||||
"resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.68.0.tgz",
|
||||
"integrity": "sha512-gTYX0c/zfvdeywLFZdHJzxczXjZf4oZHRnkTemziyn4p0R+qoqdrRK5PqY2DFnH64YkFcpreNS1JbbnfWiMQgQ=="
|
||||
"version": "6.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.69.0.tgz",
|
||||
"integrity": "sha512-L0BcVtrFG0a43mmiWo34yyQarsoAYbfD16q4yyO/A/r+fgcg3glAzFfqUxXGm7PLxt2SK9/f+jVtPZWNn2oSrg=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.11.1",
|
||||
|
@ -4661,9 +4661,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.268",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.268.tgz",
|
||||
"integrity": "sha512-PO90Bv++vEzdln+eA9qLg1IRnh0rKETus6QkTzcFm5P3Wg3EQBZud5dcnzkpYXuIKWBjKe5CO8zjz02cicvn1g=="
|
||||
"version": "1.4.270",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz",
|
||||
"integrity": "sha512-KNhIzgLiJmDDC444dj9vEOpZEgsV96ult9Iff98Vanumn+ShJHd5se8aX6KeVxdc0YQeqdrezBZv89rleDbvSg=="
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
|
@ -6410,19 +6410,19 @@
|
|||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ=="
|
||||
},
|
||||
"node_modules/flow-parser": {
|
||||
"version": "0.188.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.188.0.tgz",
|
||||
"integrity": "sha512-yu/Tc3UOhE0lXZk02MO/69N8RLdZsBtz3R6pBS+GaHiaopAxRcfUHPkqOZnI2BtotaylYTLrZGkIqFC8KqVxXQ==",
|
||||
"version": "0.188.1",
|
||||
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.188.1.tgz",
|
||||
"integrity": "sha512-6PA9S1Dphgj5j61As1VkCw8xE0sBXd/ql/WYRQRT8kgORqclp5Eh/blAKvye2p/oIrCpWYwENs1khKqTZvd2UA==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flow-remove-types": {
|
||||
"version": "2.188.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.188.0.tgz",
|
||||
"integrity": "sha512-bvNLQtjIFWJ4bkzZmLpIr4iZ83TsKv+S5Er9x/5OQMlNXvqGV0q/d/NIs/oZ7v9ZVnj0CObP2XgcfdduzKW4Dw==",
|
||||
"version": "2.188.1",
|
||||
"resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.188.1.tgz",
|
||||
"integrity": "sha512-54QoA21/OJ7j4tmdvWJf1ujPRo/6YvHSxvnjsBi+PATwEZDETlCrDxpXvxSnwzeR1PQ71zy8BNbbvFRDE1FRMw==",
|
||||
"dependencies": {
|
||||
"flow-parser": "^0.188.0",
|
||||
"flow-parser": "^0.188.1",
|
||||
"pirates": "^3.0.2",
|
||||
"vlq": "^0.2.1"
|
||||
},
|
||||
|
@ -6982,9 +6982,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hls.js": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.2.3.tgz",
|
||||
"integrity": "sha512-CC/vHi82ldiiydIhliNI3whlcepRXxI2jdpd/KKb6lyEv+74e7lXs4cGk5PHfTLxZMKOj6+m5LX9VAbvV/r7AQ=="
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.2.4.tgz",
|
||||
"integrity": "sha512-yC3K79Kzq1W+OgjT12JxKMDXv9DbfvulppxmPBl7D04SaTyd2IwWk5eNASQV1mUaPlKbjr16yI9292qpSGo0ig=="
|
||||
},
|
||||
"node_modules/hpkp": {
|
||||
"version": "2.0.0",
|
||||
|
@ -8231,15 +8231,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/log4js": {
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz",
|
||||
"integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==",
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz",
|
||||
"integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==",
|
||||
"dependencies": {
|
||||
"date-format": "^4.0.13",
|
||||
"date-format": "^4.0.14",
|
||||
"debug": "^4.3.4",
|
||||
"flatted": "^3.2.6",
|
||||
"flatted": "^3.2.7",
|
||||
"rfdc": "^1.3.0",
|
||||
"streamroller": "^3.1.2"
|
||||
"streamroller": "^3.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
|
@ -9287,9 +9287,9 @@
|
|||
"integrity": "sha512-sk96pUvpNwDV6PLrnhr68Uu1S5NohsxqLKz0GuracgrDo40BdF/r1RhHnjakUk6Q4Z0OKIybOQ7GevLKGN1iYw=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
|
||||
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
|
||||
"version": "8.4.17",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
|
||||
"integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -15054,9 +15054,9 @@
|
|||
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
|
||||
},
|
||||
"@vvo/tzdb": {
|
||||
"version": "6.68.0",
|
||||
"resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.68.0.tgz",
|
||||
"integrity": "sha512-gTYX0c/zfvdeywLFZdHJzxczXjZf4oZHRnkTemziyn4p0R+qoqdrRK5PqY2DFnH64YkFcpreNS1JbbnfWiMQgQ=="
|
||||
"version": "6.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.69.0.tgz",
|
||||
"integrity": "sha512-L0BcVtrFG0a43mmiWo34yyQarsoAYbfD16q4yyO/A/r+fgcg3glAzFfqUxXGm7PLxt2SK9/f+jVtPZWNn2oSrg=="
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.11.1",
|
||||
|
@ -16662,9 +16662,9 @@
|
|||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.268",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.268.tgz",
|
||||
"integrity": "sha512-PO90Bv++vEzdln+eA9qLg1IRnh0rKETus6QkTzcFm5P3Wg3EQBZud5dcnzkpYXuIKWBjKe5CO8zjz02cicvn1g=="
|
||||
"version": "1.4.270",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz",
|
||||
"integrity": "sha512-KNhIzgLiJmDDC444dj9vEOpZEgsV96ult9Iff98Vanumn+ShJHd5se8aX6KeVxdc0YQeqdrezBZv89rleDbvSg=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
|
@ -17931,16 +17931,16 @@
|
|||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ=="
|
||||
},
|
||||
"flow-parser": {
|
||||
"version": "0.188.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.188.0.tgz",
|
||||
"integrity": "sha512-yu/Tc3UOhE0lXZk02MO/69N8RLdZsBtz3R6pBS+GaHiaopAxRcfUHPkqOZnI2BtotaylYTLrZGkIqFC8KqVxXQ=="
|
||||
"version": "0.188.1",
|
||||
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.188.1.tgz",
|
||||
"integrity": "sha512-6PA9S1Dphgj5j61As1VkCw8xE0sBXd/ql/WYRQRT8kgORqclp5Eh/blAKvye2p/oIrCpWYwENs1khKqTZvd2UA=="
|
||||
},
|
||||
"flow-remove-types": {
|
||||
"version": "2.188.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.188.0.tgz",
|
||||
"integrity": "sha512-bvNLQtjIFWJ4bkzZmLpIr4iZ83TsKv+S5Er9x/5OQMlNXvqGV0q/d/NIs/oZ7v9ZVnj0CObP2XgcfdduzKW4Dw==",
|
||||
"version": "2.188.1",
|
||||
"resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.188.1.tgz",
|
||||
"integrity": "sha512-54QoA21/OJ7j4tmdvWJf1ujPRo/6YvHSxvnjsBi+PATwEZDETlCrDxpXvxSnwzeR1PQ71zy8BNbbvFRDE1FRMw==",
|
||||
"requires": {
|
||||
"flow-parser": "^0.188.0",
|
||||
"flow-parser": "^0.188.1",
|
||||
"pirates": "^3.0.2",
|
||||
"vlq": "^0.2.1"
|
||||
},
|
||||
|
@ -18329,9 +18329,9 @@
|
|||
"integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg=="
|
||||
},
|
||||
"hls.js": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.2.3.tgz",
|
||||
"integrity": "sha512-CC/vHi82ldiiydIhliNI3whlcepRXxI2jdpd/KKb6lyEv+74e7lXs4cGk5PHfTLxZMKOj6+m5LX9VAbvV/r7AQ=="
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.2.4.tgz",
|
||||
"integrity": "sha512-yC3K79Kzq1W+OgjT12JxKMDXv9DbfvulppxmPBl7D04SaTyd2IwWk5eNASQV1mUaPlKbjr16yI9292qpSGo0ig=="
|
||||
},
|
||||
"hpkp": {
|
||||
"version": "2.0.0",
|
||||
|
@ -19224,15 +19224,15 @@
|
|||
}
|
||||
},
|
||||
"log4js": {
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz",
|
||||
"integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==",
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz",
|
||||
"integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==",
|
||||
"requires": {
|
||||
"date-format": "^4.0.13",
|
||||
"date-format": "^4.0.14",
|
||||
"debug": "^4.3.4",
|
||||
"flatted": "^3.2.6",
|
||||
"flatted": "^3.2.7",
|
||||
"rfdc": "^1.3.0",
|
||||
"streamroller": "^3.1.2"
|
||||
"streamroller": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"loupe": {
|
||||
|
@ -20010,9 +20010,9 @@
|
|||
"integrity": "sha512-sk96pUvpNwDV6PLrnhr68Uu1S5NohsxqLKz0GuracgrDo40BdF/r1RhHnjakUk6Q4Z0OKIybOQ7GevLKGN1iYw=="
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
|
||||
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
|
||||
"version": "8.4.17",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
|
||||
"integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
|
||||
"requires": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
|
|
|
@ -82,10 +82,12 @@ export default [
|
|||
component: Login,
|
||||
meta: { title: siteTitle, auth: false, hideNav: true },
|
||||
beforeEnter: (to, from, next) => {
|
||||
if (session.isUser()) {
|
||||
next({ name: session.getHome() });
|
||||
} else {
|
||||
if (!session.isUser()) {
|
||||
next();
|
||||
} else if (config.deny("photos", "search")) {
|
||||
next({ name: "albums" });
|
||||
} else {
|
||||
next({ name: "browse" });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -105,6 +107,15 @@ export default [
|
|||
path: "/browse",
|
||||
component: Photos,
|
||||
meta: { title: siteTitle, icon: true, auth: true },
|
||||
beforeEnter: (to, from, next) => {
|
||||
if (!session.isUser()) {
|
||||
next({ name: "login" });
|
||||
} else if (config.deny("photos", "search")) {
|
||||
next({ name: "albums" });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all",
|
||||
|
|
|
@ -332,7 +332,7 @@
|
|||
</template>
|
||||
<v-list-tile v-else v-show="$config.feature('places')" to="/states" class="nav-states" @click.stop="">
|
||||
<v-list-tile-action :title="$gettext('States')">
|
||||
<v-icon>map</v-icon>
|
||||
<v-icon>near_me</v-icon>
|
||||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
|
|
|
@ -209,6 +209,7 @@ export default {
|
|||
{value: 'name', text: this.$gettext('Sort by file name')},
|
||||
{value: 'similar', text: this.$gettext('Group by similarity')},
|
||||
{value: 'relevance', text: this.$gettext('Most relevant')},
|
||||
{value: 'duration', text: this.$gettext('Duration')},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -184,7 +184,7 @@ export default {
|
|||
|
||||
const params = {
|
||||
share: true,
|
||||
count: 1000,
|
||||
count: 2000,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ export default {
|
|||
|
||||
const params = {
|
||||
q: q,
|
||||
count: 1000,
|
||||
count: 2000,
|
||||
offset: 0,
|
||||
type: "album"
|
||||
};
|
||||
|
|
|
@ -33,7 +33,7 @@ export default class Link extends Model {
|
|||
getDefaults() {
|
||||
return {
|
||||
UID: "",
|
||||
Share: "",
|
||||
ShareUID: "",
|
||||
Slug: "",
|
||||
Token: "",
|
||||
Expires: 0,
|
||||
|
@ -41,8 +41,8 @@ export default class Link extends Model {
|
|||
MaxViews: 0,
|
||||
Password: "",
|
||||
HasPassword: false,
|
||||
CanComment: false,
|
||||
CanEdit: false,
|
||||
Comment: "",
|
||||
Perm: 0,
|
||||
CreatedAt: "",
|
||||
ModifiedAt: "",
|
||||
};
|
||||
|
@ -74,7 +74,7 @@ export default class Link extends Model {
|
|||
return `${siteUrl}s/${token}/${this.Slug}`;
|
||||
}
|
||||
|
||||
return `${siteUrl}s/${token}/${this.Share}`;
|
||||
return `${siteUrl}s/${token}/${this.ShareUID}`;
|
||||
}
|
||||
|
||||
caption() {
|
||||
|
|
|
@ -172,14 +172,14 @@ export default {
|
|||
|
||||
const photo = this.results[index];
|
||||
|
||||
if (photo && photo.CellID && photo.CellID !== "zz") {
|
||||
if (this.canSearchPlaces) {
|
||||
this.$router.push({name: "places_query", params: {q: photo.CellID}});
|
||||
} else {
|
||||
this.$router.push({name: "places_scope", params: {s: this.uid, q: photo.CellID}});
|
||||
}
|
||||
} else {
|
||||
this.$router.push({name: "places_scope", params: {s: this.uid, q: ""}});
|
||||
if (!photo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.canSearchPlaces && photo.CellID && photo.CellID !== "zz") {
|
||||
this.$router.push({name: "places_query", params: {q: photo.CellID}});
|
||||
} else if (this.uid) {
|
||||
this.$router.push({name: "places_scope", params: {s: this.uid, q: photo.CellID}});
|
||||
}
|
||||
},
|
||||
editPhoto(index) {
|
||||
|
|
|
@ -234,12 +234,16 @@ export default {
|
|||
return "newest";
|
||||
},
|
||||
openLocation(index) {
|
||||
if (!this.canSearchPlaces) {
|
||||
if (!this.hasPlaces || !this.canSearchPlaces) {
|
||||
return;
|
||||
}
|
||||
|
||||
const photo = this.results[index];
|
||||
|
||||
if (!photo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (photo.CellID && photo.CellID !== "zz") {
|
||||
this.$router.push({name: "places_query", params: {q: photo.CellID}});
|
||||
} else if (photo.Country && photo.Country !== "zz") {
|
||||
|
|
|
@ -150,7 +150,7 @@ export default {
|
|||
return DateTime.fromISO(time).toLocaleString(DateTime.DATE_FULL);
|
||||
},
|
||||
load() {
|
||||
Account.search({count: 1000}).then(r => this.results = r.models);
|
||||
Account.search({count: 2000}).then(r => this.results = r.models);
|
||||
},
|
||||
remove(model) {
|
||||
this.model = model.clone();
|
||||
|
|
|
@ -257,6 +257,11 @@ func (c *Config) ClientPublic() ClientConfig {
|
|||
ReadOnly: c.ReadOnly(),
|
||||
Public: c.Public(),
|
||||
Experimental: c.Experimental(),
|
||||
Albums: entity.Albums{},
|
||||
Cameras: entity.Cameras{},
|
||||
Lenses: entity.Lenses{},
|
||||
Countries: entity.Countries{},
|
||||
People: entity.People{},
|
||||
Status: "",
|
||||
MapKey: "",
|
||||
Thumbs: Thumbs,
|
||||
|
@ -328,6 +333,11 @@ func (c *Config) ClientShare() ClientConfig {
|
|||
UploadNSFW: c.UploadNSFW(),
|
||||
Public: c.Public(),
|
||||
Experimental: c.Experimental(),
|
||||
Albums: entity.Albums{},
|
||||
Cameras: entity.Cameras{},
|
||||
Lenses: entity.Lenses{},
|
||||
Countries: entity.Countries{},
|
||||
People: entity.People{},
|
||||
Colors: colors.All.List(),
|
||||
Thumbs: Thumbs,
|
||||
Status: c.Hub().Status,
|
||||
|
@ -404,6 +414,11 @@ func (c *Config) ClientUser(withSettings bool) ClientConfig {
|
|||
UploadNSFW: c.UploadNSFW(),
|
||||
Public: c.Public(),
|
||||
Experimental: c.Experimental(),
|
||||
Albums: entity.Albums{},
|
||||
Cameras: entity.Cameras{},
|
||||
Lenses: entity.Lenses{},
|
||||
Countries: entity.Countries{},
|
||||
People: entity.People{},
|
||||
Colors: colors.All.List(),
|
||||
Thumbs: Thumbs,
|
||||
Status: c.Hub().Status,
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
|
|
@ -347,12 +347,19 @@ func FindAlbum(find Album) *Album {
|
|||
|
||||
// Create search condition.
|
||||
stmt := UnscopedDb().Where("album_type = ?", find.AlbumType)
|
||||
|
||||
// Search by slug and filter or title.
|
||||
if find.AlbumType != AlbumDefault && find.AlbumFilter != "" {
|
||||
stmt = stmt.Where("album_slug = ? OR album_filter = ?", find.AlbumSlug, find.AlbumFilter)
|
||||
} else {
|
||||
stmt = stmt.Where("album_slug = ? OR album_title LIKE ?", find.AlbumSlug, find.AlbumTitle)
|
||||
}
|
||||
|
||||
// Filter by creator if the album has not been published yet.
|
||||
if find.CreatedBy != "" {
|
||||
stmt = stmt.Where("published_at > ? OR created_by = ?", TimeStamp(), find.CreatedBy)
|
||||
}
|
||||
|
||||
// Find first matching record.
|
||||
if stmt.First(&m).RecordNotFound() {
|
||||
return nil
|
||||
|
|
|
@ -2,53 +2,49 @@ package entity
|
|||
|
||||
import "github.com/photoprism/photoprism/internal/event"
|
||||
|
||||
// AlbumAuth represents the ownership of an Album and the corresponding permissions.
|
||||
type AlbumAuth struct {
|
||||
// AlbumUser represents the user and group ownership of an Album and the corresponding permissions.
|
||||
type AlbumUser struct {
|
||||
UID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false" json:"UID" yaml:"UID"`
|
||||
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;index" json:"UserUID" yaml:"UserUID"`
|
||||
TeamUID string `gorm:"type:VARBINARY(42);index" json:"TeamUID" yaml:"TeamUID"`
|
||||
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;index" json:"UserUID,omitempty" yaml:"UserUID,omitempty"`
|
||||
TeamUID string `gorm:"type:VARBINARY(42);index" json:"TeamUID,omitempty" yaml:"TeamUID,omitempty"`
|
||||
Perm uint `json:"Perm,omitempty" yaml:"Perm,omitempty"`
|
||||
Changed int64 `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// TableName returns the database table name.
|
||||
func (AlbumAuth) TableName() string {
|
||||
return "albums_auth_dev"
|
||||
func (AlbumUser) TableName() string {
|
||||
return "albums_users"
|
||||
}
|
||||
|
||||
// NewAlbumAuth creates a new entity model.
|
||||
func NewAlbumAuth(uid, userUid, teamUid string, perm uint) *AlbumAuth {
|
||||
result := &AlbumAuth{
|
||||
// NewAlbumUser creates a new entity model.
|
||||
func NewAlbumUser(uid, userUid, teamUid string, perm uint) *AlbumUser {
|
||||
result := &AlbumUser{
|
||||
UID: uid,
|
||||
UserUID: userUid,
|
||||
TeamUID: teamUid,
|
||||
Perm: perm,
|
||||
Changed: TimeStamp().Unix(),
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Create inserts a new record into the database.
|
||||
func (m *AlbumAuth) Create() error {
|
||||
m.Changed = TimeStamp().Unix()
|
||||
func (m *AlbumUser) Create() error {
|
||||
return Db().Create(m).Error
|
||||
}
|
||||
|
||||
// Save updates the record in the database or inserts a new record if it does not already exist.
|
||||
func (m *AlbumAuth) Save() error {
|
||||
m.Changed = TimeStamp().Unix()
|
||||
func (m *AlbumUser) Save() error {
|
||||
return Db().Save(m).Error
|
||||
}
|
||||
|
||||
// FirstOrCreateAlbumUser returns the existing record or inserts a new record if it does not already exist.
|
||||
func FirstOrCreateAlbumUser(m *AlbumAuth) *AlbumAuth {
|
||||
found := AlbumAuth{}
|
||||
func FirstOrCreateAlbumUser(m *AlbumUser) *AlbumUser {
|
||||
found := AlbumUser{}
|
||||
|
||||
if err := Db().Where("uid = ?", m.UID).First(&found).Error; err == nil {
|
||||
return &found
|
||||
} else if err = m.Create(); err != nil {
|
||||
event.AuditErr([]string{"photo %s", "failed to set owner and permissions", "%s"}, m.UID, err)
|
||||
event.AuditErr([]string{"album %s", "failed to set owner and permissions", "%s"}, m.UID, err)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -29,9 +29,9 @@ type Sessions []Session
|
|||
type Session struct {
|
||||
ID string `gorm:"type:VARBINARY(2048);primary_key;auto_increment:false;" json:"ID" yaml:"ID"`
|
||||
Status int `json:"Status,omitempty" yaml:"-"`
|
||||
AuthDomain string `gorm:"type:VARBINARY(253);default:'';" json:"-" yaml:"AuthDomain,omitempty"`
|
||||
AuthMethod string `gorm:"type:VARBINARY(128);default:'';" json:"-" yaml:"AuthMethod,omitempty"`
|
||||
AuthProvider string `gorm:"type:VARBINARY(128);default:'';" json:"-" yaml:"AuthProvider,omitempty"`
|
||||
AuthDomain string `gorm:"type:VARBINARY(255);default:'';" json:"-" yaml:"AuthDomain,omitempty"`
|
||||
AuthScope string `gorm:"size:1024;default:'';" json:"-" yaml:"AuthScope,omitempty"`
|
||||
AuthID string `gorm:"type:VARBINARY(128);index;default:'';" json:"-" yaml:"AuthID,omitempty"`
|
||||
UserUID string `gorm:"type:VARBINARY(42);index;default:'';" json:"UserUID,omitempty" yaml:"UserUID,omitempty"`
|
||||
|
@ -46,18 +46,18 @@ type Session struct {
|
|||
ClientIP string `gorm:"size:64;column:client_ip;" json:"-" yaml:"ClientIP,omitempty"`
|
||||
LoginIP string `gorm:"size:64;column:login_ip" json:"-" yaml:"-"`
|
||||
LoginAt time.Time `json:"-" yaml:"-"`
|
||||
MaxAge time.Duration `json:"MaxAge,omitempty" yaml:"MaxAge,omitempty"`
|
||||
Timeout time.Duration `json:"Timeout,omitempty" yaml:"Timeout,omitempty"`
|
||||
DataJSON json.RawMessage `gorm:"type:VARBINARY(4096);" json:"Data,omitempty" yaml:"Data,omitempty"`
|
||||
data *SessionData `gorm:"-"`
|
||||
RefID string `gorm:"type:VARBINARY(16);default:'';" json:"-" yaml:"-"`
|
||||
CreatedAt time.Time `json:"CreatedAt" yaml:"CreatedAt"`
|
||||
MaxAge time.Duration `json:"MaxAge,omitempty" yaml:"MaxAge,omitempty"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"UpdatedAt"`
|
||||
Timeout time.Duration `json:"Timeout,omitempty" yaml:"Timeout,omitempty"`
|
||||
}
|
||||
|
||||
// TableName returns the entity table name.
|
||||
func (Session) TableName() string {
|
||||
return "auth_sessions_dev"
|
||||
return "auth_sessions"
|
||||
}
|
||||
|
||||
// NewSession creates a new session and returns it.
|
||||
|
|
|
@ -76,7 +76,7 @@ type Share struct {
|
|||
|
||||
// TableName returns the entity table name.
|
||||
func (Share) TableName() string {
|
||||
return "auth_shares_dev"
|
||||
return "auth_users_shares"
|
||||
}
|
||||
|
||||
// NewShare creates a new entity model.
|
||||
|
|
|
@ -76,7 +76,7 @@ type User struct {
|
|||
|
||||
// TableName returns the entity table name.
|
||||
func (User) TableName() string {
|
||||
return "auth_users_dev"
|
||||
return "auth_users"
|
||||
}
|
||||
|
||||
// NewUser creates a new user and returns it.
|
||||
|
|
|
@ -50,7 +50,7 @@ type UserDetails struct {
|
|||
|
||||
// TableName returns the entity table name.
|
||||
func (UserDetails) TableName() string {
|
||||
return "auth_users_details_dev"
|
||||
return "auth_users_details"
|
||||
}
|
||||
|
||||
// NewUserDetails creates new user details.
|
||||
|
|
|
@ -28,7 +28,7 @@ type UserSettings struct {
|
|||
|
||||
// TableName returns the entity table name.
|
||||
func (UserSettings) TableName() string {
|
||||
return "auth_users_settings_dev"
|
||||
return "auth_users_settings"
|
||||
}
|
||||
|
||||
// NewUserSettings creates new user preferences.
|
||||
|
|
|
@ -46,4 +46,14 @@ var DeprecatedTables = Deprecated{
|
|||
"faces_dev8",
|
||||
"faces_dev9",
|
||||
"faces_dev10",
|
||||
"reactions_dev",
|
||||
"albums_users_dev",
|
||||
"photos_users_dev",
|
||||
"auth_sessions_dev",
|
||||
"auth_shares_dev",
|
||||
"auth_users_details_dev",
|
||||
"auth_users_dev",
|
||||
"auth_users_logins_dev",
|
||||
"auth_users_settings_dev",
|
||||
"auth_tokens_dev",
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ const (
|
|||
const (
|
||||
SortOrderDefault = ""
|
||||
SortOrderRelevance = "relevance"
|
||||
SortOrderDuration = "duration"
|
||||
SortOrderCount = "count"
|
||||
SortOrderAdded = "added"
|
||||
SortOrderImported = "imported"
|
||||
|
|
|
@ -28,7 +28,7 @@ var Entities = Tables{
|
|||
FileShare{}.TableName(): &FileShare{},
|
||||
FileSync{}.TableName(): &FileSync{},
|
||||
Photo{}.TableName(): &Photo{},
|
||||
PhotoAuth{}.TableName(): &PhotoAuth{},
|
||||
PhotoUser{}.TableName(): &PhotoUser{},
|
||||
Details{}.TableName(): &Details{},
|
||||
Place{}.TableName(): &Place{},
|
||||
Cell{}.TableName(): &Cell{},
|
||||
|
@ -36,7 +36,7 @@ var Entities = Tables{
|
|||
Lens{}.TableName(): &Lens{},
|
||||
Country{}.TableName(): &Country{},
|
||||
Album{}.TableName(): &Album{},
|
||||
AlbumAuth{}.TableName(): &AlbumAuth{},
|
||||
AlbumUser{}.TableName(): &AlbumUser{},
|
||||
PhotoAlbum{}.TableName(): &PhotoAlbum{},
|
||||
Label{}.TableName(): &Label{},
|
||||
Category{}.TableName(): &Category{},
|
||||
|
|
|
@ -50,67 +50,68 @@ func MapKey(takenAt time.Time, cellId string) string {
|
|||
|
||||
// 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:"-"`
|
||||
UUID string `gorm:"type:VARBINARY(64);index;" json:"DocumentID,omitempty" yaml:"DocumentID,omitempty"`
|
||||
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"`
|
||||
PhotoUID string `gorm:"type:VARBINARY(42);unique_index;index:idx_photos_taken_uid;" json:"UID" yaml:"UID"`
|
||||
PhotoType string `gorm:"type:VARBINARY(8);default:'image';" json:"Type" yaml:"Type"`
|
||||
TypeSrc string `gorm:"type:VARBINARY(8);" json:"TypeSrc" yaml:"TypeSrc,omitempty"`
|
||||
PhotoTitle string `gorm:"type:VARCHAR(200);" json:"Title" yaml:"Title"`
|
||||
TitleSrc string `gorm:"type:VARBINARY(8);" json:"TitleSrc" yaml:"TitleSrc,omitempty"`
|
||||
PhotoDescription string `gorm:"type:VARCHAR(4096);" json:"Description" yaml:"Description,omitempty"`
|
||||
DescriptionSrc string `gorm:"type:VARBINARY(8);" json:"DescriptionSrc" yaml:"DescriptionSrc,omitempty"`
|
||||
PhotoPath string `gorm:"type:VARBINARY(1024);index:idx_photos_path_name;" json:"Path" yaml:"-"`
|
||||
PhotoName string `gorm:"type:VARBINARY(255);index:idx_photos_path_name;" json:"Name" yaml:"-"`
|
||||
OriginalName string `gorm:"type:VARBINARY(755);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
||||
PhotoStack int8 `json:"Stack" yaml:"Stack,omitempty"`
|
||||
PhotoFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
||||
PhotoPrivate bool `json:"Private" yaml:"Private,omitempty"`
|
||||
PhotoScan bool `json:"Scan" yaml:"Scan,omitempty"`
|
||||
PhotoPanorama bool `json:"Panorama" yaml:"Panorama,omitempty"`
|
||||
TimeZone string `gorm:"type:VARBINARY(64);" json:"TimeZone" yaml:"TimeZone,omitempty"`
|
||||
PlaceID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"PlaceID" yaml:"-"`
|
||||
PlaceSrc string `gorm:"type:VARBINARY(8);" json:"PlaceSrc" yaml:"PlaceSrc,omitempty"`
|
||||
CellID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"CellID" yaml:"-"`
|
||||
CellAccuracy int `json:"CellAccuracy" yaml:"CellAccuracy,omitempty"`
|
||||
PhotoAltitude int `json:"Altitude" yaml:"Altitude,omitempty"`
|
||||
PhotoLat float32 `gorm:"type:FLOAT;index;" json:"Lat" yaml:"Lat,omitempty"`
|
||||
PhotoLng float32 `gorm:"type:FLOAT;index;" json:"Lng" yaml:"Lng,omitempty"`
|
||||
PhotoCountry string `gorm:"type:VARBINARY(2);index:idx_photos_country_year_month;default:'zz'" json:"Country" yaml:"-"`
|
||||
PhotoYear int `gorm:"index:idx_photos_ymd;index:idx_photos_country_year_month;" json:"Year" yaml:"Year"`
|
||||
PhotoMonth int `gorm:"index:idx_photos_ymd;index:idx_photos_country_year_month;" json:"Month" yaml:"Month"`
|
||||
PhotoDay int `gorm:"index:idx_photos_ymd" json:"Day" yaml:"Day"`
|
||||
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:"Quality,omitempty"`
|
||||
PhotoFaces int `json:"Faces,omitempty" yaml:"Faces,omitempty"`
|
||||
PhotoResolution int `gorm:"type:SMALLINT" json:"Resolution" yaml:"-"`
|
||||
PhotoColor int16 `json:"Color" yaml:"-"`
|
||||
CameraID uint `gorm:"index:idx_photos_camera_lens;default:1" json:"CameraID" yaml:"-"`
|
||||
CameraSerial string `gorm:"type:VARBINARY(160);" json:"CameraSerial" yaml:"CameraSerial,omitempty"`
|
||||
CameraSrc string `gorm:"type:VARBINARY(8);" json:"CameraSrc" yaml:"-"`
|
||||
LensID uint `gorm:"index:idx_photos_camera_lens;default:1" json:"LensID" yaml:"-"`
|
||||
Details *Details `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Details" yaml:"Details"`
|
||||
Camera *Camera `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Camera" yaml:"-"`
|
||||
Lens *Lens `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Lens" yaml:"-"`
|
||||
Cell *Cell `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Cell" yaml:"-"`
|
||||
Place *Place `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Place" yaml:"-"`
|
||||
Keywords []Keyword `json:"-" yaml:"-"`
|
||||
Albums []Album `json:"-" yaml:"-"`
|
||||
Files []File `yaml:"-"`
|
||||
Labels []PhotoLabel `yaml:"-"`
|
||||
CreatedBy string `gorm:"type:VARBINARY(42);index" json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"`
|
||||
CreatedAt time.Time `yaml:"CreatedAt,omitempty"`
|
||||
UpdatedAt time.Time `yaml:"UpdatedAt,omitempty"`
|
||||
EditedAt *time.Time `yaml:"EditedAt,omitempty"`
|
||||
PublishedAt *time.Time `sql:"index" json:"PublishedAt,omitempty" yaml:"PublishedAt,omitempty"`
|
||||
CheckedAt *time.Time `sql:"index" yaml:"-"`
|
||||
EstimatedAt *time.Time `json:"EstimatedAt,omitempty" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" yaml:"DeletedAt,omitempty"`
|
||||
ID uint `gorm:"primary_key" yaml:"-"`
|
||||
UUID string `gorm:"type:VARBINARY(64);index;" json:"DocumentID,omitempty" yaml:"DocumentID,omitempty"`
|
||||
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"`
|
||||
PhotoUID string `gorm:"type:VARBINARY(42);unique_index;index:idx_photos_taken_uid;" json:"UID" yaml:"UID"`
|
||||
PhotoType string `gorm:"type:VARBINARY(8);default:'image';" json:"Type" yaml:"Type"`
|
||||
TypeSrc string `gorm:"type:VARBINARY(8);" json:"TypeSrc" yaml:"TypeSrc,omitempty"`
|
||||
PhotoTitle string `gorm:"type:VARCHAR(200);" json:"Title" yaml:"Title"`
|
||||
TitleSrc string `gorm:"type:VARBINARY(8);" json:"TitleSrc" yaml:"TitleSrc,omitempty"`
|
||||
PhotoDescription string `gorm:"type:VARCHAR(4096);" json:"Description" yaml:"Description,omitempty"`
|
||||
DescriptionSrc string `gorm:"type:VARBINARY(8);" json:"DescriptionSrc" yaml:"DescriptionSrc,omitempty"`
|
||||
PhotoPath string `gorm:"type:VARBINARY(1024);index:idx_photos_path_name;" json:"Path" yaml:"-"`
|
||||
PhotoName string `gorm:"type:VARBINARY(255);index:idx_photos_path_name;" json:"Name" yaml:"-"`
|
||||
OriginalName string `gorm:"type:VARBINARY(755);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
||||
PhotoStack int8 `json:"Stack" yaml:"Stack,omitempty"`
|
||||
PhotoFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
||||
PhotoPrivate bool `json:"Private" yaml:"Private,omitempty"`
|
||||
PhotoScan bool `json:"Scan" yaml:"Scan,omitempty"`
|
||||
PhotoPanorama bool `json:"Panorama" yaml:"Panorama,omitempty"`
|
||||
TimeZone string `gorm:"type:VARBINARY(64);" json:"TimeZone" yaml:"TimeZone,omitempty"`
|
||||
PlaceID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"PlaceID" yaml:"-"`
|
||||
PlaceSrc string `gorm:"type:VARBINARY(8);" json:"PlaceSrc" yaml:"PlaceSrc,omitempty"`
|
||||
CellID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"CellID" yaml:"-"`
|
||||
CellAccuracy int `json:"CellAccuracy" yaml:"CellAccuracy,omitempty"`
|
||||
PhotoAltitude int `json:"Altitude" yaml:"Altitude,omitempty"`
|
||||
PhotoLat float32 `gorm:"type:FLOAT;index;" json:"Lat" yaml:"Lat,omitempty"`
|
||||
PhotoLng float32 `gorm:"type:FLOAT;index;" json:"Lng" yaml:"Lng,omitempty"`
|
||||
PhotoCountry string `gorm:"type:VARBINARY(2);index:idx_photos_country_year_month;default:'zz'" json:"Country" yaml:"-"`
|
||||
PhotoYear int `gorm:"index:idx_photos_ymd;index:idx_photos_country_year_month;" json:"Year" yaml:"Year"`
|
||||
PhotoMonth int `gorm:"index:idx_photos_ymd;index:idx_photos_country_year_month;" json:"Month" yaml:"Month"`
|
||||
PhotoDay int `gorm:"index:idx_photos_ymd" json:"Day" yaml:"Day"`
|
||||
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:"Quality,omitempty"`
|
||||
PhotoFaces int `json:"Faces,omitempty" yaml:"Faces,omitempty"`
|
||||
PhotoResolution int `gorm:"type:SMALLINT" json:"Resolution" yaml:"-"`
|
||||
PhotoDuration time.Duration `json:"Duration,omitempty" yaml:"Duration,omitempty"`
|
||||
PhotoColor int16 `json:"Color" yaml:"-"`
|
||||
CameraID uint `gorm:"index:idx_photos_camera_lens;default:1" json:"CameraID" yaml:"-"`
|
||||
CameraSerial string `gorm:"type:VARBINARY(160);" json:"CameraSerial" yaml:"CameraSerial,omitempty"`
|
||||
CameraSrc string `gorm:"type:VARBINARY(8);" json:"CameraSrc" yaml:"-"`
|
||||
LensID uint `gorm:"index:idx_photos_camera_lens;default:1" json:"LensID" yaml:"-"`
|
||||
Details *Details `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Details" yaml:"Details"`
|
||||
Camera *Camera `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Camera" yaml:"-"`
|
||||
Lens *Lens `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Lens" yaml:"-"`
|
||||
Cell *Cell `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Cell" yaml:"-"`
|
||||
Place *Place `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Place" yaml:"-"`
|
||||
Keywords []Keyword `json:"-" yaml:"-"`
|
||||
Albums []Album `json:"-" yaml:"-"`
|
||||
Files []File `yaml:"-"`
|
||||
Labels []PhotoLabel `yaml:"-"`
|
||||
CreatedBy string `gorm:"type:VARBINARY(42);index" json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"`
|
||||
CreatedAt time.Time `yaml:"CreatedAt,omitempty"`
|
||||
UpdatedAt time.Time `yaml:"UpdatedAt,omitempty"`
|
||||
EditedAt *time.Time `yaml:"EditedAt,omitempty"`
|
||||
PublishedAt *time.Time `sql:"index" json:"PublishedAt,omitempty" yaml:"PublishedAt,omitempty"`
|
||||
CheckedAt *time.Time `sql:"index" yaml:"-"`
|
||||
EstimatedAt *time.Time `json:"EstimatedAt,omitempty" yaml:"-"`
|
||||
DeletedAt *time.Time `sql:"index" yaml:"DeletedAt,omitempty"`
|
||||
}
|
||||
|
||||
// TableName returns the entity table name.
|
||||
|
|
|
@ -222,6 +222,7 @@ var PhotoFixtures = PhotoMap{
|
|||
TakenAtLocal: time.Date(1990, 4, 18, 1, 0, 0, 0, time.UTC),
|
||||
TakenSrc: "meta",
|
||||
PhotoType: "video",
|
||||
PhotoDuration: time.Hour * 2,
|
||||
TypeSrc: "",
|
||||
PhotoTitle: "",
|
||||
TitleSrc: "",
|
||||
|
@ -644,6 +645,7 @@ var PhotoFixtures = PhotoMap{
|
|||
TakenAtLocal: time.Date(2016, 11, 11, 11, 7, 18, 0, time.UTC),
|
||||
TakenSrc: "manual",
|
||||
PhotoType: "video",
|
||||
PhotoDuration: time.Minute * 2,
|
||||
TypeSrc: "",
|
||||
PhotoTitle: "Title",
|
||||
TitleSrc: "",
|
||||
|
|
|
@ -2,48 +2,44 @@ package entity
|
|||
|
||||
import "github.com/photoprism/photoprism/internal/event"
|
||||
|
||||
// PhotoAuth represents the ownership of a Photo and the corresponding permissions.
|
||||
type PhotoAuth struct {
|
||||
// PhotoUser represents the user and group ownership of a Photo and the corresponding permissions.
|
||||
type PhotoUser struct {
|
||||
UID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false" json:"UID" yaml:"UID"`
|
||||
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;index" json:"UserUID" yaml:"UserUID"`
|
||||
TeamUID string `gorm:"type:VARBINARY(42);index" json:"TeamUID" yaml:"TeamUID"`
|
||||
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;index" json:"UserUID,omitempty" yaml:"UserUID,omitempty"`
|
||||
TeamUID string `gorm:"type:VARBINARY(42);index" json:"TeamUID,omitempty" yaml:"TeamUID,omitempty"`
|
||||
Perm uint `json:"Perm,omitempty" yaml:"Perm,omitempty"`
|
||||
Changed int64 `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// TableName returns the database table name.
|
||||
func (PhotoAuth) TableName() string {
|
||||
return "photos_auth_dev"
|
||||
func (PhotoUser) TableName() string {
|
||||
return "photos_users"
|
||||
}
|
||||
|
||||
// NewPhotoAuth creates a new entity model.
|
||||
func NewPhotoAuth(uid, userUid, teamUid string, perm uint) *PhotoAuth {
|
||||
result := &PhotoAuth{
|
||||
// NewPhotoUser creates a new entity model.
|
||||
func NewPhotoUser(uid, userUid, teamUid string, perm uint) *PhotoUser {
|
||||
result := &PhotoUser{
|
||||
UID: uid,
|
||||
UserUID: userUid,
|
||||
TeamUID: teamUid,
|
||||
Perm: perm,
|
||||
Changed: TimeStamp().Unix(),
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Create inserts a new record into the database.
|
||||
func (m *PhotoAuth) Create() error {
|
||||
m.Changed = TimeStamp().Unix()
|
||||
func (m *PhotoUser) Create() error {
|
||||
return Db().Create(m).Error
|
||||
}
|
||||
|
||||
// Save updates the record in the database or inserts a new record if it does not already exist.
|
||||
func (m *PhotoAuth) Save() error {
|
||||
m.Changed = TimeStamp().Unix()
|
||||
func (m *PhotoUser) Save() error {
|
||||
return Db().Save(m).Error
|
||||
}
|
||||
|
||||
// FirstOrCreatePhotoUser returns the existing record or inserts a new record if it does not already exist.
|
||||
func FirstOrCreatePhotoUser(m *PhotoAuth) *PhotoAuth {
|
||||
found := PhotoAuth{}
|
||||
func FirstOrCreatePhotoUser(m *PhotoUser) *PhotoUser {
|
||||
found := PhotoUser{}
|
||||
|
||||
if err := Db().Where("uid = ?", m.UID).First(&found).Error; err == nil {
|
||||
return &found
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
// Reaction represents a human response to content such as photos and albums.
|
||||
type Reaction struct {
|
||||
EntityUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false" json:"EntityUID,omitempty" yaml:"EntityUID,omitempty"`
|
||||
UID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false" json:"UID,omitempty" yaml:"UID,omitempty"`
|
||||
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false" json:"UserUID,omitempty" yaml:"UserUID,omitempty"`
|
||||
Reaction string `gorm:"type:VARBINARY(64);primary_key;auto_increment:false" json:"Reaction,omitempty" yaml:"Reaction,omitempty"`
|
||||
Reacted int `json:"Reacted,omitempty" yaml:"Reacted,omitempty"`
|
||||
|
@ -20,26 +20,26 @@ type Reaction struct {
|
|||
|
||||
// TableName returns the entity table name.
|
||||
func (Reaction) TableName() string {
|
||||
return "reactions_dev"
|
||||
return "reactions"
|
||||
}
|
||||
|
||||
// NewReaction creates a new Reaction struct.
|
||||
func NewReaction(entityUid, userUid string) *Reaction {
|
||||
func NewReaction(uid, userUid string) *Reaction {
|
||||
return &Reaction{
|
||||
EntityUID: entityUid,
|
||||
UserUID: userUid,
|
||||
UID: uid,
|
||||
UserUID: userUid,
|
||||
}
|
||||
}
|
||||
|
||||
// FindReaction returns the matching Reaction record or nil if it was not found.
|
||||
func FindReaction(entityUid, userUid string) (m *Reaction) {
|
||||
if entityUid == "" || userUid == "" {
|
||||
func FindReaction(uid, userUid string) (m *Reaction) {
|
||||
if uid == "" || userUid == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
m = &Reaction{}
|
||||
|
||||
if Db().First(m, "entity_uid = ? AND user_uid = ?", entityUid, userUid).RecordNotFound() {
|
||||
if Db().First(m, "uid = ? AND user_uid = ?", uid, userUid).RecordNotFound() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ func (m *Reaction) String() string {
|
|||
|
||||
// InvalidUID checks if the entity or user uid are missing or incorrect.
|
||||
func (m *Reaction) InvalidUID() bool {
|
||||
return m.EntityUID == "" || m.UserUID == ""
|
||||
return m.UID == "" || m.UserUID == ""
|
||||
}
|
||||
|
||||
// Unknown checks if the reaction data is missing or incorrect.
|
||||
|
@ -92,7 +92,7 @@ func (m *Reaction) Save() (err error) {
|
|||
values := Values{"reaction": m.Reaction, "reacted": gorm.Expr("reacted + 1"), "reacted_at": reactedAt}
|
||||
|
||||
if err = Db().Model(Reaction{}).
|
||||
Where("entity_uid = ? AND user_uid = ?", m.EntityUID, m.UserUID).
|
||||
Where("uid = ? AND user_uid = ?", m.UID, m.UserUID).
|
||||
UpdateColumns(values).Error; err == nil {
|
||||
m.Reacted += 1
|
||||
m.ReactedAt = reactedAt
|
||||
|
@ -107,7 +107,7 @@ func (m *Reaction) Create() (err error) {
|
|||
return fmt.Errorf("reaction invalid")
|
||||
}
|
||||
|
||||
r := &Reaction{EntityUID: m.EntityUID, UserUID: m.UserUID, Reaction: m.Reaction, Reacted: m.Reacted, ReactedAt: TimePointer()}
|
||||
r := &Reaction{UID: m.UID, UserUID: m.UserUID, Reaction: m.Reaction, Reacted: m.Reacted, ReactedAt: TimePointer()}
|
||||
|
||||
if err = Db().Create(r).Error; err == nil {
|
||||
m.ReactedAt = r.ReactedAt
|
||||
|
@ -123,7 +123,7 @@ func (m *Reaction) Delete() error {
|
|||
}
|
||||
|
||||
// Delete record.
|
||||
err := Db().Delete(m, "entity_uid = ? AND user_uid = ?", m.EntityUID, m.UserUID).Error
|
||||
err := Db().Delete(m, "uid = ? AND user_uid = ?", m.UID, m.UserUID).Error
|
||||
|
||||
// Ok?
|
||||
if err == nil {
|
||||
|
|
|
@ -22,19 +22,19 @@ func (m ReactionMap) Pointer(name string) *Reaction {
|
|||
|
||||
var ReactionFixtures = ReactionMap{
|
||||
"SubjectJohnLike": Reaction{
|
||||
EntityUID: SubjectFixtures.Get("john-doe").SubjUID,
|
||||
UserUID: UserFixtures.Get("alice").UserUID,
|
||||
Reaction: react.Like.String(),
|
||||
UID: SubjectFixtures.Get("john-doe").SubjUID,
|
||||
UserUID: UserFixtures.Get("alice").UserUID,
|
||||
Reaction: react.Like.String(),
|
||||
},
|
||||
"PhotoAliceLove": Reaction{
|
||||
EntityUID: PhotoFixtures.Get("Photo01").PhotoUID,
|
||||
UserUID: UserFixtures.Pointer("alice").UserUID,
|
||||
Reaction: react.Love.String(),
|
||||
UID: PhotoFixtures.Get("Photo01").PhotoUID,
|
||||
UserUID: UserFixtures.Pointer("alice").UserUID,
|
||||
Reaction: react.Love.String(),
|
||||
},
|
||||
"PhotoBobLove": Reaction{
|
||||
EntityUID: PhotoFixtures.Get("Photo01").PhotoUID,
|
||||
UserUID: UserFixtures.Pointer("bob").UserUID,
|
||||
Reaction: react.Love.String(),
|
||||
UID: PhotoFixtures.Get("Photo01").PhotoUID,
|
||||
UserUID: UserFixtures.Pointer("bob").UserUID,
|
||||
Reaction: react.Love.String(),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ func TestNewReaction(t *testing.T) {
|
|||
t.Run("Rainbow", func(t *testing.T) {
|
||||
m := NewReaction(FileFixtures.Pointer("bridge.jpg").FileUID, UserFixtures.Pointer("bob").UserUID).React("🌈")
|
||||
|
||||
assert.Equal(t, FileFixtures.Pointer("bridge.jpg").FileUID, m.EntityUID)
|
||||
assert.Equal(t, FileFixtures.Pointer("bridge.jpg").FileUID, m.UID)
|
||||
assert.Equal(t, UserFixtures.Pointer("bob").UserUID, m.UserUID)
|
||||
assert.Equal(t, "🌈", m.Reaction)
|
||||
assert.Equal(t, react.Rainbow, m.Emoji())
|
||||
|
|
|
@ -106,7 +106,7 @@ var DialectMySQL = Migrations{
|
|||
{
|
||||
ID: "20220927-000200",
|
||||
Dialect: "mysql",
|
||||
Statements: []string{"REPLACE INTO auth_users_dev (id, user_uid, super_admin, can_login, can_sync, user_role, display_name, user_name, user_email, login_at, created_at, updated_at) SELECT id, user_uid, role_admin, 1, 1, 'admin', full_name, user_name, primary_email, login_at, created_at, updated_at FROM users WHERE role_admin = 1 AND user_name NOT IN (SELECT user_name FROM auth_users) AND user_name <> '' AND user_name IS NOT NULL;"},
|
||||
Statements: []string{"REPLACE INTO auth_users (id, user_uid, super_admin, can_login, can_sync, user_role, display_name, user_name, user_email, login_at, created_at, updated_at) SELECT id, user_uid, role_admin, 1, 1, 'admin', full_name, user_name, primary_email, login_at, created_at, updated_at FROM users WHERE role_admin = 1 AND user_name NOT IN (SELECT user_name FROM auth_users) AND user_name <> '' AND user_name IS NOT NULL;"},
|
||||
},
|
||||
{
|
||||
ID: "20221002-000100",
|
||||
|
|
|
@ -61,6 +61,6 @@ var DialectSQLite3 = Migrations{
|
|||
{
|
||||
ID: "20220927-000200",
|
||||
Dialect: "sqlite3",
|
||||
Statements: []string{"REPLACE INTO auth_users_dev (id, user_uid, super_admin, can_login, can_sync, user_role, display_name, user_name, user_email, login_at, created_at, updated_at) SELECT id, user_uid, 1, 1, 1 'admin', full_name, user_name, primary_email, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND user_uid <> '' AND user_uid IS NOT NULL AND role_admin = 1 AND user_disabled = 0;"},
|
||||
Statements: []string{"REPLACE INTO auth_users (id, user_uid, super_admin, can_login, can_sync, user_role, display_name, user_name, user_email, login_at, created_at, updated_at) SELECT id, user_uid, 1, 1, 1 'admin', full_name, user_name, primary_email, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND user_uid <> '' AND user_uid IS NOT NULL AND role_admin = 1 AND user_disabled = 0;"},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
REPLACE INTO auth_users_dev (id, user_uid, super_admin, can_login, can_sync, user_role, display_name, user_name, user_email, login_at, created_at, updated_at) SELECT id, user_uid, role_admin, 1, 1, 'admin', full_name, user_name, primary_email, login_at, created_at, updated_at FROM users WHERE role_admin = 1 AND user_name NOT IN (SELECT user_name FROM auth_users) AND user_name <> '' AND user_name IS NOT NULL;
|
||||
REPLACE INTO auth_users (id, user_uid, super_admin, can_login, can_sync, user_role, display_name, user_name, user_email, login_at, created_at, updated_at) SELECT id, user_uid, role_admin, 1, 1, 'admin', full_name, user_name, primary_email, login_at, created_at, updated_at FROM users WHERE role_admin = 1 AND user_name NOT IN (SELECT user_name FROM auth_users) AND user_name <> '' AND user_name IS NOT NULL;
|
||||
|
|
|
@ -1 +1 @@
|
|||
REPLACE INTO auth_users_dev (id, user_uid, super_admin, can_login, can_sync, user_role, display_name, user_name, user_email, login_at, created_at, updated_at) SELECT id, user_uid, 1, 1, 1 'admin', full_name, user_name, primary_email, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND user_uid <> '' AND user_uid IS NOT NULL AND role_admin = 1 AND user_disabled = 0;
|
||||
REPLACE INTO auth_users (id, user_uid, super_admin, can_login, can_sync, user_role, display_name, user_name, user_email, login_at, created_at, updated_at) SELECT id, user_uid, 1, 1, 1 'admin', full_name, user_name, primary_email, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND user_uid <> '' AND user_uid IS NOT NULL AND role_admin = 1 AND user_disabled = 0;
|
|
@ -555,6 +555,10 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
|
|||
photo.PhotoResolution = res
|
||||
}
|
||||
|
||||
if file.FileDuration > photo.PhotoDuration {
|
||||
photo.PhotoDuration = file.FileDuration
|
||||
}
|
||||
|
||||
photo.SetCamera(entity.FirstOrCreateCamera(entity.NewCamera(m.CameraModel(), m.CameraMake())), entity.SrcMeta)
|
||||
photo.SetLens(entity.FirstOrCreateLens(entity.NewLens(m.LensModel(), m.LensMake())), entity.SrcMeta)
|
||||
photo.SetExposure(m.FocalLength(), m.FNumber(), m.Iso(), m.Exposure(), entity.SrcMeta)
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
@ -55,26 +54,20 @@ func UserAlbums(f form.SearchAlbums, sess *entity.Session) (results AlbumResults
|
|||
aclResource = acl.ResourcePlaces
|
||||
}
|
||||
|
||||
// Check user rights.
|
||||
// Check user permissions.
|
||||
if acl.Resources.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary, acl.AccessShared, acl.AccessOwn}) {
|
||||
return AlbumResults{}, ErrForbidden
|
||||
}
|
||||
|
||||
// Visitors and other restricted users can only access shared content.
|
||||
if sess.IsVisitor() && sess.NoShares() {
|
||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.AccessShared.String(), string(aclResource), aclRole)
|
||||
return AlbumResults{}, ErrForbidden
|
||||
}
|
||||
|
||||
// Limit results by UID, owner and path.
|
||||
if sess.IsVisitor() {
|
||||
s = s.Where("albums.album_uid IN (?)", sess.SharedUIDs())
|
||||
if sess.IsVisitor() || sess.NotRegistered() {
|
||||
s = s.Where("albums.album_uid IN (?) OR albums.published_at > ?", sess.SharedUIDs(), entity.TimeStamp())
|
||||
} else if acl.Resources.DenyAll(aclResource, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||
if user.BasePath == "" {
|
||||
s = s.Where("albums.album_uid IN (?) OR albums.created_by = ?", sess.SharedUIDs(), user.UserUID)
|
||||
s = s.Where("albums.album_uid IN (?) OR albums.created_by = ? OR albums.published_at > ?", sess.SharedUIDs(), user.UserUID, entity.TimeStamp())
|
||||
} else {
|
||||
s = s.Where("albums.album_uid IN (?) OR albums.created_by = ? OR albums.album_type = ? AND (albums.album_path = ? OR albums.album_path LIKE ?)",
|
||||
sess.SharedUIDs(), user.UserUID, entity.AlbumFolder, user.BasePath, user.BasePath+"/%")
|
||||
s = s.Where("albums.album_uid IN (?) OR albums.created_by = ? OR albums.published_at > ? OR albums.album_type = ? AND (albums.album_path = ? OR albums.album_path LIKE ?)",
|
||||
sess.SharedUIDs(), user.UserUID, entity.TimeStamp(), entity.AlbumFolder, user.BasePath, user.BasePath+"/%")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,13 +96,6 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
|||
user := sess.User()
|
||||
aclRole := user.AclRole()
|
||||
|
||||
// Visitors and other restricted users can only access shared content.
|
||||
if !sess.HasShare(f.Scope) && (sess.IsVisitor() || sess.NotRegistered()) ||
|
||||
f.Scope == "" && acl.Resources.Deny(acl.ResourcePhotos, aclRole, acl.ActionSearch) {
|
||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePhotos), aclRole)
|
||||
return PhotoResults{}, 0, ErrForbidden
|
||||
}
|
||||
|
||||
// Exclude private content?
|
||||
if acl.Resources.Deny(acl.ResourcePhotos, aclRole, acl.AccessPrivate) {
|
||||
f.Public = true
|
||||
|
@ -120,13 +113,22 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
|||
f.Hidden = false
|
||||
}
|
||||
|
||||
// Limit results by owner and path?
|
||||
// Visitors and other restricted users can only access shared content.
|
||||
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.IsVisitor() || sess.NotRegistered()) ||
|
||||
f.Scope == "" && acl.Resources.Deny(acl.ResourcePhotos, aclRole, acl.ActionSearch) {
|
||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePhotos), aclRole)
|
||||
return PhotoResults{}, 0, ErrForbidden
|
||||
}
|
||||
|
||||
// Limit results for external users.
|
||||
if f.Scope == "" && acl.Resources.DenyAll(acl.ResourcePhotos, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||
if user.BasePath == "" {
|
||||
s = s.Where("photos.created_by = ?", user.UserUID)
|
||||
if sess.IsVisitor() || sess.NotRegistered() {
|
||||
s = s.Where("photos.published_at > ?", entity.TimeStamp())
|
||||
} else if user.BasePath == "" {
|
||||
s = s.Where("photos.created_by = ? OR photos.published_at > ?", user.UserUID, entity.TimeStamp())
|
||||
} else {
|
||||
s = s.Where("photos.created_by = ? OR photos.photo_path = ? OR photos.photo_path LIKE ?",
|
||||
user.UserUID, user.BasePath, user.BasePath+"/%")
|
||||
s = s.Where("photos.created_by = ? OR photos.published_at > ? OR photos.photo_path = ? OR photos.photo_path LIKE ?",
|
||||
user.UserUID, entity.TimeStamp(), user.BasePath, user.BasePath+"/%")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +143,8 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
|||
} else {
|
||||
s = s.Order("photos.photo_quality DESC, files.time_index")
|
||||
}
|
||||
case entity.SortOrderDuration:
|
||||
s = s.Order("photos.photo_duration DESC, files.time_index")
|
||||
case entity.SortOrderNewest:
|
||||
s = s.Order("files.time_index")
|
||||
case entity.SortOrderOldest:
|
||||
|
|
|
@ -100,13 +100,6 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
|||
user := sess.User()
|
||||
aclRole := user.AclRole()
|
||||
|
||||
// Visitors and other restricted users can only access shared content.
|
||||
if !sess.HasShare(f.Scope) && (sess.IsVisitor() || sess.NotRegistered()) ||
|
||||
f.Scope == "" && acl.Resources.Deny(acl.ResourcePlaces, aclRole, acl.ActionSearch) {
|
||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePlaces), aclRole)
|
||||
return GeoResults{}, ErrForbidden
|
||||
}
|
||||
|
||||
// Exclude private content?
|
||||
if acl.Resources.Deny(acl.ResourcePlaces, aclRole, acl.AccessPrivate) {
|
||||
f.Public = true
|
||||
|
@ -119,13 +112,22 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
|||
f.Review = false
|
||||
}
|
||||
|
||||
// Limit results by owner and path?
|
||||
// Visitors and other restricted users can only access shared content.
|
||||
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.IsVisitor() || sess.NotRegistered()) ||
|
||||
f.Scope == "" && acl.Resources.Deny(acl.ResourcePlaces, aclRole, acl.ActionSearch) {
|
||||
event.AuditErr([]string{sess.IP(), "session %s", "%s %s as %s", "denied"}, sess.RefID, acl.ActionSearch.String(), string(acl.ResourcePlaces), aclRole)
|
||||
return GeoResults{}, ErrForbidden
|
||||
}
|
||||
|
||||
// Limit results for external users.
|
||||
if f.Scope == "" && acl.Resources.DenyAll(acl.ResourcePlaces, aclRole, acl.Permissions{acl.AccessAll, acl.AccessLibrary}) {
|
||||
if user.BasePath == "" {
|
||||
s = s.Where("photos.created_by = ?", user.UserUID)
|
||||
if sess.IsVisitor() || sess.NotRegistered() {
|
||||
s = s.Where("photos.published_at > ?", entity.TimeStamp())
|
||||
} else if user.BasePath == "" {
|
||||
s = s.Where("photos.created_by = ? OR photos.published_at > ?", user.UserUID, entity.TimeStamp())
|
||||
} else {
|
||||
s = s.Where("photos.created_by = ? OR photos.photo_path = ? OR photos.photo_path LIKE ?",
|
||||
user.UserUID, user.BasePath, user.BasePath+"/%")
|
||||
s = s.Where("photos.created_by = ? OR photos.published_at > ? OR photos.photo_path = ? OR photos.photo_path LIKE ?",
|
||||
user.UserUID, entity.TimeStamp(), user.BasePath, user.BasePath+"/%")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ type Photo struct {
|
|||
PhotoFaces int `json:"Faces,omitempty" select:"photos.photo_faces"`
|
||||
PhotoQuality int `json:"Quality" select:"photos.photo_quality"`
|
||||
PhotoResolution int `json:"Resolution" select:"photos.photo_resolution"`
|
||||
PhotoDuration time.Duration `json:"Duration,omitempty" yaml:"photos.photo_duration"`
|
||||
PhotoColor int16 `json:"Color" select:"photos.photo_color"`
|
||||
PhotoScan bool `json:"Scan" select:"photos.photo_scan"`
|
||||
PhotoPanorama bool `json:"Panorama" select:"photos.photo_panorama"`
|
||||
|
|
|
@ -11,6 +11,21 @@ import (
|
|||
)
|
||||
|
||||
func TestPhotos(t *testing.T) {
|
||||
t.Run("OrderDuration", func(t *testing.T) {
|
||||
var frm form.SearchPhotos
|
||||
|
||||
frm.Query = ""
|
||||
frm.Count = 10
|
||||
frm.Offset = 0
|
||||
frm.Order = "duration"
|
||||
|
||||
photos, _, err := Photos(frm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.LessOrEqual(t, 2, len(photos))
|
||||
})
|
||||
t.Run("Chinese", func(t *testing.T) {
|
||||
var frm form.SearchPhotos
|
||||
|
||||
|
|
Loading…
Reference in a new issue