Improve ISAC libraries, roles, and public profiles

This commit is contained in:
JB
2026-07-27 18:54:46 -04:00
parent fe4acec191
commit 6e12b2f3b7
40 changed files with 1196 additions and 271 deletions
+41 -1
View File
@@ -1,7 +1,8 @@
import type { Database } from '@tursodatabase/database';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import path from 'node:path';
import sharp from 'sharp';
import { GAME_ICON_TYPES, type GameDataIconType } from './game-data-icons';
import { BackgroundRemovalError, removeBackgroundPreview } from './remove-background';
export const MANAGED_IMAGE_CATEGORIES = [
@@ -14,6 +15,7 @@ const ID_PATTERN = /^[0-9a-f-]{36}$/i;
const KEY_PATTERN = /^(?:library|talent|brand_set|os_protocol|skill_chip|specialization|specialization_ability|specialization_node)-[0-9a-f-]{36}\.webp$/i;
export class ManagedImageValidationError extends Error {}
export class ManagedImageDeletionError extends Error {}
export function managedImageStorageDirectory() {
return path.resolve(process.env.TDR_GAME_ICON_STORAGE_PATH || path.join(process.cwd(), 'data', 'game-icons'));
@@ -102,6 +104,44 @@ export async function saveManagedImageFile(id: string, output: Buffer) {
return key;
}
export async function deleteManagedImage(database: Database, id: string) {
const image = await (await database.prepare('SELECT * FROM managed_images WHERE id=?')).get(id);
if (!image) return null;
if (typeof image.storage_key !== 'string') throw new ManagedImageDeletionError('Managed image is missing its storage key.');
const sourceType = typeof image.source_entity_type === 'string' ? image.source_entity_type : null;
const sourceId = typeof image.source_entity_id === 'string' ? image.source_entity_id : null;
if (Boolean(sourceType) !== Boolean(sourceId)) {
throw new ManagedImageDeletionError('This image has an incomplete source assignment and cannot be deleted safely.');
}
if (sourceType && !GAME_ICON_TYPES.includes(sourceType as GameDataIconType)) {
throw new ManagedImageDeletionError('This image has an unsupported source assignment and cannot be deleted safely.');
}
const sourceIcon = sourceType && sourceId
? await (await database.prepare('SELECT * FROM game_data_icons WHERE entity_type=? AND entity_id=?')).get(sourceType, sourceId) ?? null
: null;
if (sourceIcon && sourceIcon.storage_key !== image.storage_key) {
throw new ManagedImageDeletionError('The library image and its source assignment reference different files. Resolve the assignment before deleting.');
}
await database.exec('BEGIN');
try {
if (sourceIcon && sourceType && sourceId) {
await (await database.prepare(
'DELETE FROM game_data_icons WHERE entity_type=? AND entity_id=? AND storage_key=?'
)).run(sourceType, sourceId, image.storage_key);
}
await (await database.prepare('DELETE FROM managed_images WHERE id=? AND storage_key=?')).run(id, image.storage_key);
await database.exec('COMMIT');
} catch (error) {
await database.exec('ROLLBACK').catch(() => undefined);
throw error;
}
await rm(storagePath(image.storage_key), { force: true });
return { image, sourceIcon };
}
export async function readManagedImage(database: Database, id: string) {
if (!ID_PATTERN.test(id)) return null;
const row = await (await database.prepare('SELECT storage_key FROM managed_images WHERE id=?')).get(id);