Skip to content

Commit 76f1ff6

Browse files
authored
Merge pull request #9458 from okatu-loli/feat/short-link-support
feat: short link support
2 parents 53a111b + c4595e5 commit 76f1ff6

8 files changed

Lines changed: 1164 additions & 1 deletion

File tree

internal/db/db.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var db *gorm.DB
1212

1313
func Init(d *gorm.DB) {
1414
db = d
15-
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.Role), new(model.Label), new(model.LabelFileBinding), new(model.ObjFile), new(model.Session))
15+
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.Role), new(model.Label), new(model.LabelFileBinding), new(model.ObjFile), new(model.Session), new(model.Share))
1616
if err != nil {
1717
log.Fatalf("failed migrate database: %s", err.Error())
1818
}

internal/db/share.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package db
2+
3+
import (
4+
"time"
5+
6+
"github.com/alist-org/alist/v3/internal/model"
7+
"gorm.io/gorm"
8+
"gorm.io/gorm/clause"
9+
)
10+
11+
func GetShareByShareID(shareID string) (*model.Share, error) {
12+
var share model.Share
13+
if err := db.Where("share_id = ?", shareID).Take(&share).Error; err != nil {
14+
return nil, err
15+
}
16+
return &share, nil
17+
}
18+
19+
func GetShareByCreatorAndShareID(creatorID uint, shareID string) (*model.Share, error) {
20+
var share model.Share
21+
if err := db.Where("creator_id = ? AND share_id = ?", creatorID, shareID).Take(&share).Error; err != nil {
22+
return nil, err
23+
}
24+
return &share, nil
25+
}
26+
27+
func ShareIDExists(shareID string) (bool, error) {
28+
var count int64
29+
if err := db.Model(&model.Share{}).Where("share_id = ?", shareID).Count(&count).Error; err != nil {
30+
return false, err
31+
}
32+
return count > 0, nil
33+
}
34+
35+
func ShareIDExistsExceptID(shareID string, id uint) (bool, error) {
36+
var count int64
37+
if err := db.Model(&model.Share{}).Where("share_id = ? AND id <> ?", shareID, id).Count(&count).Error; err != nil {
38+
return false, err
39+
}
40+
return count > 0, nil
41+
}
42+
43+
func CreateShare(share *model.Share) error {
44+
return db.Create(share).Error
45+
}
46+
47+
func UpdateShare(share *model.Share) error {
48+
return db.Save(share).Error
49+
}
50+
51+
func GetSharesByCreator(creatorID uint, pageIndex, pageSize int) (shares []model.Share, count int64, err error) {
52+
tx := db.Model(&model.Share{}).Where("creator_id = ?", creatorID)
53+
err = tx.Count(&count).Error
54+
if err != nil {
55+
return nil, 0, err
56+
}
57+
err = tx.Order("created_at desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&shares).Error
58+
return
59+
}
60+
61+
func DeleteShareByShareID(creatorID uint, shareID string) error {
62+
return db.Where("creator_id = ? AND share_id = ?", creatorID, shareID).Delete(&model.Share{}).Error
63+
}
64+
65+
func DisableShareByShareID(creatorID uint, shareID string) error {
66+
return db.Model(&model.Share{}).
67+
Where("creator_id = ? AND share_id = ?", creatorID, shareID).
68+
Update("enabled", false).Error
69+
}
70+
71+
func TouchShareView(shareID string) error {
72+
now := time.Now()
73+
return db.Model(&model.Share{}).
74+
Where("share_id = ?", shareID).
75+
UpdateColumns(map[string]interface{}{
76+
"last_access_at": now,
77+
"view_count": gorm.Expr("view_count + ?", 1),
78+
}).Error
79+
}
80+
81+
func TouchShareDownload(shareID string) error {
82+
now := time.Now()
83+
return db.Model(&model.Share{}).
84+
Where("share_id = ?", shareID).
85+
UpdateColumns(map[string]interface{}{
86+
"last_access_at": now,
87+
"download_count": gorm.Expr("download_count + ?", 1),
88+
}).Error
89+
}
90+
91+
func RecordShareAccess(shareID string) (*model.Share, error) {
92+
var updated model.Share
93+
err := db.Transaction(func(tx *gorm.DB) error {
94+
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
95+
Where("share_id = ?", shareID).
96+
Take(&updated).Error; err != nil {
97+
return err
98+
}
99+
100+
now := time.Now()
101+
updated.AccessCount++
102+
updated.LastAccessAt = &now
103+
updates := map[string]interface{}{
104+
"access_count": updated.AccessCount,
105+
"last_access_at": now,
106+
}
107+
108+
limit := updated.EffectiveAccessLimit()
109+
if limit > 0 && updated.AccessCount >= limit {
110+
updated.Enabled = false
111+
updated.ConsumedAt = &now
112+
updates["enabled"] = false
113+
updates["consumed_at"] = now
114+
}
115+
116+
return tx.Model(&model.Share{}).
117+
Where("id = ?", updated.ID).
118+
Updates(updates).Error
119+
})
120+
if err != nil {
121+
return nil, err
122+
}
123+
return &updated, nil
124+
}

internal/model/share.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package model
2+
3+
import "time"
4+
5+
type Share struct {
6+
ID uint `json:"id" gorm:"primaryKey"`
7+
ShareID string `json:"share_id" gorm:"uniqueIndex;size:32;not null"`
8+
CreatorID uint `json:"creator_id" gorm:"index;not null"`
9+
Name string `json:"name" gorm:"size:255;not null"`
10+
RootPath string `json:"root_path" gorm:"size:4096;not null"`
11+
IsDir bool `json:"is_dir"`
12+
PasswordHash string `json:"-" gorm:"size:64"`
13+
PasswordSalt string `json:"-" gorm:"size:32"`
14+
BurnAfterRead bool `json:"burn_after_read" gorm:"default:false"`
15+
AccessLimit int64 `json:"access_limit"`
16+
AccessCount int64 `json:"access_count"`
17+
AllowPreview bool `json:"allow_preview" gorm:"default:true"`
18+
AllowDownload bool `json:"allow_download" gorm:"default:true"`
19+
Enabled bool `json:"enabled" gorm:"default:true;index"`
20+
ViewCount int64 `json:"view_count"`
21+
DownloadCount int64 `json:"download_count"`
22+
LastAccessAt *time.Time `json:"last_access_at"`
23+
ConsumedAt *time.Time `json:"consumed_at"`
24+
ExpiresAt *time.Time `json:"expires_at"`
25+
CreatedAt time.Time `json:"created_at"`
26+
UpdatedAt time.Time `json:"updated_at"`
27+
}
28+
29+
func (s Share) HasPassword() bool {
30+
return s.PasswordHash != ""
31+
}
32+
33+
func (s Share) EffectiveAccessLimit() int64 {
34+
if s.AccessLimit > 0 {
35+
return s.AccessLimit
36+
}
37+
if s.BurnAfterRead {
38+
return 1
39+
}
40+
return 0
41+
}
42+
43+
func (s Share) RemainingAccesses() int64 {
44+
limit := s.EffectiveAccessLimit()
45+
if limit <= 0 {
46+
return 0
47+
}
48+
remaining := limit - s.AccessCount
49+
if remaining < 0 {
50+
return 0
51+
}
52+
return remaining
53+
}
54+
55+
func (s Share) IsConsumed() bool {
56+
limit := s.EffectiveAccessLimit()
57+
return s.ConsumedAt != nil || (limit > 0 && s.AccessCount >= limit)
58+
}
59+
60+
func (s Share) IsExpired(now time.Time) bool {
61+
return s.ExpiresAt != nil && !s.ExpiresAt.After(now)
62+
}

internal/share/access.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package share
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/alist-org/alist/v3/internal/conf"
8+
"github.com/alist-org/alist/v3/internal/model"
9+
"github.com/alist-org/alist/v3/internal/setting"
10+
signPkg "github.com/alist-org/alist/v3/pkg/sign"
11+
)
12+
13+
func tokenPayload(share *model.Share) string {
14+
updatedAt := int64(0)
15+
if !share.UpdatedAt.IsZero() {
16+
updatedAt = share.UpdatedAt.Unix()
17+
}
18+
return fmt.Sprintf("%s:%s:%d", share.ShareID, share.PasswordHash, updatedAt)
19+
}
20+
21+
func signer() signPkg.Sign {
22+
return signPkg.NewHMACSign([]byte(setting.GetStr(conf.Token) + "-share-access"))
23+
}
24+
25+
func SignAccess(share *model.Share, d time.Duration) string {
26+
return signer().Sign(tokenPayload(share), time.Now().Add(d).Unix())
27+
}
28+
29+
func VerifyAccess(share *model.Share, token string) error {
30+
return signer().Verify(tokenPayload(share), token)
31+
}

0 commit comments

Comments
 (0)