Improve ISAC libraries, roles, and public profiles
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user