@axium/storage 0.1.5 → 0.2.1

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/common.d.ts CHANGED
@@ -43,10 +43,10 @@ export declare const StorageItemUpdate: z.ZodObject<{
43
43
  owner: z.ZodOptional<z.ZodUUID>;
44
44
  trash: z.ZodOptional<z.ZodBoolean>;
45
45
  restrict: z.ZodOptional<z.ZodBoolean>;
46
- visibility: z.ZodOptional<z.ZodBoolean>;
46
+ publicPermission: z.ZodOptional<z.ZodNumber>;
47
47
  }, z.core.$strip>;
48
48
  export type StorageItemUpdate = z.infer<typeof StorageItemUpdate>;
49
- export interface StorageItemMetadata {
49
+ export interface StorageItemMetadata<T extends Record<string, unknown> = Record<string, unknown>> {
50
50
  createdAt: Date;
51
51
  dataURL?: string;
52
52
  hash: string;
@@ -56,10 +56,9 @@ export interface StorageItemMetadata {
56
56
  name: string;
57
57
  userId: string;
58
58
  parentId: string | null;
59
- /** Whether editing the file is restricted to the owner */
60
- restricted: boolean;
59
+ publicPermission: number;
61
60
  size: number;
62
61
  trashedAt: Date | null;
63
62
  type: string;
64
- visibility: number;
63
+ metadata?: T;
65
64
  }
package/dist/common.js CHANGED
@@ -8,6 +8,6 @@ export const StorageItemUpdate = z
8
8
  owner: z.uuid(),
9
9
  trash: z.boolean(),
10
10
  restrict: z.boolean(),
11
- visibility: z.boolean(),
11
+ publicPermission: z.number().min(0).max(5),
12
12
  })
13
13
  .partial();
package/dist/plugin.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { formatBytes } from '@axium/core/format';
1
2
  import config from '@axium/server/config';
2
3
  import { count, database, warnExists } from '@axium/server/database';
3
4
  import { done, start } from '@axium/server/io';
@@ -5,9 +6,8 @@ import { sql } from 'kysely';
5
6
  import pkg from '../package.json' with { type: 'json' };
6
7
  import './common.js';
7
8
  import './server.js';
8
- import { formatBytes } from '@axium/core/format';
9
9
  async function statusText() {
10
- const items = await count('storage');
10
+ const { storage: items } = await count('storage');
11
11
  const { size } = await database
12
12
  .selectFrom('storage')
13
13
  .select(eb => eb.fn.sum('size').as('size'))
@@ -30,7 +30,8 @@ async function db_init(opt, db) {
30
30
  .addColumn('name', 'text', col => col.defaultTo(null))
31
31
  .addColumn('type', 'text', col => col.notNull())
32
32
  .addColumn('immutable', 'boolean', col => col.notNull())
33
- .addColumn('visibility', 'integer', col => col.notNull().defaultTo(0))
33
+ .addColumn('publicPermission', 'integer', col => col.notNull().defaultTo(0))
34
+ .addColumn('metadata', 'jsonb', col => col.defaultTo('{}'))
34
35
  .execute()
35
36
  .then(done)
36
37
  .catch(warnExists);
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Schema } from '@axium/server/database';
2
- import type { Generated, Selectable } from 'kysely';
2
+ import type { ExpressionBuilder, Generated, Selectable } from 'kysely';
3
3
  import type { StorageItemMetadata, StorageLimits, StorageUsage } from './common.js';
4
4
  import './polyfills.js';
5
5
  declare module '@axium/server/database' {
@@ -17,9 +17,13 @@ declare module '@axium/server/database' {
17
17
  trashedAt: Date | null;
18
18
  type: string;
19
19
  userId: string;
20
- visibility: Generated<number>;
20
+ publicPermission: Generated<number>;
21
+ metadata: Generated<Record<string, unknown>>;
21
22
  };
22
23
  }
24
+ interface ExpectedSchema {
25
+ storage: ColumnTypes<Schema['storage']>;
26
+ }
23
27
  }
