125 lines
2.8 KiB
Go
125 lines
2.8 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"spark-server/internal/module/user/entity"
|
|
"spark-server/internal/pkg/errs"
|
|
)
|
|
|
|
// UserDataCleaner removes a canceled user's rows in another module.
|
|
type UserDataCleaner interface {
|
|
PurgeUser(ctx context.Context, userID int64) error
|
|
}
|
|
|
|
// TokenRevoker drops every live JWT pair for a user. Auth JWT implements it.
|
|
type TokenRevoker interface {
|
|
LogoutAll(ctx context.Context, userID int64) error
|
|
}
|
|
|
|
// SessionDropper kicks every TCP connection for a user.
|
|
type SessionDropper interface {
|
|
KickUser(userID int64)
|
|
}
|
|
|
|
type cleanerFunc func(context.Context, int64) error
|
|
|
|
func (f cleanerFunc) PurgeUser(ctx context.Context, userID int64) error {
|
|
if f == nil {
|
|
return nil
|
|
}
|
|
return f(ctx, userID)
|
|
}
|
|
|
|
// CleanerFunc adapts a function to UserDataCleaner.
|
|
func CleanerFunc(fn func(context.Context, int64) error) UserDataCleaner {
|
|
return cleanerFunc(fn)
|
|
}
|
|
|
|
// BindAccountCleanup wires token revoke, TCP kick, and cross-module purge for 注销.
|
|
func (s *Service) BindAccountCleanup(tokens TokenRevoker, sessions SessionDropper, cleaners ...UserDataCleaner) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.tokens = tokens
|
|
s.sessions = sessions
|
|
s.cleaners = append(s.cleaners[:0], cleaners...)
|
|
}
|
|
|
|
func (s *Service) DeleteAccount(ctx context.Context, userID int64) error {
|
|
if !s.ready() || userID <= 0 {
|
|
return errs.ErrUnauthorized
|
|
}
|
|
u, err := s.repo.GetByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
email, err := s.lookupEmail(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.revokeAccess(ctx, userID)
|
|
if err := s.purgeRelated(ctx, userID); err != nil {
|
|
return err
|
|
}
|
|
if err := s.repo.ArchiveAndDelete(ctx, entity.SnapshotDeleted(u, email, time.Now())); err != nil {
|
|
return err
|
|
}
|
|
s.deleteAvatarObject(ctx, u.Avatar)
|
|
if email != "" && s.codes != nil {
|
|
_ = s.codes.Del(ctx, email)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) lookupEmail(ctx context.Context, userID int64) (string, error) {
|
|
email, err := s.repo.GetEmail(ctx, userID)
|
|
if err == nil {
|
|
return email, nil
|
|
}
|
|
if errors.Is(err, errs.ErrNotFound) {
|
|
return "", nil
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
func (s *Service) revokeAccess(ctx context.Context, userID int64) {
|
|
if s.tokens != nil {
|
|
_ = s.tokens.LogoutAll(ctx, userID)
|
|
}
|
|
if s.sessions != nil {
|
|
s.sessions.KickUser(userID)
|
|
}
|
|
}
|
|
|
|
func (s *Service) purgeRelated(ctx context.Context, userID int64) error {
|
|
for _, c := range s.cleaners {
|
|
if c == nil {
|
|
continue
|
|
}
|
|
if err := c.PurgeUser(ctx, userID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type objectRemover interface {
|
|
Delete(ctx context.Context, key string) error
|
|
}
|
|
|
|
func (s *Service) deleteAvatarObject(ctx context.Context, avatar string) {
|
|
key := strings.TrimSpace(avatar)
|
|
if !strings.HasPrefix(key, "perm/avatar/") {
|
|
return
|
|
}
|
|
rm, ok := s.objects.(objectRemover)
|
|
if !ok || rm == nil {
|
|
return
|
|
}
|
|
_ = rm.Delete(ctx, key)
|
|
}
|