Files
event-demo/resources/openapi/documentation.yaml
2026-08-05 00:43:12 +02:00

667 lines
21 KiB
YAML

openapi: "3.0.3"
info:
title: "event_demo API"
description: |
API for the event_demo project: a small event-sourced Uno-like card game.
The API is split in two parts:
- a classic REST API used to authenticate, create users, list games and
replay a game's history.
- a WebSocket endpoint (`/games/{id}`) used to play a game in real time:
the client sends `GameCommand` messages and receives `Notification`
messages back. See the `x-websocket-channels` section below for details,
since WebSockets are not natively described by OpenAPI 3.0.
Authentication is done with a JWT bearer token obtained from `/login/{username}`.
Note that most polymorphic messages (`GameCommand`, `Notification`) are
discriminated by a `type` field whose value is the fully-qualified Kotlin
class name of the payload (since no custom serial name is declared for
these types), while `Card` uses short discriminator values
(`Simple`, `Reverse`, `Pass`, `Plus2`, `Plus4`, `ChangeColor`).
version: "2.0.0"
servers:
- url: "https://event_demo"
tags:
- name: Auth
description: User registration and authentication
- name: Games
description: Listing games and reading/playing their state
security:
- bearerAuth: []
paths:
"/login/{username}":
post:
tags: [Auth]
summary: Log in and obtain a JWT
security: []
parameters:
- name: username
in: path
required: true
schema:
type: string
- name: password
in: query
description: The user's plain-text password.
required: true
schema:
type: string
responses:
200:
description: Successful login
content:
application/json:
schema:
$ref: "#/components/schemas/LoginResponse"
400:
description: Unknown username or invalid password
"/users/create":
post:
tags: [Auth]
summary: Create a new user account
description: Requires a valid JWT (any authenticated user can create new users).
parameters:
- name: username
in: query
required: true
schema:
type: string
- name: password
in: query
description: The plain-text password, hashed server-side before being stored.
required: true
schema:
type: string
responses:
200:
description: The newly created user
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserResponse"
401:
$ref: "#/components/responses/Unauthorized"
"/games":
get:
tags: [Games]
summary: List all known games
description: Returns up to the 100 most recent games (pagination is not yet exposed on this route).
responses:
200:
description: The list of games
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/GameList"
401:
$ref: "#/components/responses/Unauthorized"
"/games/{id}":
get:
tags: [Games]
summary: Replay a game's full notification history
description: |
Rebuilds every notification that would have been sent to the calling
player since the beginning of the game (from its event stream), so a
client reconnecting can catch up on the current game state.
This same path also accepts a WebSocket upgrade to play the game live,
see `x-websocket-channels` at the root of this document.
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
200:
description: The full list of notifications for this game, from this player's point of view
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Notification"
401:
$ref: "#/components/responses/Unauthorized"
404:
description: No game found for this id
x-websocket-channels:
"/games/{id}":
description: |
WebSocket endpoint to join and play a game in real time. Requires the
same JWT bearer authentication as the REST routes (sent the same way,
e.g. via the `Authorization` header during the WebSocket handshake).
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
send:
description: Commands sent by the client to act on the game.
schema:
$ref: "#/components/schemas/GameCommand"
receive:
description: Notifications sent by the server as the game progresses.
schema:
$ref: "#/components/schemas/Notification"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: >
JWT obtained from `POST /login/{username}`. It carries the `username`
and `userid` claims and currently expires 60 seconds after issuance.
responses:
Unauthorized:
description: Missing, invalid or expired JWT
content:
text/plain:
schema:
type: string
example: "Token is not valid or has expired"
schemas:
LoginResponse:
type: object
required: [token]
properties:
token:
type: string
description: JWT bearer token to use on subsequent requests.
CreateUserResponse:
type: object
required: [id]
properties:
id:
type: string
format: uuid
HttpErrorBadRequest:
type: object
description: Generic problem-details style error body used by some validation failures.
properties:
title:
type: string
statusCode:
type: integer
invalidParams:
type: array
items:
$ref: "#/components/schemas/InvalidParam"
InvalidParam:
type: object
required: [name, reason]
properties:
name:
type: string
reason:
type: string
PlayerId:
type: string
format: uuid
Player:
type: object
required: [name, userId, hand, id]
properties:
name:
type: string
userId:
type: string
format: uuid
id:
$ref: "#/components/schemas/PlayerId"
hand:
description: The set of cards currently held by the player.
type: array
items:
$ref: "#/components/schemas/Card"
GameList:
type: object
required: [aggregateId, status, players, winners]
properties:
aggregateId:
type: string
format: uuid
status:
$ref: "#/components/schemas/GameStatus"
players:
type: array
items:
$ref: "#/components/schemas/Player"
winners:
type: array
items:
$ref: "#/components/schemas/PlayerId"
GameStatus:
type: string
enum:
- OPENING
- IS_STARTED
- FINISH
- CANCELED
CardColor:
type: string
enum:
- Blue
- Red
- Yellow
- Green
Card:
description: >
A playing card. Discriminated by the "type" field using the short
names declared on each Kotlin subtype (@SerialName), unlike
GameCommand/Notification below.
oneOf:
- $ref: "#/components/schemas/NumericCard"
- $ref: "#/components/schemas/ReverseCard"
- $ref: "#/components/schemas/PassCard"
- $ref: "#/components/schemas/Plus2Card"
- $ref: "#/components/schemas/Plus4Card"
- $ref: "#/components/schemas/ChangeColorCard"
discriminator:
propertyName: type
mapping:
Simple: "#/components/schemas/NumericCard"
Reverse: "#/components/schemas/ReverseCard"
Pass: "#/components/schemas/PassCard"
Plus2: "#/components/schemas/Plus2Card"
Plus4: "#/components/schemas/Plus4Card"
ChangeColor: "#/components/schemas/ChangeColorCard"
NumericCard:
description: A numbered card (0-9) of a given color.
type: object
required: [type, id, number, color]
properties:
type:
type: string
enum: [Simple]
id:
type: string
format: uuid
number:
type: integer
minimum: 0
maximum: 9
color:
$ref: "#/components/schemas/CardColor"
ReverseCard:
description: Reverses the turn order.
type: object
required: [type, id, color]
properties:
type:
type: string
enum: [Reverse]
id:
type: string
format: uuid
color:
$ref: "#/components/schemas/CardColor"
PassCard:
description: Skips the next player's turn.
type: object
required: [type, id, color]
properties:
type:
type: string
enum: [Pass]
id:
type: string
format: uuid
color:
$ref: "#/components/schemas/CardColor"
Plus2Card:
description: Forces the next player to draw 2 cards and skips their turn.
type: object
required: [type, id, color]
properties:
type:
type: string
enum: [Plus2]
id:
type: string
format: uuid
color:
$ref: "#/components/schemas/CardColor"
Plus4Card:
description: >
Forces the next player to draw 4 cards and skips their turn. The new
color is chosen separately, via the `chosenColor` field of
PlayCardCommand, and is not part of the card itself.
type: object
required: [type, id]
properties:
type:
type: string
enum: [Plus4]
id:
type: string
format: uuid
ChangeColorCard:
description: >
Changes the current color. The new color is chosen separately, via
the `chosenColor` field of PlayCardCommand, and is not part of the
card itself.
type: object
required: [type, id]
properties:
type:
type: string
enum: [ChangeColor]
id:
type: string
format: uuid
GameCommand:
description: >
A command sent by the client over the game WebSocket to act on a game.
Discriminated by "type", whose value is the fully-qualified Kotlin
class name of the command (no `@SerialName` is declared on these
types).
oneOf:
- $ref: "#/components/schemas/JoinTheGameCommand"
- $ref: "#/components/schemas/PlayCardCommand"
- $ref: "#/components/schemas/ReadyToPlayCommand"
- $ref: "#/components/schemas/TakeCartFromDrawPileCommand"
discriminator:
propertyName: type
mapping:
eventDemo.contexts.game.application.command.models.JoinTheGameCommand: "#/components/schemas/JoinTheGameCommand"
eventDemo.contexts.game.application.command.models.PlayCardCommand: "#/components/schemas/PlayCardCommand"
eventDemo.contexts.game.application.command.models.ReadyToPlayCommand: "#/components/schemas/ReadyToPlayCommand"
eventDemo.contexts.game.application.command.models.TakeCartFromDrawPileCommand: "#/components/schemas/TakeCartFromDrawPileCommand"
JoinTheGameCommand:
description: Join an existing (not yet started) game.
type: object
required: [type, userId, payload]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.command.models.JoinTheGameCommand]
userId:
type: string
format: uuid
payload:
type: object
required: [aggregateId]
properties:
aggregateId:
type: string
format: uuid
description: The id of the game to join.
ReadyToPlayCommand:
description: Mark the calling player as ready, so the game can start once everyone is ready.
type: object
required: [type, userId, payload]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.command.models.ReadyToPlayCommand]
userId:
type: string
format: uuid
payload:
type: object
required: [aggregateId, playerId]
properties:
aggregateId:
type: string
format: uuid
playerId:
$ref: "#/components/schemas/PlayerId"
TakeCartFromDrawPileCommand:
description: Draw a card from the draw pile.
type: object
required: [type, userId, payload]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.command.models.TakeCartFromDrawPileCommand]
userId:
type: string
format: uuid
payload:
type: object
required: [aggregateId, playerId]
properties:
aggregateId:
type: string
format: uuid
playerId:
$ref: "#/components/schemas/PlayerId"
PlayCardCommand:
description: Play a card from the calling player's hand.
type: object
required: [type, userId, payload]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.command.models.PlayCardCommand]
userId:
type: string
format: uuid
payload:
type: object
required: [aggregateId, playerId, card]
properties:
aggregateId:
type: string
format: uuid
playerId:
$ref: "#/components/schemas/PlayerId"
card:
$ref: "#/components/schemas/Card"
chosenColor:
description: The color to switch to, only required when playing a Plus4Card or ChangeColorCard.
nullable: true
allOf:
- $ref: "#/components/schemas/CardColor"
Notification:
description: >
A notification sent by the server, either replayed by `GET /games/{id}`
or streamed live over the game WebSocket. Discriminated by "type",
whose value is the fully-qualified Kotlin class name of the
notification (no `@SerialName` is declared on these types).
oneOf:
- $ref: "#/components/schemas/WelcomeToTheGameNotification"
- $ref: "#/components/schemas/PlayerAsJoinTheGameNotification"
- $ref: "#/components/schemas/PlayerWasReadyNotification"
- $ref: "#/components/schemas/TheGameWasStartedNotification"
- $ref: "#/components/schemas/ItsTheTurnOfNotification"
- $ref: "#/components/schemas/PlayerAsPlayACardNotification"
- $ref: "#/components/schemas/YourNewCardNotification"
- $ref: "#/components/schemas/PlayerHavePassNotification"
- $ref: "#/components/schemas/PilesShuffledNotification"
- $ref: "#/components/schemas/PlayerWinNotification"
discriminator:
propertyName: type
mapping:
eventDemo.contexts.game.application.notification.models.WelcomeToTheGameNotification: "#/components/schemas/WelcomeToTheGameNotification"
eventDemo.contexts.game.application.notification.models.PlayerAsJoinTheGameNotification: "#/components/schemas/PlayerAsJoinTheGameNotification"
eventDemo.contexts.game.application.notification.models.PlayerWasReadyNotification: "#/components/schemas/PlayerWasReadyNotification"
eventDemo.contexts.game.application.notification.models.TheGameWasStartedNotification: "#/components/schemas/TheGameWasStartedNotification"
eventDemo.contexts.game.application.notification.models.ItsTheTurnOfNotification: "#/components/schemas/ItsTheTurnOfNotification"
eventDemo.contexts.game.application.notification.models.PlayerAsPlayACardNotification: "#/components/schemas/PlayerAsPlayACardNotification"
eventDemo.contexts.game.application.notification.models.YourNewCardNotification: "#/components/schemas/YourNewCardNotification"
eventDemo.contexts.game.application.notification.models.PlayerHavePassNotification: "#/components/schemas/PlayerHavePassNotification"
eventDemo.contexts.game.application.notification.models.PilesShuffledNotification: "#/components/schemas/PilesShuffledNotification"
eventDemo.contexts.game.application.notification.models.PlayerWinNotification: "#/components/schemas/PlayerWinNotification"
WelcomeToTheGameNotification:
description: Sent to a player right after they join a game, listing all players currently in it.
type: object
required: [type, id, players]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.WelcomeToTheGameNotification]
id:
type: string
format: uuid
players:
type: array
items:
$ref: "#/components/schemas/Player"
PlayerAsJoinTheGameNotification:
description: Sent to the other players when a new player joins the game.
type: object
required: [type, id, player]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.PlayerAsJoinTheGameNotification]
id:
type: string
format: uuid
player:
$ref: "#/components/schemas/Player"
PlayerWasReadyNotification:
description: Sent to all players when a player marks themselves as ready.
type: object
required: [type, id, playerId]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.PlayerWasReadyNotification]
id:
type: string
format: uuid
playerId:
$ref: "#/components/schemas/PlayerId"
TheGameWasStartedNotification:
description: Sent to each player when the game starts, with their initial hand.
type: object
required: [type, id, hand]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.TheGameWasStartedNotification]
id:
type: string
format: uuid
hand:
type: array
items:
$ref: "#/components/schemas/Card"
ItsTheTurnOfNotification:
description: Sent to all players to indicate whose turn it now is.
type: object
required: [type, id, player]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.ItsTheTurnOfNotification]
id:
type: string
format: uuid
player:
$ref: "#/components/schemas/Player"
PlayerAsPlayACardNotification:
description: Sent to all players when a player plays a card.
type: object
required: [type, id, playerId, card]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.PlayerAsPlayACardNotification]
id:
type: string
format: uuid
playerId:
$ref: "#/components/schemas/PlayerId"
card:
$ref: "#/components/schemas/Card"
YourNewCardNotification:
description: Sent to a player with the cards they just drew from the draw pile.
type: object
required: [type, id, cards]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.YourNewCardNotification]
id:
type: string
format: uuid
cards:
type: array
items:
$ref: "#/components/schemas/Card"
PlayerHavePassNotification:
description: Sent to the other players when a player draws a card and passes their turn.
type: object
required: [type, id, playerId]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.PlayerHavePassNotification]
id:
type: string
format: uuid
playerId:
$ref: "#/components/schemas/PlayerId"
PilesShuffledNotification:
description: Sent to all players when the discard pile is reshuffled into the draw pile.
type: object
required: [type, id]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.PilesShuffledNotification]
id:
type: string
format: uuid
PlayerWinNotification:
description: Sent to all players when a player wins the game.
type: object
required: [type, id, playerId]
properties:
type:
type: string
enum: [eventDemo.contexts.game.application.notification.models.PlayerWinNotification]
id:
type: string
format: uuid
playerId:
$ref: "#/components/schemas/PlayerId"