feat: add user management features with CRUD operations and password policies

- Implemented PATCH and DELETE endpoints for bug reports.
- Created user management page with user creation, display name update, password reset, and user deletion functionalities.
- Added password validation and hashing for user creation and password reset.
- Integrated dialogs for user actions (create, update, reset password, delete).
- Configured SvelteKit with Node adapter and Vite for build process.
- Set up Tailwind CSS for styling.
This commit is contained in:
Flavio Fois
2026-02-16 13:30:01 +01:00
parent 7ddd6ebac5
commit c49692a75b
182 changed files with 6617 additions and 0 deletions

249
.svelte-kit/ambient.d.ts vendored Normal file
View File

@@ -0,0 +1,249 @@
// this file is generated — do not edit it
/// <reference types="@sveltejs/kit" />
/**
* Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
*
* _Unlike_ [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), the values exported from this module are statically injected into your bundle at build time, enabling optimisations like dead code elimination.
*
* ```ts
* import { API_KEY } from '$env/static/private';
* ```
*
* Note that all environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed:
*
* ```
* MY_FEATURE_FLAG=""
* ```
*
* You can override `.env` values from the command line like so:
*
* ```sh
* MY_FEATURE_FLAG="enabled" npm run dev
* ```
*/
declare module '$env/static/private' {
export const MYSQL_HOST: string;
export const MYSQL_PORT: string;
export const MYSQL_USER: string;
export const MYSQL_PASSWORD: string;
export const MYSQL_DATABASE: string;
export const ACSetupSvcPort: string;
export const ALLUSERSPROFILE: string;
export const AMDRMPATH: string;
export const APPDATA: string;
export const BUN_INSPECT_CONNECT_TO: string;
export const CGO_ENABLED: string;
export const ChocolateyInstall: string;
export const ChocolateyLastPathUpdate: string;
export const CHROME_CRASHPAD_PIPE_NAME: string;
export const CLAUDE_CODE_SSE_PORT: string;
export const COLORTERM: string;
export const CommonProgramFiles: string;
export const CommonProgramW6432: string;
export const COMPUTERNAME: string;
export const ComSpec: string;
export const DriverData: string;
export const EFC_8892_1262719628: string;
export const EFC_8892_1592913036: string;
export const EFC_8892_2283032206: string;
export const EFC_8892_3789132940: string;
export const FPS_BROWSER_APP_PROFILE_STRING: string;
export const FPS_BROWSER_USER_PROFILE_STRING: string;
export const GIT_ASKPASS: string;
export const GIT_INSTALL_ROOT: string;
export const GIT_PAGER: string;
export const GK_GL_ADDR: string;
export const GK_GL_PATH: string;
export const GoLand: string;
export const GOPATH: string;
export const HOMEDRIVE: string;
export const HOMEPATH: string;
export const JAVA_HOME: string;
export const LANG: string;
export const LOCALAPPDATA: string;
export const LOGONSERVER: string;
export const NODE: string;
export const npm_command: string;
export const npm_config_local_prefix: string;
export const npm_config_user_agent: string;
export const npm_execpath: string;
export const npm_lifecycle_event: string;
export const npm_lifecycle_script: string;
export const npm_node_execpath: string;
export const npm_package_json: string;
export const npm_package_name: string;
export const npm_package_version: string;
export const NUMBER_OF_PROCESSORS: string;
export const OneDrive: string;
export const OS: string;
export const Path: string;
export const PATHEXT: string;
export const PORT: string;
export const PROCESSOR_ARCHITECTURE: string;
export const PROCESSOR_IDENTIFIER: string;
export const PROCESSOR_LEVEL: string;
export const PROCESSOR_REVISION: string;
export const ProgramData: string;
export const ProgramFiles: string;
export const ProgramW6432: string;
export const PSModulePath: string;
export const PUBLIC: string;
export const PWD: string;
export const RlsSvcPort: string;
export const SESSIONNAME: string;
export const SystemDrive: string;
export const SystemRoot: string;
export const TEMP: string;
export const TERM_PROGRAM: string;
export const TERM_PROGRAM_VERSION: string;
export const TMP: string;
export const USERDOMAIN: string;
export const USERDOMAIN_ROAMINGPROFILE: string;
export const USERNAME: string;
export const USERPROFILE: string;
export const VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
export const VSCODE_GIT_ASKPASS_MAIN: string;
export const VSCODE_GIT_ASKPASS_NODE: string;
export const VSCODE_GIT_IPC_HANDLE: string;
export const VSCODE_INJECTION: string;
export const VSCODE_PYTHON_AUTOACTIVATE_GUARD: string;
export const windir: string;
}
/**
* Similar to [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private), except that it only includes environment variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
*
* Values are replaced statically at build time.
*
* ```ts
* import { PUBLIC_BASE_URL } from '$env/static/public';
* ```
*/
declare module '$env/static/public' {
}
/**
* This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
*
* This module cannot be imported into client-side code.
*
* ```ts
* import { env } from '$env/dynamic/private';
* console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
* ```
*
* > [!NOTE] In `dev`, `$env/dynamic` always includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter.
*/
declare module '$env/dynamic/private' {
export const env: {
MYSQL_HOST: string;
MYSQL_PORT: string;
MYSQL_USER: string;
MYSQL_PASSWORD: string;
MYSQL_DATABASE: string;
ACSetupSvcPort: string;
ALLUSERSPROFILE: string;
AMDRMPATH: string;
APPDATA: string;
BUN_INSPECT_CONNECT_TO: string;
CGO_ENABLED: string;
ChocolateyInstall: string;
ChocolateyLastPathUpdate: string;
CHROME_CRASHPAD_PIPE_NAME: string;
CLAUDE_CODE_SSE_PORT: string;
COLORTERM: string;
CommonProgramFiles: string;
CommonProgramW6432: string;
COMPUTERNAME: string;
ComSpec: string;
DriverData: string;
EFC_8892_1262719628: string;
EFC_8892_1592913036: string;
EFC_8892_2283032206: string;
EFC_8892_3789132940: string;
FPS_BROWSER_APP_PROFILE_STRING: string;
FPS_BROWSER_USER_PROFILE_STRING: string;
GIT_ASKPASS: string;
GIT_INSTALL_ROOT: string;
GIT_PAGER: string;
GK_GL_ADDR: string;
GK_GL_PATH: string;
GoLand: string;
GOPATH: string;
HOMEDRIVE: string;
HOMEPATH: string;
JAVA_HOME: string;
LANG: string;
LOCALAPPDATA: string;
LOGONSERVER: string;
NODE: string;
npm_command: string;
npm_config_local_prefix: string;
npm_config_user_agent: string;
npm_execpath: string;
npm_lifecycle_event: string;
npm_lifecycle_script: string;
npm_node_execpath: string;
npm_package_json: string;
npm_package_name: string;
npm_package_version: string;
NUMBER_OF_PROCESSORS: string;
OneDrive: string;
OS: string;
Path: string;
PATHEXT: string;
PORT: string;
PROCESSOR_ARCHITECTURE: string;
PROCESSOR_IDENTIFIER: string;
PROCESSOR_LEVEL: string;
PROCESSOR_REVISION: string;
ProgramData: string;
ProgramFiles: string;
ProgramW6432: string;
PSModulePath: string;
PUBLIC: string;
PWD: string;
RlsSvcPort: string;
SESSIONNAME: string;
SystemDrive: string;
SystemRoot: string;
TEMP: string;
TERM_PROGRAM: string;
TERM_PROGRAM_VERSION: string;
TMP: string;
USERDOMAIN: string;
USERDOMAIN_ROAMINGPROFILE: string;
USERNAME: string;
USERPROFILE: string;
VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
VSCODE_GIT_ASKPASS_MAIN: string;
VSCODE_GIT_ASKPASS_NODE: string;
VSCODE_GIT_IPC_HANDLE: string;
VSCODE_INJECTION: string;
VSCODE_PYTHON_AUTOACTIVATE_GUARD: string;
windir: string;
[key: `PUBLIC_${string}`]: undefined;
[key: `${string}`]: string | undefined;
}
}
/**
* Similar to [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
*
* Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
*
* ```ts
* import { env } from '$env/dynamic/public';
* console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
* ```
*/
declare module '$env/dynamic/public' {
export const env: {
[key: `PUBLIC_${string}`]: string | undefined;
}
}

