feat: half-way bulk upload support.
This commit is contained in:
62
package-lock.json
generated
62
package-lock.json
generated
@@ -34,6 +34,7 @@
|
|||||||
"@ts-rest/react-query": "^3.51.0",
|
"@ts-rest/react-query": "^3.51.0",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.0.0",
|
"@types/express": "^4.0.0",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.5",
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"jotai": "^2.11.0",
|
"jotai": "^2.11.0",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
"material-react-table": "^3.1.0",
|
"material-react-table": "^3.1.0",
|
||||||
"openapi3-ts": "^4.4.0",
|
"openapi3-ts": "^4.4.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
@@ -1796,6 +1798,14 @@
|
|||||||
"yaml": "^1.10.2"
|
"yaml": "^1.10.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ts-rest/open-api/node_modules/yaml": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ts-rest/react-query": {
|
"node_modules/@ts-rest/react-query": {
|
||||||
"version": "3.51.0",
|
"version": "3.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ts-rest/react-query/-/react-query-3.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ts-rest/react-query/-/react-query-3.51.0.tgz",
|
||||||
@@ -1948,6 +1958,21 @@
|
|||||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.17.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz",
|
||||||
|
"integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash-es": {
|
||||||
|
"version": "4.17.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||||
|
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||||
@@ -2590,6 +2615,15 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cosmiconfig/node_modules/yaml": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/create-require": {
|
"node_modules/create-require": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||||
@@ -3345,6 +3379,12 @@
|
|||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash-es": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/long": {
|
"node_modules/long": {
|
||||||
"version": "5.2.3",
|
"version": "5.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
|
||||||
@@ -3662,17 +3702,6 @@
|
|||||||
"yaml": "^2.5.0"
|
"yaml": "^2.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/openapi3-ts/node_modules/yaml": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@@ -4819,11 +4848,14 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "1.10.2",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
||||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"@ts-rest/react-query": "^3.51.0",
|
"@ts-rest/react-query": "^3.51.0",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.0.0",
|
"@types/express": "^4.0.0",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.5",
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"jotai": "^2.11.0",
|
"jotai": "^2.11.0",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
"material-react-table": "^3.1.0",
|
"material-react-table": "^3.1.0",
|
||||||
"openapi3-ts": "^4.4.0",
|
"openapi3-ts": "^4.4.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const { GetSessionResponseSchema, GetSessionRequestPathSchema } =
|
|||||||
const { GetSessionsRequestQuerySchema, GetSessionsResponseSchema } =
|
const { GetSessionsRequestQuerySchema, GetSessionsResponseSchema } =
|
||||||
exportGetSessionsSchema(z);
|
exportGetSessionsSchema(z);
|
||||||
const {
|
const {
|
||||||
|
LogEntrySchema,
|
||||||
PostLogRequestBodySchema,
|
PostLogRequestBodySchema,
|
||||||
PostLogRequestParamsSchema,
|
PostLogRequestParamsSchema,
|
||||||
SingleMetadataSchema,
|
SingleMetadataSchema,
|
||||||
@@ -112,3 +113,4 @@ export const contract = c.router(
|
|||||||
export type SingleMetadata = z.infer<typeof SingleMetadataSchema>;
|
export type SingleMetadata = z.infer<typeof SingleMetadataSchema>;
|
||||||
export type GetSessionsResponse = z.infer<typeof GetSessionsResponseSchema>;
|
export type GetSessionsResponse = z.infer<typeof GetSessionsResponseSchema>;
|
||||||
export type GetSessionResponse = z.infer<typeof GetSessionResponseSchema>;
|
export type GetSessionResponse = z.infer<typeof GetSessionResponseSchema>;
|
||||||
|
export type LogEntry = z.infer<typeof LogEntrySchema>;
|
||||||
@@ -38,7 +38,7 @@ export function exportPostLogSchema(z: Z) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const PostLogRequestBodySchema = z.object({
|
const LogEntrySchema = z.object({
|
||||||
message: z.string().openapi({
|
message: z.string().openapi({
|
||||||
title: "Message",
|
title: "Message",
|
||||||
description: "A human readable message that describes the log line",
|
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 {
|
return {
|
||||||
|
LogEntrySchema,
|
||||||
SingleMetadataSchema,
|
SingleMetadataSchema,
|
||||||
PostLogRequestParamsSchema,
|
PostLogRequestParamsSchema,
|
||||||
PostLogRequestBodySchema,
|
PostLogRequestBodySchema,
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ export async function up(db: Kysely<any>): Promise<void> {
|
|||||||
await db.schema
|
await db.schema
|
||||||
.createTable("log_entries")
|
.createTable("log_entries")
|
||||||
.addColumn("id", "int4", (col) => col.primaryKey().autoIncrement())
|
.addColumn("id", "int4", (col) => col.primaryKey().autoIncrement())
|
||||||
.addColumn("session_info_id", "char(36)", (col) =>
|
.addColumn("session_info_id", "char(36)", (col) => col.notNull())
|
||||||
col
|
.addForeignKeyConstraint(
|
||||||
.notNull()
|
"log_entries_session_info_id",
|
||||||
.references("session_info.id")
|
["session_info_id"],
|
||||||
.onDelete("cascade")
|
"session_info",
|
||||||
.onUpdate("cascade")
|
["id"]
|
||||||
)
|
)
|
||||||
.addColumn("message", "varchar(255)", (col) => col.notNull())
|
.addColumn("message", "varchar(255)", (col) => col.notNull())
|
||||||
.addColumn("timestamp", "datetime", (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
|
await db.schema
|
||||||
.createTable("log_metadata")
|
.createTable("log_metadata")
|
||||||
.addColumn("id", "int4", (col) => col.primaryKey().autoIncrement())
|
.addColumn("id", "int4", (col) => col.primaryKey().autoIncrement())
|
||||||
.addColumn("log_entry_id", "int4", (col) =>
|
.addColumn("log_entry_id", "int4", (col) => col.notNull())
|
||||||
col
|
.addForeignKeyConstraint(
|
||||||
.notNull()
|
"log_metadata_log_entry_id",
|
||||||
.references("log_entries.id")
|
["log_entry_id"],
|
||||||
.onDelete("cascade")
|
"log_entries",
|
||||||
.onUpdate("cascade")
|
["id"]
|
||||||
)
|
)
|
||||||
.addColumn("key", "varchar(50)", (col) => col.notNull())
|
.addColumn("key", "varchar(50)", (col) => col.notNull())
|
||||||
.addColumn("value", "varchar(255)", (col) => col.notNull())
|
.addColumn("value", "varchar(255)", (col) => col.notNull())
|
||||||
|
|||||||
34
src/server/db/migrations/2025-01-07T15-46-56.493Z.ts
Normal file
34
src/server/db/migrations/2025-01-07T15-46-56.493Z.ts
Normal 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();
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * as migration20250107T104543020Z from "./2025-01-07T10-45-43.020Z";
|
export * as migration20250107T104543020Z from "./2025-01-07T10-45-43.020Z";
|
||||||
|
export * as migration20250107T154656493Z from "./2025-01-07T15-46-56.493Z";
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
import { SingleMetadata } from "../contract";
|
import { LogEntry, SingleMetadata } from "../contract";
|
||||||
import { HttpError } from "../http-error";
|
import { HttpError } from "../http-error";
|
||||||
import { db } from "./init";
|
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(
|
export async function postLog(
|
||||||
gameName: string,
|
gameName: string,
|
||||||
version: string,
|
version: string,
|
||||||
saveGuid: string,
|
saveGuid: string,
|
||||||
sessionGuid: string,
|
sessionGuid: string,
|
||||||
message: string,
|
entries: LogEntry[]
|
||||||
category: string | undefined,
|
// message: string,
|
||||||
metadata: SingleMetadata[]
|
// category: string | undefined,
|
||||||
|
// metadata: SingleMetadata[]
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
gameName === "" ||
|
gameName === "" ||
|
||||||
sessionGuid === "" ||
|
sessionGuid === "" ||
|
||||||
message === "" ||
|
entries.some((entry) => entry.message === "") ||
|
||||||
version === ""
|
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);
|
throw new HttpError(422, errorMessage);
|
||||||
} else {
|
} else {
|
||||||
await db.transaction().execute(async (trx) => {
|
await db.transaction().execute(async (trx) => {
|
||||||
@@ -35,26 +49,36 @@ export async function postLog(
|
|||||||
// add the main log line
|
// add the main log line
|
||||||
const insertResult = await trx
|
const insertResult = await trx
|
||||||
.insertInto("log_entries")
|
.insertInto("log_entries")
|
||||||
.values({
|
.values(
|
||||||
session_info_id: sessionGuid,
|
entries.map((entry) => ({
|
||||||
message: message,
|
session_info_id: sessionGuid,
|
||||||
timestamp: new Date(),
|
message: entry.message,
|
||||||
category,
|
timestamp: new Date(),
|
||||||
})
|
category: entry.category,
|
||||||
.executeTakeFirstOrThrow();
|
}))
|
||||||
// add all the metadata
|
)
|
||||||
if (metadata.length > 0) {
|
.returning("id")
|
||||||
await trx
|
.execute();
|
||||||
.insertInto("log_metadata")
|
console.log("insertResulttt:", JSON.stringify(insertResult, null, 2));
|
||||||
.values(
|
for (const r of insertResult) {
|
||||||
metadata.map(({ key, value }) => ({
|
console.log("insertResult", r.id);
|
||||||
log_entry_id: Number(insertResult.insertId),
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
.execute();
|
|
||||||
}
|
}
|
||||||
|
// 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();
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ export function installRouter(app: Express, openAPIObject: OpenAPIObject) {
|
|||||||
const s = initServer();
|
const s = initServer();
|
||||||
const router = s.router(contract, {
|
const router = s.router(contract, {
|
||||||
postLog: async ({ params, body }) => {
|
postLog: async ({ params, body }) => {
|
||||||
|
const entries = 'length' in body ? body : [body];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await postLog(
|
await postLog(
|
||||||
params.gameName,
|
params.gameName,
|
||||||
params.version,
|
params.version,
|
||||||
params.saveGuid,
|
params.saveGuid,
|
||||||
params.sessionGuid,
|
params.sessionGuid,
|
||||||
body.message,
|
entries
|
||||||
body.category,
|
|
||||||
body.metadata
|
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
status: 204,
|
status: 204,
|
||||||
|
|||||||
Reference in New Issue
Block a user