Sharing: Link expiration, view counter and permissions #18
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
3eece7a8ad
commit
cfd23666a9
18 changed files with 208 additions and 110 deletions
|
@ -45,10 +45,18 @@ func GetPhotos(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
// Guest permissions are limited to shared albums.
|
||||
if s.Guest() && (f.Album == "" || !s.HasShare(f.Album)){
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
// Guests may only see public content in shared albums.
|
||||
if s.Guest() {
|
||||
if f.Album == "" || !s.HasShare(f.Album) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
f.Public = true
|
||||
f.Private = false
|
||||
f.Hidden = false
|
||||
f.Archived = false
|
||||
f.Review = false
|
||||
}
|
||||
|
||||
result, count, err := query.PhotoSearch(f)
|
||||
|
|
|
@ -36,7 +36,7 @@ func CreateSession(router *gin.RouterGroup) {
|
|||
conf := service.Config()
|
||||
|
||||
if f.HasToken() {
|
||||
links := entity.FindLinks(f.Token, "")
|
||||
links := entity.FindValidLinks(f.Token, "")
|
||||
|
||||
if len(links) == 0 {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": "Invalid link"})
|
||||
|
@ -46,6 +46,7 @@ func CreateSession(router *gin.RouterGroup) {
|
|||
|
||||
for _, link := range links {
|
||||
data.Shares = append(data.Shares, link.ShareUID)
|
||||
link.Redeem()
|
||||
}
|
||||
|
||||
// Upgrade from anonymous to guest. Don't downgrade.
|
||||
|
@ -85,7 +86,7 @@ func CreateSession(router *gin.RouterGroup) {
|
|||
})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/session/
|
||||
// DELETE /api/v1/session/:id
|
||||
func DeleteSession(router *gin.RouterGroup) {
|
||||
router.DELETE("/session/:id", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
|
|
@ -2,7 +2,6 @@ package entity
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AccountMap map[string]Account
|
||||
|
@ -28,13 +27,13 @@ var AccountFixtures = AccountMap{
|
|||
SyncPath: "/Photos",
|
||||
SyncStatus: "",
|
||||
SyncInterval: 3600,
|
||||
SyncDate: sql.NullTime{Time: time.Now()},
|
||||
SyncDate: sql.NullTime{Time: Timestamp()},
|
||||
SyncUpload: true,
|
||||
SyncDownload: true,
|
||||
SyncFilenames: true,
|
||||
SyncRaw: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
},
|
||||
"webdav-dummy2": {
|
||||
|
@ -57,13 +56,13 @@ var AccountFixtures = AccountMap{
|
|||
SyncPath: "/Photos",
|
||||
SyncStatus: "test",
|
||||
SyncInterval: 3600,
|
||||
SyncDate: sql.NullTime{Time: time.Now()},
|
||||
SyncDate: sql.NullTime{Time: Timestamp()},
|
||||
SyncUpload: true,
|
||||
SyncDownload: true,
|
||||
SyncFilenames: true,
|
||||
SyncRaw: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ func AddPhotoToAlbums(photo string, albums []string) (err error) {
|
|||
|
||||
// NewAlbum creates a new album; default name is current month and year
|
||||
func NewAlbum(albumTitle, albumType string) *Album {
|
||||
now := time.Now().UTC()
|
||||
now := Timestamp()
|
||||
|
||||
if albumType == "" {
|
||||
albumType = AlbumDefault
|
||||
|
@ -121,7 +121,7 @@ func NewFolderAlbum(albumTitle, albumSlug, albumFilter string) *Album {
|
|||
return nil
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
now := Timestamp()
|
||||
|
||||
result := &Album{
|
||||
AlbumOrder: SortOrderOldest,
|
||||
|
@ -142,7 +142,7 @@ func NewMomentsAlbum(albumTitle, albumSlug, albumFilter string) *Album {
|
|||
return nil
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
now := Timestamp()
|
||||
|
||||
result := &Album{
|
||||
AlbumOrder: SortOrderOldest,
|
||||
|
@ -163,7 +163,7 @@ func NewStateAlbum(albumTitle, albumSlug, albumFilter string) *Album {
|
|||
return nil
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
now := Timestamp()
|
||||
|
||||
result := &Album{
|
||||
AlbumOrder: SortOrderOldest,
|
||||
|
@ -190,7 +190,7 @@ func NewMonthAlbum(albumTitle, albumSlug string, year, month int) *Album {
|
|||
Public: true,
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
now := Timestamp()
|
||||
|
||||
result := &Album{
|
||||
AlbumOrder: SortOrderOldest,
|
||||
|
|
|
@ -96,8 +96,8 @@ var CameraFixtures = CameraMap{
|
|||
CameraType: "",
|
||||
CameraDescription: "",
|
||||
CameraNotes: "",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ func (m *Folder) BeforeCreate(scope *gorm.Scope) error {
|
|||
|
||||
// NewFolder creates a new file system directory entity.
|
||||
func NewFolder(root, pathName string, modTime *time.Time) Folder {
|
||||
now := time.Now().UTC()
|
||||
now := Timestamp()
|
||||
|
||||
pathName = strings.Trim(pathName, string(os.PathSeparator))
|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type LabelMap map[string]Label
|
||||
|
||||
func (m LabelMap) Get(name string) Label {
|
||||
|
@ -44,8 +40,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 1,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
@ -61,8 +57,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 2,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
@ -78,8 +74,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 3,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
@ -95,8 +91,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 4,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
@ -112,8 +108,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 5,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
@ -129,8 +125,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 1,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
@ -146,8 +142,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 1,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
@ -163,8 +159,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 1,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
@ -180,8 +176,8 @@ var LabelFixtures = LabelMap{
|
|||
LabelNotes: "",
|
||||
PhotoCount: 4,
|
||||
LabelCategories: []*Label{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
DeletedAt: nil,
|
||||
New: false,
|
||||
},
|
||||
|
|
|
@ -15,12 +15,13 @@ type Link struct {
|
|||
LinkUID string `gorm:"type:varbinary(42);primary_key;" json:"UID,omitempty" yaml:"UID,omitempty"`
|
||||
ShareUID string `gorm:"type:varbinary(42);unique_index:idx_links_uid_token;" json:"ShareUID"`
|
||||
ShareToken string `gorm:"type:varbinary(255);unique_index:idx_links_uid_token;" json:"ShareToken"`
|
||||
ShareExpires int `json:"ShareExpires"`
|
||||
HasPassword bool `json:"HasPassword"`
|
||||
CanComment bool `json:"CanComment"`
|
||||
CanEdit bool `json:"CanEdit"`
|
||||
CreatedAt time.Time `deepcopier:"skip" json:"CreatedAt"`
|
||||
UpdatedAt time.Time `deepcopier:"skip" json:"UpdatedAt"`
|
||||
ShareExpires int `json:"ShareExpires" yaml:"ShareExpires,omitempty"`
|
||||
ShareViews uint `json:"ShareViews" yaml:"-"`
|
||||
HasPassword bool `json:"HasPassword" yaml:"HasPassword,omitempty"`
|
||||
CanComment bool `json:"CanComment" yaml:"CanComment,omitempty"`
|
||||
CanEdit bool `json:"CanEdit" yaml:"CanEdit,omitempty"`
|
||||
CreatedAt time.Time `deepcopier:"skip" json:"CreatedAt" yaml:"CreatedAt"`
|
||||
ModifiedAt time.Time `deepcopier:"skip" yaml:"ModifiedAt"`
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
|
@ -34,17 +35,42 @@ func (m *Link) BeforeCreate(scope *gorm.Scope) error {
|
|||
|
||||
// NewLink creates a sharing link.
|
||||
func NewLink(shareUID string, canComment, canEdit bool) Link {
|
||||
now := Timestamp()
|
||||
|
||||
result := Link{
|
||||
LinkUID: rnd.PPID('s'),
|
||||
ShareUID: shareUID,
|
||||
ShareToken: rnd.Token(10),
|
||||
CanComment: canComment,
|
||||
CanEdit: canEdit,
|
||||
CreatedAt: now,
|
||||
ModifiedAt: now,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Link) Redeem() {
|
||||
m.ShareViews += 1
|
||||
|
||||
result := Db().Model(m).UpdateColumn("ShareViews", m.ShareViews)
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
log.Warnf("link: failed updating share view counter for %s", m.LinkUID)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Link) Expired() bool {
|
||||
if m.ShareExpires <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
now := Timestamp()
|
||||
expires := m.ModifiedAt.Add(Seconds(m.ShareExpires))
|
||||
|
||||
return now.Before(expires)
|
||||
}
|
||||
|
||||
func (m *Link) SetPassword(password string) error {
|
||||
pw := NewPassword(m.LinkUID, password)
|
||||
|
||||
|
@ -71,19 +97,6 @@ func (m *Link) InvalidPassword(password string) bool {
|
|||
return pw.InvalidPassword(password)
|
||||
}
|
||||
|
||||
// Create inserts a new row to the database.
|
||||
func (m *Link) Create() error {
|
||||
if !rnd.IsPPID(m.ShareUID, 0) {
|
||||
return fmt.Errorf("link: invalid share uid (%s)", m.ShareUID)
|
||||
}
|
||||
|
||||
if m.ShareToken == "" {
|
||||
return fmt.Errorf("link: empty share token")
|
||||
}
|
||||
|
||||
return Db().Create(m).Error
|
||||
}
|
||||
|
||||
// Save inserts a new row to the database or updates a row if the primary key already exists.
|
||||
func (m *Link) Save() error {
|
||||
if !rnd.IsPPID(m.ShareUID, 0) {
|
||||
|
@ -94,9 +107,12 @@ func (m *Link) Save() error {
|
|||
return fmt.Errorf("link: empty share token")
|
||||
}
|
||||
|
||||
m.ModifiedAt = Timestamp()
|
||||
|
||||
return Db().Save(m).Error
|
||||
}
|
||||
|
||||
// Deletes the link.
|
||||
func (m *Link) Delete() error {
|
||||
if m.ShareToken == "" {
|
||||
return fmt.Errorf("link: empty share token")
|
||||
|
@ -142,6 +158,17 @@ func FindLinks(shareToken, shareUID string) (result Links) {
|
|||
return result
|
||||
}
|
||||
|
||||
// FindValidLinks returns a slice of non-expired links for a token and share UID (at least one must be provided).
|
||||
func FindValidLinks(shareToken, shareUID string) (result Links) {
|
||||
for _, link := range FindLinks(shareToken, shareUID) {
|
||||
if !link.Expired() {
|
||||
result = append(result, link)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// String returns an human readable identifier for logging.
|
||||
func (m *Link) String() string {
|
||||
return fmt.Sprintf("%s/%s", m.ShareUID, m.ShareToken)
|
||||
|
|
|
@ -14,7 +14,6 @@ var LinkFixtures = LinkMap{
|
|||
CanComment: true,
|
||||
CanEdit: false,
|
||||
CreatedAt: time.Date(2020, 3, 6, 2, 6, 51, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2020, 3, 28, 14, 6, 0, 0, time.UTC),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewLink(t *testing.T) {
|
||||
|
@ -13,3 +15,40 @@ func TestNewLink(t *testing.T) {
|
|||
assert.Equal(t, 10, len(link.ShareToken))
|
||||
assert.Equal(t, 16, len(link.LinkUID))
|
||||
}
|
||||
|
||||
func TestLink_Expired(t *testing.T) {
|
||||
const oneDay = 60 * 60 * 24
|
||||
|
||||
link := NewLink("st9lxuqxpogaaba1", true, false)
|
||||
|
||||
link.ModifiedAt = Timestamp().Add(-7* Day)
|
||||
link.ShareExpires = 0
|
||||
|
||||
assert.False(t, link.Expired())
|
||||
|
||||
link.ShareExpires = oneDay
|
||||
|
||||
assert.False(t, link.Expired())
|
||||
|
||||
link.ShareExpires = oneDay * 8
|
||||
|
||||
assert.True(t, link.Expired())
|
||||
}
|
||||
|
||||
func TestLink_Redeem(t *testing.T) {
|
||||
link := NewLink(rnd.PPID('a'), false, false)
|
||||
|
||||
assert.Equal(t, uint(0), link.ShareViews)
|
||||
|
||||
link.Redeem()
|
||||
|
||||
assert.Equal(t, uint(1), link.ShareViews)
|
||||
|
||||
if err := link.Save(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
link.Redeem()
|
||||
|
||||
assert.Equal(t, uint(2), link.ShareViews)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/s2"
|
||||
)
|
||||
|
||||
|
@ -32,8 +30,8 @@ var LocationFixtures = LocationMap{
|
|||
LocCategory: "botanical garden",
|
||||
Place: PlaceFixtures.Pointer("mexico"),
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"caravan park": {
|
||||
ID: s2.TokenPrefix + "1ef75a71a36c",
|
||||
|
@ -44,14 +42,14 @@ var LocationFixtures = LocationMap{
|
|||
LocCity: "Mandeni",
|
||||
LocState: "KwaZulu-Natal",
|
||||
LocCountry: "za",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
LocName: "Lobotes Caravan Park",
|
||||
LocCategory: "camping",
|
||||
LocSource: "manual",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"zinkwazi": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e28c",
|
||||
|
@ -60,8 +58,8 @@ var LocationFixtures = LocationMap{
|
|||
LocName: "Zinkwazi Beach",
|
||||
LocCategory: "beach",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"hassloch": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e280",
|
||||
|
@ -70,8 +68,8 @@ var LocationFixtures = LocationMap{
|
|||
LocName: "Holiday Park",
|
||||
LocCategory: "park",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"emptyNameLongCity": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e281",
|
||||
|
@ -80,8 +78,8 @@ var LocationFixtures = LocationMap{
|
|||
LocName: "",
|
||||
LocCategory: "botanical garden",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"emptyNameShortCity": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e282",
|
||||
|
@ -90,8 +88,8 @@ var LocationFixtures = LocationMap{
|
|||
LocName: "",
|
||||
LocCategory: "botanical garden",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"veryLongLocName": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e283",
|
||||
|
@ -100,8 +98,8 @@ var LocationFixtures = LocationMap{
|
|||
LocName: "longlonglonglonglonglonglonglonglonglonglonglonglongName",
|
||||
LocCategory: "cape",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"mediumLongLocName": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e283",
|
||||
|
@ -110,8 +108,8 @@ var LocationFixtures = LocationMap{
|
|||
LocName: "longlonglonglonglonglongName",
|
||||
LocCategory: "botanical garden",
|
||||
LocSource: "places",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ func SavePhotoForm(model Photo, form form.Photo, geoApi string) error {
|
|||
log.Errorf("photo: %s", err.Error())
|
||||
}
|
||||
|
||||
edited := time.Now().UTC()
|
||||
edited := Timestamp()
|
||||
model.EditedAt = &edited
|
||||
model.PhotoQuality = model.QualityScore()
|
||||
|
||||
|
@ -229,7 +229,7 @@ func (m *Photo) ClassifyLabels() classify.Labels {
|
|||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *Photo) BeforeCreate(scope *gorm.Scope) error {
|
||||
if m.TakenAt.IsZero() || m.TakenAtLocal.IsZero() {
|
||||
now := time.Now()
|
||||
now := Timestamp()
|
||||
|
||||
if err := scope.SetColumn("TakenAt", now); err != nil {
|
||||
return err
|
||||
|
@ -250,7 +250,7 @@ func (m *Photo) BeforeCreate(scope *gorm.Scope) error {
|
|||
// BeforeSave ensures the existence of TakenAt properties before indexing or updating a photo
|
||||
func (m *Photo) BeforeSave(scope *gorm.Scope) error {
|
||||
if m.TakenAt.IsZero() || m.TakenAtLocal.IsZero() {
|
||||
now := time.Now()
|
||||
now := Timestamp()
|
||||
|
||||
if err := scope.SetColumn("TakenAt", now); err != nil {
|
||||
return err
|
||||
|
@ -854,7 +854,7 @@ func (m *Photo) Approve() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
edited := time.Now().UTC()
|
||||
edited := Timestamp()
|
||||
m.EditedAt = &edited
|
||||
m.PhotoQuality = m.QualityScore()
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ func (m *Photo) Maintain() error {
|
|||
return errors.New("photo: can't maintain, id is empty")
|
||||
}
|
||||
|
||||
checked := time.Now()
|
||||
checked := Timestamp()
|
||||
m.CheckedAt = &checked
|
||||
|
||||
m.EstimatePlace()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/s2"
|
||||
)
|
||||
|
||||
|
@ -35,8 +33,8 @@ var PlaceFixtures = PlacesMap{
|
|||
LocNotes: "",
|
||||
LocFavorite: false,
|
||||
PhotoCount: 1,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"zinkwazi": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e279",
|
||||
|
@ -48,8 +46,8 @@ var PlaceFixtures = PlacesMap{
|
|||
LocNotes: "africa",
|
||||
LocFavorite: true,
|
||||
PhotoCount: 2,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"holidaypark": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e280",
|
||||
|
@ -61,8 +59,8 @@ var PlaceFixtures = PlacesMap{
|
|||
LocNotes: "germany",
|
||||
LocFavorite: true,
|
||||
PhotoCount: 2,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"emptyNameLongCity": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e281",
|
||||
|
@ -74,8 +72,8 @@ var PlaceFixtures = PlacesMap{
|
|||
LocNotes: "germany",
|
||||
LocFavorite: true,
|
||||
PhotoCount: 2,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"emptyNameShortCity": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e282",
|
||||
|
@ -87,8 +85,8 @@ var PlaceFixtures = PlacesMap{
|
|||
LocNotes: "germany",
|
||||
LocFavorite: true,
|
||||
PhotoCount: 2,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"veryLongLocName": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e283",
|
||||
|
@ -100,8 +98,8 @@ var PlaceFixtures = PlacesMap{
|
|||
LocNotes: "germany",
|
||||
LocFavorite: true,
|
||||
PhotoCount: 2,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
"mediumLongLocName": {
|
||||
ID: s2.TokenPrefix + "1ef744d1e284",
|
||||
|
@ -113,8 +111,8 @@ var PlaceFixtures = PlacesMap{
|
|||
LocNotes: "",
|
||||
LocFavorite: true,
|
||||
PhotoCount: 2,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package entity
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/s2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -67,8 +66,8 @@ func TestPlace_Find(t *testing.T) {
|
|||
LocNotes: "",
|
||||
LocFavorite: false,
|
||||
PhotoCount: 0,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: Timestamp(),
|
||||
UpdatedAt: Timestamp(),
|
||||
New: false,
|
||||
}
|
||||
err := place.Find()
|
||||
|
|
15
internal/entity/time.go
Normal file
15
internal/entity/time.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
const Day = time.Hour * 24
|
||||
|
||||
// Timestamp returns the current time in UTC rounded to seconds.
|
||||
func Timestamp() time.Time {
|
||||
return time.Now().UTC().Round(time.Second)
|
||||
}
|
||||
|
||||
// Seconds converts an int to a duration in seconds.
|
||||
func Seconds(s int) time.Duration {
|
||||
return time.Duration(s) * time.Second
|
||||
}
|
18
internal/entity/time_test.go
Normal file
18
internal/entity/time_test.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTimestamp(t *testing.T) {
|
||||
result := Timestamp()
|
||||
|
||||
if result.Location() != time.UTC {
|
||||
t.Fatal("timestamp zone must be utc")
|
||||
}
|
||||
|
||||
if result.After(time.Now().Add(time.Second)) {
|
||||
t.Fatal("timestamp should be in the past from now")
|
||||
}
|
||||
}
|
|
@ -38,15 +38,15 @@ func New(expiration time.Duration, cachePath string) *Session {
|
|||
var shared []string
|
||||
|
||||
for _, token := range saved.Tokens {
|
||||
links := entity.FindLinks(token, "")
|
||||
links := entity.FindValidLinks(token, "")
|
||||
|
||||
if len(links) > 0 {
|
||||
for _, link := range links {
|
||||
shared = append(shared, link.LinkUID)
|
||||
}
|
||||
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data := Data{User: *user, Tokens: tokens, Shares: shared}
|
||||
|
@ -64,6 +64,7 @@ func New(expiration time.Duration, cachePath string) *Session {
|
|||
return s
|
||||
}
|
||||
|
||||
// Stores all sessions in a JSON file.
|
||||
func (s *Session) Save() error {
|
||||
if s.cacheFile == "" {
|
||||
return nil
|
||||
|
|
Loading…
Reference in a new issue