View File

@@ -0,0 +1,37 @@
export { matchers } from './matchers.js';
export const nodes = [
() => import('./nodes/0'),
() => import('./nodes/1'),
() => import('./nodes/2'),
() => import('./nodes/3'),
() => import('./nodes/4'),
() => import('./nodes/5'),
() => import('./nodes/6')
];
export const server_loads = [0];
export const dictionary = {
"/": [~2],
"/login": [~3],
"/logout": [~4],
"/reports/[id]": [~5],
"/users": [~6]
};
export const hooks = {
handleError: (({ error }) => { console.error(error) }),
reroute: (() => {}),
transport: {}
};
export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode]));
export const encoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.encode]));
export const hash = false;
export const decode = (type, value) => decoders[type](value);
export { default as root } from '../root.js';

View File

@@ -0,0 +1 @@
export const matchers = {};

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/+layout.svelte";

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/+error.svelte";

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/+page.svelte";

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/login/+page.svelte";

View File

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/reports/[id]/+page.svelte";

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/users/+page.svelte";

View File

@@ -0,0 +1,3 @@
import { asClassComponent } from 'svelte/legacy';
import Root from './root.svelte';
export default asClassComponent(Root);

View File

@@ -0,0 +1,68 @@
<!-- This file is generated by @sveltejs/kit — do not edit it! -->
<svelte:options runes={true} />
<script>
import { setContext, onMount, tick } from 'svelte';
import { browser } from '$app/environment';
// stores
let { stores, page, constructors, components = [], form, data_0 = null, data_1 = null } = $props();
if (!browser) {
// svelte-ignore state_referenced_locally
setContext('__svelte__', stores);
}
if (browser) {
$effect.pre(() => stores.page.set(page));
} else {
// svelte-ignore state_referenced_locally
stores.page.set(page);
}
$effect(() => {
stores;page;constructors;components;form;data_0;data_1;
stores.page.notify();
});
let mounted = $state(false);
let navigated = $state(false);
let title = $state(null);
onMount(() => {
const unsubscribe = stores.page.subscribe(() => {
if (mounted) {
navigated = true;
tick().then(() => {
title = document.title || 'untitled page';
});
}
});
mounted = true;
return unsubscribe;
});
const Pyramid_1=$derived(constructors[1])
</script>
{#if constructors[1]}
{@const Pyramid_0 = constructors[0]}
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_0 bind:this={components[0]} data={data_0} {form} params={page.params}>
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_1 bind:this={components[1]} data={data_1} {form} params={page.params} />
</Pyramid_0>
{:else}
{@const Pyramid_0 = constructors[0]}
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_0 bind:this={components[0]} data={data_0} {form} params={page.params} />
{/if}
{#if mounted}
<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
{#if navigated}
{title}
{/if}
</div>
{/if}

View File

@@ -0,0 +1,53 @@
import root from '../root.js';
import { set_building, set_prerendering } from '__sveltekit/environment';
import { set_assets } from '$app/paths/internal/server';
import { set_manifest, set_read_implementation } from '__sveltekit/server';
import { set_private_env, set_public_env } from '../../../node_modules/@sveltejs/kit/src/runtime/shared-server.js';
export const options = {
app_template_contains_nonce: false,
async: false,
csp: {"mode":"auto","directives":{"upgrade-insecure-requests":false,"block-all-mixed-content":false},"reportOnly":{"upgrade-insecure-requests":false,"block-all-mixed-content":false}},
csrf_check_origin: true,
csrf_trusted_origins: [],
embedded: false,
env_public_prefix: 'PUBLIC_',
env_private_prefix: '',
hash_routing: false,
hooks: null, // added lazily, via `get_hooks`
preload_strategy: "modulepreload",
root,
service_worker: false,
service_worker_options: undefined,
templates: {
app: ({ head, body, assets, nonce, env }) => "<!doctype html>\r\n<html lang=\"en\" class=\"dark\">\r\n\t<head>\r\n\t\t<meta charset=\"utf-8\" />\r\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\r\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\r\n\t\t" + head + "\r\n\t</head>\r\n\t<body data-sveltekit-preload-data=\"hover\">\r\n\t\t<div style=\"display: contents\">" + body + "</div>\r\n\t</body>\r\n</html>\r\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
version_hash: "12nkzg7"
};
export async function get_hooks() {
let handle;
let handleFetch;
let handleError;
let handleValidationError;
let init;
({ handle, handleFetch, handleError, handleValidationError, init } = await import("../../../src/hooks.server.ts"));
let reroute;
let transport;
return {
handle,
handleFetch,
handleError,
handleValidationError,
init,
reroute,
transport
};
}
export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation };

57
.svelte-kit/non-ambient.d.ts vendored Normal file
View File

@@ -0,0 +1,57 @@
// this file is generated — do not edit it
declare module "svelte/elements" {
export interface HTMLAttributes<T> {
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
'data-sveltekit-preload-code'?:
| true
| ''
| 'eager'
| 'viewport'
| 'hover'
| 'tap'
| 'off'
| undefined
| null;
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
}
}
export {};
declare module "$app/types" {
export interface AppTypes {
RouteId(): "/" | "/api" | "/api/reports" | "/api/reports/refresh" | "/api/reports/[id]" | "/api/reports/[id]/download" | "/api/reports/[id]/files" | "/api/reports/[id]/files/[fileId]" | "/login" | "/logout" | "/reports" | "/reports/[id]" | "/users";
RouteParams(): {
"/api/reports/[id]": { id: string };
"/api/reports/[id]/download": { id: string };
"/api/reports/[id]/files": { id: string };
"/api/reports/[id]/files/[fileId]": { id: string; fileId: string };
"/reports/[id]": { id: string }
};
LayoutParams(): {
"/": { id?: string; fileId?: string };
"/api": { id?: string; fileId?: string };
"/api/reports": { id?: string; fileId?: string };
"/api/reports/refresh": Record<string, never>;
"/api/reports/[id]": { id: string; fileId?: string };
"/api/reports/[id]/download": { id: string };
"/api/reports/[id]/files": { id: string; fileId?: string };
"/api/reports/[id]/files/[fileId]": { id: string; fileId: string };
"/login": Record<string, never>;
"/logout": Record<string, never>;
"/reports": { id?: string };
"/reports/[id]": { id: string };
"/users": Record<string, never>
};
Pathname(): "/" | "/api" | "/api/" | "/api/reports" | "/api/reports/" | "/api/reports/refresh" | "/api/reports/refresh/" | `/api/reports/${string}` & {} | `/api/reports/${string}/` & {} | `/api/reports/${string}/download` & {} | `/api/reports/${string}/download/` & {} | `/api/reports/${string}/files` & {} | `/api/reports/${string}/files/` & {} | `/api/reports/${string}/files/${string}` & {} | `/api/reports/${string}/files/${string}/` & {} | "/login" | "/login/" | "/logout" | "/logout/" | "/reports" | "/reports/" | `/reports/${string}` & {} | `/reports/${string}/` & {} | "/users" | "/users/";
ResolvedPathname(): `${"" | `/${string}`}${ReturnType<AppTypes['Pathname']>}`;
Asset(): string & {};
}
}

55
.svelte-kit/tsconfig.json Normal file
View File

@@ -0,0 +1,55 @@
{
"compilerOptions": {
"paths": {
"$lib": [
"../src/lib"
],
"$lib/*": [
"../src/lib/*"
],
"$app/types": [
"./types/index.d.ts"
]
},
"rootDirs": [
"..",
"./types"
],
"verbatimModuleSyntax": true,
"isolatedModules": true,
"lib": [
"esnext",
"DOM",
"DOM.Iterable"
],
"moduleResolution": "bundler",
"module": "esnext",
"noEmit": true,
"target": "esnext"
},
"include": [
"ambient.d.ts",
"non-ambient.d.ts",
"./types/**/$types.d.ts",
"../vite.config.js",
"../vite.config.ts",
"../src/**/*.js",
"../src/**/*.ts",
"../src/**/*.svelte",
"../test/**/*.js",
"../test/**/*.ts",
"../test/**/*.svelte",
"../tests/**/*.js",
"../tests/**/*.ts",
"../tests/**/*.svelte"
],
"exclude": [
"../node_modules/**",
"../src/service-worker.js",
"../src/service-worker/**/*.js",
"../src/service-worker.ts",
"../src/service-worker/**/*.ts",
"../src/service-worker.d.ts",
"../src/service-worker/**/*.d.ts"
]
}

View File

@@ -0,0 +1,33 @@
{
"/": [
"src/routes/+page.server.ts",
"src/routes/+layout.server.ts",
"src/routes/+layout.server.ts"
],
"/api/reports/refresh": [
"src/routes/api/reports/refresh/+server.ts"
],
"/api/reports/[id]/download": [
"src/routes/api/reports/[id]/download/+server.ts"
],
"/api/reports/[id]/files/[fileId]": [
"src/routes/api/reports/[id]/files/[fileId]/+server.ts"
],
"/login": [
"src/routes/login/+page.server.ts",
"src/routes/+layout.server.ts"
],
"/logout": [
"src/routes/logout/+page.server.ts",
"src/routes/+layout.server.ts"
],
"/reports/[id]": [
"src/routes/reports/[id]/+page.server.ts",
"src/routes/+layout.server.ts",
"src/routes/reports/[id]/+server.ts"
],
"/users": [
"src/routes/users/+page.server.ts",
"src/routes/+layout.server.ts"
]
}

View File

@@ -0,0 +1,34 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { };
type RouteId = '/';
type MaybeWithVoid<T> = {} extends T ? T | void : T;
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
type EnsureDefined<T> = T extends null | undefined ? {} : T;
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageServerParentData = EnsureDefined<LayoutServerData>;
type PageParentData = EnsureDefined<LayoutData>;
type LayoutRouteId = RouteId | "/" | "/login" | "/logout" | "/reports/[id]" | "/users" | null
type LayoutParams = RouteParams & { id?: string }
type LayoutServerParentData = EnsureDefined<{}>;
type LayoutParentData = EnsureDefined<{}>;
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
export type ActionData = unknown;
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
export type LayoutServerLoad<OutputData extends Partial<App.PageData> & Record<string, any> | void = Partial<App.PageData> & Record<string, any> | void> = Kit.ServerLoad<LayoutParams, LayoutServerParentData, OutputData, LayoutRouteId>;
export type LayoutServerLoadEvent = Parameters<LayoutServerLoad>[0];
export type LayoutServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+layout.server.js').load>>>>>>;
export type LayoutData = Expand<Omit<LayoutParentData, keyof LayoutServerData> & EnsureDefined<LayoutServerData>>;
export type LayoutProps = { params: LayoutParams; data: LayoutData; children: import("svelte").Snippet }
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -0,0 +1,11 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { id: string };
type RouteId = '/api/reports/[id]/download';
export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;
export type RequestHandler = Kit.RequestHandler<RouteParams, RouteId>;
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -0,0 +1,11 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { id: string; fileId: string };
type RouteId = '/api/reports/[id]/files/[fileId]';
export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;
export type RequestHandler = Kit.RequestHandler<RouteParams, RouteId>;
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -0,0 +1,10 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { };
type RouteId = '/api/reports/refresh';
export type RequestHandler = Kit.RequestHandler<RouteParams, RouteId>;
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -0,0 +1,31 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { };
type RouteId = '/login';
type MaybeWithVoid<T> = {} extends T ? T | void : T;
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
type EnsureDefined<T> = T extends null | undefined ? {} : T;
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageServerParentData = EnsureDefined<import('../$types.js').LayoutServerData>;
type PageParentData = EnsureDefined<import('../$types.js').LayoutData>;
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;
type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];
type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;
type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];
type ActionsExport = typeof import('./proxy+page.server.js').actions
export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>
export type ActionData = Expand<Kit.AwaitedActions<ActionsExport>> | null;
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -0,0 +1,61 @@
// @ts-nocheck
import type { Actions, PageServerLoad } from './$types';
import { fail, redirect } from '@sveltejs/kit';
import { verify } from '@node-rs/argon2';
import { lucia } from '$lib/server/auth';
import { db } from '$lib/server/db';
import { userTable } from '$lib/schema';
import { eq } from 'drizzle-orm';
export const load = async ({ locals }: Parameters<PageServerLoad>[0]) => {
if (locals.user) {
redirect(302, '/');
}
};
export const actions = {
default: async ({ request, cookies }: import('./$types').RequestEvent) => {
const formData = await request.formData();
const username = formData.get('username');
const password = formData.get('password');
if (typeof username !== 'string' || typeof password !== 'string') {
return fail(400, { message: 'Invalid input' });
}
if (!username || !password) {
return fail(400, { message: 'Username and password are required' });
}
const [user] = await db
.select()
.from(userTable)
.where(eq(userTable.username, username))
.limit(1);
if (!user) {
return fail(400, { message: 'Invalid username or password' });
}
const validPassword = await verify(user.passwordHash, password, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1
});
if (!validPassword) {
return fail(400, { message: 'Invalid username or password' });
}
const session = await lucia.createSession(user.id, {});
const sessionCookie = lucia.createSessionCookie(session.id);
cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes
});
redirect(302, '/');
}
};
;null as any as Actions;

