129 lines
3.1 KiB
Go
129 lines
3.1 KiB
Go
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
|
|
}
|