feat: half-way bulk upload support.

This commit is contained in:
Mattia Belletti
2025-01-07 17:49:02 +01:00
parent a122f769cd
commit 0a73fc5a95
9 changed files with 154 additions and 56 deletions

View File

@@ -15,6 +15,7 @@ const { GetSessionResponseSchema, GetSessionRequestPathSchema } =
const { GetSessionsRequestQuerySchema, GetSessionsResponseSchema } =
exportGetSessionsSchema(z);
const {
LogEntrySchema,
PostLogRequestBodySchema,
PostLogRequestParamsSchema,
SingleMetadataSchema,
@@ -112,3 +113,4 @@ export const contract = c.router(
export type SingleMetadata = z.infer<typeof SingleMetadataSchema>;
export type GetSessionsResponse = z.infer<typeof GetSessionsResponseSchema>;
export type GetSessionResponse = z.infer<typeof GetSessionResponseSchema>;
export type LogEntry = z.infer<typeof LogEntrySchema>;

View File

@@ -38,7 +38,7 @@ export function exportPostLogSchema(z: Z) {
}),
});
const PostLogRequestBodySchema = z.object({
const LogEntrySchema = z.object({
message: z.string().openapi({
title: "Message",
description: "A human readable message that describes the log line",
@@ -55,7 +55,10 @@ export function exportPostLogSchema(z: Z) {
}),
});
const PostLogRequestBodySchema = LogEntrySchema.or(LogEntrySchema.array());
return {
LogEntrySchema,
SingleMetadataSchema,
PostLogRequestParamsSchema,
PostLogRequestBodySchema,

View File

@@ -12,12 +12,12 @@ export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable("log_entries")
.addColumn("id", "int4", (col) => col.primaryKey().autoIncrement())
.addColumn("session_info_id", "char(36)", (col) =>
col
.notNull()
.references("session_info.id")
.onDelete("cascade")
.onUpdate("cascade")
.addColumn("session_info_id", "char(36)", (col) => col.notNull())
.addForeignKeyConstraint(
"log_entries_session_info_id",
["session_info_id"],
"session_info",
["id"]
)
.addColumn("message", "varchar(255)", (col) => col.notNull())
.addColumn("timestamp", "datetime", (col) => col.notNull())
@@ -39,12 +39,12 @@ export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable("log_metadata")
.addColumn("id", "int4", (col) => col.primaryKey().autoIncrement())
.addColumn("log_entry_id", "int4", (col) =>
col
.notNull()
.references("log_entries.id")
.onDelete("cascade")
.onUpdate("cascade")
.addColumn("log_entry_id", "int4", (col) => col.notNull())
.addForeignKeyConstraint(
"log_metadata_log_entry_id",
["log_entry_id"],
"log_entries",
["id"]
)
.addColumn("key", "varchar(50)", (col) => col.notNull())
.addColumn("value", "varchar(255)", (col) => col.notNull())

View File

@@ -0,0 +1,34 @@
import { Kysely } from "kysely";
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable("log_metadata")
.dropConstraint("log_metadata_log_entry_id")
.execute();
await db.schema
.dropIndex("log_metadata_log_entry_id")
.on("log_metadata")
.execute();
await db.schema
.alterTable("log_metadata")
.renameTo("_log_metadata_backup")
.execute();
await db.schema
.alterTable("log_entries")
.addColumn("metadata", "json")
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable("_log_metadata_backup")
.renameTo("log_metadata")
.execute();
await db.schema
.createIndex("log_metadata_log_entry_id")
.on("log_metadata")
.column("log_entry_id")
.execute();
}

View File

@@ -1 +1,2 @@
export * as migration20250107T104543020Z from "./2025-01-07T10-45-43.020Z";
export * as migration20250107T154656493Z from "./2025-01-07T15-46-56.493Z";

View File

@@ -1,23 +1,37 @@
import { SingleMetadata } from "../contract";
import { LogEntry, SingleMetadata } from "../contract";
import { HttpError } from "../http-error";
import { db } from "./init";
import { zip } from "lodash-es";
declare global {
interface BigInt {
/** Convert to BigInt to string form in JSON.stringify */
toJSON: () => string;
}
}
BigInt.prototype.toJSON = function () {
return this.toString();
};
export async function postLog(
gameName: string,
version: string,
saveGuid: string,
sessionGuid: string,
message: string,
category: string | undefined,
metadata: SingleMetadata[]
entries: LogEntry[]
// message: string,
// category: string | undefined,
// metadata: SingleMetadata[]
) {
if (
gameName === "" ||
sessionGuid === "" ||
message === "" ||
entries.some((entry) => entry.message === "") ||
version === ""
) {
const errorMessage = `One of the necessary fields was missing: gameName=${gameName}, sessionGuid=${sessionGuid}, message=${message}, version=${version}`;
const errorMessage = `One of the necessary fields was missing: gameName=${gameName}, sessionGuid=${sessionGuid}, messages=${entries
.map((e) => e.message)
.join(", ")}, version=${version}`;
throw new HttpError(422, errorMessage);
} else {
await db.transaction().execute(async (trx) => {
@@ -35,26 +49,36 @@ export async function postLog(
// add the main log line
const insertResult = await trx
.insertInto("log_entries")
.values({
session_info_id: sessionGuid,
message: message,
timestamp: new Date(),
category,
})
.executeTakeFirstOrThrow();
// add all the metadata
if (metadata.length > 0) {
await trx
.insertInto("log_metadata")
.values(
metadata.map(({ key, value }) => ({
log_entry_id: Number(insertResult.insertId),
key,
value,
}))
)
.execute();
.values(
entries.map((entry) => ({
session_info_id: sessionGuid,
message: entry.message,
timestamp: new Date(),
category: entry.category,
}))
)
.returning("id")
.execute();
console.log("insertResulttt:", JSON.stringify(insertResult, null, 2));
for (const r of insertResult) {
console.log("insertResult", r.id);
}
// console.log("insertResult.insertId", (insertResult as any).insertId);
// console.log("keys:", Object.keys(insertResult));
throw new Error("aaa");
// add all the metadata
// if (metadata.length > 0) {
// await trx
// .insertInto("log_metadata")
// .values(
// metadata.map(({ key, value }) => ({
// log_entry_id: Number(insertResult.insertId),
// key,
// value,
// }))
// )
// .execute();
// }
});
}
}

View File

@@ -11,15 +11,15 @@ export function installRouter(app: Express, openAPIObject: OpenAPIObject) {
const s = initServer();
const router = s.router(contract, {
postLog: async ({ params, body }) => {
const entries = 'length' in body ? body : [body];
try {
await postLog(
params.gameName,
params.version,
params.saveGuid,
params.sessionGuid,
body.message,
body.category,
body.metadata
entries
);
return {
status: 204,