diff --git a/frontend/src/model/link.js b/frontend/src/model/link.js index 82a4b979a..281478bbe 100644 --- a/frontend/src/model/link.js +++ b/frontend/src/model/link.js @@ -40,6 +40,8 @@ export default class Link extends Model { Slug: "", Token: "", Expires: 0, + Views: 0, + MaxViews: 0, Password: "", HasPassword: false, CanComment: false, diff --git a/internal/api/link.go b/internal/api/link.go index cdca1a358..dd6b322c3 100644 --- a/internal/api/link.go +++ b/internal/api/link.go @@ -33,10 +33,10 @@ func UpdateLink(c *gin.Context) { link.SetSlug(f.ShareSlug) link.MaxViews = f.MaxViews - link.ShareExpires = f.ShareExpires + link.LinkExpires = f.LinkExpires - if f.ShareToken != "" { - link.ShareToken = strings.ToLower(f.ShareToken) + if f.LinkToken != "" { + link.LinkToken = strings.ToLower(f.LinkToken) } if f.Password != "" { @@ -97,7 +97,7 @@ func CreateLink(c *gin.Context) { link.SetSlug(f.ShareSlug) link.MaxViews = f.MaxViews - link.ShareExpires = f.ShareExpires + link.LinkExpires = f.LinkExpires if f.Password != "" { if err := link.SetPassword(f.Password); err != nil { diff --git a/internal/api/link_test.go b/internal/api/link_test.go index c1cf89874..fee9c7335 100644 --- a/internal/api/link_test.go +++ b/internal/api/link_test.go @@ -30,9 +30,9 @@ func TestLinkAlbum(t *testing.T) { assert.NotEmpty(t, link.LinkUID) assert.NotEmpty(t, link.ShareUID) - assert.NotEmpty(t, link.ShareToken) + assert.NotEmpty(t, link.LinkToken) assert.Equal(t, true, link.CanEdit) - assert.Equal(t, 0, link.ShareExpires) + assert.Equal(t, 0, link.LinkExpires) assert.False(t, link.CanComment) assert.True(t, link.CanEdit) }) @@ -78,8 +78,8 @@ func TestLinkPhoto(t *testing.T) { assert.NotEmpty(t, link.LinkUID) assert.NotEmpty(t, link.ShareUID) - assert.NotEmpty(t, link.ShareToken) - assert.Equal(t, 0, link.ShareExpires) + assert.NotEmpty(t, link.LinkToken) + assert.Equal(t, 0, link.LinkExpires) assert.False(t, link.CanComment) assert.True(t, link.CanEdit) }) @@ -119,8 +119,8 @@ func TestLinkLabel(t *testing.T) { assert.NotEmpty(t, link.LinkUID) assert.NotEmpty(t, link.ShareUID) - assert.NotEmpty(t, link.ShareToken) - assert.Equal(t, 0, link.ShareExpires) + assert.NotEmpty(t, link.LinkToken) + assert.Equal(t, 0, link.LinkExpires) assert.False(t, link.CanComment) assert.True(t, link.CanEdit) }) diff --git a/internal/api/share.go b/internal/api/share.go index cffe32de8..992c796de 100644 --- a/internal/api/share.go +++ b/internal/api/share.go @@ -15,9 +15,9 @@ func Shares(router *gin.RouterGroup) { router.GET("/:token", func(c *gin.Context) { conf := service.Config() - shareToken := c.Param("token") + token := c.Param("token") - links := entity.FindValidLinks(shareToken, "") + links := entity.FindValidLinks(token, "") if len(links) == 0 { log.Warn("share: invalid token") @@ -26,21 +26,21 @@ func Shares(router *gin.RouterGroup) { } clientConfig := conf.GuestConfig() - clientConfig.SiteUrl = fmt.Sprintf("%ss/%s", clientConfig.SiteUrl, shareToken) + clientConfig.SiteUrl = fmt.Sprintf("%ss/%s", clientConfig.SiteUrl, token) c.HTML(http.StatusOK, "share.tmpl", gin.H{"config": clientConfig}) }) - router.GET("/:token/:uid", func(c *gin.Context) { + router.GET("/:token/:share", func(c *gin.Context) { conf := service.Config() - shareToken := c.Param("token") - share := c.Param("uid") + token := c.Param("token") + share := c.Param("share") - links := entity.FindValidLinks(shareToken, share) + links := entity.FindValidLinks(token, share) if len(links) != 1 { - log.Warn("share: invalid token or uid") + log.Warn("share: invalid token or share") c.Redirect(http.StatusTemporaryRedirect, "/") return } @@ -48,12 +48,12 @@ func Shares(router *gin.RouterGroup) { uid := links[0].ShareUID if uid != share { - c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("/s/%s/%s", shareToken, uid)) + c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("/s/%s/%s", token, uid)) return } clientConfig := conf.GuestConfig() - clientConfig.SiteUrl = fmt.Sprintf("%ss/%s/%s", clientConfig.SiteUrl, shareToken, uid) + clientConfig.SiteUrl = fmt.Sprintf("%ss/%s/%s", clientConfig.SiteUrl, token, uid) clientConfig.SitePreview = fmt.Sprintf("%s/preview", clientConfig.SiteUrl) if a, err := query.AlbumByUID(uid); err == nil { diff --git a/internal/api/share_preview.go b/internal/api/share_preview.go index 6dd54d02e..04e718d85 100644 --- a/internal/api/share_preview.go +++ b/internal/api/share_preview.go @@ -24,12 +24,12 @@ import ( // GET /s/:token/:uid/preview // TODO: Proof of concept, needs refactoring. func SharePreview(router *gin.RouterGroup) { - router.GET("/:token/:uid/preview", func(c *gin.Context) { + router.GET("/:token/:share/preview", func(c *gin.Context) { conf := service.Config() - shareToken := c.Param("token") - shareUID := c.Param("uid") - links := entity.FindLinks(shareToken, shareUID) + token := c.Param("token") + share := c.Param("share") + links := entity.FindLinks(token, share) if len(links) != 1 { log.Warn("share: invalid token (preview)") @@ -45,17 +45,17 @@ func SharePreview(router *gin.RouterGroup) { return } - previewFilename := fmt.Sprintf("%s/%s.jpg", thumbPath, shareUID) + previewFilename := fmt.Sprintf("%s/%s.jpg", thumbPath, share) yesterday := time.Now().Add(-24 * time.Hour) if info, err := os.Stat(previewFilename); err != nil { - log.Debugf("share: creating new preview for %s", shareUID) + log.Debugf("share: creating new preview for %s", share) } else if info.ModTime().After(yesterday) { - log.Debugf("share: using cached preview for %s", shareUID) + log.Debugf("share: using cached preview for %s", share) c.File(previewFilename) return } else if err := os.Remove(previewFilename); err != nil { - log.Errorf("share: could not remove old preview of %s", shareUID) + log.Errorf("share: could not remove old preview of %s", share) c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview()) return } @@ -63,7 +63,7 @@ func SharePreview(router *gin.RouterGroup) { var f form.PhotoSearch // Previews may only contain public content in shared albums. - f.Album = shareUID + f.Album = share f.Public = true f.Private = false f.Hidden = false diff --git a/internal/entity/link.go b/internal/entity/link.go index 98555c254..25890b28b 100644 --- a/internal/entity/link.go +++ b/internal/entity/link.go @@ -15,18 +15,18 @@ type Links []Link // Link represents a sharing link. 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:"Share" yaml:"Share"` - ShareSlug string `gorm:"type:varbinary(255);index;" json:"Slug" yaml:"Slug,omitempty"` - ShareToken string `gorm:"type:varbinary(255);unique_index:idx_links_uid_token;" json:"Token" yaml:"Token,omitempty"` - ShareExpires int `json:"Expires" yaml:"Expires,omitempty"` - ShareViews uint `json:"ShareViews" yaml:"-"` - MaxViews uint `json:"MaxViews" 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"` + 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:"Share" yaml:"Share"` + ShareSlug string `gorm:"type:varbinary(255);index;" json:"Slug" yaml:"Slug,omitempty"` + LinkToken string `gorm:"type:varbinary(255);unique_index:idx_links_uid_token;" json:"Token" yaml:"Token,omitempty"` + LinkExpires int `json:"Expires" yaml:"Expires,omitempty"` + LinkViews uint `json:"Views" yaml:"-"` + MaxViews uint `json:"MaxViews" 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. @@ -45,7 +45,7 @@ func NewLink(shareUID string, canComment, canEdit bool) Link { result := Link{ LinkUID: rnd.PPID('s'), ShareUID: shareUID, - ShareToken: rnd.Token(10), + LinkToken: rnd.Token(10), CanComment: canComment, CanEdit: canEdit, CreatedAt: now, @@ -56,9 +56,9 @@ func NewLink(shareUID string, canComment, canEdit bool) Link { } func (m *Link) Redeem() { - m.ShareViews += 1 + m.LinkViews += 1 - result := Db().Model(m).UpdateColumn("ShareViews", m.ShareViews) + result := Db().Model(m).UpdateColumn("LinkViews", m.LinkViews) if result.RowsAffected == 0 { log.Warnf("link: failed updating share view counter for %s", m.LinkUID) @@ -66,16 +66,16 @@ func (m *Link) Redeem() { } func (m *Link) Expired() bool { - if m.MaxViews > 0 && m.ShareViews >= m.MaxViews { + if m.MaxViews > 0 && m.LinkViews >= m.MaxViews { return true } - if m.ShareExpires <= 0 { + if m.LinkExpires <= 0 { return false } now := Timestamp() - expires := m.ModifiedAt.Add(Seconds(m.ShareExpires)) + expires := m.ModifiedAt.Add(Seconds(m.LinkExpires)) return now.Before(expires) } @@ -116,7 +116,7 @@ func (m *Link) Save() error { return fmt.Errorf("link: invalid share uid (%s)", m.ShareUID) } - if m.ShareToken == "" { + if m.LinkToken == "" { return fmt.Errorf("link: empty share token") } @@ -127,7 +127,7 @@ func (m *Link) Save() error { // Deletes the link. func (m *Link) Delete() error { - if m.ShareToken == "" { + if m.LinkToken == "" { return fmt.Errorf("link: empty share token") } @@ -148,16 +148,16 @@ func FindLink(linkUID string) *Link { } // FindLinks returns a slice of links for a token and share UID (at least one must be provided). -func FindLinks(shareToken, share string) (result Links) { - if shareToken == "" && share == "" { +func FindLinks(token, share string) (result Links) { + if token == "" && share == "" { log.Errorf("link: share token and uid must not be empty at the same time (find links)") return []Link{} } q := Db() - if shareToken != "" { - q = q.Where("share_token = ?", shareToken) + if token != "" { + q = q.Where("link_token = ?", token) } if share != "" { @@ -176,8 +176,8 @@ func FindLinks(shareToken, share string) (result Links) { } // 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) { +func FindValidLinks(token, share string) (result Links) { + for _, link := range FindLinks(token, share) { if !link.Expired() { result = append(result, link) } @@ -188,5 +188,5 @@ func FindValidLinks(shareToken, shareUID string) (result Links) { // String returns an human readable identifier for logging. func (m *Link) String() string { - return fmt.Sprintf("%s/%s", m.ShareUID, m.ShareToken) + return m.LinkUID } diff --git a/internal/entity/link_fixtures.go b/internal/entity/link_fixtures.go index a8d04f5d9..287c4ef3f 100644 --- a/internal/entity/link_fixtures.go +++ b/internal/entity/link_fixtures.go @@ -8,12 +8,12 @@ type LinkMap map[string]Link var LinkFixtures = LinkMap{ "1jxf3jfn2k": { - ShareToken: "1jxf3jfn2k", - ShareExpires: 0, - ShareUID: "st9lxuqxpogaaba7", - CanComment: true, - CanEdit: false, - CreatedAt: time.Date(2020, 3, 6, 2, 6, 51, 0, time.UTC), + LinkToken: "1jxf3jfn2k", + LinkExpires: 0, + ShareUID: "st9lxuqxpogaaba7", + CanComment: true, + CanEdit: false, + CreatedAt: time.Date(2020, 3, 6, 2, 6, 51, 0, time.UTC), }, } diff --git a/internal/entity/link_test.go b/internal/entity/link_test.go index bcd888d0b..e4960ae30 100644 --- a/internal/entity/link_test.go +++ b/internal/entity/link_test.go @@ -12,7 +12,7 @@ func TestNewLink(t *testing.T) { assert.Equal(t, "st9lxuqxpogaaba1", link.ShareUID) assert.Equal(t, false, link.CanEdit) assert.Equal(t, true, link.CanComment) - assert.Equal(t, 10, len(link.ShareToken)) + assert.Equal(t, 10, len(link.LinkToken)) assert.Equal(t, 16, len(link.LinkUID)) } @@ -22,20 +22,20 @@ func TestLink_Expired(t *testing.T) { link := NewLink("st9lxuqxpogaaba1", true, false) link.ModifiedAt = Timestamp().Add(-7 * Day) - link.ShareExpires = 0 + link.LinkExpires = 0 assert.False(t, link.Expired()) - link.ShareExpires = oneDay + link.LinkExpires = oneDay assert.False(t, link.Expired()) - link.ShareExpires = oneDay * 8 + link.LinkExpires = oneDay * 8 assert.True(t, link.Expired()) - link.ShareExpires = oneDay - link.ShareViews = 9 + link.LinkExpires = oneDay + link.LinkViews = 9 link.MaxViews = 10 assert.False(t, link.Expired()) @@ -48,11 +48,11 @@ func TestLink_Expired(t *testing.T) { func TestLink_Redeem(t *testing.T) { link := NewLink(rnd.PPID('a'), false, false) - assert.Equal(t, uint(0), link.ShareViews) + assert.Equal(t, uint(0), link.LinkViews) link.Redeem() - assert.Equal(t, uint(1), link.ShareViews) + assert.Equal(t, uint(1), link.LinkViews) if err := link.Save(); err != nil { t.Fatal(err) @@ -60,5 +60,5 @@ func TestLink_Redeem(t *testing.T) { link.Redeem() - assert.Equal(t, uint(2), link.ShareViews) + assert.Equal(t, uint(2), link.LinkViews) } diff --git a/internal/form/link.go b/internal/form/link.go index d4b5b90fb..56e2fab9a 100644 --- a/internal/form/link.go +++ b/internal/form/link.go @@ -2,11 +2,11 @@ package form // Link represents a link sharing form. type Link struct { - Password string `json:"Password"` - ShareSlug string `json:"Slug"` - ShareToken string `json:"Token"` - ShareExpires int `json:"Expires"` - MaxViews uint `json:"MaxViews"` - CanComment bool `json:"CanComment"` - CanEdit bool `json:"CanEdit"` + Password string `json:"Password"` + ShareSlug string `json:"Slug"` + LinkToken string `json:"Token"` + LinkExpires int `json:"Expires"` + MaxViews uint `json:"MaxViews"` + CanComment bool `json:"CanComment"` + CanEdit bool `json:"CanEdit"` }