View File

@@ -0,0 +1,31 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { };
type RouteId = '/logout';
type MaybeWithVoid<T> = {} extends T ? T | void : T;
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
type EnsureDefined<T> = T extends null | undefined ? {} : T;
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageServerParentData = EnsureDefined<import('../$types.js').LayoutServerData>;
type PageParentData = EnsureDefined<import('../$types.js').LayoutData>;
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;
type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];
type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;
type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];
type ActionsExport = typeof import('./proxy+page.server.js').actions
export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>
export type ActionData = Expand<Kit.AwaitedActions<ActionsExport>> | null;
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import type { Actions, PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import { lucia } from '$lib/server/auth';
export const load = async () => {
redirect(302, '/');
};
export const actions = {
default: async ({ locals, cookies }: import('./$types').RequestEvent) => {
if (!locals.session) {
redirect(302, '/login');
}
await lucia.invalidateSession(locals.session.id);
const sessionCookie = lucia.createBlankSessionCookie();
cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes
});
redirect(302, '/login');
}
};
;null as any as PageServerLoad;;null as any as Actions;

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import type { LayoutServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { bugReports } from '$lib/schema';
import { eq, count } from 'drizzle-orm';
export const load = async ({ locals, url }: Parameters<LayoutServerLoad>[0]) => {
if (url.pathname === '/login') {
return { newCount: 0, user: null };
}
if (!locals.user) {
redirect(302, '/login');
}
const [result] = await db
.select({ count: count() })
.from(bugReports)
.where(eq(bugReports.status, 'new'));
return {
newCount: result.count,
user: locals.user
};
};

View File

@@ -0,0 +1,71 @@
// @ts-nocheck
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/db';
import { bugReports, bugReportFiles } from '$lib/schema';
import { eq, like, or, count, sql, desc, and } from 'drizzle-orm';
export const load = async ({ url }: Parameters<PageServerLoad>[0]) => {
const page = Math.max(1, Number(url.searchParams.get('page')) || 1);
const pageSize = Math.min(50, Math.max(10, Number(url.searchParams.get('pageSize')) || 20));
const status = url.searchParams.get('status') || '';
const search = url.searchParams.get('search') || '';
const conditions = [];
if (status && ['new', 'in_review', 'resolved', 'closed'].includes(status)) {
conditions.push(eq(bugReports.status, status as 'new' | 'in_review' | 'resolved' | 'closed'));
}
if (search) {
conditions.push(
or(
like(bugReports.hostname, `%${search}%`),
like(bugReports.os_user, `%${search}%`),
like(bugReports.name, `%${search}%`),
like(bugReports.email, `%${search}%`)
)
);
}
const where = conditions.length > 0 ? and(...conditions) : undefined;
// Get total count
const [{ total }] = await db
.select({ total: count() })
.from(bugReports)
.where(where);
// Get paginated reports with file count
const reports = await db
.select({
id: bugReports.id,
name: bugReports.name,
email: bugReports.email,
hostname: bugReports.hostname,
os_user: bugReports.os_user,
status: bugReports.status,
created_at: bugReports.created_at,
file_count: count(bugReportFiles.id)
})
.from(bugReports)
.leftJoin(bugReportFiles, eq(bugReports.id, bugReportFiles.report_id))
.where(where)
.groupBy(bugReports.id)
.orderBy(desc(bugReports.created_at))
.limit(pageSize)
.offset((page - 1) * pageSize);
return {
reports,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize)
},
filters: {
status,
search
}
};
};

