Restrict proclamation editing and content to Super admins only

- Only roleId <= 1 (SUPER) can create/edit/delete proclamations
- Content field is stripped from records for non-Super users
- Content field is hidden from the form for non-Super users
- Server actions reject upsert/delete from non-Super users
This commit is contained in:
NeilClack
2026-07-07 22:10:54 -05:00
parent 4cef38a1ab
commit ce42414214
3 changed files with 48 additions and 19 deletions
@@ -8,9 +8,24 @@ import * as Schemas from '$lib/svelte-admin/schemas';
const config = getModelConfig('proclamations')!; const config = getModelConfig('proclamations')!;
function viewerRoleId(locals: App.Locals): number {
return (locals.user as { roleId?: number } | null)?.roleId ?? 4;
}
function isSuper(locals: App.Locals): boolean {
return viewerRoleId(locals) <= 1;
}
export const load: PageServerLoad = async ({ locals }) => { export const load: PageServerLoad = async ({ locals }) => {
const base = await loadRecords(locals, config); const base = await loadRecords(locals, config);
// Strip content from non-Super users
if (!isSuper(locals)) {
for (const record of base.records) {
delete record.content;
}
}
const categories = await prisma.proclamationCategory.findMany({ const categories = await prisma.proclamationCategory.findMany({
select: { id: true, name: true, parentId: true }, select: { id: true, name: true, parentId: true },
orderBy: { name: 'asc' } orderBy: { name: 'asc' }
@@ -25,6 +40,10 @@ export const actions = {
return { success: false, error: 'Unauthorized' }; return { success: false, error: 'Unauthorized' };
} }
if (!isSuper(locals)) {
return { success: false, error: 'Only Super admins can edit proclamations' };
}
const formData = await request.formData(); const formData = await request.formData();
const form = await superValidate(formData, zod4(Schemas.Proclamation)); const form = await superValidate(formData, zod4(Schemas.Proclamation));
if (!form.valid) { if (!form.valid) {
@@ -70,6 +89,9 @@ export const actions = {
return { success: true }; return { success: true };
}, },
delete: async ({ request, locals }) => { delete: async ({ request, locals }) => {
if (!isSuper(locals)) {
return { success: false, error: 'Only Super admins can delete proclamations' };
}
return deleteRecord(request, config, locals); return deleteRecord(request, config, locals);
} }
}; };
@@ -10,7 +10,9 @@
let { data, form }: PageProps = $props(); let { data, form }: PageProps = $props();
const config = getModelConfig('proclamations')!; const config = getModelConfig('proclamations')!;
const canDelete = $derived((data.adminUser?.roleId ?? 4) <= 2); const viewerRoleId = $derived(data.adminUser?.roleId ?? 4);
const isSuper = $derived(viewerRoleId <= 1);
const canDelete = $derived(viewerRoleId <= 2);
let modalOpen = $state(false); let modalOpen = $state(false);
let editingRecord = $state<Record<string, unknown> | null>(null); let editingRecord = $state<Record<string, unknown> | null>(null);
@@ -63,8 +65,8 @@
searchFields={config.searchFields} searchFields={config.searchFields}
fieldTypes={config.fieldTypes} fieldTypes={config.fieldTypes}
singular={config.displayNameSingular} singular={config.displayNameSingular}
onRowClick={openEdit} onRowClick={isSuper ? openEdit : undefined}
onNew={openCreate} onNew={isSuper ? openCreate : undefined}
/> />
</div> </div>
@@ -83,6 +85,7 @@
categories={data.categories} categories={data.categories}
{fieldErrors} {fieldErrors}
{formError} {formError}
{isSuper}
submitLabel={editingRecord ? 'Update' : 'Create'} submitLabel={editingRecord ? 'Update' : 'Create'}
/> />
</AdminModal> </AdminModal>
@@ -8,6 +8,7 @@
formError = '', formError = '',
relationOptionsData = {} as Record<string, { label: string; value: string | number }[]>, relationOptionsData = {} as Record<string, { label: string; value: string | number }[]>,
categories = [] as { id: number; name: string; parentId: number | null }[], categories = [] as { id: number; name: string; parentId: number | null }[],
isSuper = false,
action = '?/upsert', action = '?/upsert',
submitLabel = 'Save' submitLabel = 'Save'
}: { }: {
@@ -16,6 +17,7 @@
formError?: string; formError?: string;
relationOptionsData?: Record<string, { label: string; value: string | number }[]>; relationOptionsData?: Record<string, { label: string; value: string | number }[]>;
categories?: { id: number; name: string; parentId: number | null }[]; categories?: { id: number; name: string; parentId: number | null }[];
isSuper?: boolean;
action?: string; action?: string;
submitLabel?: string; submitLabel?: string;
} = $props(); } = $props();
@@ -190,6 +192,7 @@
{/if} {/if}
</div> </div>
{#if isSuper}
<div> <div>
<label <label
for="field-content" for="field-content"
@@ -207,6 +210,7 @@
<p class="mt-1 text-xs text-red-600">{fieldErrors.content}</p> <p class="mt-1 text-xs text-red-600">{fieldErrors.content}</p>
{/if} {/if}
</div> </div>
{/if}
<div class="flex justify-end gap-2 pt-2"> <div class="flex justify-end gap-2 pt-2">
<button <button