Add user avatar management and moderation

This commit is contained in:
JB
2026-07-25 00:17:18 -04:00
parent c0834804d4
commit 3f78f0f9d8
30 changed files with 915 additions and 18 deletions
+34 -2
View File
@@ -5,6 +5,8 @@ import { softDeleteAccount } from '$lib/server/auth/account-deletion';
import { error, fail } from '@sveltejs/kit';
import { randomUUID } from 'node:crypto';
import type { Actions, PageServerLoad } from './$types';
import { avatarUrl } from '$lib/avatar';
import { deleteUploadedAvatar } from '$lib/server/avatars';
export const load: PageServerLoad = async ({ cookies, params }) => {
const operator = await requirePermission(await getSessionUser(cookies), 'users.view');
@@ -14,10 +16,13 @@ export const load: PageServerLoad = async ({ cookies, params }) => {
users.role,users.discord_id,users.created_at,
CASE WHEN users.password_hash IS NOT NULL THEN 1 ELSE 0 END AS has_password,
discord_profiles.username AS discord_username,discord_profiles.display_name AS discord_display_name,
discord_profiles.avatar_hash AS discord_avatar_hash,user_status.*,
discord_profiles.avatar_hash AS discord_avatar_hash,user_avatars.source AS avatar_source,
user_avatars.value AS avatar_value,user_avatars.byte_size AS avatar_byte_size,
user_avatars.updated_at AS avatar_updated_at,user_status.*,
deleted_accounts.deleted_at,deleted_accounts.deleted_by,deleted_accounts.reason AS deletion_reason
FROM users LEFT JOIN user_status ON user_status.user_id=users.id
LEFT JOIN user_profiles ON user_profiles.user_id=users.id
LEFT JOIN user_avatars ON user_avatars.user_id=users.id
LEFT JOIN discord_profiles ON discord_profiles.user_id=users.id
LEFT JOIN deleted_accounts ON deleted_accounts.user_id=users.id WHERE users.id=?`)).get(params.id),
(await database.prepare('SELECT * FROM roles ORDER BY name')).all(),
@@ -31,7 +36,10 @@ export const load: PageServerLoad = async ({ cookies, params }) => {
]);
if (!profile) error(404, 'User not found');
return {
profile,
profile: {
...profile,
avatar_url: avatarUrl(profile.avatar_source, profile.avatar_value, String(profile.id), profile.avatar_updated_at)
},
roles,
permissions,
assignedRoleIds: assignedRoles.map((row) => String(row.role_id)),
@@ -43,6 +51,30 @@ export const load: PageServerLoad = async ({ cookies, params }) => {
};
export const actions: Actions = {
removeCustomAvatar: async ({ cookies, params }) => {
const operator = await requirePermission(await getSessionUser(cookies), 'users.moderate');
const database = await getDatabase();
const avatar = await (await database.prepare(
'SELECT source,value,byte_size FROM user_avatars WHERE user_id=?'
)).get(params.id);
if (avatar?.source !== 'upload') {
return fail(409, { avatarModerationError: 'This agent does not currently use a custom avatar.' });
}
await deleteUploadedAvatar(database, params.id);
await (await database.prepare(
'INSERT INTO audit_log(id,actor_id,entity_type,entity_id,action,before_json,after_json,created_at) VALUES(?,?,?,?,?,?,?,?)'
)).run(
randomUUID(),
operator.id,
'user',
params.id,
'moderation.avatar_removed',
JSON.stringify({ source: avatar.source, storageKey: avatar.value, byteSize: avatar.byte_size }),
JSON.stringify({ source: 'preset', preset: 'field-agent' }),
new Date().toISOString()
);
return { avatarRemoved: true };
},
setAccess: async ({ cookies, request, params }) => {
const operator = await requirePermission(await getSessionUser(cookies), 'users.roles');
const form = await request.formData();