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:
33
.svelte-kit/types/route_meta_data.json
Normal file
33
.svelte-kit/types/route_meta_data.json
Normal 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"
|
||||
]
|
||||
}
|
||||
34
.svelte-kit/types/src/routes/$types.d.ts
vendored
Normal file
34
.svelte-kit/types/src/routes/$types.d.ts
vendored
Normal 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>;
|
||||
11
.svelte-kit/types/src/routes/api/reports/[id]/download/$types.d.ts
vendored
Normal file
11
.svelte-kit/types/src/routes/api/reports/[id]/download/$types.d.ts
vendored
Normal 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>;
|
||||
11
.svelte-kit/types/src/routes/api/reports/[id]/files/[fileId]/$types.d.ts
vendored
Normal file
11
.svelte-kit/types/src/routes/api/reports/[id]/files/[fileId]/$types.d.ts
vendored
Normal 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>;
|
||||
10
.svelte-kit/types/src/routes/api/reports/refresh/$types.d.ts
vendored
Normal file
10
.svelte-kit/types/src/routes/api/reports/refresh/$types.d.ts
vendored
Normal 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>;
|
||||
31
.svelte-kit/types/src/routes/login/$types.d.ts
vendored
Normal file
31
.svelte-kit/types/src/routes/login/$types.d.ts
vendored
Normal 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>;
|
||||
61
.svelte-kit/types/src/routes/login/proxy+page.server.ts
Normal file
61
.svelte-kit/types/src/routes/login/proxy+page.server.ts
Normal 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;
|
||||
31
.svelte-kit/types/src/routes/logout/$types.d.ts
vendored
Normal file
31
.svelte-kit/types/src/routes/logout/$types.d.ts
vendored
Normal 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>;
|
||||
26
.svelte-kit/types/src/routes/logout/proxy+page.server.ts
Normal file
26
.svelte-kit/types/src/routes/logout/proxy+page.server.ts
Normal 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;
|
||||
26
.svelte-kit/types/src/routes/proxy+layout.server.ts
Normal file
26
.svelte-kit/types/src/routes/proxy+layout.server.ts
Normal 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
|
||||
};
|
||||
};
|
||||
71
.svelte-kit/types/src/routes/proxy+page.server.ts
Normal file
71
.svelte-kit/types/src/routes/proxy+page.server.ts
Normal 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
|
||||
}
|
||||
};
|
||||
};
|
||||
27
.svelte-kit/types/src/routes/reports/[id]/$types.d.ts
vendored
Normal file
27
.svelte-kit/types/src/routes/reports/[id]/$types.d.ts
vendored
Normal 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>;
|
||||
@@ -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()
|
||||
}))
|
||||
};
|
||||
};
|
||||
31
.svelte-kit/types/src/routes/users/$types.d.ts
vendored
Normal file
31
.svelte-kit/types/src/routes/users/$types.d.ts
vendored
Normal 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>;
|
||||
217
.svelte-kit/types/src/routes/users/proxy+page.server.ts
Normal file
217
.svelte-kit/types/src/routes/users/proxy+page.server.ts
Normal 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;
|
||||
Reference in New Issue
Block a user