feat: store node logo/favicon in postgres instead of S3

- Add logoData and faviconData columns to nodes table
- Create /api/admin/node/upload endpoint for direct DB storage
- Create /api/node/logo and /api/node/favicon endpoints to serve images
- Update admin page to use new upload endpoint
- Remove dependency on admin's personal S3 storage for node assets
This commit is contained in:
Christomatt
2026-02-01 16:29:39 +01:00
parent 0cea2e9e1f
commit 90dfd62434
17 changed files with 913 additions and 90 deletions
+4 -4
View File
@@ -3,8 +3,8 @@ import { v4 as uuidv4 } from 'uuid';
export async function generateAndUploadAvatar(handle: string): Promise<string | null> {
try {
// 1. Fetch the avatar from DiceBear
const dicebearUrl = `https://api.dicebear.com/9.x/bottts-neutral/svg?seed=${handle}`;
// 1. Fetch the avatar from DiceBear (PNG format for better compatibility)
const dicebearUrl = `https://api.dicebear.com/9.x/bottts-neutral/png?seed=${handle}`;
const response = await fetch(dicebearUrl);
if (!response.ok) {
@@ -29,13 +29,13 @@ export async function generateAndUploadAvatar(handle: string): Promise<string |
const bucket = process.env.STORAGE_BUCKET || 'synapsis';
// Sanitize handle for filename just in case
const safeHandle = handle.replace(/[^a-zA-Z0-9]/g, '');
const filename = `${uuidv4()}-${safeHandle}-avatar.svg`;
const filename = `${uuidv4()}-${safeHandle}-avatar.png`;
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: filename,
Body: buffer,
ContentType: 'image/svg+xml',
ContentType: 'image/png',
ACL: 'public-read',
}));
+1 -1
View File
@@ -200,7 +200,7 @@ export async function registerUser(
const fullHandle = `${handle.toLowerCase()}@${nodeDomain}`;
const avatarUrl = await generateAndUploadAvatarToUserStorage(
fullHandle,
storageEndpoint || null,
storageEndpoint || undefined,
storageRegion,
storageBucket,
storageAccessKey,
+1
View File
@@ -7,6 +7,7 @@ export interface User {
id: string;
handle: string;
displayName: string;
email?: string;
avatarUrl?: string;
did?: string;
publicKey?: string;
+5 -5
View File
@@ -164,15 +164,15 @@ export async function testS3Credentials(
*/
export async function generateAndUploadAvatarToUserStorage(
handle: string,
endpoint: string | null | undefined,
endpoint: string | undefined,
region: string,
bucket: string,
accessKey: string,
secretKey: string
): Promise<string | null> {
try {
// 1. Fetch the avatar from DiceBear
const dicebearUrl = `https://api.dicebear.com/9.x/bottts-neutral/svg?seed=${handle}`;
// 1. Fetch the avatar from DiceBear (PNG format for better compatibility)
const dicebearUrl = `https://api.dicebear.com/9.x/bottts-neutral/png?seed=${handle}`;
const response = await fetch(dicebearUrl);
if (!response.ok) {
@@ -192,13 +192,13 @@ export async function generateAndUploadAvatarToUserStorage(
bucket,
});
const key = `synapsis/avatars/${handle.replace(/[^a-zA-Z0-9]/g, '')}-avatar.svg`;
const key = `synapsis/avatars/${handle.replace(/[^a-zA-Z0-9]/g, '')}-avatar.png`;
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: buffer,
ContentType: 'image/svg+xml',
ContentType: 'image/png',
}));
// 3. Return URL