@axium/storage 0.7.8 → 0.7.10

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.
@@ -0,0 +1,8 @@
1
+ import type { InitOptions, OpOptions } from '@axium/server/database';
2
+ import './common.js';
3
+ import './server.js';
4
+ export declare function statusText(): Promise<string>;
5
+ export declare function db_init(opt: InitOptions): Promise<void>;
6
+ export declare function db_wipe(opt: OpOptions): Promise<void>;
7
+ export declare function remove(opt: OpOptions): Promise<void>;
8
+ export declare function clean(opt: OpOptions): Promise<void>;
@@ -4,10 +4,9 @@ import config from '@axium/server/config';
4
4
  import { count, createIndex, database, warnExists } from '@axium/server/database';
5
5
  import { done, start } from '@axium/server/io';
6
6
  import { sql } from 'kysely';
7
- import pkg from '../package.json' with { type: 'json' };
8
7
  import './common.js';
9
8
  import './server.js';
10
- async function statusText() {
9
+ export async function statusText() {
11
10
  const { storage: items } = await count('storage');
12
11
  const { size } = await database
13
12
  .selectFrom('storage')
@@ -15,7 +14,7 @@ async function statusText() {
15
14
  .executeTakeFirstOrThrow();
16
15
  return `${items} items totaling ${formatBytes(Number(size))}`;
17
16
  }
18
- async function db_init(opt) {
17
+ export async function db_init(opt) {
19
18
  start('Creating table storage');
20
19
  await database.schema
21
20
  .createTable('storage')
@@ -39,18 +38,18 @@ async function db_init(opt) {
39
38
  await createIndex('storage', 'parentId');
40
39
  await acl.createTable('storage');
41
40
  }
42
- async function db_wipe(opt) {
41
+ export async function db_wipe(opt) {
43
42
  start('Removing data from user storage');
44
43
  await database.deleteFrom('storage').execute().then(done);
45
44
  await acl.wipeTable('storage');
46
45
  }
47
- async function remove(opt) {
46
+ export async function remove(opt) {
48
47
  start('Dropping table storage');
49
48
  await database.schema.dropTable('storage').execute();
50
49
  await acl.dropTable('storage');
51
50
  done();
52
51
  }
53
- async function clean(opt) {
52
+ export async function clean(opt) {
54
53
  start('Removing expired trash items');
55
54
  const nDaysAgo = new Date(Date.now() - 86400000 * config.storage.trash_duration);
56
55
  await database
@@ -60,8 +59,3 @@ async function clean(opt) {
60
59
  .executeTakeFirstOrThrow()
61
60
  .then(done);
62
61
  }
63
- export default {
64
- ...pkg,
65
- statusText,
66
- hooks: { db_init, db_wipe, remove, clean },
67
- };
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
- import { Permission } from '@axium/core/access';
2
- import { addEvent, audit, Severity } from '@axium/server/audit';
1
+ import { Permission, Severity } from '@axium/core';
2
+ import { addEvent, audit } from '@axium/server/audit';
3
3
  import { checkAuthForItem, checkAuthForUser, getSessionAndUser } from '@axium/server/auth';
4
4
  import { addConfigDefaults, config } from '@axium/server/config';
5
5
  import { database, expectedTypes } from '@axium/server/database';
@@ -113,19 +113,19 @@ export async function deleteRecursive(itemId, deleteSelf) {
113
113
  addRoute({
114
114
  path: '/api/storage/item/:id',
115
115
  params: { id: z.uuid() },
116
- async GET(event) {
116
+ async GET(request, params) {
117
117
  if (!config.storage.enabled)
118
118
  error(503, 'User storage is disabled');
119
- const itemId = event.params.id;
120
- const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Read);
119
+ const itemId = params.id;
120
+ const { item } = await checkAuthForItem(request, 'storage', itemId, Permission.Read);
121
121
  return parseItem(item);
122
122
  },
123
- async PATCH(event) {
123
+ async PATCH(request, params) {
124
124
  if (!config.storage.enabled)
125
125
  error(503, 'User storage is disabled');
126
- const itemId = event.params.id;
127
- const body = await parseBody(event, StorageItemUpdate);
128
- await checkAuthForItem(event, 'storage', itemId, Permission.Manage);
126
+ const itemId = params.id;
127
+ const body = await parseBody(request, StorageItemUpdate);
128
+ await checkAuthForItem(request, 'storage', itemId, Permission.Manage);
129
129
  const values = {};
130
130
  if ('publicPermission' in body)
131
131
  values.publicPermission = body.publicPermission;
@@ -145,11 +145,11 @@ addRoute({
145
145
  .executeTakeFirstOrThrow()
146
146
  .catch(withError('Could not update item')));
147
147
  },
148
- async DELETE(event) {
148
+ async DELETE(request, params) {
149
149
  if (!config.storage.enabled)
150
150
  error(503, 'User storage is disabled');
151
- const itemId = event.params.id;
152
- const auth = await checkAuthForItem(event, 'storage', itemId, Permission.Manage);
151
+ const itemId = params.id;
152
+ const auth = await checkAuthForItem(request, 'storage', itemId, Permission.Manage);
153
153
  const item = parseItem(auth.item);
154
154
  await deleteRecursive(itemId, item.type != 'inode/directory');
155
155
  return item;
@@ -158,11 +158,11 @@ addRoute({
158
158
  addRoute({
159
159
  path: '/api/storage/directory/:id',
160
160
  params: { id: z.uuid() },
161
- async GET(event) {
161
+ async GET(request, params) {
162
162
  if (!config.storage.enabled)
163
163
  error(503, 'User storage is disabled');
164
- const itemId = event.params.id;
165
- const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Read);
164
+ const itemId = params.id;
165
+ const { item } = await checkAuthForItem(request, 'storage', itemId, Permission.Read);
166
166
  if (item.type != 'inode/directory')
167
167
  error(409, 'Item is not a directory');
168
168
  const items = await database
@@ -176,20 +176,20 @@ addRoute({
176
176
  });
177
177
  addRoute({
178
178
  path: '/raw/storage',
179
- async PUT(event) {
179
+ async PUT(request) {
180
180
  if (!config.storage.enabled)
181
181
  error(503, 'User storage is disabled');
182
- const token = getToken(event);
182
+ const token = getToken(request);
183
183
  if (!token)
184
184
  error(401, 'Missing session token');
185
185
  const { userId } = await getSessionAndUser(token).catch(withError('Invalid session token', 401));
186
186
  const [usage, limits] = await Promise.all([currentUsage(userId), getLimits(userId)]).catch(withError('Could not fetch usage and/or limits'));
187
- const name = event.request.headers.get('x-name');
187
+ const name = request.headers.get('x-name');
188
188
  if (!name)
189
189
  error(400, 'Missing name header');
190
190
  if (name.length > 255)
191
191
  error(400, 'Name is too long');
192
- const maybeParentId = event.request.headers.get('x-parent');
192
+ const maybeParentId = request.headers.get('x-parent');
193
193
  const parentId = maybeParentId
194
194
  ? await z
195
195
  .uuid()
@@ -197,22 +197,22 @@ addRoute({
197
197
  .catch(() => error(400, 'Invalid parent ID'))
198
198
  : null;
199
199
  if (parentId)
200
- await checkAuthForItem(event, 'storage', parentId, Permission.Edit);
201
- const size = Number(event.request.headers.get('content-length'));
200
+ await checkAuthForItem(request, 'storage', parentId, Permission.Edit);
201
+ const size = Number(request.headers.get('content-length'));
202
202
  if (Number.isNaN(size))
203
203
  error(411, 'Missing or invalid content length header');
204
- if (usage.items >= limits.user_items)
204
+ if (limits.user_items && usage.items >= limits.user_items)
205
205
  error(409, 'Too many items');
206
- if ((usage.bytes + size) / 1_000_000 >= limits.user_size)
206
+ if (limits.user_size && (usage.bytes + size) / 1_000_000 >= limits.user_size)
207
207
  error(413, 'Not enough space');
208
- if (size > limits.item_size * 1_000_000)
208
+ if (limits.item_size && size > limits.item_size * 1_000_000)
209
209
  error(413, 'File size exceeds maximum size');
210
- const content = await event.request.bytes();
210
+ const content = await request.bytes();
211
211
  if (content.byteLength > size) {
212
212
  await audit('storage_size_mismatch', userId, { item: null });
213
213
  error(400, 'Content length does not match size header');
214
214
  }
215
- const type = event.request.headers.get('content-type') || 'application/octet-stream';
215
+ const type = request.headers.get('content-type') || 'application/octet-stream';
216
216
  const isDirectory = type == 'inode/directory';
217
217
  if (isDirectory && size > 0)
218
218
  error(400, 'Directories can not have content');
@@ -260,11 +260,11 @@ addRoute({
260
260
  addRoute({
261
261
  path: '/raw/storage/:id',
262
262
  params: { id: z.uuid() },
263
- async GET(event) {
263
+ async GET(request, params) {
264
264
  if (!config.storage.enabled)
265
265
  error(503, 'User storage is disabled');
266
- const itemId = event.params.id;
267
- const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Read);
266
+ const itemId = params.id;
267
+ const { item } = await checkAuthForItem(request, 'storage', itemId, Permission.Read);
268
268
  if (item.trashedAt)
269
269
  error(410, 'Trashed items can not be downloaded');
270
270
  const content = new Uint8Array(readFileSync(join(config.storage.data, item.id)));
@@ -275,31 +275,31 @@ addRoute({
275
275
  },
276
276
  });
277
277
  },
278
- async POST(event) {
278
+ async POST(request, params) {
279
279
  if (!config.storage.enabled)
280
280
  error(503, 'User storage is disabled');
281
- const itemId = event.params.id;
282
- const { item, session } = await checkAuthForItem(event, 'storage', itemId, Permission.Edit);
281
+ const itemId = params.id;
282
+ const { item, session } = await checkAuthForItem(request, 'storage', itemId, Permission.Edit);
283
283
  if (item.immutable)
284
284
  error(405, 'Item is immutable');
285
285
  if (item.type == 'inode/directory')
286
286
  error(409, 'Directories do not have content');
287
287
  if (item.trashedAt)
288
288
  error(410, 'Trashed items can not be changed');
289
- const type = event.request.headers.get('content-type') || 'application/octet-stream';
289
+ const type = request.headers.get('content-type') || 'application/octet-stream';
290
290
  if (type != item.type) {
291
291
  await audit('storage_type_mismatch', session?.userId, { item: item.id });
292
292
  error(400, 'Content type does not match existing item type');
293
293
  }
294
- const size = Number(event.request.headers.get('content-length'));
294
+ const size = Number(request.headers.get('content-length'));
295
295
  if (Number.isNaN(size))
296
296
  error(411, 'Missing or invalid content length header');
297
297
  const [usage, limits] = await Promise.all([currentUsage(item.userId), getLimits(item.userId)]).catch(withError('Could not fetch usage and/or limits'));
298
- if ((usage.bytes + size - item.size) / 1_000_000 >= limits.user_size)
298
+ if (limits.user_size && (usage.bytes + size - item.size) / 1_000_000 >= limits.user_size)
299
299
  error(413, 'Not enough space');
300
- if (size > limits.item_size * 1_000_000)
300
+ if (limits.item_size && size > limits.item_size * 1_000_000)
301
301
  error(413, 'File size exceeds maximum size');
302
- const content = await event.request.bytes();
302
+ const content = await request.bytes();
303
303
  if (content.byteLength > size) {
304
304
  await audit('storage_size_mismatch', session?.userId, { item: item.id });
305
305
  error(400, 'Actual content length does not match header');
@@ -326,19 +326,19 @@ addRoute({
326
326
  addRoute({
327
327
  path: '/api/users/:id/storage',
328
328
  params: { id: z.uuid() },
329
- async OPTIONS(event) {
329
+ async OPTIONS(request, params) {
330
330
  if (!config.storage.enabled)
331
331
  error(503, 'User storage is disabled');
332
- const userId = event.params.id;
333
- await checkAuthForUser(event, userId);
332
+ const userId = params.id;
333
+ await checkAuthForUser(request, userId);
334
334
  const [usage, limits] = await Promise.all([currentUsage(userId), getLimits(userId)]).catch(withError('Could not fetch data'));
335
335
  return { usage, limits };
336
336
  },
337
- async GET(event) {
337
+ async GET(request, params) {
338
338
  if (!config.storage.enabled)
339
339
  error(503, 'User storage is disabled');
340
- const userId = event.params.id;
341
- await checkAuthForUser(event, userId);
340
+ const userId = params.id;
341
+ await checkAuthForUser(request, userId);
342
342
  const [items, usage, limits] = await Promise.all([
343
343
  database.selectFrom('storage').where('userId', '=', userId).where('trashedAt', 'is', null).selectAll().execute(),
344
344
  currentUsage(userId),
@@ -350,11 +350,11 @@ addRoute({
350
350
  addRoute({
351
351
  path: '/api/users/:id/storage/root',
352
352
  params: { id: z.uuid() },
353
- async GET(event) {
353
+ async GET(request, params) {
354
354
  if (!config.storage.enabled)
355
355
  error(503, 'User storage is disabled');
356
- const userId = event.params.id;
357
- await checkAuthForUser(event, userId);
356
+ const userId = params.id;
357
+ await checkAuthForUser(request, userId);
358
358
  const items = await database
359
359
  .selectFrom('storage')
360
360
  .where('userId', '=', userId)
@@ -376,11 +376,11 @@ function existsInACL(column, userId) {
376
376
  addRoute({
377
377
  path: '/api/users/:id/storage/shared',
378
378
  params: { id: z.uuid() },
379
- async GET(event) {
379
+ async GET(request, params) {
380
380
  if (!config.storage.enabled)
381
381
  error(503, 'User storage is disabled');
382
- const userId = event.params.id;
383
- await checkAuthForUser(event, userId);
382
+ const userId = params.id;
383
+ await checkAuthForUser(request, userId);
384
384
  const items = await database
385
385
  .selectFrom('storage as item')
386
386
  .selectAll('item')
@@ -395,11 +395,11 @@ addRoute({
395
395
  addRoute({
396
396
  path: '/api/users/:id/storage/trash',
397
397
  params: { id: z.uuid() },
398
- async GET(event) {
398
+ async GET(request, params) {
399
399
  if (!config.storage.enabled)
400
400
  error(503, 'User storage is disabled');
401
- const userId = event.params.id;
402
- await checkAuthForUser(event, userId);
401
+ const userId = params.id;
402
+ await checkAuthForUser(request, userId);
403
403
  const items = await database
404
404
  .selectFrom('storage')
405
405
  .where('userId', '=', userId)
package/lib/List.svelte CHANGED
@@ -1,11 +1,11 @@
1
1
  <script lang="ts">
2
2
  import { goto } from '$app/navigation';
3
3
  import { FormDialog, Icon } from '@axium/client/components';
4
+ import '@axium/client/styles/list';
4
5
  import { formatBytes } from '@axium/core/format';
5
6
  import { forMime as iconForMime } from '@axium/core/icons';
6
7
  import { getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
7
8
  import type { StorageItemMetadata } from '@axium/storage/common';
8
- import '../styles/list.css';
9
9
 
10
10
  let {
11
11
  items = $bindable(),
package/lib/Usage.svelte CHANGED
@@ -14,9 +14,11 @@
14
14
  <p>
15
15
  <a href="/files/usage">
16
16
  <NumberBar
17
- max={info.limits.user_size * 1_000_000}
17
+ max={info.limits.user_size && info.limits.user_size * 1_000_000}
18
18
  value={info.usage.bytes}
19
- text="Using {formatBytes(info.usage.bytes)} of {formatBytes(info.limits.user_size * 1_000_000)}"
19
+ text="Using {formatBytes(info.usage.bytes)} {!info.limits.user_size
20
+ ? ''
21
+ : 'of ' + formatBytes(info.limits.user_size * 1_000_000)}"
20
22
  />
21
23
  </a>
22
24
  </p>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@axium/storage",
3
- "version": "0.7.8",
4
- "author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
3
+ "version": "0.7.10",
4
+ "author": "James Prevett <axium@jamespre.dev>",
5
5
  "description": "User file storage for Axium",
6
6
  "funding": {
7
7
  "type": "individual",
@@ -27,20 +27,19 @@
27
27
  "./components/*": "./lib/*.svelte",
28
28
  "./styles/*": "./styles/*.css"
29
29
  },
30
- "routes": "routes",
31
30
  "files": [
32
31
  "dist",
33
32
  "lib",
34
- "routes",
35
- "styles"
33
+ "build",
34
+ "routes"
36
35
  ],
37
36
  "scripts": {
38
37
  "build": "tsc"
39
38
  },
40
39
  "peerDependencies": {
41
- "@axium/client": ">=0.4.4",
42
- "@axium/core": ">=0.7.0",
43
- "@axium/server": ">=0.23.0",
40
+ "@axium/client": ">=0.5.0",
41
+ "@axium/core": ">=0.8.0",
42
+ "@axium/server": ">=0.25.0",
44
43
  "@sveltejs/kit": "^2.27.3",
45
44
  "utilium": "^2.3.8"
46
45
  },
@@ -48,11 +47,16 @@
48
47
  "blakejs": "^1.2.1",
49
48
  "zod": "^4.0.5"
50
49
  },
51
- "apps": [
52
- {
53
- "id": "files",
54
- "name": "Files",
55
- "icon": "folders"
56
- }
57
- ]
50
+ "axium": {
51
+ "http_handler": "./build/handler.js",
52
+ "hooks": "./dist/hooks.js",
53
+ "routes": "./routes",
54
+ "apps": [
55
+ {
56
+ "id": "files",
57
+ "name": "Files",
58
+ "icon": "folders"
59
+ }
60
+ ]
61
+ }
58
62
  }
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
+ import { Icon } from '@axium/client/components';
3
+ import '@axium/client/styles/list';
2
4
  import { formatBytes } from '@axium/core/format';
3
5
  import { forMime as iconForMime } from '@axium/core/icons';
4
- import { Icon } from '@axium/client/components';
5
- import '@axium/storage/styles/list';
6
6
  import type { PageProps } from './$types';
7
7
 
8
8
  const { data }: PageProps = $props();
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
+ import { FormDialog, Icon } from '@axium/client/components';
3
+ import '@axium/client/styles/list';
2
4
  import { formatBytes } from '@axium/core/format';
3
5
  import { forMime as iconForMime } from '@axium/core/icons';
4
- import { FormDialog, Icon } from '@axium/client/components';
5
6
  import { deleteItem, updateItemMetadata } from '@axium/storage/client';
6
- import '@axium/storage/styles/list';
7
7
  import type { PageProps } from './$types';
8
8
 
9
9
  const { data }: PageProps = $props();
@@ -1,11 +1,8 @@
1
1
  <script lang="ts">
2
+ import { Icon, NumberBar } from '@axium/client/components';
2
3
  import { formatBytes } from '@axium/core/format';
3
- import { forMime } from '@axium/core/icons';
4
- import { FormDialog, Icon, NumberBar } from '@axium/client/components';
5
- import { deleteItem, updateItemMetadata } from '@axium/storage/client';
6
- import type { StorageItemUpdate } from '@axium/storage/common';
7
4
  import { StorageList } from '@axium/storage/components';
8
- import '@axium/storage/styles/list';
5
+ import '@axium/client/styles/list';
9
6
 
10
7
  const { data } = $props();
11
8
  const { limits } = data.info;
@@ -14,7 +11,9 @@
14
11
  const usage = $state(data.info.usage);
15
12
 
16
13
  let dialogs = $state<Record<string, HTMLDialogElement>>({});
17
- let barText = $derived(`Using ${formatBytes(usage?.bytes)} of ${formatBytes(limits.user_size * 1_000_000)}`);
14
+ let barText = $derived(
15
+ `Using ${formatBytes(usage?.bytes)} ${limits.user_size ? 'of ' + formatBytes(limits.user_size * 1_000_000) : ''}`
16
+ );
18
17
  </script>
19
18
 
20
19
  <svelte:head>
package/dist/plugin.d.ts DELETED
@@ -1,67 +0,0 @@
1
- import type { InitOptions, OpOptions } from '@axium/server/database';
2
- import './common.js';
3
- import './server.js';
4
- declare function statusText(): Promise<string>;
5
- declare function db_init(opt: InitOptions): Promise<void>;
6
- declare function db_wipe(opt: OpOptions): Promise<void>;
7
- declare function remove(opt: OpOptions): Promise<void>;
8
- declare function clean(opt: OpOptions): Promise<void>;
9
- declare const _default: {
10
- statusText: typeof statusText;
11
- hooks: {
12
- db_init: typeof db_init;
13
- db_wipe: typeof db_wipe;
14
- remove: typeof remove;
15
- clean: typeof clean;
16
- };
17
- name: string;
18
- version: string;
19
- author: string;
20
- description: string;
21
- funding: {
22
- type: string;
23
- url: string;
24
- };
25
- license: string;
26
- repository: {
27
- type: string;
28
- url: string;
29
- };
30
- homepage: string;
31
- bugs: {
32
- url: string;
33
- };
34
- type: string;
35
- main: string;
36
- types: string;
37
- exports: {
38
- ".": string;
39
- "./*": string;
40
- "./sidebar": string;
41
- "./components": string;
42
- "./components/*": string;
43
- "./styles/*": string;
44
- };
45
- routes: string;
46
- files: string[];
47
- scripts: {
48
- build: string;
49
- };
50
- peerDependencies: {
51
- "@axium/client": string;
52
- "@axium/core": string;
53
- "@axium/server": string;
54
- "@sveltejs/kit": string;
55
- utilium: string;
56
- };
57
- dependencies: {
58
- blakejs: string;
59
- zod: string;
60
- };
61
- apps: {
62
- id: string;
63
- name: string;
64
- icon: string;
65
- }[];
66
- };
67
- export default _default;
package/styles/list.css DELETED
@@ -1,50 +0,0 @@
1
- .list {
2
- display: flex;
3
- flex-direction: column;
4
- padding: 0.5em;
5
- }
6
-
7
- .list-header {
8
- font-weight: bold;
9
- border-bottom: 1.5px solid var(--fg-accent);
10
- }
11
-
12
- .list-item-container {
13
- text-decoration: none;
14
- color: inherit;
15
- }
16
-
17
- .list-item {
18
- display: grid;
19
- grid-template-columns: 1em 4fr 15em 5em repeat(3, 1em);
20
- align-items: center;
21
- gap: 1em;
22
- padding: 0.5em;
23
- overflow: hidden;
24
- text-wrap: nowrap;
25
- }
26
-
27
- .list-item:not(.list-header, :first-child) {
28
- border-top: 1px solid var(--fg-accent);
29
- }
30
-
31
- .list-item:not(.list-header):hover {
32
- background-color: var(--bg-alt);
33
- cursor: pointer;
34
- }
35
-
36
- p.list-empty {
37
- text-align: center;
38
- color: #888;
39
- margin-top: 1em;
40
- font-style: italic;
41
- }
42
-
43
- .list-item .action {
44
- visibility: hidden;
45
- }
46
-
47
- .list-item:hover .action {
48
- visibility: visible;
49
- cursor: pointer;
50
- }