@axium/server 0.4.7 → 0.5.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/dist/cli.js CHANGED
@@ -45,12 +45,7 @@ axiumDB
45
45
  .addOption(opts.force)
46
46
  .option('-s, --skip', 'Skip existing database and/or user')
47
47
  .action(async (opt) => {
48
- await db.init(opt).catch((e) => {
49
- if (typeof e == 'number')
50
- process.exit(e);
51
- else
52
- exit(e);
53
- });
48
+ await db.init(opt).catch(handleError);
54
49
  });
55
50
  axiumDB
56
51
  .command('status')
package/dist/database.js CHANGED
@@ -108,6 +108,7 @@ export async function init(opt) {
108
108
  }
109
109
  const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
110
110
  const relationExists = someWarnings(opt.output, [/relation "\w+" already exists/, 'already exists.']);
111
+ const done = () => opt.output('done');
111
112
  await _sql('CREATE DATABASE axium', 'Creating database').catch(async (error) => {
112
113
  if (error != 'database "axium" already exists')
113
114
  throw error;
@@ -143,8 +144,8 @@ export async function init(opt) {
143
144
  .addColumn('salt', 'text')
144
145
  .addColumn('preferences', 'jsonb', col => col.notNull().defaultTo(sql `'{}'::jsonb`))
145
146
  .execute()
147
+ .then(done)
146
148
  .catch(relationExists);
147
- opt.output('done');
148
149
  opt.output('start', 'Creating table Account');
149
150
  await db.schema
150
151
  .createTable('Account')
@@ -161,11 +162,10 @@ export async function init(opt) {
161
162
  .addColumn('id_token', 'text')
162
163
  .addColumn('session_state', 'text')
163
164
  .execute()
165
+ .then(done)
164
166
  .catch(relationExists);
165
- opt.output('done');
166
167
  opt.output('start', 'Creating index for Account.userId');
167
- await db.schema.createIndex('Account_userId_index').on('Account').column('userId').execute().catch(relationExists);
168
- opt.output('done');
168
+ await db.schema.createIndex('Account_userId_index').on('Account').column('userId').execute().then(done).catch(relationExists);
169
169
  opt.output('start', 'Creating table Session');
170
170
  await db.schema
171
171
  .createTable('Session')
@@ -174,11 +174,10 @@ export async function init(opt) {
174
174
  .addColumn('sessionToken', 'text', col => col.notNull().unique())
175
175
  .addColumn('expires', 'timestamptz', col => col.notNull())
176
176
  .execute()
177
+ .then(done)
177
178
  .catch(relationExists);
178
- opt.output('done');
179
179
  opt.output('start', 'Creating index for Session.userId');
180
- await db.schema.createIndex('Session_userId_index').on('Session').column('userId').execute().catch(relationExists);
181
- opt.output('done');
180
+ await db.schema.createIndex('Session_userId_index').on('Session').column('userId').execute().then(done).catch(relationExists);
182
181
  opt.output('start', 'Creating table VerificationToken');
183
182
  await db.schema
184
183
  .createTable('VerificationToken')
@@ -186,8 +185,8 @@ export async function init(opt) {
186
185
  .addColumn('token', 'text', col => col.notNull().unique())
187
186
  .addColumn('expires', 'timestamptz', col => col.notNull())
188
187
  .execute()
188
+ .then(done)
189
189
  .catch(relationExists);
190
- opt.output('done');
191
190
  opt.output('start', 'Creating table Authenticator');
192
191
  await db.schema
193
192
  .createTable('Authenticator')
@@ -200,11 +199,10 @@ export async function init(opt) {
200
199
  .addColumn('credentialBackedUp', 'boolean', col => col.notNull())
201
200
  .addColumn('transports', 'text')
202
201
  .execute()
202
+ .then(done)
203
203
  .catch(relationExists);
204
- opt.output('done');
205
204
  opt.output('start', 'Creating index for Authenticator.credentialID');
206
- await db.schema.createIndex('Authenticator_credentialID_key').on('Authenticator').column('credentialID').execute().catch(relationExists);
207
- opt.output('done');
205
+ await db.schema.createIndex('Authenticator_credentialID_key').on('Authenticator').column('credentialID').execute().then(done).catch(relationExists);
208
206
  return config.db;
209
207
  }
210
208
  catch (e_1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.4.7",
3
+ "version": "0.5.0",
4
4
  "author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
5
5
  "funding": {
6
6
  "type": "individual",
package/web/actions.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import { Registration, User } from '@axium/core/schemas';
2
2
  import type { RequestEvent } from '@sveltejs/kit';
3
- import { fail, redirect } from '@sveltejs/kit';
3
+ import { fail } from '@sveltejs/kit';
4
4
  import { adapter, register } from '../dist/auth.js';
5
- import { web } from '../dist/config.js';
6
5
  import { parseForm } from './utils.js';
7
6
 
8
7
  export async function editEmail(event: RequestEvent) {
9
8
  const session = await event.locals.auth();
9
+ if (!session) return fail(401, { error: 'You are not signed in' });
10
10
 
11
- const [{ email }, error] = await parseForm(event, User.pick({ email: true }));
11
+ const [{ email } = {}, error] = await parseForm(event, User.pick({ email: true }));
12
12
  if (error) return error;
13
13
 
14
14
  const user = await adapter.getUserByEmail(session.user.email);
@@ -19,13 +19,14 @@ export async function editEmail(event: RequestEvent) {
19
19
  } catch (error: any) {
20
20
  return fail(400, { email, error: typeof error === 'string' ? error : error.message });
21
21
  }
22
- redirect(303, web.prefix);
22
+ return { success: true };
23
23
  }
24
24
 
25
25
  export async function editName(event: RequestEvent) {
26
26
  const session = await event.locals.auth();
27
+ if (!session) return fail(401, { error: 'You are not signed in' });
27
28
 
28
- const [{ name }, error] = await parseForm(event, User.pick({ name: true }));
29
+ const [{ name } = {}, error] = await parseForm(event, User.pick({ name: true }));
29
30
  if (error) return error;
30
31
 
31
32
  const user = await adapter.getUserByEmail(session.user.email);
@@ -36,7 +37,7 @@ export async function editName(event: RequestEvent) {
36
37
  } catch (error: any) {
37
38
  return fail(400, { name, error: typeof error === 'string' ? error : error.message });
38
39
  }
39
- redirect(303, web.prefix);
40
+ return { success: true };
40
41
  }
41
42
 
42
43
  export async function signup(event: RequestEvent) {
package/web/index.ts CHANGED
@@ -1,4 +1 @@
1
- export { default as Icon } from './lib/Icon.svelte';
2
- export { default as Account } from './routes/+page.svelte';
3
- export { default as EditName } from './routes/name/+page.svelte';
4
- export { default as EditEmail } from './routes/email/+page.svelte';
1
+ export * from './lib/index.js';
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import { goto } from '$app/navigation';
3
+ import { getUserImage } from '@axium/core';
4
+ import './account.css';
5
+ import FormDialog from './FormDialog.svelte';
6
+ import Icon from './Icon.svelte';
7
+ import './styles.css';
8
+
9
+ const { data, children = () => {}, form } = $props();
10
+
11
+ const user = $derived(data.user);
12
+
13
+ $effect(() => {
14
+ if (!user) goto('/auth/signin');
15
+ });
16
+
17
+ const image = $derived(getUserImage(user));
18
+
19
+ let changeEmail = $state(false);
20
+ let changeName = $state(false);
21
+ </script>
22
+
23
+ <div class="Account flex-content">
24
+ <img class="pfp" src={image} alt="User profile" />
25
+ <p class="greeting">Welcome, {user.name}</p>
26
+ <div class="account-section main">
27
+ <div class="account-item">
28
+ <p class="subtle">Name</p>
29
+ <p>{user.name}</p>
30
+ <button style:display="contents" class="change" onclick={() => (changeName = true)}><Icon i="chevron-right" /></button>
31
+ </div>
32
+ <div class="account-item">
33
+ <p class="subtle">Email</p>
34
+ <p>{user.email}</p>
35
+ <button style:display="contents" class="change" onclick={() => (changeEmail = true)}><Icon i="chevron-right" /></button>
36
+ </div>
37
+ <div class="account-item">
38
+ <p class="subtle">User ID <dfn title="This is your UUID."><Icon i="regular/circle-info" /></dfn></p>
39
+ <p>{user.id}</p>
40
+ </div>
41
+ <a class="signout" href="/auth/signout"><button>Sign out</button></a>
42
+ </div>
43
+ {@render children()}
44
+ </div>
45
+
46
+ <FormDialog bind:active={changeEmail} {form} submitText="Change" action="?/email">
47
+ <div>
48
+ <label for="email">Email Address</label>
49
+ <input name="email" type="email" value={form?.email || user.email || ''} required />
50
+ </div>
51
+ </FormDialog>
52
+
53
+ <FormDialog bind:active={changeName} {form} submitText="Change" action="?/name">
54
+ <div>
55
+ <label for="name">What do you want to be called?</label>
56
+ <input name="name" type="text" value={form?.name || user.name || ''} required />
57
+ </div>
58
+ </FormDialog>
59
+
60
+ <style>
61
+ .pfp {
62
+ width: 100px;
63
+ height: 100px;
64
+ border-radius: 50%;
65
+ border: 1px solid #8888;
66
+ margin-top: 3em;
67
+ }
68
+
69
+ .greeting {
70
+ font-size: 2em;
71
+ }
72
+
73
+ .signout {
74
+ margin-top: 2em;
75
+ }
76
+ </style>
@@ -0,0 +1,55 @@
1
+ <script>
2
+ let { show, children, onclose = () => (show = false), ...rest } = $props();
3
+
4
+ let dialog = $state();
5
+ $effect(() => show && dialog.showModal());
6
+ $effect(() => !show && dialog.close());
7
+ </script>
8
+
9
+ <dialog bind:this={dialog} {onclose} {...rest}>
10
+ {@render children()}
11
+ </dialog>
12
+
13
+ <!-- svelte-ignore css_unused_selector -->
14
+ <style>
15
+ dialog {
16
+ border-radius: 1em;
17
+ background: #111;
18
+ border: 1px solid #8888;
19
+ padding: 1em;
20
+
21
+ form {
22
+ display: contents;
23
+ }
24
+ }
25
+
26
+ dialog::backdrop {
27
+ background: #0003;
28
+ }
29
+
30
+ dialog[open] {
31
+ animation: zoom 0.25s cubic-bezier(0.35, 1.55, 0.65, 1);
32
+ }
33
+
34
+ @keyframes zoom {
35
+ from {
36
+ transform: scale(0.95);
37
+ }
38
+ to {
39
+ transform: scale(1);
40
+ }
41
+ }
42
+
43
+ dialog[open]::backdrop {
44
+ animation: fade 0.25s ease-out;
45
+ }
46
+
47
+ @keyframes fade {
48
+ from {
49
+ opacity: 0;
50
+ }
51
+ to {
52
+ opacity: 1;
53
+ }
54
+ }
55
+ </style>
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import { enhance } from '$app/forms';
3
+ import Dialog from './Dialog.svelte';
4
+ import './styles.css';
5
+
6
+ let { children, active = $bindable(null), form, submitText = 'Submit', oncancel = () => {}, action = '', pageMode = false, ...rest } = $props();
7
+
8
+ $effect(() => {
9
+ if (form?.success) active = null;
10
+ });
11
+
12
+ const show = $derived(!!active || pageMode);
13
+
14
+ function onclick(e: MouseEvent) {
15
+ e.preventDefault();
16
+ active = null;
17
+ oncancel(e);
18
+ }
19
+ </script>
20
+
21
+ <Dialog {show} onclose={() => (active = null)}>
22
+ <form method="POST" {action} use:enhance class="main" {...rest}>
23
+ {#if form?.error}
24
+ <div class="error">{form.error}</div>
25
+ {/if}
26
+ {@render children()}
27
+ {#if pageMode}
28
+ <button type="submit" class="submit">{submitText}</button>
29
+ {:else}
30
+ <div class="actions">
31
+ <button type="button" {onclick}>Cancel</button>
32
+ <button type="submit" class="submit">{submitText}</button>
33
+ </div>
34
+ {/if}
35
+ </form>
36
+ </Dialog>
37
+
38
+ <style>
39
+ .actions {
40
+ display: flex;
41
+ gap: 1em;
42
+ flex-direction: row;
43
+ justify-content: space-between;
44
+ }
45
+ </style>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import FormDialog from './FormDialog.svelte';
3
+ import './styles.css';
4
+ let { form } = $props();
5
+ </script>
6
+
7
+ <FormDialog pageMode={true} {form} submitText="Sign Up">
8
+ <div>
9
+ <label for="name">Display Name</label>
10
+ <input name="name" type="text" value={form?.name || ''} required />
11
+ </div>
12
+ <div>
13
+ <label for="email">Email</label>
14
+ <input name="email" type="email" value={form?.email || ''} required />
15
+ </div>
16
+ <div>
17
+ <label for="password">Password</label>
18
+ <input name="password" type="password" />
19
+ </div>
20
+ </FormDialog>
@@ -0,0 +1,5 @@
1
+ export { default as Account } from './Account.svelte';
2
+ export { default as Dialog } from './Dialog.svelte';
3
+ export { default as FormDialog } from './FormDialog.svelte';
4
+ export { default as Icon } from './Icon.svelte';
5
+ export { default as SignUp } from './SignUp.svelte';
@@ -1,10 +1,16 @@
1
+ import type { Actions } from '@sveltejs/kit';
1
2
  import { adapter } from '../../dist/auth.js';
2
- import { web } from '../../dist/config.js';
3
+ import { editEmail, editName } from '../actions.js';
3
4
  import { loadSession } from '../utils.js';
4
5
  import type { PageServerLoadEvent } from './$types.js';
5
6
 
6
7
  export async function load(event: PageServerLoadEvent) {
7
8
  const { session } = await loadSession(event);
8
9
  const user = await adapter.getUserByEmail(session.user.email);
9
- return { session, user, prefix: web.prefix };
10
+ return { session, user };
10
11
  }
12
+
13
+ export const actions = {
14
+ email: editEmail,
15
+ name: editName,
16
+ } satisfies Actions;
@@ -1,60 +1,10 @@
1
1
  <script lang="ts">
2
- import { goto } from '$app/navigation';
3
- import { getUserImage } from '@axium/core';
4
- import '../lib/account.css';
5
- import Icon from '../lib/Icon.svelte';
6
- import '../lib/styles.css';
7
- import type { PageData } from './$types.js';
8
-
9
- const { data, children = () => {} }: { data: PageData; children?: () => any } = $props();
10
-
11
- const { user } = data;
12
- if (!user) goto('/auth/signin');
13
-
14
- const image = getUserImage(user);
2
+ import Account from '../lib/Account.svelte';
3
+ const { data, form } = $props();
15
4
  </script>
16
5
 
17
6
  <svelte:head>
18
7
  <title>Account</title>
19
8
  </svelte:head>
20
9
 
21
- <div class="Account flex-content">
22
- <img class="pfp" src={image} alt="User profile" />
23
- <p class="greeting">Welcome, {user.name}</p>
24
- <div class="account-section main">
25
- <div class="account-item">
26
- <p class="subtle">Name</p>
27
- <p>{user.name}</p>
28
- <a class="change" href="{data.prefix}/name"><Icon i="chevron-right" /></a>
29
- </div>
30
- <div class="account-item">
31
- <p class="subtle">Email</p>
32
- <p>{user.email}</p>
33
- <a class="change" href="{data.prefix}/email"><Icon i="chevron-right" /></a>
34
- </div>
35
- <div class="account-item">
36
- <p class="subtle">User ID <dfn title="This is your UUID."><Icon i="regular/circle-info" /></dfn></p>
37
- <p>{user.id}</p>
38
- </div>
39
- <a class="signout" href="/auth/signout"><button>Sign out</button></a>
40
- </div>
41
- {@render children()}
42
- </div>
43
-
44
- <style>
45
- .pfp {
46
- width: 100px;
47
- height: 100px;
48
- border-radius: 50%;
49
- border: 1px solid #8888;
50
- margin-top: 3em;
51
- }
52
-
53
- .greeting {
54
- font-size: 2em;
55
- }
56
-
57
- .signout {
58
- margin-top: 2em;
59
- }
60
- </style>
10
+ <Account {data} {form}></Account>
@@ -1,25 +1,20 @@
1
1
  <script lang="ts">
2
- import { enhance } from '$app/forms';
2
+ import { goto } from '$app/navigation';
3
+ import FormDialog from '../../lib/FormDialog.svelte';
3
4
  import '../../lib/styles.css';
4
5
  let { form, data } = $props();
5
- const { user } = data.session;
6
+ $effect(() => {
7
+ if (form?.success) goto('/');
8
+ });
6
9
  </script>
7
10
 
8
11
  <svelte:head>
9
12
  <title>Edit Name</title>
10
13
  </svelte:head>
11
14
 
12
- <div class="EditName">
13
- <form method="POST" class="main" use:enhance>
14
- {#if form?.error}
15
- <div class="error">
16
- {typeof form.error === 'string' ? form.error : JSON.stringify(form.error)}
17
- </div>
18
- {/if}
19
- <div>
20
- <label for="name">What do you want to be called?</label>
21
- <input name="name" type="text" value={form?.name || user.name || ''} required />
22
- </div>
23
- <button type="submit">Continue</button>
24
- </form>
25
- </div>
15
+ <FormDialog pageMode={true} {form} submitText="Change">
16
+ <div>
17
+ <label for="name">What do you want to be called?</label>
18
+ <input name="name" type="text" value={form?.name || data.session.user.name || ''} required />
19
+ </div>
20
+ </FormDialog>
@@ -1,28 +1,15 @@
1
1
  <script lang="ts">
2
- import { enhance } from '$app/forms';
2
+ import { goto } from '$app/navigation';
3
+ import SignUp from '../../lib/SignUp.svelte';
3
4
  import '../../lib/styles.css';
4
5
  let { form } = $props();
6
+ $effect(() => {
7
+ if (form?.success) goto('/');
8
+ });
5
9
  </script>
6
10
 
7
- <div class="Signup">
8
- <form method="POST" class="main" use:enhance>
9
- {#if form?.error}
10
- <div class="error">
11
- {typeof form.error === 'string' ? form.error : JSON.stringify(form.error)}
12
- </div>
13
- {/if}
14
- <div>
15
- <label for="name">Display Name</label>
16
- <input name="name" type="text" value={form?.name || ''} required />
17
- </div>
18
- <div>
19
- <label for="email">Email</label>
20
- <input name="email" type="email" value={form?.email || ''} required />
21
- </div>
22
- <div>
23
- <label for="password">Password</label>
24
- <input name="password" type="password" />
25
- </div>
26
- <button type="submit">Register</button>
27
- </form>
28
- </div>
11
+ <svelte:head>
12
+ <title>Sign Up</title>
13
+ </svelte:head>
14
+
15
+ <SignUp {form} />
@@ -1,5 +0,0 @@
1
- import type { Actions } from '@sveltejs/kit';
2
- import { editEmail } from '../../actions.js';
3
-
4
- export { loadSession as load } from '../../utils.js';
5
- export const actions = { default: editEmail } satisfies Actions;
@@ -1,24 +0,0 @@
1
- <script lang="ts">
2
- import { enhance } from '$app/forms';
3
- import '../../lib/styles.css';
4
- let { form, data } = $props();
5
- </script>
6
-
7
- <svelte:head>
8
- <title>Edit Email</title>
9
- </svelte:head>
10
-
11
- <div class="EditEmail">
12
- <form method="POST" class="main" use:enhance>
13
- {#if form?.error}
14
- <div class="error">
15
- {typeof form.error === 'string' ? form.error : JSON.stringify(form.error)}
16
- </div>
17
- {/if}
18
- <div>
19
- <label for="email">Email Address</label>
20
- <input name="email" type="email" value={form?.email || data.session.user.email || ''} required />
21
- </div>
22
- <button type="submit">Continue</button>
23
- </form>
24
- </div>