package readapi import ( "encoding/json" "fmt" "net/http" "sort" "strings" "github.com/gofiber/fiber/v2" "gitlab.com/mbugroup/lti-api.git/internal/config" "gopkg.in/yaml.v3" ) type Artifacts struct { OpenAPIJSON []byte OpenAPIYAML []byte PostmanCollectionJSON []byte PostmanEnvironmentJSON []byte } type normalizedRoute struct { Method string Path string Params []string } type securityMode string const ( securityNone securityMode = "none" securityBearer securityMode = "bearer" securityAPIOrBearer securityMode = "api_or_bearer" ) type parameterMeta struct { Name string In string Description string Required bool Example any PostmanValue string IncludePostman bool } type routeMeta struct { Group string Tag string Summary string Description string Security securityMode ListStyle bool QueryParams []parameterMeta Exclude bool } func RegisterRoutes(router fiber.Router) { router.Get("/openapi/read.json", serveOpenAPIJSON) router.Get("/openapi/read.yaml", serveOpenAPIYAML) } func PrimeBuildConfig() { if strings.TrimSpace(config.S3Bucket) == "" { config.S3Bucket = "read-api-artifacts" } if strings.TrimSpace(config.S3Region) == "" { config.S3Region = "us-east-1" } if strings.TrimSpace(config.S3AccessKey) == "" { config.S3AccessKey = "local-access-key" } if strings.TrimSpace(config.S3SecretKey) == "" { config.S3SecretKey = "local-secret-key" } if strings.TrimSpace(config.S3Endpoint) == "" { config.S3Endpoint = "http://localhost:9000" } if strings.TrimSpace(config.S3PublicBaseURL) == "" { config.S3PublicBaseURL = "http://localhost:9000/read-api-artifacts" } } func BuildArtifactsFromApp(app *fiber.App) (Artifacts, error) { if app == nil { return Artifacts{}, fmt.Errorf("app is required") } routes := normalizeRoutes(app.GetRoutes(true)) return buildArtifactsFromNormalized(routes) } func BuildArtifacts(routes []fiber.Route) (Artifacts, error) { normalized := normalizeRoutes(routes) return buildArtifactsFromNormalized(normalized) } func buildArtifactsFromNormalized(normalized []normalizedRoute) (Artifacts, error) { specDoc := buildOpenAPIDocument(normalized) collectionDoc := buildPostmanCollection(normalized) environmentDoc := buildPostmanEnvironment(normalized) openAPIJSON, err := json.MarshalIndent(specDoc, "", " ") if err != nil { return Artifacts{}, err } openAPIYAML, err := yaml.Marshal(specDoc) if err != nil { return Artifacts{}, err } postmanCollectionJSON, err := json.MarshalIndent(collectionDoc, "", " ") if err != nil { return Artifacts{}, err } postmanEnvironmentJSON, err := json.MarshalIndent(environmentDoc, "", " ") if err != nil { return Artifacts{}, err } return Artifacts{ OpenAPIJSON: openAPIJSON, OpenAPIYAML: openAPIYAML, PostmanCollectionJSON: postmanCollectionJSON, PostmanEnvironmentJSON: postmanEnvironmentJSON, }, nil } func serveOpenAPIJSON(c *fiber.Ctx) error { artifacts, err := BuildArtifactsFromApp(c.App()) if err != nil { return err } return c.Status(fiber.StatusOK).Type("json").Send(artifacts.OpenAPIJSON) } func serveOpenAPIYAML(c *fiber.Ctx) error { artifacts, err := BuildArtifactsFromApp(c.App()) if err != nil { return err } c.Set(fiber.HeaderContentType, "application/yaml") return c.Status(fiber.StatusOK).Send(artifacts.OpenAPIYAML) } func normalizeRoutes(routes []fiber.Route) []normalizedRoute { seen := make(map[string]struct{}, len(routes)) normalized := make([]normalizedRoute, 0, len(routes)) for _, route := range routes { if route.Method != http.MethodGet { continue } if route.Path == "" || strings.HasPrefix(route.Path, "/api/openapi/") { continue } key := route.Method + " " + route.Path if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} normalized = append(normalized, normalizedRoute{ Method: route.Method, Path: route.Path, Params: append([]string(nil), route.Params...), }) } sort.Slice(normalized, func(i, j int) bool { if normalized[i].Path == normalized[j].Path { return normalized[i].Method < normalized[j].Method } return normalized[i].Path < normalized[j].Path }) return normalized } func buildOpenAPIDocument(routes []normalizedRoute) map[string]any { paths := make(map[string]any, len(routes)) for _, route := range routes { meta := describeRoute(route) if meta.Exclude { continue } openAPIPath := toOpenAPIPath(route.Path) operation := map[string]any{ "summary": meta.Summary, "description": meta.Description, "tags": []string{meta.Tag}, "responses": map[string]any{ "200": map[string]any{ "description": "Successful response", "content": map[string]any{ "application/json": map[string]any{ "schema": successSchema(meta), }, }, }, "401": map[string]any{ "description": "Unauthorized", "content": map[string]any{ "application/json": map[string]any{ "schema": map[string]any{"$ref": "#/components/schemas/ErrorEnvelope"}, }, }, }, "403": map[string]any{ "description": "Forbidden", "content": map[string]any{ "application/json": map[string]any{ "schema": map[string]any{"$ref": "#/components/schemas/ErrorEnvelope"}, }, }, }, }, } params := buildParameters(route, meta) if len(params) > 0 { operation["parameters"] = params } switch meta.Security { case securityBearer: operation["security"] = []map[string][]string{{"BearerAuth": {}}} case securityAPIOrBearer: operation["security"] = []map[string][]string{ {"ApiKeyAuth": {}}, {"BearerAuth": {}}, } } paths[openAPIPath] = map[string]any{ "get": operation, } } return map[string]any{ "openapi": "3.1.0", "info": map[string]any{ "title": "LTI ERP Read API", "version": "v1", "description": "Read-only OpenAPI surface for dashboard integrations and GET endpoint exploration.", }, "servers": []map[string]any{ {"url": "http://localhost:8081"}, }, "paths": paths, "components": map[string]any{ "securitySchemes": map[string]any{ "ApiKeyAuth": map[string]any{ "type": "apiKey", "in": "header", "name": "X-API-Key", }, "BearerAuth": map[string]any{ "type": "http", "scheme": "bearer", }, }, "schemas": map[string]any{ "SuccessEnvelope": map[string]any{ "type": "object", "properties": map[string]any{ "code": map[string]any{"type": "integer", "example": 200}, "status": map[string]any{"type": "string", "example": "success"}, "message": map[string]any{"type": "string", "example": "Request completed successfully"}, "data": map[string]any{"type": "object", "additionalProperties": true}, }, }, "PaginatedEnvelope": map[string]any{ "type": "object", "properties": map[string]any{ "code": map[string]any{"type": "integer", "example": 200}, "status": map[string]any{"type": "string", "example": "success"}, "message": map[string]any{"type": "string", "example": "Request completed successfully"}, "meta": map[string]any{ "type": "object", "properties": map[string]any{ "page": map[string]any{"type": "integer", "example": 1}, "limit": map[string]any{"type": "integer", "example": 10}, "total_pages": map[string]any{"type": "integer", "example": 1}, "total_results": map[string]any{"type": "integer", "example": 0}, }, }, "data": map[string]any{ "type": "array", "items": map[string]any{"type": "object", "additionalProperties": true}, }, }, }, "ErrorEnvelope": map[string]any{ "type": "object", "properties": map[string]any{ "code": map[string]any{"type": "integer", "example": 401}, "status": map[string]any{"type": "string", "example": "error"}, "message": map[string]any{"type": "string", "example": "Please authenticate"}, "errors": map[string]any{"type": "object", "additionalProperties": true}, }, }, }, }, } } func buildParameters(route normalizedRoute, meta routeMeta) []map[string]any { params := make([]map[string]any, 0, len(route.Params)+len(meta.QueryParams)) for _, param := range route.Params { params = append(params, map[string]any{ "name": param, "in": "path", "required": true, "description": fmt.Sprintf("Path parameter `%s`.", param), "schema": map[string]any{ "type": "string", "example": defaultExampleForVariable(inferPostmanPathVariable(route.Path, param)), }, }) } for _, query := range meta.QueryParams { parameter := map[string]any{ "name": query.Name, "in": query.In, "required": query.Required, "description": query.Description, "schema": inferSchema(query.Example), } if query.Example != nil { parameter["example"] = query.Example } params = append(params, parameter) } return params } func successSchema(meta routeMeta) map[string]any { if meta.ListStyle { return map[string]any{"$ref": "#/components/schemas/PaginatedEnvelope"} } return map[string]any{"$ref": "#/components/schemas/SuccessEnvelope"} } func buildPostmanCollection(routes []normalizedRoute) map[string]any { type folder struct { name string items []any } folders := map[string]map[string]*folder{ "Public": {}, "Dashboard API Key": {}, "Internal/OAuth Reference": {}, } for _, route := range routes { meta := describeRoute(route) if meta.Exclude { continue } tagFolder := folders[meta.Group] group, ok := tagFolder[meta.Tag] if !ok { group = &folder{name: meta.Tag} tagFolder[meta.Tag] = group } group.items = append(group.items, buildPostmanRequest(route, meta)) } rootItems := make([]any, 0, len(folders)) for _, groupName := range []string{"Public", "Dashboard API Key", "Internal/OAuth Reference"} { tagFolder := folders[groupName] tagNames := make([]string, 0, len(tagFolder)) for tagName := range tagFolder { tagNames = append(tagNames, tagName) } sort.Strings(tagNames) groupItems := make([]any, 0, len(tagNames)) for _, tagName := range tagNames { groupItems = append(groupItems, map[string]any{ "name": tagName, "item": tagFolder[tagName].items, }) } folder := map[string]any{ "name": groupName, "item": groupItems, } if events := postmanFolderEvents(groupName); len(events) > 0 { folder["event"] = events } rootItems = append(rootItems, folder) } return map[string]any{ "info": map[string]any{ "name": "LTI ERP Read API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", }, "item": rootItems, } } func postmanFolderEvents(groupName string) []map[string]any { switch groupName { case "Dashboard API Key": return []map[string]any{ { "listen": "prerequest", "script": map[string]any{ "type": "text/javascript", "exec": []string{ "const apiKey = pm.environment.get('api_key');", "const bearerToken = pm.environment.get('bearer_token');", "if (apiKey) {", " pm.request.headers.upsert({ key: 'X-API-Key', value: apiKey });", " pm.request.headers.remove('Authorization');", "} else if (bearerToken) {", " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", " pm.request.headers.remove('X-API-Key');", "} else {", " pm.request.headers.remove('Authorization');", " pm.request.headers.remove('X-API-Key');", "}", }, }, }, } case "Internal/OAuth Reference": return []map[string]any{ { "listen": "prerequest", "script": map[string]any{ "type": "text/javascript", "exec": []string{ "const bearerToken = pm.environment.get('bearer_token');", "pm.request.headers.remove('X-API-Key');", "if (bearerToken) {", " pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + bearerToken });", "} else {", " pm.request.headers.remove('Authorization');", "}", }, }, }, } default: return nil } } func buildPostmanRequest(route normalizedRoute, meta routeMeta) map[string]any { return map[string]any{ "name": meta.Summary, "request": map[string]any{ "method": http.MethodGet, "header": []map[string]any{ {"key": "Accept", "value": "application/json"}, }, "url": buildPostmanURL(route, meta), }, } } func buildPostmanURL(route normalizedRoute, meta routeMeta) string { path := route.Path for _, param := range route.Params { path = strings.ReplaceAll(path, ":"+param, "{{"+inferPostmanPathVariable(route.Path, param)+"}}") } query := make([]string, 0, len(meta.QueryParams)) for _, param := range meta.QueryParams { if !param.IncludePostman { continue } query = append(query, fmt.Sprintf("%s=%v", param.Name, param.PostmanValue)) } if len(query) == 0 { return "{{base_url}}" + path } return "{{base_url}}" + path + "?" + strings.Join(query, "&") } func buildPostmanEnvironment(routes []normalizedRoute) map[string]any { values := map[string]string{ "base_url": "http://localhost:8081", "api_key": "", "bearer_token": "", "id": "1", "bank_id": "1", "customer_id": "1", "expense_id": "1", "location_id": "1", "project_flock_id": "1", "project_flock_kandang_id": "1", "supplier_id": "1", } for _, route := range routes { meta := describeRoute(route) if meta.Exclude { continue } for _, param := range route.Params { name := inferPostmanPathVariable(route.Path, param) if _, ok := values[name]; !ok { values[name] = defaultExampleForVariable(name) } } for _, query := range meta.QueryParams { if query.IncludePostman && strings.HasPrefix(query.PostmanValue, "{{") && strings.HasSuffix(query.PostmanValue, "}}") { name := strings.TrimSuffix(strings.TrimPrefix(query.PostmanValue, "{{"), "}}") if _, ok := values[name]; !ok { values[name] = defaultExampleForVariable(name) } } } } keys := make([]string, 0, len(values)) for key := range values { keys = append(keys, key) } sort.Strings(keys) items := make([]map[string]any, 0, len(keys)) for _, key := range keys { items = append(items, map[string]any{ "key": key, "value": values[key], "enabled": true, }) } return map[string]any{ "id": "lti-read-api-local", "name": "LTI ERP Read API.local", "values": items, "_postman_variable_scope": "environment", "_postman_exported_at": "2026-04-14T00:00:00Z", "_postman_exported_using": "Codex", } } func describeRoute(route normalizedRoute) routeMeta { meta := routeMeta{ Group: "Dashboard API Key", Tag: inferTag(route.Path), Summary: defaultSummary(route.Path), Description: fmt.Sprintf("Read access to `%s`.", route.Path), Security: securityAPIOrBearer, ListStyle: !strings.Contains(route.Path, ":"), } switch { case route.Path == "/healthz" || route.Path == "/readyz" || route.Path == "/api/constants": meta.Group = "Public" meta.Security = securityNone meta.ListStyle = false case strings.HasPrefix(route.Path, "/api/sso/"): meta.Group = "Internal/OAuth Reference" meta.ListStyle = false if route.Path == "/api/sso/userinfo" { meta.Security = securityBearer } else { meta.Security = securityNone } } switch route.Path { case "/api/dashboards": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "search", In: "query", Description: "Search keyword.", Example: "farm"}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, {Name: "analysis_mode", In: "query", Description: "Dashboard analysis mode.", Example: "OVERVIEW"}, {Name: "comparison_type", In: "query", Description: "Required when analysis_mode is COMPARISON.", Example: "PREVIOUS_PERIOD"}, {Name: "metric", In: "query", Description: "Metric to compare.", Example: "egg_mass"}, {Name: "location_ids", In: "query", Description: "Comma separated location ids.", Example: "1,2"}, {Name: "flock_ids", In: "query", Description: "Comma separated flock ids.", Example: "1,2"}, {Name: "kandang_ids", In: "query", Description: "Comma separated kandang ids.", Example: "1,2"}, {Name: "include", In: "query", Description: "Comma separated dashboard sections to include.", Example: "performance,summary"}, } case "/api/closings/:projectFlockId/sapronak", "/api/closings/:projectFlockId/sapronak/summary": meta.ListStyle = route.Path == "/api/closings/:projectFlockId/sapronak" meta.QueryParams = []parameterMeta{ {Name: "type", In: "query", Description: "Required sapronak direction.", Required: true, Example: "incoming", PostmanValue: "incoming", IncludePostman: true}, {Name: "search", In: "query", Description: "Search keyword.", Example: "pakan"}, {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1}, } case "/api/closings/:projectFlockId/production-data", "/api/closings/:projectFlockId/keuangan": meta.ListStyle = false meta.QueryParams = []parameterMeta{ {Name: "kandang_id", In: "query", Description: "Optional kandang id filter.", Example: 1}, } case "/api/closings/:project_flock_id/expedition-hpp": meta.ListStyle = false meta.QueryParams = []parameterMeta{ {Name: "project_flock_kandang_id", In: "query", Description: "Optional project flock kandang id filter.", Example: 1}, } case "/api/reports/expense": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "search", In: "query", Description: "Search keyword.", Example: "operasional"}, {Name: "category", In: "query", Description: "Expense category filter.", Example: "BOP"}, {Name: "supplier_id", In: "query", Description: "Supplier id filter.", Example: 1}, {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, {Name: "area_id", In: "query", Description: "Area id filter.", Example: 1}, {Name: "realization_date", In: "query", Description: "Realization date filter (YYYY-MM-DD).", Example: "2026-01-15"}, } case "/api/reports/marketing": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, {Name: "customer_id", In: "query", Description: "Customer id filter.", Example: 1}, {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, } case "/api/reports/purchase-supplier": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, {Name: "supplier_id", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, {Name: "area_id", In: "query", Description: "Comma separated area ids.", Example: "1,2"}, } case "/api/reports/debt-supplier": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2", PostmanValue: "{{supplier_id}}", IncludePostman: true}, } case "/api/reports/customer-payment": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "start_date", In: "query", Description: "Period start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "Period end date (YYYY-MM-DD).", Example: "2026-01-31"}, {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2", PostmanValue: "{{customer_id}}", IncludePostman: true}, } case "/api/reports/hpp-per-kandang": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "period", In: "query", Description: "Daily period filter (YYYY-MM).", Example: "2026-01"}, {Name: "location_id", In: "query", Description: "Location id filter.", Example: 1}, {Name: "kandang_id", In: "query", Description: "Kandang id filter.", Example: 1}, } case "/api/finance/transactions": meta.QueryParams = []parameterMeta{ {Name: "page", In: "query", Description: "Page number.", Example: 1}, {Name: "limit", In: "query", Description: "Page size.", Example: 10}, {Name: "search", In: "query", Description: "Search keyword.", Example: "invoice"}, {Name: "bank_ids", In: "query", Description: "Comma separated bank ids.", Example: "1,2", PostmanValue: "{{bank_id}}", IncludePostman: true}, {Name: "customer_ids", In: "query", Description: "Comma separated customer ids.", Example: "1,2"}, {Name: "supplier_ids", In: "query", Description: "Comma separated supplier ids.", Example: "1,2"}, {Name: "transaction_types", In: "query", Description: "Comma separated transaction types.", Example: "payment,initial_balance"}, {Name: "start_date", In: "query", Description: "Start date (YYYY-MM-DD).", Example: "2026-01-01"}, {Name: "end_date", In: "query", Description: "End date (YYYY-MM-DD).", Example: "2026-01-31"}, } } if route.Path == "/healthz" { meta.Summary = "Health check" meta.Description = "Simple liveness probe." } else if route.Path == "/readyz" { meta.Summary = "Readiness check" meta.Description = "Readiness probe for database and Redis." } return meta } func inferTag(path string) string { switch { case path == "/healthz" || path == "/readyz": return "System" case strings.HasPrefix(path, "/api/master-data/"): return "Master Data" case strings.HasPrefix(path, "/api/finance/"): return "Finance" case strings.HasPrefix(path, "/api/inventory/"): return "Inventory" case strings.HasPrefix(path, "/api/production/"): return "Production" case strings.HasPrefix(path, "/api/reports/"): return "Reports" case strings.HasPrefix(path, "/api/closings/"): return "Closings" case strings.HasPrefix(path, "/api/expenses"): return "Expenses" case strings.HasPrefix(path, "/api/dashboards"): return "Dashboards" case strings.HasPrefix(path, "/api/purchases"): return "Purchases" case strings.HasPrefix(path, "/api/marketing"): return "Marketing" case strings.HasPrefix(path, "/api/users"): return "Users" case strings.HasPrefix(path, "/api/daily-checklists"): return "Daily Checklists" case strings.HasPrefix(path, "/api/sso"): return "SSO" default: return "API" } } func defaultSummary(path string) string { switch path { case "/healthz": return "Health check" case "/readyz": return "Readiness check" } trimmed := strings.Trim(path, "/") if trimmed == "" { return "List root" } parts := strings.Split(trimmed, "/") for i, part := range parts { parts[i] = strings.ReplaceAll(part, "-", " ") } return "GET " + strings.Join(parts, " / ") } func toOpenAPIPath(path string) string { segments := strings.Split(path, "/") for i, segment := range segments { if strings.HasPrefix(segment, ":") { segments[i] = "{" + strings.TrimPrefix(segment, ":") + "}" } } return strings.Join(segments, "/") } func inferPostmanPathVariable(path, param string) string { if param != "id" { return param } switch { case strings.HasPrefix(path, "/api/expenses/"): return "expense_id" case strings.HasPrefix(path, "/api/finance/payments/"): return "payment_id" case strings.HasPrefix(path, "/api/finance/transactions/"): return "transaction_id" case strings.HasPrefix(path, "/api/finance/initial-balances/"): return "initial_balance_id" case strings.HasPrefix(path, "/api/finance/injections/"): return "injection_id" case strings.HasPrefix(path, "/api/purchases/"): return "purchase_id" case strings.HasPrefix(path, "/api/inventory/adjustments/"): return "adjustment_id" case strings.HasPrefix(path, "/api/inventory/transfers/"): return "transfer_id" case strings.HasPrefix(path, "/api/master-data/banks/"): return "bank_id" case strings.HasPrefix(path, "/api/master-data/customers/"): return "customer_id" case strings.HasPrefix(path, "/api/master-data/suppliers/"): return "supplier_id" case strings.HasPrefix(path, "/api/master-data/locations/"): return "location_id" case strings.HasPrefix(path, "/api/master-data/areas/"): return "area_id" case strings.HasPrefix(path, "/api/master-data/products/"): return "product_id" case strings.HasPrefix(path, "/api/master-data/product-categories/"): return "product_category_id" case strings.HasPrefix(path, "/api/master-data/nonstocks/"): return "nonstock_id" case strings.HasPrefix(path, "/api/master-data/employees/"): return "employee_id" case strings.HasPrefix(path, "/api/master-data/flocks/"): return "flock_id" case strings.HasPrefix(path, "/api/master-data/warehouses/"): return "warehouse_id" case strings.HasPrefix(path, "/api/master-data/uoms/"): return "uom_id" case strings.HasPrefix(path, "/api/users/"): return "user_id" case strings.HasPrefix(path, "/api/production/recordings/"): return "recording_id" case strings.HasPrefix(path, "/api/production/uniformities/"): return "uniformity_id" case strings.HasPrefix(path, "/api/production/chickins/"): return "chickin_id" default: return "id" } } func defaultExampleForVariable(name string) string { if strings.Contains(name, "date") { return "2026-01-01" } if strings.Contains(name, "token") || strings.Contains(name, "key") { return "" } return "1" } func inferSchema(example any) map[string]any { switch example.(type) { case int, int32, int64, uint, uint32, uint64, float64: return map[string]any{"type": "integer"} default: return map[string]any{"type": "string"} } }