129 lines
2.6 KiB
Go
129 lines
2.6 KiB
Go
|
|
package store
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"sync"
|
||
|
|
)
|
||
|
|
|
||
|
|
// User represents a user in the system
|
||
|
|
type User struct {
|
||
|
|
Email string `json:"email"`
|
||
|
|
GivenName string `json:"given_name"`
|
||
|
|
FamilyName string `json:"family_name"`
|
||
|
|
UserID string `json:"user_id"`
|
||
|
|
Picture string `json:"picture,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// UserStore provides thread-safe user storage
|
||
|
|
type UserStore struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
users map[string]*User
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewUserStore creates a new user store
|
||
|
|
func NewUserStore() *UserStore {
|
||
|
|
return &UserStore{
|
||
|
|
users: make(map[string]*User),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// LoadFromFile loads users from a JSON file
|
||
|
|
func (s *UserStore) LoadFromFile(path string) error {
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
if os.IsNotExist(err) {
|
||
|
|
return nil // File doesn't exist, start with empty store
|
||
|
|
}
|
||
|
|
return fmt.Errorf("read users file: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
var rawUsers map[string]json.RawMessage
|
||
|
|
if err := json.Unmarshal(data, &rawUsers); err != nil {
|
||
|
|
return fmt.Errorf("parse users file: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
|
||
|
|
for email, raw := range rawUsers {
|
||
|
|
var user User
|
||
|
|
if err := json.Unmarshal(raw, &user); err != nil {
|
||
|
|
return fmt.Errorf("parse user %s: %w", email, err)
|
||
|
|
}
|
||
|
|
user.Email = email // Ensure email is set
|
||
|
|
if user.UserID == "" {
|
||
|
|
user.UserID = email
|
||
|
|
}
|
||
|
|
s.users[email] = &user
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetByEmail retrieves a user by email
|
||
|
|
func (s *UserStore) GetByEmail(email string) (*User, bool) {
|
||
|
|
s.mu.RLock()
|
||
|
|
defer s.mu.RUnlock()
|
||
|
|
|
||
|
|
user, ok := s.users[email]
|
||
|
|
if !ok {
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
// Return a copy to prevent external modification
|
||
|
|
userCopy := *user
|
||
|
|
return &userCopy, true
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create adds a new user
|
||
|
|
func (s *UserStore) Create(email string, user *User) {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
|
||
|
|
user.Email = email
|
||
|
|
if user.UserID == "" {
|
||
|
|
user.UserID = email
|
||
|
|
}
|
||
|
|
s.users[email] = user
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update modifies an existing user
|
||
|
|
func (s *UserStore) Update(email string, updates *User) (*User, bool) {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
|
||
|
|
existing, ok := s.users[email]
|
||
|
|
if !ok {
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// Apply updates (only non-empty fields)
|
||
|
|
if updates.GivenName != "" {
|
||
|
|
existing.GivenName = updates.GivenName
|
||
|
|
}
|
||
|
|
if updates.FamilyName != "" {
|
||
|
|
existing.FamilyName = updates.FamilyName
|
||
|
|
}
|
||
|
|
if updates.Picture != "" {
|
||
|
|
existing.Picture = updates.Picture
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return a copy
|
||
|
|
userCopy := *existing
|
||
|
|
return &userCopy, true
|
||
|
|
}
|
||
|
|
|
||
|
|
// List returns all users
|
||
|
|
func (s *UserStore) List() []*User {
|
||
|
|
s.mu.RLock()
|
||
|
|
defer s.mu.RUnlock()
|
||
|
|
|
||
|
|
users := make([]*User, 0, len(s.users))
|
||
|
|
for _, user := range s.users {
|
||
|
|
userCopy := *user
|
||
|
|
users = append(users, &userCopy)
|
||
|
|
}
|
||
|
|
return users
|
||
|
|
}
|