// Package logic is the user application use-case layer, split by function (profile, login, email code). package logic import ( "context" "time" smsentity "spark-server/internal/module/sms/entity" "spark-server/internal/module/user/entity" ) // Repository is user persistence. Model implements it; do not split this by table. type Repository interface { GetByID(ctx context.Context, id int64) (*entity.User, error) GetByEmail(ctx context.Context, email string) (*entity.User, error) GetBySparkID(ctx context.Context, sparkID string) (*entity.User, error) GetByQRCode(ctx context.Context, qrcode string) (*entity.User, error) CreateWithEmail(ctx context.Context, u *entity.User, email string) error UpdateSparkID(ctx context.Context, userID int64, sparkID string) error UpdateQRCode(ctx context.Context, userID int64, qrcode string) error UpdateProfile(ctx context.Context, u *entity.User) error TouchLogin(ctx context.Context, userID int64, at time.Time) error UpsertDevice(ctx context.Context, d *entity.UserDeviceLink) error GetDevice(ctx context.Context, userID int64, deviceID uint64) (*entity.UserDeviceLink, error) TouchDeviceActive(ctx context.Context, userID int64, deviceID uint64, at time.Time) error InsertLoginLog(ctx context.Context, log *entity.LoginLog) error GetEmail(ctx context.Context, userID int64) (string, error) GetDeleted(ctx context.Context, id int64) (*entity.Deleted, error) ArchiveAndDelete(ctx context.Context, d *entity.Deleted) error } // IDGen issues time-ordered Snowflake IDs (user PK). type IDGen interface { NextID() (int64, error) } // Objects stores the generated default avatar. Nil or Put error keeps an SVG data-URI fallback. type Objects interface { Put(ctx context.Context, key string, body []byte, contentType string) error } // OTP checks and clears verification codes issued by the sms module. type OTP interface { CheckAndConsume(ctx context.Context, ch smsentity.Channel, purpose smsentity.Purpose, target, code string) error ClearTarget(ctx context.Context, ch smsentity.Channel, target string) } // Service is the user application use-case (HTTP and TCP share it). type Service struct { repo Repository otp OTP ids IDGen objects Objects tokens TokenRevoker sessions SessionDropper cleaners []UserDataCleaner } func NewService(repo Repository, otp OTP, ids IDGen, objects Objects) *Service { return &Service{repo: repo, otp: otp, ids: ids, objects: objects} } func (s *Service) ready() bool { return s != nil && s.repo != nil && s.ids != nil }