diff --git a/backend/internal/store/user_mem.go b/backend/internal/store/user_mem.go new file mode 100644 index 0000000..73a1be2 --- /dev/null +++ b/backend/internal/store/user_mem.go @@ -0,0 +1,120 @@ +package store + +import ( + "context" + "sync" + "time" + + "github.com/google/uuid" +) + +// MemUserRepository 基于内存的 UserRepository 实现(测试用)。 +type MemUserRepository struct { + mu sync.RWMutex + users map[string]*User // id -> user + byUsername map[string]string // username -> id + refreshTokens map[string]string // tokenHash -> userID + tokenExpiry map[string]time.Time // tokenHash -> expiresAt +} + +// NewMemUserRepository 创建 MemUserRepository。 +func NewMemUserRepository() *MemUserRepository { + return &MemUserRepository{ + users: make(map[string]*User), + byUsername: make(map[string]string), + refreshTokens: make(map[string]string), + tokenExpiry: make(map[string]time.Time), + } +} + +func (r *MemUserRepository) Create(_ context.Context, username, passwordHash string) (string, error) { + r.mu.Lock() + defer r.mu.Unlock() + + if _, exists := r.byUsername[username]; exists { + return "", ErrUsernameTaken + } + + id := uuid.New().String() + now := time.Now() + user := &User{ + ID: id, + Username: username, + PasswordHash: passwordHash, + CreatedAt: now, + UpdatedAt: now, + } + r.users[id] = user + r.byUsername[username] = id + return id, nil +} + +func (r *MemUserRepository) FindByUsername(_ context.Context, username string) (*User, error) { + r.mu.RLock() + defer r.mu.RUnlock() + + id, ok := r.byUsername[username] + if !ok { + return nil, ErrUserNotFound + } + u := r.users[id] + copy := *u + return ©, nil +} + +func (r *MemUserRepository) FindByID(_ context.Context, id string) (*User, error) { + r.mu.RLock() + defer r.mu.RUnlock() + + u, ok := r.users[id] + if !ok { + return nil, ErrUserNotFound + } + copy := *u + return ©, nil +} + +func (r *MemUserRepository) SaveRefreshToken(_ context.Context, userID, tokenHash string, expiresAt time.Time) error { + r.mu.Lock() + defer r.mu.Unlock() + + r.refreshTokens[tokenHash] = userID + r.tokenExpiry[tokenHash] = expiresAt + return nil +} + +func (r *MemUserRepository) FindRefreshToken(_ context.Context, tokenHash string) (string, error) { + r.mu.RLock() + defer r.mu.RUnlock() + + userID, ok := r.refreshTokens[tokenHash] + if !ok { + return "", ErrRefreshTokenNotFound + } + if time.Now().After(r.tokenExpiry[tokenHash]) { + return "", ErrRefreshTokenNotFound + } + return userID, nil +} + +func (r *MemUserRepository) DeleteRefreshToken(_ context.Context, tokenHash string) error { + r.mu.Lock() + defer r.mu.Unlock() + + delete(r.refreshTokens, tokenHash) + delete(r.tokenExpiry, tokenHash) + return nil +} + +func (r *MemUserRepository) DeleteUserRefreshTokens(_ context.Context, userID string) error { + r.mu.Lock() + defer r.mu.Unlock() + + for hash, uid := range r.refreshTokens { + if uid == userID { + delete(r.refreshTokens, hash) + delete(r.tokenExpiry, hash) + } + } + return nil +}