docs: align v2 bootstrap api contract

This commit is contained in:
2026-05-19 02:40:21 +02:00
parent 5d5e810886
commit 45dbe81d57
4 changed files with 453 additions and 70 deletions

View File

@@ -10,6 +10,7 @@ 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
@@ -94,11 +95,19 @@ buildOpenApiSpec = do
, "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
@@ -106,6 +115,12 @@ buildOpenApiSpec = do
, "OutcomeCorrelation" .= outcomeCorrelationSchema
, "PatternPerformanceRecord" .= patternPerformanceSchema
, "InstitutionalKnowledgeEntry" .= institutionalKnowledgeSchema
, "HubRegistryEntry" .= hubRegistryEntrySchema
, "HubManifestSummary" .= hubManifestSummarySchema
, "WidgetPattern" .= widgetPatternSchema
, "WidgetPatternDetail" .= widgetPatternDetailSchema
, "WidgetPatternVersion" .= widgetPatternVersionSchema
, "PatternAdoptionResponse" .= patternAdoptionResponseSchema
]
, "securitySchemes" .= object
[ "BearerAuth" .= object
@@ -134,10 +149,20 @@ buildPaths = object
]
, "/hub-capability-manifests/{id}" .= object
[ "get" .= showOp "HubCapabilityManifest"
, "patch" .= writeOpWithSummary "Update HubCapabilityManifest" "HubCapabilityManifest" "UpdateHubCapabilityManifestRequest"
, "patch" .= writeOpWithStatusAndParams
"Update HubCapabilityManifest"
"HubCapabilityManifest"
"UpdateHubCapabilityManifestRequest"
True
"200"
[pathParam "id"]
]
, "/hub-capability-manifests/{id}/activate" .= object
[ "post" .= writeOpWithSummary "Activate HubCapabilityManifest" "HubCapabilityManifest" "ActivateHubCapabilityManifestRequest"
[ "post" .= postNoBodyOpWithStatusAndParams
"Activate HubCapabilityManifest"
"HubCapabilityManifest"
"200"
[pathParam "id"]
]
, "/api-consumers" .= object
[ "get" .= listOp "ApiConsumer" []
@@ -145,7 +170,13 @@ buildPaths = object
]
, "/api-consumers/{id}" .= getShowPath "ApiConsumer"
, "/api-consumers/{id}/api-keys" .= object
[ "post" .= writeOpWithSummary "Create ApiKey" "ApiKey" "CreateApiKeyRequest"
[ "post" .= writeOpWithResponseStatusAndParams
"Create ApiKey"
"ApiKeyCreatedResponse"
"CreateApiKeyRequest"
False
"201"
[pathParam "id"]
]
, "/widgets" .= object
[ "get" .= listOp "Widget" []
@@ -181,11 +212,15 @@ buildPaths = object
, "/token" .= tokenPath
-- Phase 10 — Hub Registry and Widget Marketplace
, "/hub-registry" .= getListPath "HubRegistryEntry"
, "/hub-registry/{hubId}" .= getShowPath "HubRegistryEntry"
, "/hub-registry/{hubId}" .= getShowPathWithParam "HubRegistryEntry" "hubId"
, "/widget-patterns" .= getListPath "WidgetPattern"
, "/widget-patterns/{id}" .= getShowPath "WidgetPattern"
, "/widget-patterns/{id}" .= getShowPath "WidgetPatternDetail"
, "/widget-patterns/{id}/adopt" .= object
[ "post" .= writeOp "PatternAdoption" "AdoptPatternRequest"
[ "post" .= postNoBodyOpWithStatusAndParams
"Adopt WidgetPattern"
"PatternAdoptionResponse"
"200"
[pathParam "id"]
]
]
@@ -197,6 +232,10 @@ 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)
@@ -230,10 +269,13 @@ listOp schemaName extraParams = object
]
showOp :: Text -> Value
showOp schemaName = object
showOp schemaName = showOpWithParam schemaName "id"
showOpWithParam :: Text -> Text -> Value
showOpWithParam schemaName paramName = object
[ "summary" .= ("Get " <> schemaName)
, "security" .= [object ["BearerAuth" .= ([] :: [Text])]]
, "parameters" .= [object ["name" .= ("id" :: Text), "in" .= ("path" :: Text), "required" .= True, "schema" .= object ["type" .= ("string" :: Text), "format" .= ("uuid" :: Text)]]]
, "parameters" .= [pathParam paramName]
, "responses" .= object
[ "200" .= object
[ "description" .= ("OK" :: Text)
@@ -251,23 +293,65 @@ writeOp :: Text -> Text -> Value
writeOp schemaName reqSchema = writeOpWithSummary ("Create " <> schemaName) schemaName reqSchema
writeOpWithSummary :: Text -> Text -> Text -> Value
writeOpWithSummary summaryText schemaName _reqSchema = object
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" .= True
[ "required" .= bodyRequired
, "content" .= object
[ "application/json" .= object
["schema" .= object ["$ref" .= ("#/components/schemas/" <> schemaName)]]
["schema" .= object ["$ref" .= ("#/components/schemas/" <> reqSchema)]]
]
]
, "responses" .= object
[ "201" .= object ["description" .= ("Created" :: Text)]
[ 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
@@ -360,6 +444,22 @@ widgetSchema = object
]
]
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)
@@ -380,6 +480,32 @@ manifestSchema = object
]
]
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)
@@ -397,6 +523,19 @@ apiConsumerSchema = object
]
]
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)
@@ -413,6 +552,27 @@ apiKeySchema = object
]
]
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)
@@ -428,6 +588,18 @@ interactionEventSchema = object
]
]
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)
@@ -445,6 +617,17 @@ annotationSchema = object
]
]
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)
@@ -546,12 +729,114 @@ institutionalKnowledgeSchema = object
]
]
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)]