feat: embrace full REST API and OpenAPI+Swagger
This commit is contained in:
@@ -1,65 +1,227 @@
|
||||
import { initContract } from "@ts-rest/core";
|
||||
import { z } from "zod";
|
||||
import { extendZodWithOpenApi } from "@anatine/zod-openapi";
|
||||
import { OpenAPIObject } from "openapi3-ts/oas31";
|
||||
|
||||
////// INITIALIZATION
|
||||
|
||||
extendZodWithOpenApi(z);
|
||||
|
||||
const c = initContract();
|
||||
|
||||
//////
|
||||
const ErrorSchema = z.object({
|
||||
code: z.number().optional().openapi({
|
||||
title: "Error code",
|
||||
description: "Optional code to uniquely identify this error",
|
||||
}),
|
||||
message: z.string().openapi({
|
||||
title: "Error message",
|
||||
description: "Human readable message to describe the error",
|
||||
}),
|
||||
});
|
||||
|
||||
////// POST LOG
|
||||
|
||||
const SingleMetadataSchema = z.object({
|
||||
key: z.string().openapi({
|
||||
title: "Key",
|
||||
description: "Name of this metadata property",
|
||||
example: "attack-power",
|
||||
}),
|
||||
value: z.string().openapi({
|
||||
title: "Value",
|
||||
description: "Value of the metadata property",
|
||||
example: "923",
|
||||
}),
|
||||
});
|
||||
|
||||
export type SingleMetadata = z.infer<typeof SingleMetadataSchema>;
|
||||
|
||||
const PostLogRequestParamsSchema = z.object({
|
||||
gameName: z.string().openapi({
|
||||
title: "Game name",
|
||||
description: "Name of the game that generated this log",
|
||||
example: "Asteroid",
|
||||
}),
|
||||
version: z.string().openapi({
|
||||
title: "Version",
|
||||
description: "Version of the game that generated this log",
|
||||
example: "1.7.0",
|
||||
}),
|
||||
saveGuid: z.string().uuid().optional().openapi({
|
||||
title: "Save GUID",
|
||||
description:
|
||||
"A unique identifier for a whole game, which can be part of a multi-session game",
|
||||
example: "11111111-2222-3333-4444-555555555555",
|
||||
}),
|
||||
sessionGuid: z.string().uuid().openapi({
|
||||
title: "Session GUID",
|
||||
description: "A unique identifier for the single play session",
|
||||
example: "11111111-2222-3333-4444-555555555555",
|
||||
}),
|
||||
});
|
||||
|
||||
const PostLogRequestBodySchema = z.object({
|
||||
message: z.string().openapi({
|
||||
title: "Message",
|
||||
description: "A human readable message that describes the log line",
|
||||
example: "Clicked on the telephone",
|
||||
}),
|
||||
metadata: SingleMetadataSchema.array().openapi({
|
||||
title: "Metadata",
|
||||
description: "Extra data saved with the log message",
|
||||
}),
|
||||
});
|
||||
|
||||
////// GET SESSIONS
|
||||
|
||||
const GetSessionsResponseSchema = z.object({
|
||||
total: z.number().positive(),
|
||||
total: z.number().positive().openapi({
|
||||
title: "Total logs",
|
||||
description:
|
||||
"The total number of logs satisfying the filters, regardless of pagination",
|
||||
}),
|
||||
sessions: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
save_id: z.string(),
|
||||
game_name: z.string(),
|
||||
version: z.string(),
|
||||
start_time: z.date(),
|
||||
end_time: z.date(),
|
||||
num_events: z.number().positive(),
|
||||
id: z.string().openapi({
|
||||
title: "Session GUID",
|
||||
description: "A unique identifier for the single play session",
|
||||
example: "11111111-2222-3333-4444-555555555555",
|
||||
}),
|
||||
save_id: z.string().openapi({
|
||||
title: "Save GUID",
|
||||
description:
|
||||
"A unique identifier for a whole game, which can be part of a multi-session game",
|
||||
example: "11111111-2222-3333-4444-555555555555",
|
||||
}),
|
||||
game_name: z.string().openapi({
|
||||
title: "Game name",
|
||||
description: "Name of the game that generated this log",
|
||||
example: "Asteroid",
|
||||
}),
|
||||
version: z.string().openapi({
|
||||
title: "Version",
|
||||
description: "Version of the game that generated this log",
|
||||
example: "1.7.0",
|
||||
}),
|
||||
start_time: z.date().openapi({
|
||||
title: "Start Time",
|
||||
description: "Time of the first log event for this session",
|
||||
example: "2025-01-06T14:46:38.000Z",
|
||||
}),
|
||||
end_time: z.date().openapi({
|
||||
title: "Start Time",
|
||||
description: "Time of the last log event for this session",
|
||||
example: "2025-01-06T14:46:38.000Z",
|
||||
}),
|
||||
num_events: z.number().positive().openapi({
|
||||
title: "# Events",
|
||||
description: "Number of events in this session",
|
||||
example: "48",
|
||||
}),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type GetSessionsResponse = z.infer<typeof GetSessionsResponseSchema>;
|
||||
|
||||
const GetSessionsRequestQuery = z.object({
|
||||
skip: z.coerce.number().optional(),
|
||||
count: z.coerce.number().optional(),
|
||||
id: z.string().optional(),
|
||||
const GetSessionsRequestQuerySchema = z.object({
|
||||
skip: z.coerce.number().optional().openapi({
|
||||
title: "Skip",
|
||||
default:
|
||||
"How many log events to skip from the beginning of this session (used for pagination)",
|
||||
example: "30",
|
||||
}),
|
||||
count: z.coerce.number().optional().openapi({
|
||||
title: "Count",
|
||||
default: "How many log events to return at most (used for pagination)",
|
||||
example: "10",
|
||||
}),
|
||||
id: z.string().optional().openapi({
|
||||
title: "Session GUID",
|
||||
description: "A filter for the session GUID",
|
||||
example: "11111111-2222-3333-4444-555555555555",
|
||||
}),
|
||||
});
|
||||
|
||||
//////
|
||||
////// GET SESSION
|
||||
|
||||
const GetSessionResponseSchema = z.object({
|
||||
game_name: z.string(),
|
||||
version: z.string(),
|
||||
log_entries: z.object({
|
||||
message: z.string(),
|
||||
timestamp: z.date(),
|
||||
metadata: z.record(z.string(), z.string())
|
||||
}).array(),
|
||||
game_name: z.string().openapi({
|
||||
title: "Game name",
|
||||
description: "Name of the game that generated this log",
|
||||
example: "Asteroid",
|
||||
}),
|
||||
version: z.string().openapi({
|
||||
title: "Version",
|
||||
description: "Version of the game that generated this log",
|
||||
example: "1.7.0",
|
||||
}),
|
||||
log_entries: z
|
||||
.object({
|
||||
message: z.string().openapi({
|
||||
title: "Message",
|
||||
description: "A human readable message that describes the log line",
|
||||
example: "Clicked on the telephone",
|
||||
}),
|
||||
timestamp: z.date().openapi({
|
||||
title: "Timestamp",
|
||||
description: "Date/time when the event occoured",
|
||||
example: "2025-01-06T14:46:38.000Z",
|
||||
}),
|
||||
metadata: z.record(z.string(), z.string()).openapi({
|
||||
title: "Metadata",
|
||||
description: "Extra data saved with the log message",
|
||||
}),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type GetSessionResponse = z.infer<typeof GetSessionResponseSchema>;
|
||||
|
||||
//////
|
||||
////// CONTRACT
|
||||
|
||||
export const contract = c.router({
|
||||
getSessions: {
|
||||
method: "GET",
|
||||
path: "/api/get-sessions",
|
||||
responses: {
|
||||
200: GetSessionsResponseSchema,
|
||||
export const contract = c.router(
|
||||
{
|
||||
postLog: {
|
||||
method: "POST",
|
||||
path: "/logs/:gameName/:version/:saveGuid?/:sessionGuid",
|
||||
body: PostLogRequestBodySchema,
|
||||
pathParams: PostLogRequestParamsSchema,
|
||||
responses: {
|
||||
204: c.noBody(),
|
||||
422: ErrorSchema,
|
||||
500: ErrorSchema,
|
||||
},
|
||||
summary: "post a new log",
|
||||
},
|
||||
getSessions: {
|
||||
method: "GET",
|
||||
path: "/get-sessions",
|
||||
responses: {
|
||||
200: GetSessionsResponseSchema,
|
||||
},
|
||||
query: GetSessionsRequestQuerySchema,
|
||||
summary: "get all the sessions",
|
||||
},
|
||||
getSession: {
|
||||
method: "GET",
|
||||
path: "/get-session/:id",
|
||||
responses: {
|
||||
200: GetSessionResponseSchema,
|
||||
},
|
||||
summary: "get a session by id",
|
||||
},
|
||||
getOpenApi: {
|
||||
method: "GET",
|
||||
path: "/get-openapi",
|
||||
responses: {
|
||||
200: c.type<OpenAPIObject>(),
|
||||
},
|
||||
summary: "get the OpenAPI document for this API"
|
||||
},
|
||||
query: GetSessionsRequestQuery,
|
||||
summary: "get all the sessions",
|
||||
},
|
||||
getSession: {
|
||||
method: "GET",
|
||||
path: "/api/get-session/:id",
|
||||
responses: {
|
||||
200: GetSessionResponseSchema,
|
||||
},
|
||||
summary: "get a session by id",
|
||||
{
|
||||
pathPrefix: "/api",
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user