diff --git a/.svelte-kit/generated/client/app.js b/.svelte-kit/generated/client/app.js
index 99133679..241470c7 100644
--- a/.svelte-kit/generated/client/app.js
+++ b/.svelte-kit/generated/client/app.js
@@ -13,7 +13,8 @@ export const nodes = [
() => import('./nodes/9'),
() => import('./nodes/10'),
() => import('./nodes/11'),
- () => import('./nodes/12')
+ () => import('./nodes/12'),
+ () => import('./nodes/13')
];
export const server_loads = [0];
@@ -25,9 +26,10 @@ export const dictionary = {
"/notifications": [~7],
"/profile": [8],
"/training": [~9],
+ "/training/admin": [~11],
"/training/[id]": [~10],
- "/users": [~11],
- "/users/[id]": [~12],
+ "/users": [~12],
+ "/users/[id]": [~13],
"/[site]": [~4,[2]]
};
diff --git a/.svelte-kit/generated/server/internal.js b/.svelte-kit/generated/server/internal.js
index e6c7ce81..30d359e5 100644
--- a/.svelte-kit/generated/server/internal.js
+++ b/.svelte-kit/generated/server/internal.js
@@ -24,7 +24,7 @@ export const options = {
app: ({ head, body, assets, nonce, env }) => "\n\n
\n \n \n \n \n " + head + "\n \n \n " + body + "
\n \n\n",
error: ({ status, message }) => "\n\n\t\n\t\t\n\t\t" + message + "\n\n\t\t\n\t\n\t\n\t\t\n\t\t\t
" + status + "\n\t\t\t
\n\t\t\t\t
" + message + "
\n\t\t\t\n\t\t
\n\t\n\n"
},
- version_hash: "n9d9oa"
+ version_hash: "sskapp"
};
export async function get_hooks() {
diff --git a/.svelte-kit/non-ambient.d.ts b/.svelte-kit/non-ambient.d.ts
index c2924641..44a3e86c 100644
--- a/.svelte-kit/non-ambient.d.ts
+++ b/.svelte-kit/non-ambient.d.ts
@@ -27,7 +27,7 @@ export {};
declare module "$app/types" {
export interface AppTypes {
- RouteId(): "/" | "/api" | "/api/darkmode" | "/api/notifications" | "/api/notifications/[id]" | "/api/sso" | "/api/sso/exchange" | "/login" | "/logout" | "/notifications" | "/profile" | "/training" | "/training/[id]" | "/users" | "/users/[id]" | "/[site]";
+ RouteId(): "/" | "/api" | "/api/darkmode" | "/api/notifications" | "/api/notifications/[id]" | "/api/sso" | "/api/sso/exchange" | "/login" | "/logout" | "/notifications" | "/profile" | "/training" | "/training/admin" | "/training/[id]" | "/users" | "/users/[id]" | "/[site]";
RouteParams(): {
"/api/notifications/[id]": { id: string };
"/training/[id]": { id: string };
@@ -47,12 +47,13 @@ declare module "$app/types" {
"/notifications": Record;
"/profile": Record;
"/training": { id?: string };
+ "/training/admin": Record;
"/training/[id]": { id: string };
"/users": { id?: string };
"/users/[id]": { id: string };
"/[site]": { site: string }
};
- Pathname(): "/" | "/api/darkmode" | "/api/notifications" | `/api/notifications/${string}` & {} | "/api/sso/exchange" | "/login" | "/logout" | "/notifications" | "/profile" | "/training" | `/training/${string}` & {} | "/users" | `/users/${string}` & {} | `/${string}` & {};
+ Pathname(): "/" | "/api/darkmode" | "/api/notifications" | `/api/notifications/${string}` & {} | "/api/sso/exchange" | "/login" | "/logout" | "/notifications" | "/profile" | "/training" | "/training/admin" | `/training/${string}` & {} | "/users" | `/users/${string}` & {} | `/${string}` & {};
ResolvedPathname(): `${"" | `/${string}`}${ReturnType}`;
Asset(): string & {};
}
diff --git a/.svelte-kit/types/route_meta_data.json b/.svelte-kit/types/route_meta_data.json
index 11473b2d..0495d508 100644
--- a/.svelte-kit/types/route_meta_data.json
+++ b/.svelte-kit/types/route_meta_data.json
@@ -34,6 +34,10 @@
"src/routes/training/+page.server.ts",
"src/routes/+layout.server.ts"
],
+ "/training/admin": [
+ "src/routes/training/admin/+page.server.ts",
+ "src/routes/+layout.server.ts"
+ ],
"/training/[id]": [
"src/routes/training/[id]/+page.server.ts",
"src/routes/+layout.server.ts"
diff --git a/.svelte-kit/types/src/routes/$types.d.ts b/.svelte-kit/types/src/routes/$types.d.ts
index 6391558a..a1e6dcbd 100644
--- a/.svelte-kit/types/src/routes/$types.d.ts
+++ b/.svelte-kit/types/src/routes/$types.d.ts
@@ -12,7 +12,7 @@ type EnsureDefined = T extends null | undefined ? {} : T;
type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never;
export type Snapshot = Kit.Snapshot;
type PageParentData = EnsureDefined;
-type LayoutRouteId = RouteId | "/" | "/[site]" | "/login" | "/logout" | "/notifications" | "/profile" | "/training" | "/training/[id]" | "/users" | "/users/[id]" | null
+type LayoutRouteId = RouteId | "/" | "/[site]" | "/login" | "/logout" | "/notifications" | "/profile" | "/training" | "/training/[id]" | "/training/admin" | "/users" | "/users/[id]" | null
type LayoutParams = RouteParams & { site?: string; id?: string }
type LayoutServerParentData = EnsureDefined<{}>;
type LayoutParentData = EnsureDefined<{}>;
diff --git a/src/routes/training/+page.server.ts b/src/routes/training/+page.server.ts
index 334cf203..cee4df91 100644
--- a/src/routes/training/+page.server.ts
+++ b/src/routes/training/+page.server.ts
@@ -46,10 +46,12 @@ export const load: PageServerLoad = async ({ locals }) => {
});
const canUpload = Boolean(user?.training_admin);
+ const isAdmin = Boolean((user as any)?.Admin);
return {
videos,
- canUpload
+ canUpload,
+ isAdmin
};
};
diff --git a/src/routes/training/+page.svelte b/src/routes/training/+page.svelte
index 325dd8a5..8012fc92 100644
--- a/src/routes/training/+page.svelte
+++ b/src/routes/training/+page.svelte
@@ -20,11 +20,12 @@
};
} | null;
- let { data, form }: { data: { videos: TrainingVideo[]; canUpload: boolean }; form: UploadFormState } =
+ let { data, form }: { data: { videos: TrainingVideo[]; canUpload: boolean; isAdmin: boolean }; form: UploadFormState } =
$props();
const videos = $derived(data.videos);
const canUpload = $derived(data.canUpload);
+ const isAdmin = $derived(data.isAdmin);
function formatDate(created: string) {
if (!created) return '';
@@ -87,6 +88,11 @@
{#if canUpload}
diff --git a/src/routes/training/admin/+page.server.ts b/src/routes/training/admin/+page.server.ts
new file mode 100644
index 00000000..f02efe8e
--- /dev/null
+++ b/src/routes/training/admin/+page.server.ts
@@ -0,0 +1,98 @@
+import { error, fail, redirect } from '@sveltejs/kit';
+import type { Actions, PageServerLoad } from './$types';
+
+type TrainingVideo = {
+ id: string;
+ title: string;
+ description: string | null;
+ created: string;
+ videoUrl: string;
+};
+
+const VIDEO_COLLECTION = 'media';
+const VIDEO_LINK_FIELD = 'Link';
+const VIDEO_FILE_FIELD = 'Media';
+const VIDEO_TITLE_FIELD = 'Title';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ const user = locals.user;
+
+ if (!user) {
+ throw redirect(303, '/login');
+ }
+
+ if (!(user as any)?.Admin) {
+ throw error(403, 'You do not have access to training admin.');
+ }
+
+ let records: any[] = [];
+
+ try {
+ records = await locals.pb.collection(VIDEO_COLLECTION).getFullList({
+ sort: '-created'
+ });
+ } catch (e) {
+ console.error('Failed to load training videos from PocketBase', e);
+ records = [];
+ }
+
+ const videos: TrainingVideo[] = records.map((record: any) => {
+ let videoUrl = (record[VIDEO_LINK_FIELD] as string) ?? '';
+
+ if (!videoUrl && record[VIDEO_FILE_FIELD]) {
+ videoUrl = locals.pb.files.getUrl(record, record[VIDEO_FILE_FIELD]);
+ }
+
+ return {
+ id: record.id as string,
+ title: (record[VIDEO_TITLE_FIELD] as string) ?? (record.title as string) ?? 'Untitled video',
+ description: (record.description as string) ?? null,
+ created: record.created as string,
+ videoUrl
+ };
+ });
+
+ return {
+ videos
+ };
+};
+
+export const actions: Actions = {
+ delete: async ({ request, locals }) => {
+ const user = locals.user;
+
+ if (!user) {
+ throw redirect(303, '/login');
+ }
+
+ if (!(user as any)?.Admin) {
+ return fail(403, {
+ error: 'You do not have permission to manage training videos.'
+ });
+ }
+
+ const formData = await request.formData();
+ const id = String(formData.get('id') ?? '').trim();
+
+ if (!id) {
+ return fail(400, {
+ error: 'Missing video id.'
+ });
+ }
+
+ try {
+ await locals.pb.collection(VIDEO_COLLECTION).delete(id);
+ } catch (e) {
+ const message =
+ e && typeof e === 'object' && 'message' in e
+ ? String((e as { message: string }).message)
+ : 'Could not delete video.';
+ return fail(500, {
+ error: message
+ });
+ }
+
+ throw redirect(303, '/training/admin');
+ }
+};
+
diff --git a/src/routes/training/admin/+page.svelte b/src/routes/training/admin/+page.svelte
new file mode 100644
index 00000000..5cabe673
--- /dev/null
+++ b/src/routes/training/admin/+page.svelte
@@ -0,0 +1,100 @@
+
+
+
+ Training admin — Base
+
+
+
+
+
+
+
+
+ Videos
+ Manage training videos in the Media collection.
+
+
+ {#if form?.error}
+ {form.error}
+ {/if}
+
+ {#if videos.length === 0}
+ No videos found.
+ {:else}
+
+ {#each videos as video (video.id)}
+
+
+
+
{video.title}
+
{formatDate(video.created)}
+
+ {#if video.description}
+
+ {video.description}
+
+ {/if}
+ {#if video.videoUrl}
+
+ {video.videoUrl}
+
+ {/if}
+
+
+
+ {/each}
+
+ {/if}
+
+
+
+
+