Some checks failed
CI / Scraper / Lint (push) Successful in 15s
CI / Scraper / Test (push) Successful in 19s
CI / UI / Build (push) Successful in 21s
CI / Scraper / Lint (pull_request) Successful in 14s
iOS CI / Test (push) Has been cancelled
iOS CI / Build (push) Has been cancelled
CI / Scraper / Test (pull_request) Successful in 15s
CI / UI / Build (pull_request) Successful in 16s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
iOS CI / Build (pull_request) Failing after 1m32s
iOS CI / Test (pull_request) Has been skipped
CI / UI / Docker Push (push) Successful in 6m42s
CI / Scraper / Docker Push (push) Successful in 7m12s
80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import { fail, redirect } from '@sveltejs/kit';
|
|
import type { Actions, PageServerLoad } from './$types';
|
|
import { changePassword, listUserSessions, getUserByUsername } from '$lib/server/pocketbase';
|
|
import { presignAvatarUrl } from '$lib/server/minio';
|
|
import { log } from '$lib/server/logger';
|
|
|
|
export const load: PageServerLoad = async ({ locals }) => {
|
|
if (!locals.user) {
|
|
redirect(302, '/login');
|
|
}
|
|
|
|
let sessions: Awaited<ReturnType<typeof listUserSessions>> = [];
|
|
try {
|
|
sessions = await listUserSessions(locals.user.id);
|
|
} catch (e) {
|
|
log.warn('profile', 'listUserSessions failed (non-fatal)', { err: String(e) });
|
|
}
|
|
|
|
// Fetch avatar presigned URL if user has one
|
|
let avatarUrl: string | null = null;
|
|
try {
|
|
const record = await getUserByUsername(locals.user.username);
|
|
if (record?.avatar_url) {
|
|
avatarUrl = await presignAvatarUrl(locals.user.id);
|
|
}
|
|
} catch (e) {
|
|
log.warn('profile', 'avatar fetch failed (non-fatal)', { err: String(e) });
|
|
}
|
|
|
|
return {
|
|
user: locals.user,
|
|
avatarUrl,
|
|
sessions: sessions.map((s) => ({
|
|
id: s.id,
|
|
user_agent: s.user_agent,
|
|
ip: s.ip,
|
|
created_at: s.created_at,
|
|
last_seen: s.last_seen,
|
|
is_current: s.session_id === locals.user!.authSessionId
|
|
}))
|
|
};
|
|
};
|
|
|
|
export const actions: Actions = {
|
|
changePassword: async ({ request, locals }) => {
|
|
if (!locals.user) {
|
|
return fail(401, { error: 'Not logged in.' });
|
|
}
|
|
|
|
const data = await request.formData();
|
|
const current = (data.get('current') as string | null) ?? '';
|
|
const next = (data.get('next') as string | null) ?? '';
|
|
const confirm = (data.get('confirm') as string | null) ?? '';
|
|
|
|
if (!current || !next || !confirm) {
|
|
return fail(400, { error: 'All fields are required.' });
|
|
}
|
|
if (next.length < 8) {
|
|
return fail(400, { error: 'New password must be at least 8 characters.' });
|
|
}
|
|
if (next !== confirm) {
|
|
return fail(400, { error: 'New passwords do not match.' });
|
|
}
|
|
|
|
let ok: boolean;
|
|
try {
|
|
ok = await changePassword(locals.user.id, current, next);
|
|
} catch (e) {
|
|
log.error('profile', 'changePassword failed', { err: String(e) });
|
|
return fail(500, { error: 'An error occurred. Please try again.' });
|
|
}
|
|
|
|
if (!ok) {
|
|
return fail(401, { error: 'Current password is incorrect.' });
|
|
}
|
|
|
|
return { success: true };
|
|
}
|
|
};
|