127 lines
3.1 KiB
Cheetah
127 lines
3.1 KiB
Cheetah
package repository
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"{{.Module}}/internal/model"
|
|
)
|
|
|
|
// MemoryUsers stores data only for this process. Replace it with a database
|
|
// implementation of UserRepository when persistence is required.
|
|
type MemoryUsers struct {
|
|
mu sync.RWMutex
|
|
users map[int64]model.User
|
|
emails map[string]int64
|
|
nextID int64
|
|
}
|
|
|
|
func NewMemoryUsers() *MemoryUsers {
|
|
return &MemoryUsers{
|
|
users: make(map[int64]model.User),
|
|
emails: make(map[string]int64),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (r *MemoryUsers) Create(ctx context.Context, input model.UserInput) (model.User, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if err := ctx.Err(); err != nil {
|
|
return model.User{}, err
|
|
}
|
|
if _, exists := r.emails[input.Email]; exists {
|
|
return model.User{}, model.ErrEmailTaken
|
|
}
|
|
now := time.Now().UTC()
|
|
user := model.User{
|
|
ID: r.nextID, Name: input.Name, Email: input.Email,
|
|
CreatedAt: now, UpdatedAt: now,
|
|
}
|
|
r.nextID++
|
|
r.users[user.ID] = user
|
|
r.emails[user.Email] = user.ID
|
|
return user, nil
|
|
}
|
|
|
|
func (r *MemoryUsers) Get(ctx context.Context, id int64) (model.User, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
if err := ctx.Err(); err != nil {
|
|
return model.User{}, err
|
|
}
|
|
user, exists := r.users[id]
|
|
if !exists {
|
|
return model.User{}, model.ErrUserNotFound
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (r *MemoryUsers) List(ctx context.Context, page, pageSize int) (model.UserPage, error) {
|
|
if page < 1 || pageSize < 1 || pageSize > 100 {
|
|
return model.UserPage{}, model.ErrInvalidPage
|
|
}
|
|
r.mu.RLock()
|
|
if err := ctx.Err(); err != nil {
|
|
r.mu.RUnlock()
|
|
return model.UserPage{}, err
|
|
}
|
|
users := make([]model.User, 0, len(r.users))
|
|
for _, user := range r.users {
|
|
users = append(users, user)
|
|
}
|
|
r.mu.RUnlock()
|
|
|
|
// A stable order is essential: Go map iteration order changes between calls.
|
|
sort.Slice(users, func(i, j int) bool { return users[i].ID < users[j].ID })
|
|
result := model.UserPage{
|
|
Items: []model.User{}, Total: len(users), Page: page, PageSize: pageSize,
|
|
}
|
|
// Check the page before multiplying so even a very large page cannot overflow.
|
|
if len(users) == 0 || page-1 > (len(users)-1)/pageSize {
|
|
return result, nil
|
|
}
|
|
start := (page - 1) * pageSize
|
|
count := min(pageSize, len(users)-start)
|
|
result.Items = users[start : start+count]
|
|
return result, nil
|
|
}
|
|
|
|
func (r *MemoryUsers) Update(ctx context.Context, id int64, input model.UserInput) (model.User, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if err := ctx.Err(); err != nil {
|
|
return model.User{}, err
|
|
}
|
|
user, exists := r.users[id]
|
|
if !exists {
|
|
return model.User{}, model.ErrUserNotFound
|
|
}
|
|
if owner, exists := r.emails[input.Email]; exists && owner != id {
|
|
return model.User{}, model.ErrEmailTaken
|
|
}
|
|
delete(r.emails, user.Email)
|
|
user.Name, user.Email = input.Name, input.Email
|
|
user.UpdatedAt = time.Now().UTC()
|
|
r.users[id] = user
|
|
r.emails[user.Email] = id
|
|
return user, nil
|
|
}
|
|
|
|
func (r *MemoryUsers) Delete(ctx context.Context, id int64) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
user, exists := r.users[id]
|
|
if !exists {
|
|
return model.ErrUserNotFound
|
|
}
|
|
delete(r.users, id)
|
|
delete(r.emails, user.Email)
|
|
return nil
|
|
}
|