From dbded55113c3adfb672071aea06e446f8abc3370 Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Thu, 8 May 2025 19:19:10 -0400 Subject: [PATCH] feat: identify unused objects --- server/internal/objects/fsBackend.ts | 3 + server/internal/objects/objectHandler.ts | 8 ++ server/tasks/cleanup/objects.ts | 121 +++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 server/tasks/cleanup/objects.ts diff --git a/server/internal/objects/fsBackend.ts b/server/internal/objects/fsBackend.ts index a3e33fe..3f0b865 100644 --- a/server/internal/objects/fsBackend.ts +++ b/server/internal/objects/fsBackend.ts @@ -152,6 +152,9 @@ export class FsObjectBackend extends ObjectBackend { await store.save(id, hashResult); return typeof hashResult; } + async listAll(): Promise { + return fs.readdirSync(this.baseObjectPath); + } } class FsHashStore { diff --git a/server/internal/objects/objectHandler.ts b/server/internal/objects/objectHandler.ts index 6fe9afe..edbf8ad 100644 --- a/server/internal/objects/objectHandler.ts +++ b/server/internal/objects/objectHandler.ts @@ -65,6 +65,7 @@ export abstract class ObjectBackend { metadata: ObjectMetadata, ): Promise; abstract fetchHash(id: ObjectReference): Promise; + abstract listAll(): Promise; } export class ObjectHandler { @@ -244,4 +245,11 @@ export class ObjectHandler { async deleteAsSystem(id: ObjectReference) { return await this.backend.delete(id); } + + /** + * List all objects + */ + async listAll() { + return await this.backend.listAll(); + } } diff --git a/server/tasks/cleanup/objects.ts b/server/tasks/cleanup/objects.ts new file mode 100644 index 0000000..5c3e719 --- /dev/null +++ b/server/tasks/cleanup/objects.ts @@ -0,0 +1,121 @@ +import prisma from "~/server/internal/db/database"; +import objectHandler from "~/server/internal/objects"; + +type FieldReferenceMap = { + [modelName: string]: { + model: unknown; // Prisma model + fields: string[]; // Fields that may contain IDs + arrayFields: string[]; // Fields that are arrays that may contain IDs + }; +}; + +export default defineTask({ + meta: { + name: "cleanup:objects", + }, + async run() { + console.log("[Task cleanup:objects]: Cleaning unreferenced objects"); + + const objects = await objectHandler.listAll(); + console.log( + `[Task cleanup:objects]: searching for ${objects.length} objects`, + ); + console.log(objects); + const results = await findUnreferencedStrings(objects, buildRefMap()); + console.log("[Task cleanup:objects]: Unreferenced objects: ", results); + + console.log("[Task cleanup:objects]: Done"); + return { result: true }; + }, +}); + +function buildRefMap(): FieldReferenceMap { + const tables = Object.keys(prisma).filter( + (v) => !(v.startsWith("$") || v.startsWith("_") || v === "constructor"), + ); + // type test = Prisma.ModelName + // prisma.game.fields.mIconId. + + const result: FieldReferenceMap = {}; + + for (const model of tables) { + // @ts-expect-error can't get model to typematch key names + const fields = Object.keys(prisma[model]["fields"]); + + const single = fields.filter((v) => v.toLowerCase().endsWith("objectid")); + const array = fields.filter((v) => v.toLowerCase().endsWith("objectids")); + + result[model] = { + // @ts-expect-error im not dealing with this + model: prisma[model], + fields: single, + arrayFields: array, + }; + } + + return result; +} + +async function isReferencedInModelFields( + id: string, + fieldRefMap: FieldReferenceMap, +): Promise { + for (const { model, fields, arrayFields } of Object.values(fieldRefMap)) { + const singleFieldOrConditions = fields + ? fields.map((field) => ({ + [field]: { + equals: id, + }, + })) + : []; + const arrayFieldOrConditions = arrayFields + ? arrayFields.map((field) => ({ + [field]: { + has: id, + }, + })) + : []; + + // prisma.game.findFirst({ + // where: { + // OR: [ + // // single item + // { + // mIconId: { + // equals: "", + // }, + // }, + // // array + // { + // mImageCarousel: { + // has: "", + // }, + // }, + // ], + // }, + // }); + + // @ts-expect-error using unknown because im not typing this mess omg + const found = await model.findFirst({ + where: { OR: [...singleFieldOrConditions, ...arrayFieldOrConditions] }, + }); + + if (found) return true; + } + + return false; +} + +async function findUnreferencedStrings( + objects: string[], + fieldRefMap: FieldReferenceMap, +): Promise { + const unreferenced: string[] = []; + + for (const obj of objects) { + const isRef = await isReferencedInModelFields(obj, fieldRefMap); + if (!isRef) unreferenced.push(obj); + } + + return unreferenced; +}