View File

@@ -0,0 +1,27 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { id: string };
type RouteId = '/reports/[id]';
type MaybeWithVoid<T> = {} extends T ? T | void : T;
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
type EnsureDefined<T> = T extends null | undefined ? {} : T;
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageServerParentData = EnsureDefined<import('../../$types.js').LayoutServerData>;
type PageParentData = EnsureDefined<import('../../$types.js').LayoutData>;
export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
export type ActionData = unknown;
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
export type RequestHandler = Kit.RequestHandler<RouteParams, RouteId>;
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -0,0 +1,45 @@
// @ts-nocheck
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/db';
import { bugReports, bugReportFiles } from '$lib/schema';
import { eq } from 'drizzle-orm';
import { error } from '@sveltejs/kit';
export const load = async ({ params }: Parameters<PageServerLoad>[0]) => {
const id = Number(params.id);
if (isNaN(id)) throw error(400, 'Invalid report ID');
const [report] = await db
.select()
.from(bugReports)
.where(eq(bugReports.id, id))
.limit(1);
if (!report) throw error(404, 'Report not found');
const files = await db
.select({
id: bugReportFiles.id,
report_id: bugReportFiles.report_id,
file_role: bugReportFiles.file_role,
filename: bugReportFiles.filename,
mime_type: bugReportFiles.mime_type,
file_size: bugReportFiles.file_size,
created_at: bugReportFiles.created_at
})
.from(bugReportFiles)
.where(eq(bugReportFiles.report_id, id));
return {
report: {
...report,
system_info: report.system_info ? JSON.stringify(report.system_info, null, 2) : null,
created_at: report.created_at.toISOString(),
updated_at: report.updated_at.toISOString()
},
files: files.map((f) => ({
...f,
created_at: f.created_at.toISOString()
}))
};
};

