@axium/server 0.3.12 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.3.12",
3
+ "version": "0.4.0",
4
4
  "author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -46,7 +46,8 @@
46
46
  "kysely": "^0.27.5",
47
47
  "logzen": "^0.6.2",
48
48
  "pg": "^8.14.1",
49
- "utilium": "^2.2.3"
49
+ "utilium": "^2.2.3",
50
+ "zod-validation-error": "^3.4.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@auth/sveltekit": "^1.8.0",
package/web/actions.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { Registration, User } from '@axium/core/schemas';
2
+ import type { RequestEvent } from '@sveltejs/kit';
3
+ import { fail, redirect } from '@sveltejs/kit';
4
+ import { adapter, register } from '../dist/auth.js';
5
+ import { web } from '../dist/config.js';
6
+ import { parseForm } from './utils.js';
7
+
8
+ export async function editEmail(event: RequestEvent) {
9
+ const session = await event.locals.auth();
10
+
11
+ const [{ email }, error] = await parseForm(event, User.pick({ email: true }));
12
+ if (error) return error;
13
+
14
+ const user = await adapter.getUserByEmail(session.user.email);
15
+ if (!user) return fail(401, { email, error: 'You are not signed in' });
16
+
17
+ try {
18
+ await adapter.updateUser({ id: user.id, email, image: user.image });
19
+ } catch (error: any) {
20
+ return fail(400, { email, error: typeof error === 'string' ? error : error.message });
21
+ }
22
+ redirect(303, web.prefix);
23
+ }
24
+
25
+ export async function editName(event: RequestEvent) {
26
+ const session = await event.locals.auth();
27
+
28
+ const [{ name }, error] = await parseForm(event, User.pick({ name: true }));
29
+ if (error) return error;
30
+
31
+ const user = await adapter.getUserByEmail(session.user.email);
32
+ if (!user) return fail(401, { name, error: 'You are not signed in' });
33
+
34
+ try {
35
+ await adapter.updateUser({ id: user.id, name, image: user.image });
36
+ } catch (error: any) {
37
+ return fail(400, { name, error: typeof error === 'string' ? error : error.message });
38
+ }
39
+ redirect(303, web.prefix);
40
+ }
41
+
42
+ export async function signup(event: RequestEvent) {
43
+ const [data, error] = await parseForm(event, Registration);
44
+ if (error) return error;
45
+
46
+ try {
47
+ const { session } = await register(data);
48
+ event.cookies.set('session', session.sessionToken, {
49
+ path: '/',
50
+ expires: session.expires,
51
+ httpOnly: true,
52
+ });
53
+ return { ...data, success: true, data: session.sessionToken };
54
+ } catch (error: any) {
55
+ return fail(400, { ...data, error: typeof error === 'string' ? error : error.message });
56
+ }
57
+ }
@@ -1,4 +1,2 @@
1
- export * as account from './routes/+page.server.js';
2
- export * as email from './routes/email/+page.server.js';
3
- export * as name from './routes/name/+page.server.js';
4
- export * as signup from './routes/signup/+page.server.js';
1
+ export * from './actions.js';
2
+ export * from './utils.js';
@@ -1,15 +1,10 @@
1
- import { redirect } from '@sveltejs/kit';
2
1
  import { adapter } from '../../dist/auth.js';
3
- import type { PageServerLoadEvent } from './$types.js';
4
2
  import { web } from '../../dist/config.js';
3
+ import { loadSession } from '../utils.js';
4
+ import type { PageServerLoadEvent } from './$types.js';
5
5
 
6
6
  export async function load(event: PageServerLoadEvent) {
7
- const session = await event.locals.auth();
8
-
9
- if (!session) redirect(307, '/auth/signin');
10
- if (!session.user.name) redirect(307, web.prefix + '/name');
11
-
7
+ const { session } = await loadSession(event);
12
8
  const user = await adapter.getUserByEmail(session.user.email);
13
-
14
9
  return { session, user, prefix: web.prefix };
15
10
  }
@@ -18,7 +18,7 @@
18
18
  <title>Account</title>
19
19
  </svelte:head>
20
20
 
21
- <div class="flex-content">
21
+ <div class="Account flex-content">
22
22
  <img class="pfp" src={image} alt="User profile" />
