c2bb835b0f
remove sanitize-filename because IDs are internally generated remove pulse animation on NO GAME cards add migration refactors to be inline with other stuff
124 lines
3.2 KiB
TypeScript
124 lines
3.2 KiB
TypeScript
import Stream, { Readable } from "stream";
|
|
import prisma from "../db/database";
|
|
import { applicationSettings } from "../config/application-configuration";
|
|
import objectHandler from "../objects";
|
|
import { randomUUID, createHash } from "node:crypto";
|
|
import { IncomingMessage } from "http";
|
|
|
|
class SaveManager {
|
|
async deleteObjectFromSave(
|
|
gameId: string,
|
|
userId: string,
|
|
index: number,
|
|
objectId: string
|
|
) {
|
|
await objectHandler.deleteWithPermission(objectId, userId);
|
|
}
|
|
|
|
async pushSave(
|
|
gameId: string,
|
|
userId: string,
|
|
index: number,
|
|
stream: IncomingMessage,
|
|
clientId: string | undefined = undefined
|
|
) {
|
|
const save = await prisma.saveSlot.findUnique({
|
|
where: {
|
|
id: {
|
|
userId,
|
|
gameId,
|
|
index,
|
|
},
|
|
},
|
|
});
|
|
if (!save)
|
|
throw createError({ statusCode: 404, statusMessage: "Save not found" });
|
|
|
|
const newSaveObjectId = randomUUID();
|
|
const newSaveStream = await objectHandler.createWithStream(
|
|
newSaveObjectId,
|
|
{ saveSlot: JSON.stringify({ userId, gameId, index }) },
|
|
[]
|
|
);
|
|
if (!newSaveStream)
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: "Failed to create writing stream to storage backend.",
|
|
});
|
|
|
|
let hash: string | undefined;
|
|
const hashPromise = Stream.promises.pipeline(
|
|
stream,
|
|
createHash("sha256").setEncoding("hex"),
|
|
async function (source) {
|
|
// Not sure how to get this to be typed
|
|
// @ts-expect-error
|
|
hash = (await source.toArray())[0];
|
|
}
|
|
);
|
|
|
|
const uploadStream = Stream.promises.pipeline(stream, newSaveStream);
|
|
|
|
await Promise.all([hashPromise, uploadStream]);
|
|
|
|
if (!hash) {
|
|
await objectHandler.deleteAsSystem(newSaveObjectId);
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: "Hash failed to generate",
|
|
});
|
|
}
|
|
|
|
const newSave = await prisma.saveSlot.update({
|
|
where: {
|
|
id: {
|
|
userId,
|
|
gameId,
|
|
index,
|
|
},
|
|
},
|
|
data: {
|
|
history: {
|
|
push: newSaveObjectId,
|
|
},
|
|
historyChecksums: {
|
|
push: hash,
|
|
},
|
|
...(clientId && { lastUsedClientId: clientId }),
|
|
},
|
|
});
|
|
|
|
const historyLimit = await applicationSettings.get("saveSlotHistoryLimit");
|
|
if (newSave.history.length > historyLimit) {
|
|
// Delete previous
|
|
const safeFromIndex = newSave.history.length - historyLimit;
|
|
|
|
const toDelete = newSave.history.slice(0, safeFromIndex);
|
|
const toKeepObjects = newSave.history.slice(safeFromIndex);
|
|
const toKeepHashes = newSave.historyChecksums.slice(safeFromIndex);
|
|
|
|
// Delete objects first, so if we error out, we don't lose track of objects in backend
|
|
for (const objectId of toDelete) {
|
|
await this.deleteObjectFromSave(gameId, userId, index, objectId);
|
|
}
|
|
|
|
await prisma.saveSlot.update({
|
|
where: {
|
|
id: {
|
|
userId,
|
|
gameId,
|
|
index,
|
|
},
|
|
},
|
|
data: {
|
|
history: toKeepObjects,
|
|
historyChecksums: toKeepHashes,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export const saveManager = new SaveManager();
|
|
export default saveManager;
|