View File

@@ -0,0 +1,31 @@
import type * as Kit from '@sveltejs/kit';
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { };
type RouteId = '/users';
type MaybeWithVoid<T> = {} extends T ? T | void : T;
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
type EnsureDefined<T> = T extends null | undefined ? {} : T;
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageServerParentData = EnsureDefined<import('../$types.js').LayoutServerData>;
type PageParentData = EnsureDefined<import('../$types.js').LayoutData>;
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;
type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];
type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;
type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];
type ActionsExport = typeof import('./proxy+page.server.js').actions
export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>
export type ActionData = Expand<Kit.AwaitedActions<ActionsExport>> | null;
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;

View File

@@ -0,0 +1,217 @@
// @ts-nocheck
import type { Actions, PageServerLoad } from './$types';
import { fail, redirect } from '@sveltejs/kit';
import { hash } from '@node-rs/argon2';
import { generateIdFromEntropySize } from 'lucia';
import { db } from '$lib/server/db';
import { userTable } from '$lib/schema';
import { eq } from 'drizzle-orm';
const PASSWORD_MIN_LENGTH = 8;
const PASSWORD_MAX_LENGTH = 255;
function validatePassword(password: string): string | null {
if (password.length < PASSWORD_MIN_LENGTH || password.length > PASSWORD_MAX_LENGTH) {
return `Password must be ${PASSWORD_MIN_LENGTH}-${PASSWORD_MAX_LENGTH} characters`;
}
if (!/[A-Z]/.test(password)) {
return 'Password must contain at least one uppercase letter';
}
if (!/[a-z]/.test(password)) {
return 'Password must contain at least one lowercase letter';
}
if (!/[0-9]/.test(password)) {
return 'Password must contain at least one number';
}
if (!/[^A-Za-z0-9]/.test(password)) {
return 'Password must contain at least one special character';
}
return null;
}
async function hashPassword(password: string): Promise<string> {
return hash(password, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1
});
}
export const load = async ({ locals }: Parameters<PageServerLoad>[0]) => {
if (!locals.user || locals.user.role !== 'admin') {
redirect(302, '/');
}
const users = await db
.select({
id: userTable.id,
username: userTable.username,
displayname: userTable.displayname,
role: userTable.role,
createdAt: userTable.createdAt
})
.from(userTable)
.orderBy(userTable.createdAt);
return { users };
};
export const actions = {
create: async ({ request, locals }: import('./$types').RequestEvent) => {
if (!locals.user || locals.user.role !== 'admin') {
return fail(403, { message: 'Unauthorized' });
}
const formData = await request.formData();
const username = formData.get('username');
const displayname = formData.get('displayname') || '';
const password = formData.get('password');
const confirmPassword = formData.get('confirmPassword');
const role = formData.get('role');
if (
typeof username !== 'string' ||
typeof displayname !== 'string' ||
typeof password !== 'string' ||
typeof confirmPassword !== 'string' ||
typeof role !== 'string'
) {
return fail(400, { message: 'Invalid input' });
}
if (!username || !password) {
return fail(400, { message: 'Username and password are required' });
}
if (username.length < 3 || username.length > 255) {
return fail(400, { message: 'Username must be 3-255 characters' });
}
if (password !== confirmPassword) {
return fail(400, { message: 'Passwords do not match' });
}
const passwordError = validatePassword(password);
if (passwordError) {
return fail(400, { message: passwordError });
}
if (role !== 'admin' && role !== 'user') {
return fail(400, { message: 'Invalid role' });
}
// Check if username already exists
const [existing] = await db
.select({ id: userTable.id })
.from(userTable)
.where(eq(userTable.username, username))
.limit(1);
if (existing) {
return fail(400, { message: 'Username already exists' });
}
const passwordHash = await hashPassword(password);
const userId = generateIdFromEntropySize(10);
await db.insert(userTable).values({
id: userId,
username,
displayname,
passwordHash,
role: role as 'admin' | 'user'
});
return { success: true };
},
updateDisplayname: async ({ request, locals }: import('./$types').RequestEvent) => {
if (!locals.user || locals.user.role !== 'admin') {
return fail(403, { message: 'Unauthorized' });
}
const formData = await request.formData();
const userId = formData.get('userId');
const displayname = formData.get('displayname');
if (typeof userId !== 'string' || typeof displayname !== 'string') {
return fail(400, { message: 'Invalid input' });
}
await db.update(userTable).set({ displayname }).where(eq(userTable.id, userId));
return { success: true };
},
resetPassword: async ({ request, locals }: import('./$types').RequestEvent) => {
if (!locals.user || locals.user.role !== 'admin') {
return fail(403, { message: 'Unauthorized' });
}
const formData = await request.formData();
const userId = formData.get('userId');
if (typeof userId === 'string' && userId === locals.user.id) {
return fail(400, { message: 'Cannot reset your own password from here' });
}
const newPassword = formData.get('newPassword');
const confirmPassword = formData.get('confirmPassword');
if (
typeof userId !== 'string' ||
typeof newPassword !== 'string' ||
typeof confirmPassword !== 'string'
) {
return fail(400, { message: 'Invalid input' });
}
if (newPassword !== confirmPassword) {
return fail(400, { message: 'Passwords do not match' });
}
const passwordError = validatePassword(newPassword);
if (passwordError) {
return fail(400, { message: passwordError });
}
const passwordHash = await hashPassword(newPassword);
await db.update(userTable).set({ passwordHash }).where(eq(userTable.id, userId));
return { success: true };
},
delete: async ({ request, locals }: import('./$types').RequestEvent) => {
if (!locals.user || locals.user.role !== 'admin') {
return fail(403, { message: 'Unauthorized' });
}
const formData = await request.formData();
const userId = formData.get('userId');
if (typeof userId !== 'string') {
return fail(400, { message: 'Invalid input' });
}
if (userId === locals.user.id) {
return fail(400, { message: 'Cannot delete your own account' });
}
// Prevent deleting admin users
const [targetUser] = await db
.select({ role: userTable.role })
.from(userTable)
.where(eq(userTable.id, userId))
.limit(1);
if (targetUser?.role === 'admin') {
return fail(400, { message: 'Cannot delete an admin user' });
}
await db.delete(userTable).where(eq(userTable.id, userId));
return { success: true };
}
};
;null as any as Actions;