fix: honor v2 interaction event contract

This commit is contained in:
2026-05-16 04:32:58 +02:00
parent 301a7b96d0
commit 0a4646bf44
3 changed files with 83 additions and 7 deletions

View File

@@ -4,8 +4,8 @@ import Web.Types
import Generated.Types
import IHP.Prelude
import IHP.ControllerPrelude
import Data.Aeson (object, (.=))
import qualified Data.Text as T
import Data.Aeson (Value(..), object, (.=))
import IHP.ControllerSupport (ControllerContext, getHeader, requestBodyJSON)
import Web.Controller.Api.V2.Auth
( requireApiConsumer, paginatedResponse, getPageParams
, respondWithStatus )
@@ -13,8 +13,13 @@ import Application.Helper.TypeRegistry (validateEventType)
import Web.Job.WebhookDeliveryJob (dispatchWebhooks)
import Control.Concurrent (forkIO)
import Control.Monad (void)
import Data.Maybe (fromMaybe, mapMaybe)
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy.Char8 as LBSC
import qualified Data.UUID as UUID
import qualified Data.Aeson as A
import qualified Data.Vector as V
instance Controller ApiV2InteractionEventsController where
@@ -47,6 +52,7 @@ instance Controller ApiV2InteractionEventsController where
let widgetIdText = paramOrNothing @Text "widgetId"
eventType = paramOrNothing @Text "eventType"
viewContext = paramOrNothing @Text "viewContext"
metadata = metadataFromRequest
let missing = catMaybes
[ if isNothing widgetIdText then Just ("widgetId" :: Text) else Nothing
@@ -76,9 +82,7 @@ instance Controller ApiV2InteractionEventsController where
forM_ consumer.hubCapabilityManifestId $ \manifestId -> do
manifest <- fetch manifestId
when (manifest.status == "active") do
let declared = case manifest.declaredEventTypes of
_ -> [] :: [Text] -- JSONB array decoded via aeson
unless (null declared || evType `elem` declared) do
unless (manifestAllowsEvent evType manifest.declaredEventTypes) do
respondWithStatus 422 $ object
[ "error" .= ("Event type not declared in hub manifest" :: Text)
, "code" .= ("event_type_not_in_manifest" :: Text)
@@ -100,6 +104,7 @@ instance Controller ApiV2InteractionEventsController where
|> set #eventType evType
|> set #actorType "api"
|> set #viewContextRef viewContext
|> set #metadata metadata
|> createRecord
-- Dispatch webhooks fire-and-forget
let webhookPayload = object
@@ -109,7 +114,7 @@ instance Controller ApiV2InteractionEventsController where
, "eventType" .= event.eventType
, "occurredAt" .= event.occurredAt
]
liftIO $ void $ forkIO $ dispatchWebhooks "clicked" webhookPayload
liftIO $ void $ forkIO $ dispatchWebhooks evType webhookPayload
renderJson (eventToJson event)
eventToJson :: InteractionEvent -> Value
@@ -123,3 +128,33 @@ eventToJson e = object
, "metadata" .= e.metadata
, "occurredAt" .= e.occurredAt
]
declaredEventTypeNames :: A.Value -> [Text]
declaredEventTypeNames (Array values) = mapMaybe extractText (V.toList values)
where
extractText (String value) = Just value
extractText _ = Nothing
declaredEventTypeNames _ = []
manifestAllowsEvent :: Text -> A.Value -> Bool
manifestAllowsEvent eventType declaredEventTypes =
let declared = declaredEventTypeNames declaredEventTypes
in null declared || eventType `elem` declared
metadataParamOrEmpty :: Maybe A.Value -> A.Value
metadataParamOrEmpty = fromMaybe (object [])
metadataFromRequest :: (?context :: ControllerContext) => A.Value
metadataFromRequest =
case getHeader "Content-Type" of
Just contentType | "application/json" `BS.isPrefixOf` contentType ->
metadataParamOrEmpty (metadataFromJsonBody requestBodyJSON)
_ ->
metadataParamOrEmpty (metadataFromText =<< paramOrNothing @Text "metadata")
metadataFromJsonBody :: A.Value -> Maybe A.Value
metadataFromJsonBody (Object body) = KM.lookup "metadata" body
metadataFromJsonBody _ = Nothing
metadataFromText :: Text -> Maybe A.Value
metadataFromText raw = A.decode (LBSC.pack (cs raw))