feat: optimize profile image sizes, silent PWA updates, storage cleanup fixes
- Resize avatars/icons to 256px and banners to 1280px (client crop + server safety net) - Add server-side resizeProfileImage() for federation/API uploads without crop modal - Fix unconstrained crop on RegisterPage and CreateSpace (was missing maxOutputDimension) - PWA: switch to autoUpdate with skipWaiting/clientsClaim for seamless deploys - Storage janitor: exclude profile images from unlinked cleanup, delete stale thumbnails - Add deleteAttachmentByFilename() to clean orphaned attachment records for profile images - Migration: one-time cleanup of stale profile image attachment records - GeneralPanel: wrap in <form> to prevent implicit submission
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import path from 'path';
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { eq, and, inArray } from 'drizzle-orm';
|
||||
import { getDb, schema } from '../db/index.js';
|
||||
@@ -7,7 +8,9 @@ import { isMember, isSpaceOwner, isBanned, hasPermission, computePermissions, Pe
|
||||
import { DEFAULT_EVERYONE_PERMISSIONS, ALL_PERMISSIONS, permissionsToString } from '@backspace/shared/src/permissions.js';
|
||||
import crypto from 'crypto';
|
||||
import { connectionManager } from '../ws/handler.js';
|
||||
import { deleteAttachmentFiles, deleteUploadFile } from '../utils/fileCleanup.js';
|
||||
import { deleteAttachmentFiles, deleteUploadFile, deleteAttachmentByFilename } from '../utils/fileCleanup.js';
|
||||
import { resizeProfileImage } from '../utils/thumbnail.js';
|
||||
import { config } from '../config.js';
|
||||
import type {
|
||||
CreateSpaceRequest,
|
||||
UpdateSpaceRequest,
|
||||
@@ -173,6 +176,24 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
// Register the creator in connectionManager so they receive WS broadcasts for this space
|
||||
connectionManager.addUserSpace(request.userId, spaceId);
|
||||
|
||||
// Clean up attachment records for icon/banner — reference is now in spaces table
|
||||
if (icon && typeof icon === 'string' && icon.includes('/api/uploads/')) {
|
||||
deleteAttachmentByFilename(icon);
|
||||
}
|
||||
if (banner && typeof banner === 'string' && banner.includes('/api/uploads/')) {
|
||||
deleteAttachmentByFilename(banner);
|
||||
}
|
||||
|
||||
// Resize profile images to optimal dimensions
|
||||
if (icon && typeof icon === 'string' && !icon.startsWith('http')) {
|
||||
const filePath = path.join(config.uploadDir, path.basename(icon));
|
||||
await resizeProfileImage(filePath, 'icon');
|
||||
}
|
||||
if (banner && typeof banner === 'string' && !banner.startsWith('http')) {
|
||||
const filePath = path.join(config.uploadDir, path.basename(banner));
|
||||
await resizeProfileImage(filePath, 'banner');
|
||||
}
|
||||
|
||||
return reply.code(201).send(rowToSpace(server));
|
||||
});
|
||||
|
||||
@@ -412,9 +433,29 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
// Clean up old icon/banner files that were replaced
|
||||
if (icon !== undefined && oldIcon && oldIcon !== (icon || null) && !oldIcon.startsWith('http')) {
|
||||
deleteUploadFile(oldIcon);
|
||||
deleteAttachmentByFilename(oldIcon);
|
||||
}
|
||||
if (banner !== undefined && oldBanner && oldBanner !== (banner || null) && !oldBanner.startsWith('http')) {
|
||||
deleteUploadFile(oldBanner);
|
||||
deleteAttachmentByFilename(oldBanner);
|
||||
}
|
||||
// Clean up attachment records for newly-set profile images — the reference
|
||||
// now lives in the spaces table, so the attachment record is unnecessary
|
||||
if (icon && typeof icon === 'string' && icon.includes('/api/uploads/')) {
|
||||
deleteAttachmentByFilename(icon);
|
||||
}
|
||||
if (banner && typeof banner === 'string' && banner.includes('/api/uploads/')) {
|
||||
deleteAttachmentByFilename(banner);
|
||||
}
|
||||
|
||||
// Resize profile images to optimal dimensions
|
||||
if (icon && typeof icon === 'string' && !icon.startsWith('http')) {
|
||||
const filePath = path.join(config.uploadDir, path.basename(icon));
|
||||
await resizeProfileImage(filePath, 'icon');
|
||||
}
|
||||
if (banner && typeof banner === 'string' && !banner.startsWith('http')) {
|
||||
const filePath = path.join(config.uploadDir, path.basename(banner));
|
||||
await resizeProfileImage(filePath, 'banner');
|
||||
}
|
||||
|
||||
const updated = db.select().from(schema.spaces).where(eq(schema.spaces.id, id)).get();
|
||||
|
||||
Reference in New Issue
Block a user