This repository has been archived on 2025-08-19. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
coinstorge/website/src/routes/api/settings/+server.ts

103 lines
3.1 KiB
TypeScript
Raw Normal View History

import { auth } from '$lib/auth';
import { uploadProfilePicture } from '$lib/server/s3';
import { error, json } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
import { MAX_FILE_SIZE } from '$lib/data/constants';
2025-05-30 14:35:15 +03:00
import { isNameAppropriate } from '$lib/server/moderation';
2025-05-30 14:35:15 +03:00
async function validateInputs(name: string, bio: string, username: string, avatarFile: File | null) {
if (!name || !name.trim()) {
throw error(400, 'Display name is required');
}
if (name.trim().length < 2) {
throw error(400, 'Display name must be at least 2 characters');
}
if (name.trim().length > 50) {
throw error(400, 'Display name must be 50 characters or less');
}
2025-06-01 20:38:29 +03:00
if (name && !(await isNameAppropriate(name.trim()))) {
2025-05-30 14:35:15 +03:00
throw error(400, 'Name contains inappropriate content');
}
if (bio && bio.length > 160) {
throw error(400, 'Bio must be 160 characters or less');
}
if (username && (username.length < 3 || username.length > 30)) {
throw error(400, 'Username must be between 3 and 30 characters');
}
if (username) {
2025-05-31 17:39:30 +03:00
const alphanumericRegex = /^[a-z0-9_]+$/;
if (!alphanumericRegex.test(username)) {
2025-05-31 17:39:30 +03:00
throw error(400, 'Username must contain only lowercase letters, numbers, and underscores');
}
}
2025-05-30 14:35:15 +03:00
if (username && !(await isNameAppropriate(username))) {
throw error(400, 'Username contains inappropriate content');
}
2025-05-30 16:50:56 +03:00
if (bio && !(await isNameAppropriate(bio))) {
throw error(400, 'Bio contains inappropriate content');
}
if (avatarFile && avatarFile.size > MAX_FILE_SIZE) {
throw error(400, 'Avatar file must be smaller than 1MB');
}
}
export async function POST({ request }) {
const session = await auth.api.getSession({
headers: request.headers
});
if (!session?.user) {
throw error(401, 'Not authenticated');
}
const formData = await request.formData();
2025-06-02 11:17:22 +03:00
let name = (formData.get('name') as string)?.trim();
const bio = formData.get('bio') as string;
2025-06-01 20:38:29 +03:00
const username = (formData.get('username') as string)?.toLowerCase().trim();
const avatarFile = formData.get('avatar') as File | null;
2025-06-02 11:17:22 +03:00
name = name?.trim().replace(/\s+/g, ' ');
2025-05-30 14:35:15 +03:00
await validateInputs(name, bio, username, avatarFile);
const updates: Record<string, any> = {
name,
bio,
username,
updatedAt: new Date()
};
if (avatarFile && avatarFile.size > 0) {
try {
const arrayBuffer = await avatarFile.arrayBuffer();
const key = await uploadProfilePicture(
session.user.id,
new Uint8Array(arrayBuffer),
avatarFile.type,
avatarFile.size
);
updates.image = key;
} catch (e) {
console.error('Avatar upload failed, continuing without update:', e);
}
}
await db.update(user)
.set(updates)
.where(eq(user.id, Number(session.user.id)));
return json({ success: true });
}