171 lines
3.9 KiB
Go
171 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"wx_service/internal/model"
|
|
smokemodel "wx_service/internal/smoke/model"
|
|
)
|
|
|
|
var (
|
|
ErrSmokeShareNotFound = errors.New("smoke share not found")
|
|
ErrSmokeShareExpired = errors.New("smoke share expired")
|
|
ErrSmokeShareRevoked = errors.New("smoke share revoked")
|
|
ErrSmokeShareForbidden = errors.New("smoke share forbidden")
|
|
)
|
|
|
|
type SmokeShareService struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
type CreateSmokeShareRequest struct {
|
|
Days int
|
|
}
|
|
|
|
type SmokeShareOwner struct {
|
|
NickName string `json:"nickname"`
|
|
AvatarURL string `json:"avatar_url"`
|
|
}
|
|
|
|
func NewSmokeShareService(db *gorm.DB) *SmokeShareService {
|
|
return &SmokeShareService{db: db}
|
|
}
|
|
|
|
func (s *SmokeShareService) Create(ctx context.Context, uid int, req CreateSmokeShareRequest) (*smokemodel.SmokeShare, error) {
|
|
days := normalizeShareDays(req.Days)
|
|
now := time.Now().In(time.Local)
|
|
|
|
row := &smokemodel.SmokeShare{
|
|
UID: uid,
|
|
ShareToken: generateShareToken(),
|
|
ExpireAt: now.AddDate(0, 0, days),
|
|
}
|
|
|
|
if err := s.db.WithContext(ctx).Create(row).Error; err != nil {
|
|
return nil, fmt.Errorf("create smoke share: %w", err)
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (s *SmokeShareService) GetByToken(ctx context.Context, token string) (*smokemodel.SmokeShare, error) {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return nil, ErrSmokeShareNotFound
|
|
}
|
|
|
|
var row smokemodel.SmokeShare
|
|
if err := s.db.WithContext(ctx).
|
|
Where("share_token = ?", token).
|
|
First(&row).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrSmokeShareNotFound
|
|
}
|
|
return nil, fmt.Errorf("load smoke share by token: %w", err)
|
|
}
|
|
|
|
if row.RevokedAt != nil {
|
|
return nil, ErrSmokeShareRevoked
|
|
}
|
|
if row.ExpireAt.Before(time.Now().In(time.Local)) {
|
|
return nil, ErrSmokeShareExpired
|
|
}
|
|
|
|
return &row, nil
|
|
}
|
|
|
|
func (s *SmokeShareService) Revoke(ctx context.Context, uid int, token string) error {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return ErrSmokeShareNotFound
|
|
}
|
|
|
|
var row smokemodel.SmokeShare
|
|
if err := s.db.WithContext(ctx).
|
|
Where("share_token = ?", token).
|
|
First(&row).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return ErrSmokeShareNotFound
|
|
}
|
|
return fmt.Errorf("load smoke share for revoke: %w", err)
|
|
}
|
|
if row.UID != uid {
|
|
return ErrSmokeShareForbidden
|
|
}
|
|
if row.RevokedAt != nil {
|
|
return nil
|
|
}
|
|
|
|
now := time.Now().In(time.Local)
|
|
if err := s.db.WithContext(ctx).
|
|
Model(&smokemodel.SmokeShare{}).
|
|
Where("id = ?", row.ID).
|
|
Update("revoked_at", now).Error; err != nil {
|
|
return fmt.Errorf("revoke smoke share: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SmokeShareService) TouchViewed(ctx context.Context, id uint) error {
|
|
now := time.Now().In(time.Local)
|
|
if err := s.db.WithContext(ctx).
|
|
Model(&smokemodel.SmokeShare{}).
|
|
Where("id = ?", id).
|
|
Updates(map[string]any{
|
|
"last_viewed_at": now,
|
|
"view_count": gorm.Expr("view_count + 1"),
|
|
}).Error; err != nil {
|
|
return fmt.Errorf("touch smoke share viewed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SmokeShareService) GetOwnerPublic(ctx context.Context, uid int) (*SmokeShareOwner, error) {
|
|
var user model.User
|
|
if err := s.db.WithContext(ctx).
|
|
Select("id, nick_name, avatar_url").
|
|
Where("id = ?", uid).
|
|
First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return &SmokeShareOwner{NickName: "戒烟用户"}, nil
|
|
}
|
|
return nil, fmt.Errorf("load share owner: %w", err)
|
|
}
|
|
|
|
name := strings.TrimSpace(user.NickName)
|
|
if name == "" {
|
|
name = "戒烟用户"
|
|
}
|
|
|
|
return &SmokeShareOwner{
|
|
NickName: name,
|
|
AvatarURL: user.AvatarURL,
|
|
}, nil
|
|
}
|
|
|
|
func normalizeShareDays(days int) int {
|
|
if days <= 0 {
|
|
return 7
|
|
}
|
|
if days > 30 {
|
|
return 30
|
|
}
|
|
return days
|
|
}
|
|
|
|
func generateShareToken() string {
|
|
buf := make([]byte, 16)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
fallback := time.Now().UnixNano()
|
|
return fmt.Sprintf("%x", fallback)
|
|
}
|
|
return hex.EncodeToString(buf)
|
|
}
|