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 }