23
23
  <p class="greeting">Welcome, {user.name}</p>
24
24
  <div class="account-section main">
@@ -1,32 +1,5 @@
1
- import { fail, redirect, type Actions } from '@sveltejs/kit';
2
- import * as z from 'zod';
3
- import { adapter } from '../../../dist/auth.js';
4
- import { web } from '../../../dist/config.js';
5
- import { tryZod } from '../../utils.js';
6
- import type { PageServerLoadEvent } from './$types.js';
1
+ import type { Actions } from '@sveltejs/kit';
2
+ import { editEmail } from '../../actions.js';
7
3
 
8
- export async function load(event: PageServerLoadEvent) {
9
- const session = await event.locals.auth();
10
- if (!session) redirect(307, '/auth/signin');
11
- return { session };
12
- }
13
-
14
- export const actions = {
15
- async default(event) {
16
- const session = await event.locals.auth();
17
-
18
- const rawEmail = (await event.request.formData()).get('email');
19
- const [email, error] = tryZod(z.string().email().safeParse(rawEmail));
20
- if (error) return error;
21
-
22
- const user = await adapter.getUserByEmail(session.user.email);
23
- if (!user) return fail(500, { email, error: 'User does not exist' });
24
-
25
- try {
26
- await adapter.updateUser({ id: user.id, email, image: user.image });
27
- } catch (error: any) {
28
- return fail(400, { email, error: typeof error === 'string' ? error : error.message });
29
- }
30
- redirect(303, web.prefix);
31
- },
32
- } satisfies Actions;
4
+ export { loadSession as load } from '../../utils.js';
5
+ export const actions = { default: editEmail } satisfies Actions;
@@ -2,14 +2,13 @@
2
2
  import { enhance } from '$app/forms';
3
3
  import '../../lib/styles.css';
4
4
  let { form, data } = $props();
5
- const { user } = data.session;
6
5
  </script>
7
6
 
8
7
  <svelte:head>
9
8
  <title>Edit Email</title>
10
9
  </svelte:head>
11
10
 
12
- <div>
11
+ <div class="EditEmail">
13
12
  <form method="POST" class="main" use:enhance>
