- Rich profile (/u/[username]): wallet (credits/duckets/diamonds), friends grid (messenger_friendships), and owned rooms sections. - Login history: new website_login_logs table (model + migration 0007), recorded on every successful sign-in (ip + user-agent), surfaced on a new /settings/sessions page (with failed-attempt list from failed_logins). - Photos lightbox + home article slider (client components, no Swiper dep). - /client/flash launcher (SSO ticket like the Nitro page). - Admin: private chatlogs section in /admin/logs, /admin/radio/moderation (shout moderation), a "users by rank" inline bar chart on the dashboard, and a TinyMCE rich-text editor on the article admin forms. - Niche API: /api/values/[id], /api/guilds(+/[id]), /api/radio/auto-play. Verified live (prod, amx_test): login recorded → /settings/sessions shows it with device; profile renders wallet/friends/rooms; dashboard chart + private-chat logs + /client/flash + /api/guilds all OK. Reverted test data. tsc 0, vitest 49/49, next build 0.
44 lines
1.3 KiB
TypeScript
44 lines
1.3 KiB
TypeScript
'use server';
|
|
|
|
import { revalidatePath } from 'next/cache';
|
|
import { requireStaff } from '@/lib/admin/guard';
|
|
import { logStaffActivity } from '@/lib/services/staff-activity';
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
/** Parse a FormData field into a positive BigInt id, or null when invalid. */
|
|
function parseId(raw: FormDataEntryValue | null): bigint | null {
|
|
if (typeof raw !== 'string' || raw.trim() === '') return null;
|
|
try {
|
|
const id = BigInt(raw.trim());
|
|
return id > 0n ? id : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a radio shout from the DJ moderation page. Re-reads auth via
|
|
* requireStaff, writes a staff-activity audit entry and revalidates the
|
|
* moderation route. Fails soft if the row is already gone.
|
|
*/
|
|
export async function deleteShout(formData: FormData): Promise<void> {
|
|
const staff = await requireStaff();
|
|
const id = parseId(formData.get('id'));
|
|
if (id === null) return;
|
|
|
|
try {
|
|
await prisma.radioShouts.delete({ where: { id } });
|
|
await logStaffActivity({
|
|
staffId: staff.id,
|
|
action: 'radio.shout.delete',
|
|
description: `Deleted radio shout #${id}`,
|
|
targetType: 'radio_shout',
|
|
targetId: Number(id),
|
|
});
|
|
} catch {
|
|
// Row may already be gone; ignore so the action does not throw.
|
|
}
|
|
|
|
revalidatePath('/admin/radio/moderation');
|
|
}
|