24
28
  declare module '@axium/server/config' {
25
29
  interface Config {
@@ -49,12 +53,15 @@ declare module '@axium/server/config' {
49
53
  export interface StorageItem extends StorageItemMetadata {
50
54
  data: Uint8Array<ArrayBufferLike>;
51
55
  }
52
- export declare function parseItem(item: Selectable<Schema['storage']>): StorageItemMetadata;
56
+ export declare function parseItem<T extends Record<string, unknown> = Record<string, unknown>>(item: Selectable<Schema['storage']> & {
57
+ hash: string;
58
+ }): StorageItemMetadata<T>;
53
59
  /**
54
60
  * Returns the current usage of the storage for a user in bytes.
55
61
  */
56
62
  export declare function currentUsage(userId: string): Promise<StorageUsage>;
57
- export declare function get(itemId: string): Promise<StorageItemMetadata>;
63
+ export declare function get<T extends Record<string, unknown> = Record<string, unknown>>(itemId: string): Promise<StorageItemMetadata<T>>;
64
+ export declare function withEncodedHash(eb: ExpressionBuilder<Schema, 'storage'>): import("kysely").AliasedExpression<string, "hash">;
58
65
  export type ExternalLimitHandler = (userId?: string) => StorageLimits | Promise<StorageLimits>;
59
66
  /**
60
67
  * Define the handler to get limits for a user externally.
package/dist/server.js CHANGED
@@ -1,8 +1,9 @@
1
- import { getSessionAndUser } from '@axium/server/auth';
1
+ import { Permission } from '@axium/core';
2
+ import { checkAuthForItem, checkAuthForUser, getSessionAndUser } from '@axium/server/auth';
2
3
  import { addConfigDefaults, config } from '@axium/server/config';
3
- import { connect, database } from '@axium/server/database';
4
+ import { connect, database, expectedTypes } from '@axium/server/database';
4
5
  import { dirs } from '@axium/server/io';
5
- import { checkAuth, getToken, parseBody, withError } from '@axium/server/requests';
6
+ import { getToken, parseBody, withError } from '@axium/server/requests';
6
7
  import { addRoute } from '@axium/server/routes';
7
8
  import { error } from '@sveltejs/kit';
8
9
  import { createHash } from 'node:crypto';
@@ -12,6 +13,22 @@ import { join } from 'node:path/posix';
12
13
  import * as z from 'zod';
13
14
  import { StorageItemUpdate } from './common.js';
14
15
  import './polyfills.js';
16
+ expectedTypes.storage = {
17
+ createdAt: { type: 'timestamptz', required: true, hasDefault: true },
18
+ hash: { type: 'bytea', required: true },
19
+ id: { type: 'uuid', required: true, hasDefault: true },
20
+ immutable: { type: 'bool', required: true, hasDefault: true },
21
+ modifiedAt: { type: 'timestamptz', required: true, hasDefault: true },
22
+ name: { type: 'text' },
23
+ parentId: { type: 'uuid' },
24
+ restricted: { type: 'bool', required: true, hasDefault: true },
25
+ size: { type: 'int4', required: true },
26
+ trashedAt: { type: 'timestampz' },
27
+ type: { type: 'text', required: true },
28
+ userId: { type: 'uuid', required: true, hasDefault: true },
29
+ publicPermission: { type: 'int4', required: true, hasDefault: true },
30
+ metadata: { type: 'jsonb', required: true, hasDefault: true },
31
+ };
15
32
  const defaultCASMime = [/video\/.*/, /audio\/.*/];
16
33
  addConfigDefaults({
17
34
  storage: {
@@ -34,7 +51,7 @@ addConfigDefaults({
34
51
  export function parseItem(item) {
35
52
  return {
36
53
  ...item,
37
- hash: item.hash.toHex(),
54
+ metadata: item.metadata,
38
55
  dataURL: `/raw/storage/${item.id}`,
39
56
  };
40
57
  }
@@ -53,9 +70,17 @@ export async function currentUsage(userId) {
53
70
  }
54
71
  export async function get(itemId) {
55
72
  connect();
56
- const result = await database.selectFrom('storage').where('id', '=', itemId).selectAll().executeTakeFirstOrThrow();
73
+ const result = await database
74
+ .selectFrom('storage')
75
+ .where('id', '=', itemId)
76
+ .selectAll()
77
+ .select(withEncodedHash)
78
+ .executeTakeFirstOrThrow();
57
79
  return parseItem(result);
58
80
  }
81
+ export function withEncodedHash(eb) {
82
+ return eb.fn('encode', ['hash', eb.val('hex')]).as('hash');
83
+ }
59
84
  let _getLimits = null;
60
85
  /**
61
86
  * Define the handler to get limits for a user externally.
@@ -78,10 +103,7 @@ addRoute({
78
103
  if (!config.storage.enabled)
79
104
  error(503, 'User storage is disabled');
80
105
  const itemId = event.params.id;
81
- const item = await get(itemId);
82
- if (!item)
83
- error(404, 'Item not found');
84
- await checkAuth(event, item.userId);
106
+ const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Read);
85
107
  return item;
86
108
  },
87
109
  async PATCH(event) {
@@ -89,13 +111,10 @@ addRoute({
89
111
  error(503, 'User storage is disabled');
90
112
  const itemId = event.params.id;
91
113
  const body = await parseBody(event, StorageItemUpdate);
92
- const item = await get(itemId);
93
- if (!item)
94
- error(404, 'Item not found');
95
- await checkAuth(event, item.userId);
114
+ await checkAuthForItem(event, 'storage', itemId, Permission.Manage);
96
115
  const values = {};
97
- if ('restrict' in body)
98
- values.restricted = body.restrict;
116
+ if ('publicPermission' in body)
117
+ values.publicPermission = body.publicPermission;
99
118
  if ('trash' in body)
100
119
  values.trashedAt = body.trash ? new Date() : null;
101
120
  if ('owner' in body)
@@ -109,6 +128,7 @@ addRoute({
109
128
  .where('id', '=', itemId)
110
129
  .set(values)
111
130
  .returningAll()
131
+ .returning(withEncodedHash)
112
132
  .executeTakeFirstOrThrow()
113
133
  .catch(withError('Could not update item')));
114
134
  },
@@ -116,10 +136,7 @@ addRoute({
116
136
  if (!config.storage.enabled)
117
137
  error(503, 'User storage is disabled');
118
138
  const itemId = event.params.id;
119
- const item = await get(itemId);
120
- if (!item)
121
- error(404, 'Item not found');
122
- await checkAuth(event, item.userId);
139
+ const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Manage);
123
140
  await database
124
141
  .deleteFrom('storage')
125
142
  .where('id', '=', itemId)
@@ -143,10 +160,7 @@ addRoute({
143
160
  if (!config.storage.enabled)
144
161
  error(503, 'User storage is disabled');
145
162
  const itemId = event.params.id;
146
- const item = await get(itemId);
147
- if (!item)
148
- error(404, 'Item not found');
149
- await checkAuth(event, item.userId);
163
+ const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Read);
150
164
  if (item.type != 'inode/directory')
151
165
  error(409, 'Item is not a directory');
152
166
  const items = await database
@@ -154,6 +168,7 @@ addRoute({
154
168
  .where('parentId', '=', itemId)
155
169
  .where('trashedAt', '!=', null)
156
170
  .selectAll()
171
+ .select(withEncodedHash)
157
172
  .execute();
158
173
  return items.map(parseItem);
159
174
  },
@@ -202,6 +217,7 @@ addRoute({
202
217
  .insertInto('storage')
203
218
  .values({ userId: userId, hash, name, size, type, immutable: useCAS, parentId })
204
219
  .returningAll()
220
+ .returning(withEncodedHash)
205
221
  .executeTakeFirstOrThrow()
206
222
  .catch(withError('Could not create item'));
207
223
  const path = join(config.storage.data, result.id);
@@ -230,10 +246,7 @@ addRoute({
230
246
  if (!config.storage.enabled)
231
247
  error(503, 'User storage is disabled');
232
248
  const itemId = event.params.id;
233
- const item = await get(itemId);
234
- if (!item)
235
- error(404, 'Item not found');
236
- await checkAuth(event, item.userId);
249
+ const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Read);
237
250
  if (item.trashedAt)
238
251
  error(410, 'Trashed items can not be downloaded');
239
252
  const content = new Uint8Array(readFileSync(join(config.storage.data, item.id)));
@@ -248,16 +261,11 @@ addRoute({
248
261
  if (!config.storage.enabled)
249
262
  error(503, 'User storage is disabled');
250
263
  const itemId = event.params.id;
251
- const item = await get(itemId);
252
- if (!item)
253
- error(404, 'Item not found');
254
- const { accessor } = await checkAuth(event, item.userId);
264
+ const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Edit);
255
265
  if (item.immutable)
256
266
  error(403, 'Item is immutable');
257
267
  if (item.trashedAt)
258
268
  error(410, 'Trashed items can not be changed');
259
- if (item.restricted && item.userId != accessor.id)
260
- error(403, 'Item editing is restricted to the owner');
261
269
  const type = event.request.headers.get('content-type') || 'application/octet-stream';
262
270
  // @todo: add this to the audit log
263
271
  if (type != item.type)
@@ -281,6 +289,7 @@ addRoute({
281
289
  .where('id', '=', itemId)
282
290
  .set({ size, modifiedAt: new Date(), hash })
283
291
  .returningAll()
292
+ .returning(withEncodedHash)
284
293
  .executeTakeFirstOrThrow()
285
294
  .catch(withError('Could not update item'));
286
295
  await writeFile(join(config.storage.data, result.id), content).catch(withError('Could not write'));
@@ -294,7 +303,7 @@ addRoute({
294
303
  if (!config.storage.enabled)
295
304
  error(503, 'User storage is disabled');
296
305
  const userId = event.params.id;
297
- await checkAuth(event, userId);
306
+ await checkAuthForUser(event, userId);
298
307
  const [usage, limits] = await Promise.all([currentUsage(userId), getLimits(userId)]).catch(withError('Could not fetch data'));
299
308
  return { usage, limits };
300
309
  },
@@ -302,9 +311,9 @@ addRoute({
302
311
  if (!config.storage.enabled)
303
312
  error(503, 'User storage is disabled');
304
313
  const userId = event.params.id;
305
- await checkAuth(event, userId);
314
+ await checkAuthForUser(event, userId);
306
315
  const [items, usage, limits] = await Promise.all([
307
- database.selectFrom('storage').where('userId', '=', userId).selectAll().execute(),
316
+ database.selectFrom('storage').where('userId', '=', userId).selectAll().select(withEncodedHash).execute(),
308
317
  currentUsage(userId),
309
318
  getLimits(userId),
310
319
  ]).catch(withError('Could not fetch data'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/storage",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
5
5
  "description": "User file storage for Axium",
6
6
  "funding": {
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@axium/client": ">=0.1.0",
35
- "@axium/core": ">=0.4.0",
36
- "@axium/server": ">=0.16.0",
35
+ "@axium/core": ">=0.5.0",
36
+ "@axium/server": ">=0.18.0",
37
37
  "@sveltejs/kit": "^2.23.0",
38
38
  "utilium": "^2.3.8"
39
39
  },