14
13
  {#if form?.error}
15
14
  <div class="error">
@@ -18,7 +17,7 @@
18
17
  {/if}
19
18
  <div>
20
19
  <label for="email">Email Address</label>
21
- <input name="email" type="email" value={form?.email || user.email || ''} required />
20
+ <input name="email" type="email" value={form?.email || data.session.user.email || ''} required />
22
21
  </div>
23
22
  <button type="submit">Continue</button>
24
23
  </form>
@@ -1,32 +1,5 @@
1
- import { Name } from '@axium/core/schemas';
2
- import { fail, redirect, type Actions } from '@sveltejs/kit';
3
- import { adapter } from '../../../dist/auth.js';
4
- import { web } from '../../../dist/config.js';
5
- import { tryZod } from '../../utils.js';
6
- import type { PageServerLoadEvent } from './$types.js';
1
+ import type { Actions } from '@sveltejs/kit';
2
+ import { editName } from '../../actions.js';
7
3
 
8
- export async function load(event: PageServerLoadEvent) {
9
- const session = await event.locals.auth();
10
- if (!session) redirect(307, '/auth/signin');
11
- return { session };
12
- }
13
-
14
- export const actions = {
15
- async default(event) {
16
- const session = await event.locals.auth();
17
-
18
- const rawName = (await event.request.formData()).get('name');
19
- const [name, error] = tryZod(Name.safeParse(rawName));
20
- if (error) error;
21
-
22
- const user = await adapter.getUserByEmail(session.user.email);
23
- if (!user) return fail(500, { name, error: 'User does not exist' });
24
-
25
- try {
26
- await adapter.updateUser({ id: user.id, name, image: user.image });
27
- } catch (error: any) {
28
- return fail(400, { name, error: typeof error === 'string' ? error : error.message });
29
- }
30
- redirect(303, web.prefix);
31
- },
32
- } satisfies Actions;
4
+ export { loadSession as load } from '../../utils.js';
5
+ export const actions = { default: editName } satisfies Actions;
@@ -9,7 +9,7 @@
9
9
  <title>Edit Name</title>
10
10
  </svelte:head>
11
11
 
12
- <div>
12
+ <div class="EditName">
13
13
  <form method="POST" class="main" use:enhance>
14
14
  {#if form?.error}
15
15
  <div class="error">
@@ -1,33 +1,10 @@
1
- import { Registration } from '@axium/core/schemas';
2
- import { fail, redirect } from '@sveltejs/kit';
3
- import * as auth from '../../../dist/auth.js';
1
+ import { redirect } from '@sveltejs/kit';
4
2
  import * as config from '../../../dist/config.js';
3
+ import { signup } from '../../actions.js';
5
4
  import type { Actions } from './$types.js';
6
5
 
7
6
  export function load() {
8
7
  if (!config.auth.credentials) return redirect(307, '/auth/signin');
9
8
  }
10
9
 
11
- export const actions = {
12
- async default(event) {
13
- const { data, success, error } = Registration.safeParse(Object.fromEntries(await event.request.formData()));
14
-
15
- if (!success)
16
- return fail(400, {
17
- ...data,
18
- error: error.flatten().formErrors[0] || Object.values(error.flatten().fieldErrors).flat()[0],
19
- });
20
-
21
- try {
22
- const { session } = await auth.register(data);
23
- event.cookies.set('session', session.sessionToken, {
24
- path: '/',
25
- expires: session.expires,
26
- httpOnly: true,
27
- });
28
- return { ...data, success: true, data: session.sessionToken };
29
- } catch (error: any) {
30
- return fail(400, { ...data, error: typeof error === 'string' ? error : error.message });
31
- }
32
- },
33
- } satisfies Actions;
10
+ export const actions = { default: signup } satisfies Actions;
@@ -4,7 +4,7 @@
4
4
  let { form } = $props();
5
5
  </script>
6
6
 
7
- <div>
7
+ <div class="Signup">
8
8
  <form method="POST" class="main" use:enhance>
9
9
  {#if form?.error}
10
10
  <div class="error">
package/web/utils.ts CHANGED
@@ -1,14 +1,31 @@
1
- import { fail, type ActionFailure } from '@sveltejs/kit';
1
+ import type { Session } from '@auth/sveltekit';
2
+ import type { ActionFailure, RequestEvent } from '@sveltejs/kit';
3
+ import { fail, redirect } from '@sveltejs/kit';
2
4
  import type * as z from 'zod';
5
+ import { fromError } from 'zod-validation-error';
6
+ import { web } from '../dist/config.js';
3
7
 
4
- export function failZod<T extends Record<string, unknown>>(error: z.ZodError, data: T = {} as T): ActionFailure<T & { error: string }> {
5
- return fail(400, {
6
- ...data,
7
- error: error.flatten().formErrors[0] || Object.values(error.flatten().fieldErrors).flat()[0],
8
- });
8
+ export async function loadSession(event: RequestEvent): Promise<{ session: Session }> {
9
+ const session = await event.locals.auth();
10
+ if (!session) redirect(307, '/auth/signin');
11
+ if (!session.user.name) redirect(307, web.prefix + '/name');
12
+ return { session };
9
13
  }
10
14
 
11
- export function tryZod<Input, Output>(result: z.SafeParseReturnType<Input, Output>): [Output, null] | [null, ActionFailure<Partial<Output> & { error: string }>] {
12
- if (result.success) return [result.data, null];
13
- return [null, failZod(result.error)];
15
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
16
+ export interface FormFail<S extends z.AnyZodObject> extends ActionFailure<z.infer<S> & { error: string }> {}
17
+
18
+ export async function parseForm<S extends z.AnyZodObject>(event: RequestEvent, schema: S): Promise<[z.infer<S>, FormFail<S> | null]> {
19
+ const formData = Object.fromEntries(await event.request.formData());
20
+ const { data, error, success } = schema.safeParse(formData);
21
+
22
+ if (success) return [data, null];
23
+
24
+ return [
25
+ data,
26
+ fail(400, {
27
+ ...data,
28
+ error: fromError(error, { prefix: null }).toString(),
29
+ }),
30
+ ];
14
31
  }