152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"git.threesix.ai/jordan/persona-community-5/pkg/app"
|
|
"git.threesix.ai/jordan/persona-community-5/pkg/httperror"
|
|
"git.threesix.ai/jordan/persona-community-5/pkg/httpresponse"
|
|
"git.threesix.ai/jordan/persona-community-5/pkg/logging"
|
|
"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/domain"
|
|
"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/service"
|
|
)
|
|
|
|
// Persona handles HTTP requests for persona CRUD operations.
|
|
type Persona struct {
|
|
svc *service.PersonaService
|
|
logger *logging.Logger
|
|
}
|
|
|
|
// NewPersona creates a new Persona handler with injected dependencies.
|
|
func NewPersona(svc *service.PersonaService, logger *logging.Logger) *Persona {
|
|
return &Persona{
|
|
svc: svc,
|
|
logger: logger.WithComponent("PersonaHandler"),
|
|
}
|
|
}
|
|
|
|
// CreatePersonaRequest is the request body for creating a persona.
|
|
type CreatePersonaRequest struct {
|
|
Description string `json:"description" validate:"required,min=3,max=1000"`
|
|
Gender string `json:"gender" validate:"required,oneof=woman man non_binary"`
|
|
CustomName string `json:"custom_name"`
|
|
}
|
|
|
|
// PersonaResponse is the API representation of a persona.
|
|
type PersonaResponse struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Handle string `json:"handle"`
|
|
Gender string `json:"gender"`
|
|
Description string `json:"description"`
|
|
Tags []string `json:"tags"`
|
|
SpecJSON any `json:"spec_json,omitempty"`
|
|
AnchorURL string `json:"anchor_url,omitempty"`
|
|
AvatarURL string `json:"avatar_url,omitempty"`
|
|
BannerURL string `json:"banner_url,omitempty"`
|
|
ImageURLs []string `json:"image_urls"`
|
|
VideoURLs []string `json:"video_urls"`
|
|
Status string `json:"status"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
func toPersonaResponse(p *domain.Persona) PersonaResponse {
|
|
resp := PersonaResponse{
|
|
ID: p.ID.String(),
|
|
Name: p.Name,
|
|
Handle: p.Handle,
|
|
Gender: p.Gender,
|
|
Description: p.Description,
|
|
Tags: p.Tags,
|
|
AnchorURL: p.AnchorURL,
|
|
AvatarURL: p.AvatarURL,
|
|
BannerURL: p.BannerURL,
|
|
ImageURLs: p.ImageURLs,
|
|
VideoURLs: p.VideoURLs,
|
|
Status: string(p.Status),
|
|
CreatedAt: p.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
}
|
|
if p.SpecJSON != nil {
|
|
resp.SpecJSON = p.SpecJSON
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// Create creates a new persona and enqueues a generate_spec job.
|
|
// Returns 202 Accepted with the created persona.
|
|
func (h *Persona) Create(w http.ResponseWriter, r *http.Request) error {
|
|
var req CreatePersonaRequest
|
|
if err := app.BindAndValidate(r, &req); err != nil {
|
|
return err
|
|
}
|
|
|
|
persona, err := h.svc.Create(r.Context(), req.Description, req.Gender, req.CustomName)
|
|
if err != nil {
|
|
return mapPersonaDomainError(err)
|
|
}
|
|
|
|
httpresponse.Accepted(w, r, toPersonaResponse(persona))
|
|
return nil
|
|
}
|
|
|
|
// GetByID returns a single persona by ID.
|
|
func (h *Persona) GetByID(w http.ResponseWriter, r *http.Request) error {
|
|
id := chi.URLParam(r, "id")
|
|
if id == "" {
|
|
return httperror.BadRequest("persona ID is required")
|
|
}
|
|
|
|
persona, err := h.svc.GetByID(r.Context(), domain.PersonaID(id))
|
|
if err != nil {
|
|
return mapPersonaDomainError(err)
|
|
}
|
|
|
|
httpresponse.OK(w, r, toPersonaResponse(persona))
|
|
return nil
|
|
}
|
|
|
|
// List returns a paginated list of personas.
|
|
func (h *Persona) List(w http.ResponseWriter, r *http.Request) error {
|
|
limit := 20
|
|
offset := 0
|
|
|
|
if v := r.URL.Query().Get("limit"); v != "" {
|
|
if parsed, err := strconv.Atoi(v); err == nil {
|
|
limit = parsed
|
|
}
|
|
}
|
|
if v := r.URL.Query().Get("offset"); v != "" {
|
|
if parsed, err := strconv.Atoi(v); err == nil {
|
|
offset = parsed
|
|
}
|
|
}
|
|
|
|
personas, err := h.svc.List(r.Context(), limit, offset)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
results := make([]PersonaResponse, len(personas))
|
|
for i, p := range personas {
|
|
results[i] = toPersonaResponse(p)
|
|
}
|
|
|
|
httpresponse.OK(w, r, results)
|
|
return nil
|
|
}
|
|
|
|
func mapPersonaDomainError(err error) error {
|
|
switch {
|
|
case errors.Is(err, domain.ErrPersonaNotFound):
|
|
return httperror.NotFound("persona not found")
|
|
case errors.Is(err, domain.ErrDuplicateHandle):
|
|
return httperror.Conflict("persona with this handle already exists")
|
|
default:
|
|
return err
|
|
}
|
|
}
|