feat: 更新SproutGate前后端代码
This commit is contained in:
219
sproutgate-backend/internal/storage/profile_likes.go
Normal file
219
sproutgate-backend/internal/storage/profile_likes.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"sproutgate-backend/internal/models"
|
||||
)
|
||||
|
||||
// MaxProfileLikesPerDay 每个点赞者每个自然日最多给多少位不同用户的公开主页点赞。
|
||||
const MaxProfileLikesPerDay = 5
|
||||
|
||||
var (
|
||||
ErrCannotLikeOwnProfile = errors.New("cannot like own profile")
|
||||
ErrAlreadyLikedToday = errors.New("already liked today")
|
||||
ErrDailyLikeLimitReached = errors.New("daily like limit reached")
|
||||
ErrProfileLikeTarget = errors.New("user not found")
|
||||
ErrProfileLikeLiker = errors.New("invalid liker")
|
||||
)
|
||||
|
||||
// DBProfileLikeDailyQuota 点赞者当日剩余可点赞人数(与 profile_likes 计数同步;新自然日新行即视为刷新为满额)。
|
||||
type DBProfileLikeDailyQuota struct {
|
||||
LikerAccount string `gorm:"primaryKey;column:liker_account;size:255"`
|
||||
LikeDate string `gorm:"primaryKey;column:like_date;size:10"`
|
||||
Remaining int `gorm:"column:remaining;not null"`
|
||||
UpdatedAt string `gorm:"column:updated_at;size:50"`
|
||||
}
|
||||
|
||||
func (DBProfileLikeDailyQuota) TableName() string { return "profile_like_daily_quota" }
|
||||
|
||||
// DBProfileLike 记录「谁在某日给谁的主页点赞」(每人每天对同一主页最多一条)。
|
||||
type DBProfileLike struct {
|
||||
LikerAccount string `gorm:"primaryKey;column:liker_account;size:255"`
|
||||
LikedAccount string `gorm:"primaryKey;column:liked_account;size:255"`
|
||||
LikeDate string `gorm:"primaryKey;column:like_date;size:10"` // YYYY-MM-DD,与签到同历
|
||||
CreatedAt string `gorm:"column:created_at;size:50"`
|
||||
}
|
||||
|
||||
func (DBProfileLike) TableName() string { return "profile_likes" }
|
||||
|
||||
// CountProfileLikes 主页累计获赞次数(历史所有日的点赞条数之和)。
|
||||
func (s *Store) CountProfileLikes(likedAccount string) (int64, error) {
|
||||
likedAccount = strings.TrimSpace(likedAccount)
|
||||
if likedAccount == "" {
|
||||
return 0, nil
|
||||
}
|
||||
var n int64
|
||||
if err := s.db.Model(&DBProfileLike{}).Where("liked_account = ?", likedAccount).Count(&n).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ViewerHasLikedProfileToday 当前自然日(Asia/Shanghai)内是否已为该主页点过赞。
|
||||
func (s *Store) ViewerHasLikedProfileToday(likerAccount, likedAccount string) (bool, error) {
|
||||
likerAccount = strings.TrimSpace(likerAccount)
|
||||
likedAccount = strings.TrimSpace(likedAccount)
|
||||
if likerAccount == "" || likedAccount == "" {
|
||||
return false, nil
|
||||
}
|
||||
today := models.CurrentActivityDate()
|
||||
var n int64
|
||||
err := s.db.Model(&DBProfileLike{}).
|
||||
Where("liker_account = ? AND liked_account = ? AND like_date = ?", likerAccount, likedAccount, today).
|
||||
Count(&n).Error
|
||||
return n > 0, err
|
||||
}
|
||||
|
||||
// ProfileLikeRemainingToday 当日还可给多少人点赞(根据 profile_likes 计数计算,并与 quota 表对齐)。
|
||||
func (s *Store) ProfileLikeRemainingToday(likerAccount string) (int, error) {
|
||||
likerAccount = strings.TrimSpace(likerAccount)
|
||||
if likerAccount == "" {
|
||||
return 0, nil
|
||||
}
|
||||
today := models.CurrentActivityDate()
|
||||
var used int64
|
||||
if err := s.db.Model(&DBProfileLike{}).
|
||||
Where("liker_account = ? AND like_date = ?", likerAccount, today).
|
||||
Count(&used).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rem := MaxProfileLikesPerDay - int(used)
|
||||
if rem < 0 {
|
||||
rem = 0
|
||||
}
|
||||
var q DBProfileLikeDailyQuota
|
||||
err := s.db.Where("liker_account = ? AND like_date = ?", likerAccount, today).First(&q).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return rem, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if q.Remaining != rem {
|
||||
_ = s.db.Model(&DBProfileLikeDailyQuota{}).
|
||||
Where("liker_account = ? AND like_date = ?", likerAccount, today).
|
||||
Updates(map[string]interface{}{
|
||||
"remaining": rem,
|
||||
"updated_at": models.NowISO(),
|
||||
}).Error
|
||||
}
|
||||
return rem, nil
|
||||
}
|
||||
|
||||
// AddProfileLike 为公开主页点赞(每人每天每主页一次;每人每天最多给 MaxProfileLikesPerDay 位用户点赞)。返回新的累计赞数。
|
||||
func (s *Store) AddProfileLike(likerAccount, likedAccount string) (int64, error) {
|
||||
likerAccount = strings.TrimSpace(likerAccount)
|
||||
likedAccount = strings.TrimSpace(likedAccount)
|
||||
if likerAccount == "" || likedAccount == "" {
|
||||
return 0, errors.New("account is required")
|
||||
}
|
||||
if strings.EqualFold(likerAccount, likedAccount) {
|
||||
return 0, ErrCannotLikeOwnProfile
|
||||
}
|
||||
|
||||
likedUser, found, err := s.GetUser(likedAccount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !found || likedUser.Banned {
|
||||
return 0, ErrProfileLikeTarget
|
||||
}
|
||||
likerUser, foundL, err := s.GetUser(likerAccount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !foundL || likerUser.Banned {
|
||||
return 0, ErrProfileLikeLiker
|
||||
}
|
||||
|
||||
today := models.CurrentActivityDate()
|
||||
likerCanon := likerUser.Account
|
||||
likedCanon := likedUser.Account
|
||||
row := DBProfileLike{
|
||||
LikerAccount: likerCanon,
|
||||
LikedAccount: likedCanon,
|
||||
LikeDate: today,
|
||||
CreatedAt: models.NowISO(),
|
||||
}
|
||||
|
||||
txErr := s.db.Transaction(func(tx *gorm.DB) error {
|
||||
var quota DBProfileLikeDailyQuota
|
||||
qErr := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("liker_account = ? AND like_date = ?", likerCanon, today).
|
||||
First("a).Error
|
||||
if errors.Is(qErr, gorm.ErrRecordNotFound) {
|
||||
quota = DBProfileLikeDailyQuota{
|
||||
LikerAccount: likerCanon,
|
||||
LikeDate: today,
|
||||
Remaining: MaxProfileLikesPerDay,
|
||||
UpdatedAt: models.NowISO(),
|
||||
}
|
||||
if cerr := tx.Create("a).Error; cerr != nil {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("liker_account = ? AND like_date = ?", likerCanon, today).
|
||||
First("a).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if qErr != nil {
|
||||
return qErr
|
||||
}
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("liker_account = ? AND like_date = ?", likerCanon, today).
|
||||
First("a).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var used int64
|
||||
if err := tx.Model(&DBProfileLike{}).
|
||||
Where("liker_account = ? AND like_date = ?", likerCanon, today).
|
||||
Count(&used).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if int(used) >= MaxProfileLikesPerDay {
|
||||
if err := tx.Model(&DBProfileLikeDailyQuota{}).
|
||||
Where("liker_account = ? AND like_date = ?", likerCanon, today).
|
||||
Updates(map[string]interface{}{
|
||||
"remaining": 0,
|
||||
"updated_at": models.NowISO(),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrDailyLikeLimitReached
|
||||
}
|
||||
|
||||
res := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&row)
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return ErrAlreadyLikedToday
|
||||
}
|
||||
|
||||
var usedAfter int64
|
||||
if err := tx.Model(&DBProfileLike{}).
|
||||
Where("liker_account = ? AND like_date = ?", likerCanon, today).
|
||||
Count(&usedAfter).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
rem := MaxProfileLikesPerDay - int(usedAfter)
|
||||
if rem < 0 {
|
||||
rem = 0
|
||||
}
|
||||
return tx.Model(&DBProfileLikeDailyQuota{}).
|
||||
Where("liker_account = ? AND like_date = ?", likerCanon, today).
|
||||
Updates(map[string]interface{}{
|
||||
"remaining": rem,
|
||||
"updated_at": models.NowISO(),
|
||||
}).Error
|
||||
})
|
||||
if txErr != nil {
|
||||
return 0, txErr
|
||||
}
|
||||
return s.CountProfileLikes(likedCanon)
|
||||
}
|
||||
Reference in New Issue
Block a user