feat: category export and API cleanup.
This commit is contained in:
@@ -1,227 +0,0 @@
|
||||
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().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().openapi({
|
||||
title: "Total logs",
|
||||
description:
|
||||
"The total number of logs satisfying the filters, regardless of pagination",
|
||||
}),
|
||||
sessions: z
|
||||
.object({
|
||||
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 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().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(
|
||||
{
|
||||
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"
|
||||
},
|
||||
},
|
||||
{
|
||||
pathPrefix: "/api",
|
||||
}
|
||||
);
|
||||
20
src/common/contract/get-categories.ts
Normal file
20
src/common/contract/get-categories.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Z } from "./types";
|
||||
|
||||
export function exportGetCategoriesSchema(z: Z) {
|
||||
const GetCategoriesRequestQuerySchema = z.object({
|
||||
sessionId: z.string().uuid().openapi({
|
||||
title: "Session GUID",
|
||||
description:
|
||||
"The GUID of the session where the log entries are retrieved from",
|
||||
example: "11111111-2222-3333-4444-555555555555",
|
||||
}),
|
||||
});
|
||||
|
||||
const GetCategoriesResponseSchema = z.string().array().openapi({
|
||||
title: "Categories",
|
||||
description: "The categories present for this session",
|
||||
example: "UI.save-load",
|
||||
});
|
||||
|
||||
return { GetCategoriesRequestQuerySchema, GetCategoriesResponseSchema };
|
||||
}
|
||||
27
src/common/contract/get-entries.ts
Normal file
27
src/common/contract/get-entries.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { exportGetSessionSchema } from "./get-session";
|
||||
import { Z } from "./types";
|
||||
|
||||
export function exportGetEntriesSchema(z: Z) {
|
||||
const { LogEntrySchema } = exportGetSessionSchema(z);
|
||||
|
||||
const GetEntriesRequestPathSchema = z.object({
|
||||
sessionId: z.string().uuid().openapi({
|
||||
title: "Session GUID",
|
||||
description:
|
||||
"The GUID of the session where the log entries are retrieved from",
|
||||
example: "11111111-2222-3333-4444-555555555555",
|
||||
}),
|
||||
});
|
||||
|
||||
const GetEntriesRequestQuerySchema = z.object({
|
||||
category: z.string().optional().openapi({
|
||||
title: "Category",
|
||||
description: "Optional category filter",
|
||||
example: "UI.save-load",
|
||||
}),
|
||||
});
|
||||
|
||||
const GetEntriesResponseSchema = LogEntrySchema.array();
|
||||
|
||||
return { GetEntriesRequestPathSchema, GetEntriesRequestQuerySchema, GetEntriesResponseSchema };
|
||||
}
|
||||
51
src/common/contract/get-session.ts
Normal file
51
src/common/contract/get-session.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Z } from "./types";
|
||||
|
||||
export function exportGetSessionSchema(z: Z) {
|
||||
const LogEntrySchema = 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",
|
||||
}),
|
||||
category: z.string().optional().openapi({
|
||||
title: "Category",
|
||||
description:
|
||||
"An optional category to identify the family of the message",
|
||||
example: "UI.save-load.loading",
|
||||
}),
|
||||
});
|
||||
|
||||
const GetSessionResponseSchema = z.object({
|
||||
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: LogEntrySchema.array(),
|
||||
});
|
||||
|
||||
const GetSessionRequestPathSchema = z.object({
|
||||
id: z.string().uuid().openapi({
|
||||
title: "Session GUID",
|
||||
description: "GUID of the session to retrieve",
|
||||
example: "11111111-2222-3333-4444-555555555555",
|
||||
}),
|
||||
});
|
||||
|
||||
return { GetSessionResponseSchema, GetSessionRequestPathSchema, LogEntrySchema };
|
||||
}
|
||||
72
src/common/contract/get-sessions.ts
Normal file
72
src/common/contract/get-sessions.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Z } from "./types";
|
||||
|
||||
export function exportGetSessionsSchema(z: Z) {
|
||||
const GetSessionsResponseSchema = z.object({
|
||||
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().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(),
|
||||
});
|
||||
|
||||
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",
|
||||
}),
|
||||
});
|
||||
|
||||
return { GetSessionsResponseSchema, GetSessionsRequestQuerySchema };
|
||||
}
|
||||
114
src/common/contract/index.ts
Normal file
114
src/common/contract/index.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { initContract } from "@ts-rest/core";
|
||||
import { z } from "zod";
|
||||
import { extendZodWithOpenApi } from "@anatine/zod-openapi";
|
||||
import { OpenAPIObject } from "openapi3-ts/oas31";
|
||||
import { exportGetSessionSchema } from "./get-session";
|
||||
import { exportGetSessionsSchema } from "./get-sessions";
|
||||
import { exportPostLogSchema } from "./post-log";
|
||||
import { exportGetEntriesSchema } from "./get-entries";
|
||||
import { exportGetCategoriesSchema } from "./get-categories";
|
||||
|
||||
extendZodWithOpenApi(z);
|
||||
|
||||
const { GetSessionResponseSchema, GetSessionRequestPathSchema } =
|
||||
exportGetSessionSchema(z);
|
||||
const { GetSessionsRequestQuerySchema, GetSessionsResponseSchema } =
|
||||
exportGetSessionsSchema(z);
|
||||
const {
|
||||
PostLogRequestBodySchema,
|
||||
PostLogRequestParamsSchema,
|
||||
SingleMetadataSchema,
|
||||
} = exportPostLogSchema(z);
|
||||
const {
|
||||
GetEntriesRequestPathSchema,
|
||||
GetEntriesResponseSchema,
|
||||
GetEntriesRequestQuerySchema,
|
||||
} = exportGetEntriesSchema(z);
|
||||
const { GetCategoriesRequestQuerySchema, GetCategoriesResponseSchema } =
|
||||
exportGetCategoriesSchema(z);
|
||||
|
||||
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",
|
||||
}),
|
||||
});
|
||||
|
||||
////// CONTRACT
|
||||
|
||||
const c = initContract();
|
||||
|
||||
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",
|
||||
pathParams: GetSessionRequestPathSchema,
|
||||
responses: {
|
||||
200: GetSessionResponseSchema,
|
||||
},
|
||||
summary: "get a session by id",
|
||||
},
|
||||
getEntries: {
|
||||
method: "GET",
|
||||
path: "/sessions/:sessionId/entries",
|
||||
pathParams: GetEntriesRequestPathSchema,
|
||||
query: GetEntriesRequestQuerySchema,
|
||||
responses: {
|
||||
200: GetEntriesResponseSchema,
|
||||
},
|
||||
summary: "get the entries of a session",
|
||||
},
|
||||
getCategories: {
|
||||
method: "GET",
|
||||
path: "/categories",
|
||||
query: GetCategoriesRequestQuerySchema,
|
||||
responses: {
|
||||
200: GetCategoriesResponseSchema,
|
||||
},
|
||||
summary: "Get the log categories present for this session",
|
||||
},
|
||||
getOpenApi: {
|
||||
method: "GET",
|
||||
path: "/get-openapi",
|
||||
responses: {
|
||||
200: c.type<OpenAPIObject>(),
|
||||
},
|
||||
summary: "get the OpenAPI document for this API",
|
||||
},
|
||||
},
|
||||
{
|
||||
pathPrefix: "/api",
|
||||
}
|
||||
);
|
||||
|
||||
///// TYPES
|
||||
|
||||
export type SingleMetadata = z.infer<typeof SingleMetadataSchema>;
|
||||
export type GetSessionsResponse = z.infer<typeof GetSessionsResponseSchema>;
|
||||
export type GetSessionResponse = z.infer<typeof GetSessionResponseSchema>;
|
||||
63
src/common/contract/post-log.ts
Normal file
63
src/common/contract/post-log.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Z } from "./types";
|
||||
|
||||
export function exportPostLogSchema(z: Z) {
|
||||
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",
|
||||
}),
|
||||
});
|
||||
|
||||
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().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",
|
||||
}),
|
||||
category: z.string().optional().openapi({
|
||||
title: "Category",
|
||||
description: "An optional category to identify the family of the message",
|
||||
example: "UI.save-load.loading",
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
SingleMetadataSchema,
|
||||
PostLogRequestParamsSchema,
|
||||
PostLogRequestBodySchema,
|
||||
};
|
||||
}
|
||||
3
src/common/contract/types.ts
Normal file
3
src/common/contract/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export type Z = typeof z;
|
||||
Reference in New Issue
Block a user