feat: notification service
This commit is contained in:
@@ -0,0 +1,556 @@
|
||||
asyncapi: 3.1.0
|
||||
info:
|
||||
title: Notification Service Intent Contract
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Stable Redis Streams contract for normalized notification intents
|
||||
published by upstream services toward Notification Service.
|
||||
channels:
|
||||
intents:
|
||||
address: notification:intents
|
||||
messages:
|
||||
notificationIntent:
|
||||
$ref: '#/components/messages/NotificationIntent'
|
||||
operations:
|
||||
publishNotificationIntent:
|
||||
action: send
|
||||
summary: Publish one normalized notification intent.
|
||||
channel:
|
||||
$ref: '#/channels/intents'
|
||||
messages:
|
||||
- $ref: '#/channels/intents/messages/notificationIntent'
|
||||
components:
|
||||
messages:
|
||||
NotificationIntent:
|
||||
name: NotificationIntent
|
||||
title: Notification intent
|
||||
summary: One normalized notification request published into Notification Service.
|
||||
payload:
|
||||
$ref: '#/components/schemas/NotificationIntentEnvelope'
|
||||
examples:
|
||||
- name: gameTurnReady
|
||||
summary: User-targeted game-turn notification.
|
||||
payload:
|
||||
notification_type: game.turn.ready
|
||||
producer: game_master
|
||||
audience_kind: user
|
||||
recipient_user_ids_json: '["user-1","user-2"]'
|
||||
idempotency_key: game-master:game-123:turn-54
|
||||
occurred_at_ms: "1775121700000"
|
||||
request_id: request-123
|
||||
trace_id: trace-123
|
||||
payload_json: '{"game_id":"game-123","game_name":"Nebula Clash","turn_number":54}'
|
||||
- name: geoReviewRecommended
|
||||
summary: Administrator email notification.
|
||||
payload:
|
||||
notification_type: geo.review_recommended
|
||||
producer: geoprofile
|
||||
audience_kind: admin_email
|
||||
idempotency_key: geoprofile:user-123:review-true:1775121700001
|
||||
occurred_at_ms: "1775121700001"
|
||||
payload_json: '{"user_id":"user-123","user_email":"pilot@example.com","observed_country":"DE","usual_connection_country":"PL","review_reason":"country_mismatch"}'
|
||||
- name: lobbyApplicationSubmittedPublic
|
||||
summary: Public-game application notification sent to configured admins.
|
||||
payload:
|
||||
notification_type: lobby.application.submitted
|
||||
producer: game_lobby
|
||||
audience_kind: admin_email
|
||||
idempotency_key: game-lobby:game-456:application-submitted:user-42
|
||||
occurred_at_ms: "1775121700002"
|
||||
payload_json: '{"game_id":"game-456","game_name":"Orion Front","applicant_user_id":"user-42","applicant_name":"Nova Pilot"}'
|
||||
schemas:
|
||||
NotificationIntentEnvelope:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
description: |
|
||||
Stable producer-to-notification envelope for one normalized
|
||||
notification intent.
|
||||
|
||||
Duplicate handling is scoped by `(producer, idempotency_key)`.
|
||||
A replay with the same normalized content is a successful duplicate.
|
||||
A replay with different normalized content is a conflict.
|
||||
|
||||
`request_id` and `trace_id` are observability-only metadata and do not
|
||||
participate in idempotency fingerprinting.
|
||||
required:
|
||||
- notification_type
|
||||
- producer
|
||||
- audience_kind
|
||||
- idempotency_key
|
||||
- occurred_at_ms
|
||||
- payload_json
|
||||
properties:
|
||||
notification_type:
|
||||
type: string
|
||||
enum:
|
||||
- geo.review_recommended
|
||||
- game.turn.ready
|
||||
- game.finished
|
||||
- game.generation_failed
|
||||
- lobby.runtime_paused_after_start
|
||||
- lobby.application.submitted
|
||||
- lobby.membership.approved
|
||||
- lobby.membership.rejected
|
||||
- lobby.invite.created
|
||||
- lobby.invite.redeemed
|
||||
- lobby.invite.expired
|
||||
description: |
|
||||
Exact v1 notification type catalog. `lobby.invite.revoked`
|
||||
deliberately remains outside the supported catalog because it
|
||||
produces no notification.
|
||||
producer:
|
||||
type: string
|
||||
enum:
|
||||
- geoprofile
|
||||
- game_master
|
||||
- game_lobby
|
||||
description: |
|
||||
Stable producer identifier. The exact producer value is frozen per
|
||||
`notification_type` by the v1 catalog.
|
||||
audience_kind:
|
||||
type: string
|
||||
enum:
|
||||
- user
|
||||
- admin_email
|
||||
description: |
|
||||
Delivery audience selector.
|
||||
`user` targets concrete `user_id` values from the producer.
|
||||
`admin_email` targets configured administrator email lists.
|
||||
recipient_user_ids_json:
|
||||
type: string
|
||||
description: |
|
||||
JSON-encoded array of unique stable `user_id` values.
|
||||
|
||||
Required for `audience_kind=user`. Forbidden for
|
||||
`audience_kind=admin_email`.
|
||||
|
||||
`Notification Service` treats the recipient set as unordered for
|
||||
idempotency purposes: duplicate `user_id` values are invalid and
|
||||
element order does not change normalized content.
|
||||
contentMediaType: application/json
|
||||
contentSchema:
|
||||
type: array
|
||||
minItems: 1
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
idempotency_key:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: |
|
||||
Producer-owned idempotency key scoped together with `producer`.
|
||||
occurred_at_ms:
|
||||
type: string
|
||||
pattern: '^[0-9]+$'
|
||||
description: Milliseconds since Unix epoch as a base-10 string.
|
||||
request_id:
|
||||
type: string
|
||||
description: Optional observability request identifier.
|
||||
trace_id:
|
||||
type: string
|
||||
description: Optional observability trace identifier.
|
||||
payload_json:
|
||||
type: string
|
||||
description: |
|
||||
JSON-encoded type-specific payload. Payload normalization ignores
|
||||
insignificant whitespace and object key order, while array order
|
||||
remains significant. Required payload fields are frozen per
|
||||
`notification_type`.
|
||||
contentMediaType: application/json
|
||||
contentSchema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
audience_kind:
|
||||
const: user
|
||||
required:
|
||||
- audience_kind
|
||||
then:
|
||||
required:
|
||||
- recipient_user_ids_json
|
||||
- if:
|
||||
properties:
|
||||
audience_kind:
|
||||
const: admin_email
|
||||
required:
|
||||
- audience_kind
|
||||
then:
|
||||
not:
|
||||
required:
|
||||
- recipient_user_ids_json
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: geo.review_recommended
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: geoprofile
|
||||
audience_kind:
|
||||
const: admin_email
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/GeoReviewRecommendedPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: game.turn.ready
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_master
|
||||
audience_kind:
|
||||
const: user
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/GameTurnReadyPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: game.finished
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_master
|
||||
audience_kind:
|
||||
const: user
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/GameFinishedPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: game.generation_failed
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_master
|
||||
audience_kind:
|
||||
const: admin_email
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/GameGenerationFailedPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: lobby.runtime_paused_after_start
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_lobby
|
||||
audience_kind:
|
||||
const: admin_email
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/LobbyRuntimePausedAfterStartPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: lobby.application.submitted
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_lobby
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/LobbyApplicationSubmittedPayload'
|
||||
oneOf:
|
||||
- properties:
|
||||
audience_kind:
|
||||
const: user
|
||||
required:
|
||||
- audience_kind
|
||||
- properties:
|
||||
audience_kind:
|
||||
const: admin_email
|
||||
required:
|
||||
- audience_kind
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: lobby.membership.approved
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_lobby
|
||||
audience_kind:
|
||||
const: user
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/LobbyMembershipApprovedPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: lobby.membership.rejected
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_lobby
|
||||
audience_kind:
|
||||
const: user
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/LobbyMembershipRejectedPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: lobby.invite.created
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_lobby
|
||||
audience_kind:
|
||||
const: user
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/LobbyInviteCreatedPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: lobby.invite.redeemed
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_lobby
|
||||
audience_kind:
|
||||
const: user
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/LobbyInviteRedeemedPayload'
|
||||
- if:
|
||||
properties:
|
||||
notification_type:
|
||||
const: lobby.invite.expired
|
||||
required:
|
||||
- notification_type
|
||||
then:
|
||||
properties:
|
||||
producer:
|
||||
const: game_lobby
|
||||
audience_kind:
|
||||
const: user
|
||||
payload_json:
|
||||
contentSchema:
|
||||
$ref: '#/components/schemas/LobbyInviteExpiredPayload'
|
||||
GeoReviewRecommendedPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- user_id
|
||||
- user_email
|
||||
- observed_country
|
||||
- usual_connection_country
|
||||
- review_reason
|
||||
properties:
|
||||
user_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
user_email:
|
||||
type: string
|
||||
minLength: 1
|
||||
observed_country:
|
||||
type: string
|
||||
minLength: 1
|
||||
usual_connection_country:
|
||||
type: string
|
||||
minLength: 1
|
||||
review_reason:
|
||||
type: string
|
||||
minLength: 1
|
||||
GameTurnReadyPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
- turn_number
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
turn_number:
|
||||
type: integer
|
||||
minimum: 1
|
||||
GameFinishedPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
- final_turn_number
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
final_turn_number:
|
||||
type: integer
|
||||
minimum: 1
|
||||
GameGenerationFailedPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
- failure_reason
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
failure_reason:
|
||||
type: string
|
||||
minLength: 1
|
||||
LobbyRuntimePausedAfterStartPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
LobbyApplicationSubmittedPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
- applicant_user_id
|
||||
- applicant_name
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
applicant_user_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
applicant_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
LobbyMembershipApprovedPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
LobbyMembershipRejectedPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
LobbyInviteCreatedPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
- inviter_user_id
|
||||
- inviter_name
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
inviter_user_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
inviter_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
LobbyInviteRedeemedPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
- invitee_user_id
|
||||
- invitee_name
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
invitee_user_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
invitee_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
LobbyInviteExpiredPayload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
required:
|
||||
- game_id
|
||||
- game_name
|
||||
- invitee_user_id
|
||||
- invitee_name
|
||||
properties:
|
||||
game_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
game_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
invitee_user_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
invitee_name:
|
||||
type: string
|
||||
minLength: 1
|
||||
Reference in New Issue
Block a user