package memory import ( "context" "encoding/json" "sort" "sync" "github.com/google/uuid" "git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/domain" "git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/port" ) // Compile-time verification that PersonaRepository implements port.PersonaRepository. var _ port.PersonaRepository = (*PersonaRepository)(nil) // PersonaRepository is a thread-safe in-memory implementation of port.PersonaRepository. type PersonaRepository struct { mu sync.RWMutex personas map[domain.PersonaID]*domain.Persona handles map[string]domain.PersonaID } // NewPersonaRepository creates a new in-memory persona repository. func NewPersonaRepository() *PersonaRepository { return &PersonaRepository{ personas: make(map[domain.PersonaID]*domain.Persona), handles: make(map[string]domain.PersonaID), } } func (r *PersonaRepository) Create(_ context.Context, persona *domain.Persona) error { r.mu.Lock() defer r.mu.Unlock() if _, exists := r.handles[persona.Handle]; exists { return domain.ErrDuplicateHandle } if persona.ID.IsZero() { persona.ID = domain.PersonaID(uuid.New().String()) } r.personas[persona.ID] = copyPersona(persona) r.handles[persona.Handle] = persona.ID return nil } func (r *PersonaRepository) GetByID(_ context.Context, id domain.PersonaID) (*domain.Persona, error) { r.mu.RLock() defer r.mu.RUnlock() p, ok := r.personas[id] if !ok { return nil, domain.ErrPersonaNotFound } return copyPersona(p), nil } func (r *PersonaRepository) List(_ context.Context, limit, offset int) ([]*domain.Persona, error) { r.mu.RLock() defer r.mu.RUnlock() all := make([]*domain.Persona, 0, len(r.personas)) for _, p := range r.personas { all = append(all, p) } sort.Slice(all, func(i, j int) bool { return all[i].CreatedAt.After(all[j].CreatedAt) }) if offset >= len(all) { return []*domain.Persona{}, nil } end := offset + limit if end > len(all) { end = len(all) } result := make([]*domain.Persona, 0, end-offset) for _, p := range all[offset:end] { result = append(result, copyPersona(p)) } return result, nil } func (r *PersonaRepository) Update(_ context.Context, persona *domain.Persona) error { r.mu.Lock() defer r.mu.Unlock() existing, ok := r.personas[persona.ID] if !ok { return domain.ErrPersonaNotFound } if existing.Handle != persona.Handle { if _, exists := r.handles[persona.Handle]; exists { return domain.ErrDuplicateHandle } delete(r.handles, existing.Handle) r.handles[persona.Handle] = persona.ID } r.personas[persona.ID] = copyPersona(persona) return nil } func copyPersona(p *domain.Persona) *domain.Persona { cp := *p if p.Tags != nil { cp.Tags = make([]string, len(p.Tags)) copy(cp.Tags, p.Tags) } if p.ImageURLs != nil { cp.ImageURLs = make([]string, len(p.ImageURLs)) copy(cp.ImageURLs, p.ImageURLs) } if p.VideoURLs != nil { cp.VideoURLs = make([]string, len(p.VideoURLs)) copy(cp.VideoURLs, p.VideoURLs) } if p.SpecJSON != nil { cp.SpecJSON = make(json.RawMessage, len(p.SpecJSON)) copy(cp.SpecJSON, p.SpecJSON) } return &cp }