Files
wx_service/internal/smoke/service/smoke_share_service.go
T

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)
}