Sharing: Refactor UserShare entity #98 #782

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2022-10-04 00:54:39 +02:00
parent b390e34b78
commit 693108fd53
7 changed files with 71 additions and 54 deletions

View file

@ -62,6 +62,7 @@ type User struct {
BornAt *time.Time `sql:"index" json:"BornAt,omitempty" yaml:"BornAt,omitempty"`
UserDetails *UserDetails `gorm:"PRELOAD:true;foreignkey:UserUID;association_foreignkey:UserUID;" json:"Details,omitempty" yaml:"Details,omitempty"`
UserSettings *UserSettings `gorm:"PRELOAD:true;foreignkey:UserUID;association_foreignkey:UserUID;" json:"Settings,omitempty" yaml:"Settings,omitempty"`
UserShares UserShares `gorm:"-" json:"Shares,omitempty" yaml:"Shares,omitempty"`
ResetToken string `gorm:"type:VARBINARY(64);" json:"-" yaml:"-"`
PreviewToken string `gorm:"type:VARBINARY(64);column:preview_token;" json:"-" yaml:"-"`
DownloadToken string `gorm:"type:VARBINARY(64);column:download_token;" json:"-" yaml:"-"`
@ -71,7 +72,6 @@ type User struct {
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
Shares Shares `gorm:"-" json:"Shares,omitempty" yaml:"Shares,omitempty"`
}
// TableName returns the entity table name.
@ -635,7 +635,7 @@ func (m *User) SetFormValues(frm form.User) *User {
// RefreshShares updates the list of shares.
func (m *User) RefreshShares() *User {
m.Shares = FindShares(m.UID())
m.UserShares = FindUserShares(m.UID())
return m
}
@ -645,7 +645,7 @@ func (m *User) NoShares() bool {
return true
}
return m.Shares.Empty()
return m.UserShares.Empty()
}
// HasShares checks if the user has any shares.
@ -659,16 +659,16 @@ func (m *User) HasShare(uid string) bool {
return false
}
return m.Shares.Contains(uid)
return m.UserShares.Contains(uid)
}
// SharedUIDs returns shared entity UIDs.
func (m *User) SharedUIDs() UIDs {
if m.IsRegistered() && m.Shares.Empty() {
if m.IsRegistered() && m.UserShares.Empty() {
m.RefreshShares()
}
return m.Shares.UIDs()
return m.UserShares.UIDs()
}
// RedeemToken updates shared entity UIDs using the specified token.
@ -687,8 +687,8 @@ func (m *User) RedeemToken(token string) (n int) {
// Find shares.
for _, link := range links {
if found := FindShare(Share{UserUID: m.UID(), ShareUID: link.ShareUID}); found == nil {
share := NewShare(m.UID(), link.ShareUID, link.Perm, link.ExpiresAt())
if found := FindUserShare(UserShare{UserUID: m.UID(), ShareUID: link.ShareUID}); found == nil {
share := NewUserShare(m.UID(), link.ShareUID, link.Perm, link.ExpiresAt())
share.LinkUID = link.LinkUID
share.Comment = link.Comment

View file

@ -27,11 +27,11 @@ const (
SharePrefix = "share"
)
// Shares represents shared content.
type Shares []Share
// UserShares represents shared content.
type UserShares []UserShare
// UIDs returns shared UIDs.
func (m Shares) UIDs() UIDs {
func (m UserShares) UIDs() UIDs {
result := make(UIDs, len(m))
for i, share := range m {
@ -42,12 +42,12 @@ func (m Shares) UIDs() UIDs {
}
// Empty checks if there are no shares.
func (m Shares) Empty() bool {
func (m UserShares) Empty() bool {
return m == nil || len(m) == 0
}
// Contains checks the uid is shared.
func (m Shares) Contains(uid string) bool {
func (m UserShares) Contains(uid string) bool {
if len(m) == 0 {
return false
}
@ -61,8 +61,8 @@ func (m Shares) Contains(uid string) bool {
return false
}
// Share represents content shared with a user.
type Share struct {
// UserShare represents content shared with a user.
type UserShare struct {
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"-" yaml:"UserUID"`
ShareUID string `gorm:"type:VARBINARY(42);primary_key;index;" json:"ShareUID" yaml:"ShareUID"`
LinkUID string `gorm:"type:VARBINARY(42);" json:"LinkUID,omitempty" yaml:"LinkUID,omitempty"`
@ -75,13 +75,13 @@ type Share struct {
}
// TableName returns the entity table name.
func (Share) TableName() string {
func (UserShare) TableName() string {
return "auth_users_shares"
}
// NewShare creates a new entity model.
func NewShare(userUID, shareUid string, perm uint, expires *time.Time) *Share {
result := &Share{
// NewUserShare creates a new entity model.
func NewUserShare(userUID, shareUid string, perm uint, expires *time.Time) *UserShare {
result := &UserShare{
UserUID: userUID,
ShareUID: shareUid,
Perm: perm,
@ -94,13 +94,13 @@ func NewShare(userUID, shareUid string, perm uint, expires *time.Time) *Share {
return result
}
// FindShare fetches the matching record or returns null if it was not found.
func FindShare(find Share) *Share {
// FindUserShare fetches the matching record or returns null if it was not found.
func FindUserShare(find UserShare) *UserShare {
if !find.HasID() {
return nil
}
m := &Share{}
m := &UserShare{}
// Find matching record.
if UnscopedDb().First(m, "user_uid = ? AND share_uid = ?", find.UserUID, find.ShareUID).RecordNotFound() {
@ -110,9 +110,9 @@ func FindShare(find Share) *Share {
return m
}
// FindShares finds all shares to which the user has access.
func FindShares(userUid string) Shares {
found := Shares{}
// FindUserShares finds all shares to which the user has access.
func FindUserShares(userUid string) UserShares {
found := UserShares{}
if rnd.InvalidUID(userUid, UserUID) {
return found
@ -128,27 +128,27 @@ func FindShares(userUid string) Shares {
}
// HasID tests if the entity has a valid uid.
func (m *Share) HasID() bool {
func (m *UserShare) HasID() bool {
return rnd.IsUID(m.UserUID, UserUID) && rnd.IsUID(m.ShareUID, 0)
}
// Create inserts a new record into the database.
func (m *Share) Create() error {
func (m *UserShare) 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 *Share) Save() error {
func (m *UserShare) Save() error {
return Db().Save(m).Error
}
// Updates changes multiple record values.
func (m *Share) Updates(values interface{}) error {
func (m *UserShare) Updates(values interface{}) error {
return UnscopedDb().Model(m).Updates(values).Error
}
// UpdateLink updates the share data using the Link provided.
func (m *Share) UpdateLink(link Link) error {
func (m *UserShare) UpdateLink(link Link) error {
if m.ShareUID != link.ShareUID {
return fmt.Errorf("shared uid does not match")
}

View file

@ -4,28 +4,28 @@ import (
"github.com/photoprism/photoprism/pkg/rnd"
)
type ShareMap map[string]Share
type UserShareMap map[string]UserShare
// Get returns a fixture for use in tests.
func (m ShareMap) Get(name string) Share {
func (m UserShareMap) Get(name string) UserShare {
if result, ok := m[name]; ok {
return result
}
return Share{}
return UserShare{}
}
// Pointer returns a fixture pointer for use in tests.
func (m ShareMap) Pointer(name string) *Share {
func (m UserShareMap) Pointer(name string) *UserShare {
if result, ok := m[name]; ok {
return &result
}
return &Share{}
return &UserShare{}
}
// ShareFixtures specifies fixtures for use in tests.
var ShareFixtures = ShareMap{
// UserShareFixtures specifies fixtures for use in tests.
var UserShareFixtures = UserShareMap{
"AliceAlbum": {
UserUID: "uqxetse3cy5eo9z2",
ShareUID: "at9lxuqxpogaaba9",
@ -38,9 +38,9 @@ var ShareFixtures = ShareMap{
},
}
// CreateShareFixtures creates the fixtures specified above.
func CreateShareFixtures() {
for _, entity := range ShareFixtures {
// CreateUserShareFixtures creates the fixtures specified above.
func CreateUserShareFixtures() {
for _, entity := range UserShareFixtures {
Db().Create(&entity)
}
}

View file

@ -9,9 +9,9 @@ import (
"github.com/photoprism/photoprism/pkg/rnd"
)
func TestNewShare(t *testing.T) {
func TestNewUserShare(t *testing.T) {
expires := TimeStamp().Add(time.Hour * 48)
m := NewShare(Admin.UID(), AlbumFixtures.Get("berlin-2019").AlbumUID, PermReact, &expires)
m := NewUserShare(Admin.UID(), AlbumFixtures.Get("berlin-2019").AlbumUID, PermReact, &expires)
assert.True(t, m.HasID())
assert.True(t, rnd.IsRefID(m.RefID))
@ -35,11 +35,11 @@ func TestPerm(t *testing.T) {
assert.Equal(t, uint(128), PermAll)
}
func TestFindShare(t *testing.T) {
func TestFindUserShare(t *testing.T) {
t.Run("AliceAlbum", func(t *testing.T) {
m := FindShare(Share{UserUID: "uqxetse3cy5eo9z2", ShareUID: "at9lxuqxpogaaba9"})
m := FindUserShare(UserShare{UserUID: "uqxetse3cy5eo9z2", ShareUID: "at9lxuqxpogaaba9"})
expected := ShareFixtures.Get("AliceAlbum")
expected := UserShareFixtures.Get("AliceAlbum")
assert.NotNil(t, m)
assert.True(t, m.HasID())
@ -55,13 +55,13 @@ func TestFindShare(t *testing.T) {
})
}
func TestFindShares(t *testing.T) {
found := FindShares(UserFixtures.Pointer("alice").UID())
func TestFindUserShares(t *testing.T) {
found := FindUserShares(UserFixtures.Pointer("alice").UID())
assert.NotNil(t, found)
assert.Len(t, found, 1)
m := found[0]
expected := ShareFixtures.Get("AliceAlbum")
expected := UserShareFixtures.Get("AliceAlbum")
assert.NotNil(t, m)
assert.True(t, m.HasID())

View file

@ -48,7 +48,7 @@ var Entities = Tables{
Face{}.TableName(): &Face{},
Marker{}.TableName(): &Marker{},
Reaction{}.TableName(): &Reaction{},
Share{}.TableName(): &Share{},
UserShare{}.TableName(): &UserShare{},
}
// WaitForMigration waits for the database migration to be successful.

View file

@ -31,5 +31,5 @@ func CreateTestFixtures() {
CreateSessionFixtures()
CreateReactionFixtures()
CreatePasswordFixtures()
CreateShareFixtures()
CreateUserShareFixtures()
}

View file

@ -4,17 +4,18 @@ import (
"fmt"
"time"
"github.com/photoprism/photoprism/internal/event"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/txt"
)
// LinkPrefix for RefID.
const (
LinkUID = byte('s')
LinkUID = byte('s')
LinkPrefix = "link"
)
type Links []Link
@ -45,7 +46,7 @@ func (Link) TableName() string {
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
func (m *Link) BeforeCreate(scope *gorm.Scope) error {
if rnd.InvalidRefID(m.RefID) {
m.RefID = rnd.RefID(SessionPrefix)
m.RefID = rnd.RefID(LinkPrefix)
Log("link", "set ref id", scope.SetColumn("RefID", m.RefID))
}
@ -165,6 +166,13 @@ func (m *Link) Save() error {
func (m *Link) Delete() error {
if m.LinkToken == "" {
return fmt.Errorf("empty link token")
} else if m.LinkUID == "" {
return fmt.Errorf("empty link uid")
}
// Remove related user shares.
if err := UnscopedDb().Delete(UserShare{}, "link_uid = ?", m.LinkUID).Error; err != nil {
event.AuditErr([]string{"link %s", "failed to remove related user shares", "%s"}, clean.Log(m.RefID), err)
}
return Db().Delete(m).Error
@ -172,6 +180,15 @@ func (m *Link) Delete() error {
// DeleteShareLinks removes all links that match the shared UID.
func DeleteShareLinks(shareUid string) error {
if shareUid == "" {
return fmt.Errorf("empty share uid")
}
// Remove related user shares.
if err := UnscopedDb().Delete(UserShare{}, "share_uid = ?", shareUid).Error; err != nil {
event.AuditErr([]string{"share %s", "failed to remove related user shares", "%s"}, clean.Log(shareUid), err)
}
return Db().Delete(&Link{}, "share_uid = ?", shareUid).Error
}