package logic import ( "context" "strconv" "time" "spark-server/internal/pkg/media" "spark-server/internal/pkg/ratelimit" "go.uber.org/zap" ) // Presigner issues short-lived object-storage URLs. Infrastructure implements it. type Presigner interface { PresignPut(ctx context.Context, key, contentType string, size int64, ttl time.Duration) (string, error) PresignGet(ctx context.Context, key string, ttl time.Duration) (string, error) } type AvatarACL interface { CanSetAvatar(ctx context.Context, userID, groupID int64) error } type Options struct { TTL time.Duration Limit *ratelimit.Limiter Now func() time.Time Groups AvatarACL Media media.Policy Log *zap.Logger } // Service issues upload/download credentials. HTTP and TCP share it. type Service struct { store Presigner ttl time.Duration limit *ratelimit.Limiter now func() time.Time groups AvatarACL media media.Policy log *zap.Logger } func NewService(store Presigner, opt Options) *Service { ttl := opt.TTL if ttl <= 0 { ttl = 15 * time.Minute } clock := opt.Now if clock == nil { clock = func() time.Time { return time.Now().UTC() } } log := opt.Log if log == nil { log = zap.NewNop() } return &Service{store: store, ttl: ttl, limit: opt.Limit, now: clock, groups: opt.Groups, media: opt.Media, log: log} } func (s *Service) ready() bool { return s != nil && s.store != nil } func (s *Service) expiresIn() int64 { return int64(s.ttl / time.Second) } func (s *Service) allow(userID int64) bool { if s.limit == nil { return true } return s.limit.Allow("upload:" + strconv.FormatInt(userID, 10)) } func dayOf(t time.Time) string { return t.UTC().Format("20060102") }