@axium/server 0.9.0 → 0.11.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/assets/icons/brands.svg +1493 -0
- package/{web/api/index.ts → dist/api/index.d.ts} +0 -2
- package/dist/api/index.js +5 -0
- package/dist/api/metadata.d.ts +1 -0
- package/dist/api/metadata.js +28 -0
- package/dist/api/passkeys.d.ts +1 -0
- package/dist/api/passkeys.js +50 -0
- package/dist/api/register.d.ts +1 -0
- package/dist/api/register.js +70 -0
- package/dist/api/session.d.ts +1 -0
- package/dist/api/session.js +31 -0
- package/dist/api/users.d.ts +1 -0
- package/dist/api/users.js +244 -0
- package/dist/apps.d.ts +0 -5
- package/dist/apps.js +2 -9
- package/dist/auth.d.ts +14 -30
- package/dist/auth.js +12 -18
- package/dist/cli.js +289 -32
- package/dist/config.d.ts +21 -8
- package/dist/config.js +46 -17
- package/dist/database.d.ts +12 -12
- package/dist/database.js +83 -84
- package/dist/io.d.ts +19 -20
- package/dist/io.js +85 -56
- package/dist/linking.d.ts +10 -0
- package/dist/linking.js +76 -0
- package/dist/plugins.d.ts +28 -12
- package/dist/plugins.js +29 -25
- package/dist/requests.d.ts +14 -0
- package/dist/requests.js +67 -0
- package/dist/routes.d.ts +12 -13
- package/dist/routes.js +21 -22
- package/dist/serve.d.ts +7 -0
- package/dist/serve.js +11 -0
- package/dist/state.d.ts +4 -0
- package/dist/state.js +22 -0
- package/dist/sveltekit.d.ts +8 -0
- package/dist/sveltekit.js +94 -0
- package/package.json +17 -8
- package/{web/routes → routes}/account/+page.svelte +6 -5
- package/svelte.config.js +37 -0
- package/web/hooks.server.ts +8 -3
- package/web/lib/Dialog.svelte +0 -1
- package/web/lib/FormDialog.svelte +0 -1
- package/web/lib/Upload.svelte +58 -0
- package/web/lib/icons/Icon.svelte +2 -7
- package/web/lib/icons/index.ts +6 -3
- package/web/lib/icons/mime.json +2 -1
- package/web/template.html +18 -0
- package/web/tsconfig.json +2 -2
- package/web/api/metadata.ts +0 -35
- package/web/api/passkeys.ts +0 -56
- package/web/api/readme.md +0 -1
- package/web/api/register.ts +0 -83
- package/web/api/schemas.ts +0 -22
- package/web/api/session.ts +0 -33
- package/web/api/users.ts +0 -351
- package/web/api/utils.ts +0 -66
- package/web/app.html +0 -14
- package/web/auth.ts +0 -8
- package/web/index.server.ts +0 -1
- package/web/index.ts +0 -1
- package/web/lib/auth.ts +0 -12
- package/web/lib/index.ts +0 -5
- package/web/routes/+layout.svelte +0 -6
- package/web/routes/[...path]/+page.server.ts +0 -13
- package/web/routes/[appId]/[...page]/+page.server.ts +0 -14
- package/web/routes/api/[...path]/+server.ts +0 -49
- package/web/utils.ts +0 -26
- /package/{web/lib → assets}/icons/light.svg +0 -0
- /package/{web/lib → assets}/icons/regular.svg +0 -0
- /package/{web/lib → assets}/icons/solid.svg +0 -0
- /package/{web/lib → assets}/styles.css +0 -0
- /package/{web/routes → routes}/_axium/default/+page.svelte +0 -0
- /package/{web/routes → routes}/login/+page.svelte +0 -0
- /package/{web/routes → routes}/logout/+page.svelte +0 -0
- /package/{web/routes → routes}/register/+page.svelte +0 -0
package/dist/routes.js
CHANGED
|
@@ -1,39 +1,47 @@
|
|
|
1
|
+
import { apps } from './apps.js';
|
|
2
|
+
import config from './config.js';
|
|
3
|
+
import { output } from './io.js';
|
|
4
|
+
import { _unique } from './state.js';
|
|
1
5
|
/**
|
|
2
6
|
* @internal
|
|
3
7
|
*/
|
|
4
|
-
export const routes = new Map();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const route = { ...opt, server: !('page' in opt), [kBuiltin]: false };
|
|
8
|
+
export const routes = _unique('routes', new Map());
|
|
9
|
+
export function addRoute(opt) {
|
|
10
|
+
const route = { ...opt, server: !('page' in opt) };
|
|
8
11
|
if (!route.path.startsWith('/')) {
|
|
9
12
|
throw new Error(`Route path must start with a slash: ${route.path}`);
|
|
10
13
|
}
|
|
11
|
-
if (route.path.startsWith('/api/')
|
|
14
|
+
if (route.path.startsWith('/api/'))
|
|
15
|
+
route.api = true;
|
|
16
|
+
if (route.api && !route.server)
|
|
12
17
|
throw new Error(`API routes cannot have a client page: ${route.path}`);
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
routes.set(route.path, route);
|
|
19
|
+
output.debug('Added route: ' + route.path);
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
17
22
|
* Resolve a request URL into a route.
|
|
18
23
|
* This handles parsing of parameters in the URL.
|
|
19
24
|
*/
|
|
20
|
-
export function resolveRoute(event
|
|
25
|
+
export function resolveRoute(event) {
|
|
21
26
|
const { pathname } = event.url;
|
|
22
|
-
if (
|
|
23
|
-
return
|
|
27
|
+
if (routes.has(pathname) && !pathname.split('/').some(p => p.startsWith(':')))
|
|
28
|
+
return routes.get(pathname);
|
|
24
29
|
// Otherwise we must have a parameterized route
|
|
25
|
-
|
|
30
|
+
_routes: for (const route of routes.values()) {
|
|
26
31
|
const params = {};
|
|
27
32
|
// Split the path and route into parts, zipped together
|
|
28
33
|
const pathParts = pathname.split('/').filter(Boolean);
|
|
34
|
+
// Skips routes in disabled apps
|
|
35
|
+
if (apps.has(pathParts[0]) && config.apps.disabled.includes(pathParts[0]))
|
|
36
|
+
continue;
|
|
29
37
|
for (const routePart of route.path.split('/').filter(Boolean)) {
|
|
30
38
|
const pathPart = pathParts.shift();
|
|
31
39
|
if (!pathPart)
|
|
32
|
-
continue
|
|
40
|
+
continue _routes;
|
|
33
41
|
if (pathPart == routePart)
|
|
34
42
|
continue;
|
|
35
43
|
if (!routePart.startsWith(':'))
|
|
36
|
-
continue
|
|
44
|
+
continue _routes;
|
|
37
45
|
params[routePart.slice(1)] = pathPart;
|
|
38
46
|
}
|
|
39
47
|
// we didn't find a match, since an exact match would have been found already
|
|
@@ -43,12 +51,3 @@ export function resolveRoute(event, _routeMap = routes) {
|
|
|
43
51
|
return route;
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
|
-
/**
|
|
47
|
-
* This function marks all existing routes as built-in.
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
50
|
-
export function _markDefaults() {
|
|
51
|
-
for (const route of routes.values()) {
|
|
52
|
-
route[kBuiltin] = true;
|
|
53
|
-
}
|
|
54
|
-
}
|
package/dist/serve.d.ts
ADDED
package/dist/serve.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { createServer as createSecureServer } from 'node:https';
|
|
4
|
+
import config from './config.js';
|
|
5
|
+
const _handlerPath = '../build/handler.js';
|
|
6
|
+
export async function serve(opt) {
|
|
7
|
+
const { handler } = await import(_handlerPath);
|
|
8
|
+
if (!opt.secure && !config.web.secure)
|
|
9
|
+
return createServer(handler);
|
|
10
|
+
return createSecureServer({ key: readFileSync(opt.ssl_key || config.web.ssl_key), cert: readFileSync(opt.ssl_cert || config.web.ssl_cert) }, handler);
|
|
11
|
+
}
|
package/dist/state.d.ts
ADDED
package/dist/state.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
const sym = Symbol.for('Axium:state');
|
|
3
|
+
globalThis[sym] ||= Object.create({ _errored: false });
|
|
4
|
+
/**
|
|
5
|
+
* Prevent duplicate shared state.
|
|
6
|
+
*/
|
|
7
|
+
export function _unique(id, value) {
|
|
8
|
+
const state = globalThis[sym];
|
|
9
|
+
const _err = new Error();
|
|
10
|
+
Error.captureStackTrace(_err, _unique);
|
|
11
|
+
const stack = _err.stack.slice(6);
|
|
12
|
+
if (!(id in state)) {
|
|
13
|
+
state[id] = { value, stack };
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
if (!state._errored) {
|
|
17
|
+
console.error(styleText('red', 'Duplicate Axium server state! You might have multiple instances of the same module loaded.'));
|
|
18
|
+
state._errored = true;
|
|
19
|
+
}
|
|
20
|
+
console.warn(styleText('yellow', `Mitigating duplicate state! (${id})\n${stack}\nFrom original\n${state[id].stack}`));
|
|
21
|
+
return state[id].value;
|
|
22
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RequestEvent, ResolveOptions } from '@sveltejs/kit';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
export declare function handle({ event, resolve, }: {
|
|
6
|
+
event: RequestEvent;
|
|
7
|
+
resolve: (event: RequestEvent, opts?: ResolveOptions) => Promise<Response>;
|
|
8
|
+
}): Promise<Response>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { error, json } from '@sveltejs/kit';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { styleText } from 'node:util';
|
|
4
|
+
import { render } from 'svelte/server';
|
|
5
|
+
import z from 'zod/v4';
|
|
6
|
+
import { config } from './config.js';
|
|
7
|
+
import { resolveRoute } from './routes.js';
|
|
8
|
+
async function handleAPIRequest(event, route) {
|
|
9
|
+
const method = event.request.method;
|
|
10
|
+
const _warnings = [];
|
|
11
|
+
if (route.api && !event.request.headers.get('Accept')?.includes('application/json')) {
|
|
12
|
+
_warnings.push('Only application/json is supported');
|
|
13
|
+
event.request.headers.set('Accept', 'application/json');
|
|
14
|
+
}
|
|
15
|
+
for (const [key, type] of Object.entries(route.params || {})) {
|
|
16
|
+
if (!type)
|
|
17
|
+
continue;
|
|
18
|
+
try {
|
|
19
|
+
event.params[key] = type.parse(event.params[key]);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
error(400, `Invalid parameter: ${z.prettifyError(e)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (typeof route[method] != 'function')
|
|
26
|
+
error(405, `Method ${method} not allowed for ${route.path}`);
|
|
27
|
+
const result = await route[method](event);
|
|
28
|
+
if (result instanceof Response)
|
|
29
|
+
return result;
|
|
30
|
+
result._warnings ||= [];
|
|
31
|
+
result._warnings.push(..._warnings);
|
|
32
|
+
return json(result);
|
|
33
|
+
}
|
|
34
|
+
function handleError(e) {
|
|
35
|
+
if ('body' in e)
|
|
36
|
+
return json(e.body, { status: e.status });
|
|
37
|
+
if ('location' in e)
|
|
38
|
+
return Response.redirect(e.location, e.status);
|
|
39
|
+
console.error(e);
|
|
40
|
+
return json({ message: 'Internal Error' + (config.debug ? ': ' + e.message : '') }, { status: 500 });
|
|
41
|
+
}
|
|
42
|
+
let template = null;
|
|
43
|
+
function fillTemplate({ head, body }, env = {}, nonce = '') {
|
|
44
|
+
template ||= readFileSync(config.web.template, 'utf-8');
|
|
45
|
+
return (template
|
|
46
|
+
.replace('%sveltekit.head%', head)
|
|
47
|
+
.replace('%sveltekit.body%', body)
|
|
48
|
+
.replace(/%sveltekit\.assets%/g, config.web.assets)
|
|
49
|
+
// Unused for now.
|
|
50
|
+
.replace(/%sveltekit\.nonce%/g, nonce)
|
|
51
|
+
.replace(/%sveltekit\.env\.([^%]+)%/g, (_match, key) => env[key] ?? ''));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export async function handle({ event, resolve, }) {
|
|
57
|
+
const route = resolveRoute(event);
|
|
58
|
+
if (!route && event.url.pathname === '/' && config.debug)
|
|
59
|
+
return new Response(null, { status: 303, headers: { Location: '/_axium/default' } });
|
|
60
|
+
if (config.debug)
|
|
61
|
+
console.log(styleText('blueBright', event.request.method.padEnd(7)), route ? route.path : event.url.pathname);
|
|
62
|
+
if (!route)
|
|
63
|
+
return await resolve(event).catch(handleError);
|
|
64
|
+
if (route.server == true) {
|
|
65
|
+
if (route.api)
|
|
66
|
+
return await handleAPIRequest(event, route).catch(handleError);
|
|
67
|
+
const run = route[event.request.method];
|
|
68
|
+
if (typeof run !== 'function') {
|
|
69
|
+
error(405, `Method ${event.request.method} not allowed for ${route.path}`);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const result = await run(event);
|
|
73
|
+
if (result instanceof Response)
|
|
74
|
+
return result;
|
|
75
|
+
return json(result);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
return handleError(e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const data = await route.load?.(event);
|
|
82
|
+
const body = fillTemplate(render(route.page));
|
|
83
|
+
return new Response(body, {
|
|
84
|
+
headers: config.web.disable_cache
|
|
85
|
+
? {
|
|
86
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
87
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
88
|
+
Pragma: 'no-cache',
|
|
89
|
+
Expires: '0',
|
|
90
|
+
}
|
|
91
|
+
: {},
|
|
92
|
+
status: 200,
|
|
93
|
+
});
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -20,12 +20,19 @@
|
|
|
20
20
|
"exports": {
|
|
21
21
|
".": "./dist/index.js",
|
|
22
22
|
"./*": "./dist/*.js",
|
|
23
|
-
"./
|
|
24
|
-
"
|
|
23
|
+
"./lib/*": "./web/lib/*",
|
|
24
|
+
"./$hooks": "./web/hooks.server.ts",
|
|
25
|
+
"./$routes": "./routes",
|
|
26
|
+
"./$template": "./web/template.html",
|
|
27
|
+
"./svelte.config.js": "./svelte.config.js"
|
|
25
28
|
},
|
|
26
29
|
"files": [
|
|
30
|
+
"assets",
|
|
31
|
+
"build",
|
|
27
32
|
"dist",
|
|
28
|
-
"
|
|
33
|
+
"routes",
|
|
34
|
+
"web",
|
|
35
|
+
"svelte.config.js"
|
|
29
36
|
],
|
|
30
37
|
"bin": {
|
|
31
38
|
"axium": "dist/cli.js"
|
|
@@ -34,24 +41,26 @@
|
|
|
34
41
|
"build": "tsc"
|
|
35
42
|
},
|
|
36
43
|
"peerDependencies": {
|
|
37
|
-
"@axium/
|
|
38
|
-
"@axium/
|
|
44
|
+
"@axium/client": ">=0.1.0",
|
|
45
|
+
"@axium/core": ">=0.4.0",
|
|
39
46
|
"utilium": "^2.3.8",
|
|
40
47
|
"zod": "^3.25.61"
|
|
41
48
|
},
|
|
42
49
|
"dependencies": {
|
|
50
|
+
"@axium/server": "file:.",
|
|
43
51
|
"@simplewebauthn/server": "^13.1.1",
|
|
44
52
|
"@sveltejs/kit": "^2.20.2",
|
|
45
53
|
"@types/pg": "^8.11.11",
|
|
46
54
|
"commander": "^13.1.0",
|
|
55
|
+
"cookie": "^1.0.2",
|
|
47
56
|
"kysely": "^0.28.0",
|
|
48
57
|
"logzen": "^0.7.0",
|
|
49
58
|
"mime": "^4.0.7",
|
|
50
|
-
"pg": "^8.14.1"
|
|
59
|
+
"pg": "^8.14.1",
|
|
60
|
+
"svelte": "^5.25.3"
|
|
51
61
|
},
|
|
52
62
|
"devDependencies": {
|
|
53
63
|
"@sveltejs/adapter-node": "^5.2.12",
|
|
54
|
-
"svelte": "^5.25.3",
|
|
55
64
|
"vite-plugin-mkcert": "^1.17.8"
|
|
56
65
|
}
|
|
57
66
|
}
|
|
@@ -191,11 +191,12 @@
|
|
|
191
191
|
</div>
|
|
192
192
|
<FormDialog
|
|
193
193
|
bind:dialog={dialogs['logout#' + session.id]}
|
|
194
|
-
submit={() =>
|
|
195
|
-
logout(user.id, session.id)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
submit={async () => {
|
|
195
|
+
await logout(user.id, session.id);
|
|
196
|
+
dialogs['logout#' + session.id].remove();
|
|
197
|
+
sessions.splice(sessions.indexOf(session), 1);
|
|
198
|
+
if (session.id == currentSession.id) goto('/');
|
|
199
|
+
}}
|
|
199
200
|
submitText="Logout"
|
|
200
201
|
>
|
|
201
202
|
<p>Are you sure you want to log out this session?</p>
|
package/svelte.config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import node from '@sveltejs/adapter-node';
|
|
2
|
+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
|
3
|
+
import { join } from 'node:path/posix';
|
|
4
|
+
import config from '@axium/server/config';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Paths relative to the directory of this file.
|
|
8
|
+
* This allows this file to be imported from other projects and still resolve to the correct paths.
|
|
9
|
+
*/
|
|
10
|
+
const fixed = p => join(import.meta.dirname, p);
|
|
11
|
+
|
|
12
|
+
/** @type {import('@sveltejs/kit').Config} */
|
|
13
|
+
export default {
|
|
14
|
+
compilerOptions: {
|
|
15
|
+
runes: true,
|
|
16
|
+
},
|
|
17
|
+
preprocess: vitePreprocess({ script: true }),
|
|
18
|
+
vitePlugin: {
|
|
19
|
+
exclude: '@axium/server/**',
|
|
20
|
+
},
|
|
21
|
+
kit: {
|
|
22
|
+
adapter: node(),
|
|
23
|
+
alias: {
|
|
24
|
+
$stores: fixed('web/stores'),
|
|
25
|
+
$lib: fixed('web/lib'),
|
|
26
|
+
},
|
|
27
|
+
files: {
|
|
28
|
+
routes: config.web.routes,
|
|
29
|
+
lib: fixed('web/lib'),
|
|
30
|
+
assets: fixed('assets'),
|
|
31
|
+
appTemplate: fixed('web/template.html'),
|
|
32
|
+
hooks: {
|
|
33
|
+
server: fixed('web/hooks.server.ts'),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
package/web/hooks.server.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import '@axium/server/api/index';
|
|
1
2
|
import { loadDefaultConfigs } from '@axium/server/config';
|
|
2
3
|
import { clean, database } from '@axium/server/database';
|
|
3
|
-
import {
|
|
4
|
-
import '
|
|
4
|
+
import { dirs, logger } from '@axium/server/io';
|
|
5
|
+
import { allLogLevels } from 'logzen';
|
|
6
|
+
import { createWriteStream } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path/posix';
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
logger.attach(createWriteStream(join(dirs.at(-1), 'server.log')), { output: allLogLevels });
|
|
7
10
|
await loadDefaultConfigs();
|
|
8
11
|
await clean({});
|
|
9
12
|
|
|
10
13
|
process.on('beforeExit', async () => {
|
|
11
14
|
await database.destroy();
|
|
12
15
|
});
|
|
16
|
+
|
|
17
|
+
export { handle } from '@axium/server/sveltekit';
|
package/web/lib/Dialog.svelte
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from './icons/Icon.svelte';
|
|
3
|
+
import { iconForMime, iconForPath } from './icons';
|
|
4
|
+
|
|
5
|
+
let { files = $bindable(), name = 'files', ...rest }: { files?: FileList; name?: string; multiple?: any; required?: any } = $props();
|
|
6
|
+
|
|
7
|
+
let input = $state<HTMLInputElement>();
|
|
8
|
+
|
|
9
|
+
const id = 'input:' + Math.random().toString(36).slice(2);
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div>
|
|
13
|
+
{#if files?.length}
|
|
14
|
+
<label for={id} class="file">
|
|
15
|
+
{#each files as file}
|
|
16
|
+
<Icon i={iconForMime(file.type) || iconForPath(file.name) || 'file'} />
|
|
17
|
+
<span>{file.name}</span>
|
|
18
|
+
<button
|
|
19
|
+
onclick={e => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
const dt = new DataTransfer();
|
|
22
|
+
for (let f of files) if (file !== f) dt.items.add(f);
|
|
23
|
+
input.files = files = dt.files;
|
|
24
|
+
}}
|
|
25
|
+
style:display="contents"
|
|
26
|
+
>
|
|
27
|
+
<Icon i="trash" />
|
|
28
|
+
</button>
|
|
29
|
+
{/each}
|
|
30
|
+
</label>
|
|
31
|
+
{:else}
|
|
32
|
+
<label for={id}><Icon i="upload" />Upload</label>
|
|
33
|
+
{/if}
|
|
34
|
+
|
|
35
|
+
<input bind:this={input} {name} {id} type="file" bind:files {...rest} />
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
input {
|
|
40
|
+
display: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
label {
|
|
44
|
+
padding: 0.5em 1em;
|
|
45
|
+
border: 1px solid #cccc;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 0.5em;
|
|
50
|
+
border-radius: 0.5em;
|
|
51
|
+
width: 20em;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
label.file {
|
|
55
|
+
display: grid;
|
|
56
|
+
grid-template-columns: 2em 1fr 2em;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import light from './light.svg';
|
|
3
|
-
import solid from './solid.svg';
|
|
4
|
-
import regular from './regular.svg';
|
|
5
|
-
const urls = { light, solid, regular };
|
|
6
2
|
const { i } = $props();
|
|
7
|
-
|
|
8
3
|
const [style, id] = $derived(i.includes('/') ? i.split('/') : ['solid', i]);
|
|
9
|
-
const
|
|
4
|
+
const href = $derived(`/icons/${style}.svg#${id}`);
|
|
10
5
|
</script>
|
|
11
6
|
|
|
12
7
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em">
|
|
13
|
-
<use href
|
|
8
|
+
<use {href} />
|
|
14
9
|
</svg>
|
|
15
10
|
|
|
16
11
|
<style>
|
package/web/lib/icons/index.ts
CHANGED
|
@@ -3,8 +3,11 @@ import mimeIcons from './mime.json' with { type: 'json' };
|
|
|
3
3
|
|
|
4
4
|
export { default as Icon } from './Icon.svelte';
|
|
5
5
|
|
|
6
|
-
export function
|
|
7
|
-
|
|
6
|
+
export function iconForMime(mimeType: string): string {
|
|
7
|
+
return mimeIcons[mimeType as keyof typeof mimeIcons] || 'file';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function iconForPath(path: string): string {
|
|
8
11
|
const type = mime.getType(path) || 'application/octet-stream';
|
|
9
|
-
return
|
|
12
|
+
return iconForMime(type);
|
|
10
13
|
}
|
package/web/lib/icons/mime.json
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"text/css": "css",
|
|
22
22
|
"text/csv": "file-csv",
|
|
23
23
|
"text/html": "code",
|
|
24
|
-
"text/javascript": "square-js",
|
|
24
|
+
"text/javascript": "brands/square-js",
|
|
25
|
+
"application/x-javascript": "brands/square-js",
|
|
25
26
|
"text/plain": "file-lines",
|
|
26
27
|
"text/xml": "code",
|
|
27
28
|
"video": "clapperboard-play"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
<meta name="color-scheme" content="dark light" />
|
|
8
|
+
<link rel="stylesheet" href="%sveltekit.assets%/styles.css" />
|
|
9
|
+
<link rel="preload" href="%sveltekit.assets%/icons/light.svg" as="image" type="image/svg+xml" />
|
|
10
|
+
<link rel="preload" href="%sveltekit.assets%/icons/regular.svg" as="image" type="image/svg+xml" />
|
|
11
|
+
<link rel="preload" href="%sveltekit.assets%/icons/solid.svg" as="image" type="image/svg+xml" />
|
|
12
|
+
%sveltekit.head%
|
|
13
|
+
</head>
|
|
14
|
+
|
|
15
|
+
<body>
|
|
16
|
+
<div style="display: contents">%sveltekit.body%</div>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
package/web/tsconfig.json
CHANGED
package/web/api/metadata.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { Result } from '@axium/core/api';
|
|
2
|
-
import { requestMethods } from '@axium/core/requests';
|
|
3
|
-
import { config } from '@axium/server/config';
|
|
4
|
-
import { plugins } from '@axium/server/plugins';
|
|
5
|
-
import { addRoute, routes } from '@axium/server/routes';
|
|
6
|
-
import { error } from '@sveltejs/kit';
|
|
7
|
-
import pkg from '../../package.json' with { type: 'json' };
|
|
8
|
-
|
|
9
|
-
addRoute({
|
|
10
|
-
path: '/api/metadata',
|
|
11
|
-
async GET(): Result<'GET', 'metadata'> {
|
|
12
|
-
if (config.api.disable_metadata) {
|
|
13
|
-
error(401, { message: 'API metadata is disabled' });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
version: pkg.version,
|
|
18
|
-
routes: Object.fromEntries(
|
|
19
|
-
routes
|
|
20
|
-
.entries()
|
|
21
|
-
.filter(([path]) => path.startsWith('/api/'))
|
|
22
|
-
.map(([path, route]) => [
|
|
23
|
-
path,
|
|
24
|
-
{
|
|
25
|
-
params: Object.fromEntries(
|
|
26
|
-
Object.entries(route.params || {}).map(([key, type]) => [key, type ? type.def.type : null])
|
|
27
|
-
),
|
|
28
|
-
methods: requestMethods.filter(m => m in route),
|
|
29
|
-
},
|
|
30
|
-
])
|
|
31
|
-
),
|
|
32
|
-
plugins: Object.fromEntries(plugins.values().map(plugin => [plugin.id, plugin.version])),
|
|
33
|
-
};
|
|
34
|
-
},
|
|
35
|
-
});
|
package/web/api/passkeys.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { Result } from '@axium/core/api';
|
|
2
|
-
import { PasskeyChangeable } from '@axium/core/schemas';
|
|
3
|
-
import { getPasskey } from '@axium/server/auth';
|
|
4
|
-
import { database as db } from '@axium/server/database';
|
|
5
|
-
import { addRoute } from '@axium/server/routes';
|
|
6
|
-
import { error } from '@sveltejs/kit';
|
|
7
|
-
import { omit } from 'utilium';
|
|
8
|
-
import z from 'zod/v4';
|
|
9
|
-
import { checkAuth, parseBody, withError } from './utils';
|
|
10
|
-
|
|
11
|
-
addRoute({
|
|
12
|
-
path: '/api/passkeys/:id',
|
|
13
|
-
params: {
|
|
14
|
-
id: z.string(),
|
|
15
|
-
},
|
|
16
|
-
async GET(event): Result<'GET', 'passkeys/:id'> {
|
|
17
|
-
const passkey = await getPasskey(event.params.id);
|
|
18
|
-
await checkAuth(event, passkey.userId);
|
|
19
|
-
return omit(passkey, 'counter', 'publicKey');
|
|
20
|
-
},
|
|
21
|
-
async PATCH(event): Result<'PATCH', 'passkeys/:id'> {
|
|
22
|
-
const body = await parseBody(event, PasskeyChangeable);
|
|
23
|
-
const passkey = await getPasskey(event.params.id);
|
|
24
|
-
await checkAuth(event, passkey.userId);
|
|
25
|
-
const result = await db
|
|
26
|
-
.updateTable('passkeys')
|
|
27
|
-
.set(body)
|
|
28
|
-
.where('id', '=', passkey.id)
|
|
29
|
-
.returningAll()
|
|
30
|
-
.executeTakeFirstOrThrow()
|
|
31
|
-
.catch(withError('Could not update passkey'));
|
|
32
|
-
|
|
33
|
-
return omit(result, 'counter', 'publicKey');
|
|
34
|
-
},
|
|
35
|
-
async DELETE(event): Result<'DELETE', 'passkeys/:id'> {
|
|
36
|
-
const passkey = await getPasskey(event.params.id);
|
|
37
|
-
await checkAuth(event, passkey.userId);
|
|
38
|
-
|
|
39
|
-
const { count } = await db
|
|
40
|
-
.selectFrom('passkeys')
|
|
41
|
-
.select(db.fn.countAll().as('count'))
|
|
42
|
-
.where('userId', '=', passkey.userId)
|
|
43
|
-
.executeTakeFirstOrThrow();
|
|
44
|
-
|
|
45
|
-
if (Number(count) <= 1) error(409, 'At least one passkey is required');
|
|
46
|
-
|
|
47
|
-
const result = await db
|
|
48
|
-
.deleteFrom('passkeys')
|
|
49
|
-
.where('id', '=', passkey.id)
|
|
50
|
-
.returningAll()
|
|
51
|
-
.executeTakeFirstOrThrow()
|
|
52
|
-
.catch(withError('Could not delete passkey'));
|
|
53
|
-
|
|
54
|
-
return omit(result, 'counter', 'publicKey');
|
|
55
|
-
},
|
|
56
|
-
});
|
package/web/api/readme.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
This is the web-facing API, not a TypeScript API. In this directory you'll find the `addRoute` calls for the built-in `/api` routes along with the utilities and other helpers used.
|