Delete orphan art tags routine

This commit is contained in:
Kalle 2026-03-20 18:16:02 +02:00
parent 254425ef7f
commit 011cf39dee
5 changed files with 71 additions and 3 deletions

View File

@ -303,6 +303,54 @@ describe("deleteById", () => {
}); });
}); });
describe("deleteOrphanTags", () => {
beforeEach(async () => {
imageCounter = 0;
await dbInsertUsers(1);
});
afterEach(() => {
dbReset();
});
test("deletes tags with no associated art", async () => {
const art = await ArtRepository.insert({
authorId: 1,
url: "https://example.com/image-1.png",
validatedAt: Date.now(),
description: null,
linkedUsers: [],
tags: [{ name: "Orphan1" }, { name: "Orphan2" }],
});
await ArtRepository.deleteById(art.id);
const deletedCount = await ArtRepository.deleteOrphanTags();
expect(deletedCount).toBe(2);
const tags = await ArtRepository.findAllTags();
expect(tags).toHaveLength(0);
});
test("does not delete tags that are still linked to art", async () => {
await ArtRepository.insert({
authorId: 1,
url: "https://example.com/image-1.png",
validatedAt: Date.now(),
description: null,
linkedUsers: [],
tags: [{ name: "InUse" }],
});
const deletedCount = await ArtRepository.deleteOrphanTags();
expect(deletedCount).toBe(0);
const tags = await ArtRepository.findAllTags();
expect(tags).toHaveLength(1);
expect(tags[0].name).toBe("InUse");
});
});
describe("insert", () => { describe("insert", () => {
beforeEach(async () => { beforeEach(async () => {
imageCounter = 0; imageCounter = 0;

View File

@ -171,6 +171,15 @@ export async function findAllTags() {
return db.selectFrom("ArtTag").select(["id", "name"]).execute(); return db.selectFrom("ArtTag").select(["id", "name"]).execute();
} }
export async function deleteOrphanTags() {
const result = await db
.deleteFrom("ArtTag")
.where("id", "not in", db.selectFrom("TaggedArt").select("TaggedArt.tagId"))
.executeTakeFirst();
return Number(result.numDeletedRows);
}
export async function findArtsByUserId( export async function findArtsByUserId(
userId: number, userId: number,
{ includeAuthored = true, includeTagged = true } = {}, { includeAuthored = true, includeTagged = true } = {},

View File

@ -27,9 +27,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
: null; : null;
if (!filteredTag) { if (!filteredTag) {
return filteredTagName return filteredTagName ? { ...cachedArts, showcaseArts: [] } : cachedArts;
? { ...cachedArts, showcaseArts: [] }
: cachedArts;
} }
return { return {

View File

@ -0,0 +1,11 @@
import * as ArtRepository from "../features/art/ArtRepository.server";
import { logger } from "../utils/logger";
import { Routine } from "./routine.server";
export const DeleteOrphanArtTagsRoutine = new Routine({
name: "DeleteOrphanArtTags",
func: async () => {
const deletedCount = await ArtRepository.deleteOrphanTags();
logger.info(`Deleted ${deletedCount} orphan art tags`);
},
});

View File

@ -1,5 +1,6 @@
import { CloseExpiredCommissionsRoutine } from "./closeExpiredCommissions"; import { CloseExpiredCommissionsRoutine } from "./closeExpiredCommissions";
import { DeleteOldNotificationsRoutine } from "./deleteOldNotifications"; import { DeleteOldNotificationsRoutine } from "./deleteOldNotifications";
import { DeleteOrphanArtTagsRoutine } from "./deleteOrphanArtTags";
import { NotifyCheckInStartRoutine } from "./notifyCheckInStart"; import { NotifyCheckInStartRoutine } from "./notifyCheckInStart";
import { NotifyPlusServerVotingRoutine } from "./notifyPlusServerVoting"; import { NotifyPlusServerVotingRoutine } from "./notifyPlusServerVoting";
import { NotifyScrimStartingSoonRoutine } from "./notifyScrimStartingSoon"; import { NotifyScrimStartingSoonRoutine } from "./notifyScrimStartingSoon";
@ -28,6 +29,7 @@ export const everyHourAt30 = [
export const daily = [ export const daily = [
DeleteOldNotificationsRoutine, DeleteOldNotificationsRoutine,
CloseExpiredCommissionsRoutine, CloseExpiredCommissionsRoutine,
DeleteOrphanArtTagsRoutine,
]; ];
/** List of Routines that should occur every 2 minutes */ /** List of Routines that should occur every 2 minutes */