Files
inter-hub/Web/Controller/Api/V2/OpenApi.hs
tegwick 5c13de1b8f
All checks were successful
Build and Deploy / build-push-deploy (push) Successful in 3m6s
Make hub discovery public
2026-06-14 22:48:53 +02:00

894 lines
34 KiB
Haskell

module Web.Controller.Api.V2.OpenApi where
-- GET /api/v2/openapi.json — OpenAPI 3.1 spec with live type registry enums
-- GET /api/v2/openapi.yaml — YAML convenience alias
-- GET /api/v2/docs — Swagger UI
import Web.Types
import Generated.Types
import IHP.Prelude
import IHP.ControllerPrelude
import Data.Aeson (object, (.=), Array, toJSON)
import qualified Data.Aeson as A
import qualified Data.Aeson.Key as K
import qualified Data.Vector as V
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Yaml as Yaml -- yaml package
import qualified Data.ByteString.Lazy as LBS
import Application.Helper.TypeRegistry
( activeWidgetTypes, activeEventTypes, activeAnnotationCategories
, activePolicyScopes )
import Network.HTTP.Types (status200)
import Network.Wai (responseLBS)
instance Controller ApiV2OpenApiController where
action ApiV2OpenApiJsonAction = do
spec <- buildOpenApiSpec
respondAndExit $ responseLBS status200
[("Content-Type", "application/json")]
(A.encode spec)
action ApiV2OpenApiYamlAction = do
spec <- buildOpenApiSpec
let yaml = Yaml.encode spec
respondAndExit $ responseLBS status200
[("Content-Type", "application/yaml")]
(LBS.fromStrict yaml)
action ApiV2DocsAction = do
respondAndExit $ responseLBS status200
[("Content-Type", "text/html; charset=utf-8")]
swaggerUiHtml
-- | Build the full OpenAPI 3.1 document from live registry data.
buildOpenApiSpec :: (?modelContext :: ModelContext) => IO Value
buildOpenApiSpec = do
(fwWidgetTypes, ownedWidgetTypes) <- activeWidgetTypes
let allWidgetTypes = fwWidgetTypes ++ ownedWidgetTypes
eventTypes <- activeEventTypes
annCats <- activeAnnotationCategories
policyScopes <- activePolicyScopes
let wtEnum = toJSON $ map (.name) allWidgetTypes
let etEnum = toJSON $ map (.name) eventTypes
let acEnum = toJSON $ map (.name) annCats
let psEnum = toJSON $ map (.name) policyScopes
pure $ object
[ "openapi" .= ("3.1.0" :: Text)
, "info" .= object
[ "title" .= ("Interaction Hub Framework API" :: Text)
, "version" .= ("2.0" :: Text)
, "description" .= ("IHF external API v2. For the human-readable contract see /contracts/functional/interaction-reporting-v1.md" :: Text)
]
, "x-ihf-contract" .= ("/contracts/functional/interaction-reporting-v1.md" :: Text)
, "servers" .= [object ["url" .= ("/api/v2" :: Text)]]
, "paths" .= buildPaths
, "components" .= object
[ "schemas" .= object
[ "WidgetType" .= object
[ "type" .= ("string" :: Text)
, "enum" .= wtEnum
]
, "EventType" .= object
[ "type" .= ("string" :: Text)
, "enum" .= etEnum
]
, "AnnotationCategory" .= object
[ "type" .= ("string" :: Text)
, "enum" .= acEnum
]
, "PolicyScope" .= object
[ "type" .= ("string" :: Text)
, "enum" .= psEnum
]
, "PaginationMeta" .= object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "page" .= object ["type" .= ("integer" :: Text)]
, "per_page" .= object ["type" .= ("integer" :: Text)]
, "total" .= object ["type" .= ("integer" :: Text)]
]
]
, "Hub" .= hubSchema
, "CreateHubRequest" .= createHubRequestSchema
, "HubCapabilityManifest" .= manifestSchema
, "CreateHubCapabilityManifestRequest" .= createManifestRequestSchema
, "UpdateHubCapabilityManifestRequest" .= updateManifestRequestSchema
, "ApiConsumer" .= apiConsumerSchema
, "CreateApiConsumerRequest" .= createApiConsumerRequestSchema
, "ApiKey" .= apiKeySchema
, "CreateApiKeyRequest" .= createApiKeyRequestSchema
, "ApiKeyCreatedResponse" .= apiKeyCreatedResponseSchema
, "Widget" .= widgetSchema
, "CreateWidgetRequest" .= createWidgetRequestSchema
, "InteractionEvent" .= interactionEventSchema
, "CreateInteractionEventRequest" .= createInteractionEventRequestSchema
, "Annotation" .= annotationSchema
, "CreateAnnotationRequest" .= createAnnotationRequestSchema
, "RequirementCandidate" .= rcSchema
, "DecisionRecord" .= drSchema
, "DeploymentRecord" .= depSchema
, "OutcomeSignal" .= sigSchema
, "OutcomeCorrelation" .= outcomeCorrelationSchema
, "PatternPerformanceRecord" .= patternPerformanceSchema
, "InstitutionalKnowledgeEntry" .= institutionalKnowledgeSchema
, "HubRegistryEntry" .= hubRegistryEntrySchema
, "HubManifestSummary" .= hubManifestSummarySchema
, "WidgetPattern" .= widgetPatternSchema
, "WidgetPatternDetail" .= widgetPatternDetailSchema
, "WidgetPatternVersion" .= widgetPatternVersionSchema
, "PatternAdoptionResponse" .= patternAdoptionResponseSchema
]
, "securitySchemes" .= object
[ "BearerAuth" .= object
[ "type" .= ("http" :: Text)
, "scheme" .= ("bearer" :: Text)
, "description" .= ("API key or OAuth token obtained via POST /api/v2/token" :: Text)
]
]
]
, "security" .= [object ["BearerAuth" .= ([] :: [Text])]]
]
buildPaths :: Value
buildPaths = object
[ "/hubs" .= object
[ "get" .= publicPaginatedListOp "Hub" []
, "post" .= writeOp "Hub" "CreateHubRequest"
]
, "/hubs/{id}" .= getShowPath "Hub"
, "/hub-capability-manifests" .= object
[ "get" .= listOp "HubCapabilityManifest"
[ ("hubId", "string", "uuid")
, ("status", "string", "")
]
, "post" .= writeOp "HubCapabilityManifest" "CreateHubCapabilityManifestRequest"
]
, "/hub-capability-manifests/{id}" .= object
[ "get" .= showOp "HubCapabilityManifest"
, "patch" .= writeOpWithStatusAndParams
"Update HubCapabilityManifest"
"HubCapabilityManifest"
"UpdateHubCapabilityManifestRequest"
True
"200"
[pathParam "id"]
]
, "/hub-capability-manifests/{id}/activate" .= object
[ "post" .= postNoBodyOpWithStatusAndParams
"Activate HubCapabilityManifest"
"HubCapabilityManifest"
"200"
[pathParam "id"]
]
, "/api-consumers" .= object
[ "get" .= listOp "ApiConsumer" []
, "post" .= writeOp "ApiConsumer" "CreateApiConsumerRequest"
]
, "/api-consumers/{id}" .= getShowPath "ApiConsumer"
, "/api-consumers/{id}/api-keys" .= object
[ "post" .= writeOpWithResponseStatusAndParams
"Create ApiKey"
"ApiKeyCreatedResponse"
"CreateApiKeyRequest"
False
"201"
[pathParam "id"]
]
, "/widgets" .= object
[ "get" .= listOp "Widget" []
, "post" .= writeOp "Widget" "CreateWidgetRequest"
]
, "/widgets/{id}" .= getShowPath "Widget"
, "/interaction-events" .= object
[ "get" .= listOp "InteractionEvent"
[ ("widgetId", "string", "uuid")
, ("eventType", "string", "")
]
, "post" .= writeOp "InteractionEvent" "CreateInteractionEventRequest"
]
, "/annotations" .= object
[ "get" .= listOp "Annotation"
[ ("widgetId", "string", "uuid")
, ("category", "string", "")
]
, "post" .= writeOp "Annotation" "CreateAnnotationRequest"
]
, "/requirement-candidates" .= getListPath "RequirementCandidate"
, "/requirement-candidates/{id}" .= getShowPath "RequirementCandidate"
, "/decision-records" .= getListPath "DecisionRecord"
, "/decision-records/{id}" .= getShowPath "DecisionRecord"
, "/deployment-records" .= getListPath "DeploymentRecord"
, "/deployment-records/{id}" .= getShowPath "DeploymentRecord"
, "/outcome-signals" .= getListPath "OutcomeSignal"
, "/outcome-signals/{id}" .= getShowPath "OutcomeSignal"
, "/widget-types" .= publicListPath "WidgetTypeRegistry"
, "/event-types" .= publicListPath "EventTypeRegistry"
, "/annotation-categories" .= publicListPath "AnnotationCategoryRegistry"
, "/policy-scopes" .= publicListPath "PolicyScopeRegistry"
, "/token" .= tokenPath
-- Phase 10 — Hub Registry and Widget Marketplace
, "/hub-registry" .= getListPath "HubRegistryEntry"
, "/hub-registry/{hubId}" .= getShowPathWithParam "HubRegistryEntry" "hubId"
, "/widget-patterns" .= getListPath "WidgetPattern"
, "/widget-patterns/{id}" .= getShowPath "WidgetPatternDetail"
, "/widget-patterns/{id}/adopt" .= object
[ "post" .= postNoBodyOpWithStatusAndParams
"Adopt WidgetPattern"
"PatternAdoptionResponse"
"200"
[pathParam "id"]
]
]
getListPath :: Text -> Value
getListPath schemaName = object
[ "get" .= listOp schemaName [] ]
getShowPath :: Text -> Value
getShowPath schemaName = object
[ "get" .= showOp schemaName ]
getShowPathWithParam :: Text -> Text -> Value
getShowPathWithParam schemaName paramName = object
[ "get" .= showOpWithParam schemaName paramName ]
listOp :: Text -> [(Text, Text, Text)] -> Value
listOp schemaName extraParams = object
[ "summary" .= ("List " <> schemaName)
, "security" .= [object ["BearerAuth" .= ([] :: [Text])]]
, "parameters" .= (pageParams ++ map toParam extraParams)
, "responses" .= object
[ "200" .= object
[ "description" .= ("OK" :: Text)
, "content" .= object
[ "application/json" .= object
[ "schema" .= object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "data" .= object
[ "type" .= ("array" :: Text)
, "items" .= object ["$ref" .= ("#/components/schemas/" <> schemaName)]
]
, "meta" .= object ["$ref" .= ("#/components/schemas/PaginationMeta" :: Text)]
]
]
]
]
]
, "401" .= object ["description" .= ("Unauthorized" :: Text)]
]
]
where
toParam (name, typ, fmt) = object $
[ "name" .= name, "in" .= ("query" :: Text)
, "schema" .= object (["type" .= typ] ++ if fmt /= "" then [("format", A.String fmt)] else [])
]
publicPaginatedListOp :: Text -> [(Text, Text, Text)] -> Value
publicPaginatedListOp schemaName extraParams = object
[ "summary" .= ("List " <> schemaName)
, "security" .= ([] :: [Value])
, "parameters" .= (pageParams ++ map toParam extraParams)
, "responses" .= object
[ "200" .= object
[ "description" .= ("OK" :: Text)
, "content" .= object
[ "application/json" .= object
[ "schema" .= object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "data" .= object
[ "type" .= ("array" :: Text)
, "items" .= object ["$ref" .= ("#/components/schemas/" <> schemaName)]
]
, "meta" .= object ["$ref" .= ("#/components/schemas/PaginationMeta" :: Text)]
]
]
]
]
]
]
]
where
toParam (name, typ, fmt) = object $
[ "name" .= name, "in" .= ("query" :: Text)
, "schema" .= object (["type" .= typ] ++ if fmt /= "" then [("format", A.String fmt)] else [])
]
showOp :: Text -> Value
showOp schemaName = showOpWithParam schemaName "id"
showOpWithParam :: Text -> Text -> Value
showOpWithParam schemaName paramName = object
[ "summary" .= ("Get " <> schemaName)
, "security" .= [object ["BearerAuth" .= ([] :: [Text])]]
, "parameters" .= [pathParam paramName]
, "responses" .= object
[ "200" .= object
[ "description" .= ("OK" :: Text)
, "content" .= object
[ "application/json" .= object
["schema" .= object ["$ref" .= ("#/components/schemas/" <> schemaName)]]
]
]
, "401" .= object ["description" .= ("Unauthorized" :: Text)]
, "404" .= object ["description" .= ("Not found" :: Text)]
]
]
writeOp :: Text -> Text -> Value
writeOp schemaName reqSchema = writeOpWithSummary ("Create " <> schemaName) schemaName reqSchema
writeOpWithSummary :: Text -> Text -> Text -> Value
writeOpWithSummary summaryText schemaName reqSchema =
writeOpWithStatusAndParams summaryText schemaName reqSchema True "201" []
writeOpWithStatusAndParams :: Text -> Text -> Text -> Bool -> Text -> [Value] -> Value
writeOpWithStatusAndParams = writeOpWithResponseStatusAndParams
writeOpWithResponseStatusAndParams :: Text -> Text -> Text -> Bool -> Text -> [Value] -> Value
writeOpWithResponseStatusAndParams summaryText responseSchema reqSchema bodyRequired successStatus params = object
[ "summary" .= summaryText
, "security" .= [object ["BearerAuth" .= ([] :: [Text])]]
, "parameters" .= params
, "requestBody" .= object
[ "required" .= bodyRequired
, "content" .= object
[ "application/json" .= object
["schema" .= object ["$ref" .= ("#/components/schemas/" <> reqSchema)]]
]
]
, "responses" .= object
[ K.fromText successStatus .= object
[ "description" .= ("OK" :: Text)
, "content" .= object
[ "application/json" .= object
["schema" .= object ["$ref" .= ("#/components/schemas/" <> responseSchema)]]
]
]
, "400" .= object ["description" .= ("Invalid request" :: Text)]
, "401" .= object ["description" .= ("Unauthorized" :: Text)]
, "422" .= object ["description" .= ("Validation error" :: Text)]
]
]
postNoBodyOpWithStatusAndParams :: Text -> Text -> Text -> [Value] -> Value
postNoBodyOpWithStatusAndParams summaryText responseSchema successStatus params = object
[ "summary" .= summaryText
, "security" .= [object ["BearerAuth" .= ([] :: [Text])]]
, "parameters" .= params
, "responses" .= object
[ K.fromText successStatus .= object
[ "description" .= ("OK" :: Text)
, "content" .= object
[ "application/json" .= object
["schema" .= object ["$ref" .= ("#/components/schemas/" <> responseSchema)]]
]
]
, "400" .= object ["description" .= ("Invalid request" :: Text)]
, "401" .= object ["description" .= ("Unauthorized" :: Text)]
, "422" .= object ["description" .= ("Validation error" :: Text)]
]
]
pathParam :: Text -> Value
pathParam name = object
[ "name" .= name
, "in" .= ("path" :: Text)
, "required" .= True
, "schema" .= uuidProp
]
publicListPath :: Text -> Value
publicListPath schemaName = object
[ "get" .= object
[ "summary" .= ("List registered " <> schemaName <> " values" :: Text)
, "security" .= ([] :: [Value])
, "responses" .= object
[ "200" .= object ["description" .= ("OK" :: Text)] ]
]
]
tokenPath :: Value
tokenPath = object
[ "post" .= object
[ "summary" .= ("Obtain OAuth access token (client credentials)" :: Text)
, "requestBody" .= object
[ "required" .= True
, "content" .= object
[ "application/x-www-form-urlencoded" .= object
[ "schema" .= object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "grant_type" .= object ["type" .= ("string" :: Text), "enum" .= ["client_credentials" :: Text]]
, "client_id" .= object ["type" .= ("string" :: Text), "format" .= ("uuid" :: Text)]
, "client_secret" .= object ["type" .= ("string" :: Text)]
, "scope" .= object ["type" .= ("string" :: Text)]
]
]
]
]
]
, "responses" .= object
[ "200" .= object ["description" .= ("Access token issued" :: Text)]
, "400" .= object ["description" .= ("Invalid request or credentials" :: Text)]
]
]
]
pageParams :: [Value]
pageParams =
[ object ["name" .= ("page" :: Text), "in" .= ("query" :: Text), "schema" .= object ["type" .= ("integer" :: Text)]]
, object ["name" .= ("per_page" :: Text), "in" .= ("query" :: Text), "schema" .= object ["type" .= ("integer" :: Text)]]
]
-- Schemas for all resource types
hubSchema :: Value
hubSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "slug" .= strProp
, "name" .= strProp
, "domain" .= strProp
, "hubKind" .= object ["type" .= ("string" :: Text), "enum" .= ["domain" :: Text, "shared"]]
, "hubFamily" .= object ["type" .= ("string" :: Text), "enum" .= ["vsm" :: Text]]
, "vsmFunction" .= strProp
, "vsmSystem" .= object ["type" .= ("string" :: Text), "enum" .= ["1" :: Text, "2", "3", "3*", "4", "5", "environment"]]
, "createdAt" .= object ["type" .= ("string" :: Text), "format" .= ("date-time" :: Text)]
]
]
createHubRequestSchema :: Value
createHubRequestSchema = object
[ "type" .= ("object" :: Text)
, "required" .= (["slug", "name", "domain"] :: [Text])
, "properties" .= object
[ "slug" .= strProp
, "name" .= strProp
, "domain" .= strProp
, "hubKind" .= object ["type" .= ("string" :: Text), "enum" .= ["domain" :: Text, "shared"]]
, "hubFamily" .= object ["type" .= ("string" :: Text), "enum" .= ["vsm" :: Text]]
, "vsmFunction" .= strProp
, "vsmSystem" .= object ["type" .= ("string" :: Text), "enum" .= ["1" :: Text, "2", "3", "3*", "4", "5", "environment"]]
]
]
widgetSchema :: Value
widgetSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "hubId" .= uuidProp
, "name" .= strProp
, "widgetType" .= object ["$ref" .= ("#/components/schemas/WidgetType" :: Text)]
, "capabilityRef" .= strProp
, "viewContext" .= strProp
, "policyScope" .= strProp
, "status" .= strProp
, "version" .= object ["type" .= ("integer" :: Text)]
, "createdAt" .= object ["type" .= ("string" :: Text), "format" .= ("date-time" :: Text)]
]
]
createWidgetRequestSchema :: Value
createWidgetRequestSchema = object
[ "type" .= ("object" :: Text)
, "required" .= (["hubId", "name", "widgetType"] :: [Text])
, "properties" .= object
[ "hubId" .= uuidProp
, "name" .= strProp
, "widgetType" .= object ["$ref" .= ("#/components/schemas/WidgetType" :: Text)]
, "capabilityRef" .= strProp
, "viewContext" .= strProp
, "policyScope" .= object ["$ref" .= ("#/components/schemas/PolicyScope" :: Text)]
, "status" .= object ["type" .= ("string" :: Text), "enum" .= ["active" :: Text, "deprecated", "draft"]]
, "adapterSpecId" .= uuidProp
]
]
manifestSchema :: Value
manifestSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "hubId" .= uuidProp
, "manifestVersion" .= strProp
, "declaredWidgetTypes" .= object ["type" .= ("array" :: Text), "items" .= object ["$ref" .= ("#/components/schemas/WidgetType" :: Text)]]
, "declaredEventTypes" .= object ["type" .= ("array" :: Text), "items" .= object ["$ref" .= ("#/components/schemas/EventType" :: Text)]]
, "declaredAnnotationCategories" .= object ["type" .= ("array" :: Text), "items" .= object ["$ref" .= ("#/components/schemas/AnnotationCategory" :: Text)]]
, "declaredPolicyScopes" .= object ["type" .= ("array" :: Text), "items" .= object ["$ref" .= ("#/components/schemas/PolicyScope" :: Text)]]
, "capabilityDescription" .= strProp
, "contact" .= strProp
, "status" .= strProp
, "activatedAt" .= dtProp
, "createdAt" .= dtProp
, "updatedAt" .= dtProp
]
]
createManifestRequestSchema :: Value
createManifestRequestSchema = object
[ "type" .= ("object" :: Text)
, "required" .= (["hubId"] :: [Text])
, "properties" .= manifestRequestProperties True
]
updateManifestRequestSchema :: Value
updateManifestRequestSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= manifestRequestProperties False
]
manifestRequestProperties :: Bool -> Value
manifestRequestProperties includeHubId =
object $
(if includeHubId then ["hubId" .= uuidProp] else [])
++ [ "manifestVersion" .= strProp
, "declaredWidgetTypes" .= arrayOfRef "WidgetType"
, "declaredEventTypes" .= arrayOfRef "EventType"
, "declaredAnnotationCategories" .= arrayOfRef "AnnotationCategory"
, "declaredPolicyScopes" .= arrayOfRef "PolicyScope"
, "capabilityDescription" .= strProp
, "contact" .= strProp
]
apiConsumerSchema :: Value
apiConsumerSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "name" .= strProp
, "description" .= strProp
, "hubCapabilityManifestId" .= uuidProp
, "rateLimitPerMinute" .= object ["type" .= ("integer" :: Text)]
, "quotaPerDay" .= object ["type" .= ("integer" :: Text)]
, "quotaResetsAt" .= dtProp
, "isActive" .= object ["type" .= ("boolean" :: Text)]
, "createdAt" .= dtProp
, "updatedAt" .= dtProp
]
]
createApiConsumerRequestSchema :: Value
createApiConsumerRequestSchema = object
[ "type" .= ("object" :: Text)
, "required" .= (["name"] :: [Text])
, "properties" .= object
[ "name" .= strProp
, "description" .= strProp
, "hubCapabilityManifestId" .= uuidProp
, "rateLimitPerMinute" .= object ["type" .= ("integer" :: Text), "minimum" .= (1 :: Int), "default" .= (60 :: Int)]
, "quotaPerDay" .= object ["type" .= ("integer" :: Text), "minimum" .= (1 :: Int), "default" .= (10000 :: Int)]
]
]
apiKeySchema :: Value
apiKeySchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "apiConsumerId" .= uuidProp
, "keyPrefix" .= strProp
, "scopes" .= strProp
, "tokenType" .= strProp
, "expiresAt" .= dtProp
, "revokedAt" .= dtProp
, "lastUsedAt" .= dtProp
, "createdAt" .= dtProp
]
]
createApiKeyRequestSchema :: Value
createApiKeyRequestSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "scopes" .= strProp
]
]
apiKeyCreatedResponseSchema :: Value
apiKeyCreatedResponseSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "apiKey" .= object ["$ref" .= ("#/components/schemas/ApiKey" :: Text)]
, "fullKey" .= object
[ "type" .= ("string" :: Text)
, "description" .= ("Static API key secret. Returned only in this creation response; it is stored hashed and cannot be recovered later." :: Text)
]
, "displayOnce" .= boolProp
]
]
interactionEventSchema :: Value
interactionEventSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "widgetId" .= uuidProp
, "eventType" .= object ["$ref" .= ("#/components/schemas/EventType" :: Text)]
, "actorId" .= uuidProp
, "actorType" .= strProp
, "viewContextRef" .= strProp
, "metadata" .= object ["type" .= ("object" :: Text)]
, "occurredAt" .= dtProp
]
]
createInteractionEventRequestSchema :: Value
createInteractionEventRequestSchema = object
[ "type" .= ("object" :: Text)
, "required" .= (["widgetId", "eventType"] :: [Text])
, "properties" .= object
[ "widgetId" .= uuidProp
, "eventType" .= object ["$ref" .= ("#/components/schemas/EventType" :: Text)]
, "viewContext" .= strProp
, "metadata" .= objectProp
]
]
annotationSchema :: Value
annotationSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "widgetId" .= uuidProp
, "parentId" .= uuidProp
, "body" .= strProp
, "category" .= object ["$ref" .= ("#/components/schemas/AnnotationCategory" :: Text)]
, "severity" .= strProp
, "threadId" .= uuidProp
, "actorId" .= uuidProp
, "actorType" .= strProp
, "createdAt" .= dtProp
]
]
createAnnotationRequestSchema :: Value
createAnnotationRequestSchema = object
[ "type" .= ("object" :: Text)
, "required" .= (["widgetId", "category", "body"] :: [Text])
, "properties" .= object
[ "widgetId" .= uuidProp
, "category" .= object ["$ref" .= ("#/components/schemas/AnnotationCategory" :: Text)]
, "body" .= strProp
]
]
rcSchema :: Value
rcSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "title" .= strProp
, "description" .= strProp
, "sourceWidgetId" .= uuidProp
, "category" .= strProp
, "status" .= strProp
, "createdAt" .= dtProp
]
]
drSchema :: Value
drSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "title" .= strProp
, "rationale" .= strProp
, "outcome" .= strProp
, "requirementId" .= uuidProp
, "candidateId" .= uuidProp
, "decidedAt" .= dtProp
, "notes" .= strProp
, "createdAt" .= dtProp
]
]
depSchema :: Value
depSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "decisionId" .= uuidProp
, "versionRef" .= strProp
, "deployedAt" .= dtProp
, "notes" .= strProp
, "createdAt" .= dtProp
]
]
sigSchema :: Value
sigSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "widgetId" .= uuidProp
, "deploymentId" .= uuidProp
, "signalType" .= strProp
, "value" .= object ["type" .= ("number" :: Text)]
, "observedAt" .= dtProp
]
]
outcomeCorrelationSchema :: Value
outcomeCorrelationSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "hubId" .= uuidProp
, "annotationCategory" .= strProp
, "correlationType" .= strProp
, "correlationScore" .= object ["type" .= ("number" :: Text)]
, "sampleCount" .= object ["type" .= ("integer" :: Text)]
, "computedAt" .= dtProp
]
]
patternPerformanceSchema :: Value
patternPerformanceSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "widgetPatternId" .= uuidProp
, "hubId" .= uuidProp
, "adoptionCount" .= object ["type" .= ("integer" :: Text)]
, "positiveOutcomeCount" .= object ["type" .= ("integer" :: Text)]
, "totalOutcomeCount" .= object ["type" .= ("integer" :: Text)]
, "positiveOutcomeRate" .= object ["type" .= ("number" :: Text)]
, "meanOutcomeValue" .= object ["type" .= ("number" :: Text)]
, "outcomeRank" .= object ["type" .= ("integer" :: Text)]
, "calibratedAt" .= dtProp
]
]
institutionalKnowledgeSchema :: Value
institutionalKnowledgeSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "hubId" .= uuidProp
, "decisionRecordId" .= uuidProp
, "summary" .= strProp
, "tags" .= object ["type" .= ("array" :: Text)]
, "createdAt" .= dtProp
, "updatedAt" .= dtProp
]
]
hubRegistryEntrySchema :: Value
hubRegistryEntrySchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "name" .= strProp
, "slug" .= strProp
, "domain" .= strProp
, "hubKind" .= strProp
, "hubFamily" .= strProp
, "vsmFunction" .= strProp
, "vsmSystem" .= strProp
, "gaafStatus" .= object ["type" .= ("string" :: Text), "enum" .= ["compliant" :: Text, "draft_only", "no_manifest"]]
, "manifest" .= object ["$ref" .= ("#/components/schemas/HubManifestSummary" :: Text)]
, "healthScore" .= intProp
, "healthAt" .= dtProp
]
]
hubManifestSummarySchema :: Value
hubManifestSummarySchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "manifestVersion" .= strProp
, "status" .= strProp
, "declaredWidgetTypes" .= arrayOfRef "WidgetType"
, "declaredEventTypes" .= arrayOfRef "EventType"
, "declaredAnnotationCategories" .= arrayOfRef "AnnotationCategory"
, "declaredPolicyScopes" .= arrayOfRef "PolicyScope"
, "activatedAt" .= dtProp
]
]
widgetPatternSchema :: Value
widgetPatternSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "hubId" .= uuidProp
, "name" .= strProp
, "description" .= strProp
, "widgetType" .= object ["$ref" .= ("#/components/schemas/WidgetType" :: Text)]
, "isCrossHub" .= boolProp
, "isPublished" .= boolProp
, "createdAt" .= dtProp
, "updatedAt" .= dtProp
]
]
widgetPatternDetailSchema :: Value
widgetPatternDetailSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "pattern" .= object ["$ref" .= ("#/components/schemas/WidgetPattern" :: Text)]
, "versions" .= object
[ "type" .= ("array" :: Text)
, "items" .= object ["$ref" .= ("#/components/schemas/WidgetPatternVersion" :: Text)]
]
, "adopterCount" .= intProp
]
]
widgetPatternVersionSchema :: Value
widgetPatternVersionSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "id" .= uuidProp
, "versionNumber" .= intProp
, "definition" .= objectProp
, "changelog" .= strProp
, "publishedAt" .= dtProp
]
]
patternAdoptionResponseSchema :: Value
patternAdoptionResponseSchema = object
[ "type" .= ("object" :: Text)
, "properties" .= object
[ "adopted" .= boolProp
, "adoptionId" .= uuidProp
]
]
uuidProp :: Value
uuidProp = object ["type" .= ("string" :: Text), "format" .= ("uuid" :: Text)]
strProp :: Value
strProp = object ["type" .= ("string" :: Text)]
intProp :: Value
intProp = object ["type" .= ("integer" :: Text)]
boolProp :: Value
boolProp = object ["type" .= ("boolean" :: Text)]
objectProp :: Value
objectProp = object
[ "type" .= ("object" :: Text)
, "additionalProperties" .= True
]
arrayOfRef :: Text -> Value
arrayOfRef schemaName = object
[ "type" .= ("array" :: Text)
, "items" .= object ["$ref" .= ("#/components/schemas/" <> schemaName)]
]
dtProp :: Value
dtProp = object ["type" .= ("string" :: Text), "format" .= ("date-time" :: Text)]
-- | Embedded Swagger UI HTML using CDN assets (no build step required)
swaggerUiHtml :: LBS.ByteString
swaggerUiHtml = LBS.fromStrict $ TE.encodeUtf8 swaggerUiHtmlText
swaggerUiHtmlText :: Text
swaggerUiHtmlText =
"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" />" <>
"<title>IHF API v2 \x2014 Documentation</title>" <>
"<link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\" />" <>
"</head><body>" <>
"<div id=\"swagger-ui\"></div>" <>
"<script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>" <>
"<script>window.onload=function(){SwaggerUIBundle({" <>
"url:\"/api/v2/openapi.json\"," <>
"dom_id:\"#swagger-ui\"," <>
"presets:[SwaggerUIBundle.presets.apis,SwaggerUIBundle.SwaggerUIStandalonePreset]," <>
"layout:\"StandaloneLayout\"" <>
"});}</script>" <>
"</body></html>"