persona-community-5/services/persona-api/internal/api/spec.go
rdev-worker 9c009926d1
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
build: /implement-feature persona-model --requirements 'DB migration in pers...
2026-02-24 07:58:27 +00:00

182 lines
8.4 KiB
Go

package api
import "git.threesix.ai/jordan/persona-community-5/pkg/openapi"
// NewServiceSpec builds the OpenAPI specification for the persona-api service.
func NewServiceSpec() *openapi.OpenAPISpec {
spec := openapi.NewOpenAPISpec("persona-api API", "1.0.0").
WithDescription("REST API for the persona-api service").
WithBearerSecurity("bearer", "JWT authentication token").
WithTag("Health", "Service health endpoints").
WithTag("Examples", "Example CRUD endpoints").
WithTag("Personas", "AI persona generation and management")
// Define reusable schemas
spec.WithSchema("Example", openapi.Object(map[string]openapi.Schema{
"id": openapi.UUID().WithDescription("Unique identifier"),
"name": openapi.String().WithDescription("Name of the example").WithExample("My Example"),
"description": openapi.String().WithDescription("Optional description").WithExample("A description"),
"created_at": openapi.DateTime().WithDescription("Creation timestamp"),
"updated_at": openapi.DateTime().WithDescription("Last update timestamp"),
}, "id", "name"))
spec.WithSchema("CreateExampleRequest", openapi.Object(map[string]openapi.Schema{
"name": openapi.StringWithMinMax(1, 100).WithDescription("Name of the example"),
"description": openapi.StringWithMinMax(0, 500).WithDescription("Optional description"),
}, "name"))
spec.WithSchema("UpdateExampleRequest", openapi.Object(map[string]openapi.Schema{
"name": openapi.StringWithMinMax(1, 100).WithDescription("Updated name"),
"description": openapi.StringWithMinMax(0, 500).WithDescription("Updated description"),
}))
// Health
spec.AddPath("/api/persona-api/health", "get", map[string]any{
"summary": "Health check",
"tags": []string{"Health"},
"responses": map[string]any{
"200": openapi.OpResponse("Service is healthy", openapi.Object(map[string]openapi.Schema{
"service": openapi.String(),
"status": openapi.String(),
})),
},
})
// List examples
spec.AddPath("/api/persona-api/examples", "get", map[string]any{
"summary": "List examples",
"description": "Returns a paginated list of examples.",
"tags": []string{"Examples"},
"parameters": []any{openapi.PageParam(), openapi.PerPageParam()},
"responses": map[string]any{
"200": openapi.OpResponse("Success", openapi.ResponseSchema(openapi.RefArray("Example"))),
},
})
// Get example
spec.AddPath("/api/persona-api/examples/{id}", "get", map[string]any{
"summary": "Get example by ID",
"tags": []string{"Examples"},
"parameters": []any{openapi.IDParam()},
"responses": map[string]any{
"200": openapi.OpResponse("Success", openapi.ResponseSchema(openapi.Ref("Example"))),
"404": openapi.OpResponse("Not found", openapi.ErrorResponseSchema()),
},
})
// Create example
spec.AddPath("/api/persona-api/examples", "post", map[string]any{
"summary": "Create example",
"description": "Creates a new example. Requires authentication.",
"tags": []string{"Examples"},
"security": []map[string][]string{{"bearer": {}}},
"requestBody": openapi.RequestBody(openapi.Ref("CreateExampleRequest"), true),
"responses": map[string]any{
"201": openapi.OpResponse("Created", openapi.ResponseSchema(openapi.Ref("Example"))),
"400": openapi.OpResponse("Bad request", openapi.ErrorResponseSchema()),
"401": openapi.OpResponse("Unauthorized", openapi.ErrorResponseSchema()),
"422": openapi.OpResponse("Validation error", openapi.ErrorResponseSchema()),
},
})
// Update example
spec.AddPath("/api/persona-api/examples/{id}", "put", map[string]any{
"summary": "Update example",
"description": "Updates an existing example. Requires authentication.",
"tags": []string{"Examples"},
"security": []map[string][]string{{"bearer": {}}},
"parameters": []any{openapi.IDParam()},
"requestBody": openapi.RequestBody(openapi.Ref("UpdateExampleRequest"), true),
"responses": map[string]any{
"200": openapi.OpResponse("Updated", openapi.ResponseSchema(openapi.Ref("Example"))),
"400": openapi.OpResponse("Bad request", openapi.ErrorResponseSchema()),
"401": openapi.OpResponse("Unauthorized", openapi.ErrorResponseSchema()),
"404": openapi.OpResponse("Not found", openapi.ErrorResponseSchema()),
},
})
// Delete example
spec.AddPath("/api/persona-api/examples/{id}", "delete", map[string]any{
"summary": "Delete example",
"description": "Deletes an example by ID. Requires authentication.",
"tags": []string{"Examples"},
"security": []map[string][]string{{"bearer": {}}},
"parameters": []any{openapi.IDParam()},
"responses": map[string]any{
"204": openapi.OpResponseNoContent(),
"401": openapi.OpResponse("Unauthorized", openapi.ErrorResponseSchema()),
"404": openapi.OpResponse("Not found", openapi.ErrorResponseSchema()),
},
})
// ----- Persona schemas -----
spec.WithSchema("Persona", openapi.Object(map[string]openapi.Schema{
"id": openapi.UUID().WithDescription("Unique identifier"),
"name": openapi.String().WithDescription("Persona display name"),
"handle": openapi.String().WithDescription("Unique URL-safe handle"),
"gender": openapi.String().WithDescription("Gender identity"),
"description": openapi.String().WithDescription("Natural-language persona concept"),
"tags": openapi.Array(openapi.String()).WithDescription("Tags"),
"spec_json": openapi.Object(map[string]openapi.Schema{}, "").WithDescription("Generated persona specification"),
"anchor_url": openapi.String().WithDescription("Anchor image URL"),
"avatar_url": openapi.String().WithDescription("Avatar image URL"),
"banner_url": openapi.String().WithDescription("Banner image URL"),
"image_urls": openapi.Array(openapi.String()).WithDescription("Gallery image URLs"),
"video_urls": openapi.Array(openapi.String()).WithDescription("Video URLs"),
"status": openapi.String().WithDescription("Generation status: pending, generating, complete, failed"),
"created_at": openapi.DateTime().WithDescription("Creation timestamp"),
}, "id", "name", "handle", "gender", "description", "status"))
spec.WithSchema("CreatePersonaRequest", openapi.Object(map[string]openapi.Schema{
"description": openapi.StringWithMinMax(3, 1000).WithDescription("Natural-language persona concept"),
"gender": openapi.String().WithDescription("Gender identity: woman, man, or non_binary"),
"custom_name": openapi.String().WithDescription("Optional custom name override"),
}, "description", "gender"))
// ----- Persona endpoints -----
spec.AddPath("/api/persona-api/personas", "post", map[string]any{
"summary": "Create persona",
"description": "Creates a new persona and enqueues a generate_spec job. Returns 202 Accepted.",
"tags": []string{"Personas"},
"security": []map[string][]string{{"bearer": {}}},
"requestBody": openapi.RequestBody(openapi.Ref("CreatePersonaRequest"), true),
"responses": map[string]any{
"202": openapi.OpResponse("Accepted", openapi.ResponseSchema(openapi.Ref("Persona"))),
"400": openapi.OpResponse("Bad request", openapi.ErrorResponseSchema()),
"401": openapi.OpResponse("Unauthorized", openapi.ErrorResponseSchema()),
"422": openapi.OpResponse("Validation error", openapi.ErrorResponseSchema()),
},
})
spec.AddPath("/api/persona-api/personas", "get", map[string]any{
"summary": "List personas",
"description": "Returns a paginated list of personas ordered by creation date (newest first).",
"tags": []string{"Personas"},
"security": []map[string][]string{{"bearer": {}}},
"parameters": []any{
openapi.QueryParam("limit", "Maximum number of results (default 20, max 100)", false),
openapi.QueryParam("offset", "Number of results to skip (default 0)", false),
},
"responses": map[string]any{
"200": openapi.OpResponse("Success", openapi.ResponseSchema(openapi.RefArray("Persona"))),
"401": openapi.OpResponse("Unauthorized", openapi.ErrorResponseSchema()),
},
})
spec.AddPath("/api/persona-api/personas/{id}", "get", map[string]any{
"summary": "Get persona by ID",
"tags": []string{"Personas"},
"security": []map[string][]string{{"bearer": {}}},
"parameters": []any{openapi.IDParam()},
"responses": map[string]any{
"200": openapi.OpResponse("Success", openapi.ResponseSchema(openapi.Ref("Persona"))),
"401": openapi.OpResponse("Unauthorized", openapi.ErrorResponseSchema()),
"404": openapi.OpResponse("Not found", openapi.ErrorResponseSchema()),
},
})
return spec
}