package auth import ( "context" "strings" "sync" "time" "img_infinite_canvas/internal/domain/design" ) type MemoryStore struct { mu sync.RWMutex users map[string]User codes map[string]VerificationCode } func NewMemoryStore() *MemoryStore { return &MemoryStore{ users: make(map[string]User), codes: make(map[string]VerificationCode), } } func (s *MemoryStore) FindUserByID(ctx context.Context, id string) (User, error) { if err := ctx.Err(); err != nil { return User{}, err } s.mu.RLock() defer s.mu.RUnlock() user, ok := s.users[strings.TrimSpace(id)] if !ok { return User{}, design.ErrNotFound } return user, nil } func (s *MemoryStore) FindUserByPhone(ctx context.Context, countryCode string, phone string) (User, error) { return s.find(ctx, func(user User) bool { return user.CountryCode == normalizeCountryCode(countryCode) && user.Phone == normalizePhone(phone) }) } func (s *MemoryStore) FindUserByEmail(ctx context.Context, email string) (User, error) { email = normalizeEmail(email) return s.find(ctx, func(user User) bool { return normalizeEmail(user.Email) == email }) } func (s *MemoryStore) FindUserByGoogleSubject(ctx context.Context, subject string) (User, error) { subject = strings.TrimSpace(subject) return s.find(ctx, func(user User) bool { return user.GoogleSubject == subject }) } func (s *MemoryStore) FindUserByWechat(ctx context.Context, openID string, unionID string) (User, error) { openID = strings.TrimSpace(openID) unionID = strings.TrimSpace(unionID) return s.find(ctx, func(user User) bool { if unionID != "" && user.WechatUnionID == unionID { return true } return openID != "" && user.WechatOpenID == openID }) } func (s *MemoryStore) UpsertUser(ctx context.Context, user User) (User, error) { if err := ctx.Err(); err != nil { return User{}, err } user.ID = normalizeUUID(user.ID) user.Email = normalizeEmail(user.Email) user.Phone = normalizePhone(user.Phone) user.CountryCode = normalizeCountryCode(user.CountryCode) user.Name = strings.TrimSpace(user.Name) user.AvatarURL = strings.TrimSpace(user.AvatarURL) user.Status = activeStatus(user.Status) s.mu.Lock() defer s.mu.Unlock() s.users[user.ID] = user return user, nil } func (s *MemoryStore) UpdateUserProfile(ctx context.Context, userID string, patch ProfilePatch) (User, error) { if err := ctx.Err(); err != nil { return User{}, err } userID = normalizeUUID(userID) s.mu.Lock() defer s.mu.Unlock() user, ok := s.users[userID] if !ok { return User{}, design.ErrNotFound } if patch.Name != nil { user.Name = strings.TrimSpace(*patch.Name) } if patch.AvatarURL != nil { user.AvatarURL = strings.TrimSpace(*patch.AvatarURL) } s.users[user.ID] = user return user, nil } func (s *MemoryStore) CreateVerificationCode(ctx context.Context, code VerificationCode) error { if err := ctx.Err(); err != nil { return err } s.mu.Lock() defer s.mu.Unlock() s.codes[code.ID] = code return nil } func (s *MemoryStore) FindActiveVerificationCode(ctx context.Context, region string, channel string, target string, purpose string) (VerificationCode, error) { if err := ctx.Err(); err != nil { return VerificationCode{}, err } s.mu.RLock() defer s.mu.RUnlock() var latest VerificationCode for _, code := range s.codes { if code.Region != region || code.Channel != channel || code.Target != target || code.Purpose != purpose { continue } if code.ConsumedAt != nil { continue } if latest.ID == "" || code.ExpiresAt.After(latest.ExpiresAt) { latest = code } } if latest.ID == "" { return VerificationCode{}, design.ErrNotFound } return latest, nil } func (s *MemoryStore) MarkVerificationCodeConsumed(ctx context.Context, id string, consumedAt time.Time) error { if err := ctx.Err(); err != nil { return err } s.mu.Lock() defer s.mu.Unlock() code, ok := s.codes[id] if !ok { return design.ErrNotFound } code.ConsumedAt = &consumedAt s.codes[id] = code return nil } func (s *MemoryStore) IncrementVerificationCodeAttempts(ctx context.Context, id string) error { if err := ctx.Err(); err != nil { return err } s.mu.Lock() defer s.mu.Unlock() code, ok := s.codes[id] if !ok { return design.ErrNotFound } code.Attempts++ s.codes[id] = code return nil } func (s *MemoryStore) Close() error { return nil } func (s *MemoryStore) find(ctx context.Context, match func(User) bool) (User, error) { if err := ctx.Err(); err != nil { return User{}, err } s.mu.RLock() defer s.mu.RUnlock() for _, user := range s.users { if match(user) { return user, nil } } return User{}, design.ErrNotFound }