Auth: Rename database tables and delete temporary tables #98 #782

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2022-10-02 22:09:02 +02:00
parent 6e74f16a77
commit 4c516cac38
38 changed files with 302 additions and 240 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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>

View file

@ -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')},
],
},
};

View file

@ -184,7 +184,7 @@ export default {
const params = {
share: true,
count: 1000,
count: 2000,
offset: 0,
};

View file

@ -134,7 +134,7 @@ export default {
const params = {
q: q,
count: 1000,
count: 2000,
offset: 0,
type: "album"
};

View file

@ -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() {

View file

@ -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) {

View file

@ -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") {

View file

@ -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();

View file

@ -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,

View file

@ -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"

View file

@ -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

View file

@ -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
}

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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",
}

View file

@ -56,6 +56,7 @@ const (
const (
SortOrderDefault = ""
SortOrderRelevance = "relevance"
SortOrderDuration = "duration"
SortOrderCount = "count"
SortOrderAdded = "added"
SortOrderImported = "imported"

View file

@ -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{},

View file

@ -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.

View file

@ -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: "",

View file

@ -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

View file

@ -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 {

View file

@ -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(),
},
}

View file

@ -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())

View file

@ -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",

View file

@ -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;"},
},
}

View file

@ -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;

View file

@ -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;

View file

@ -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)

View file

@ -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+"/%")
}
}

View file

@ -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:

View file

@ -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+"/%")
}
}
}

View file

@ -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"`

View file

@ -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