@axium/storage 0.1.4 → 0.2.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/client.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { StorageItemMetadata, StorageItemUpdate, UserFilesInfo } from './common.js';
2
2
  import type { ItemSelection } from './selection.js';
3
+ export declare function parseItem(result: StorageItemMetadata): StorageItemMetadata;
3
4
  export declare function uploadItem(file: File): Promise<StorageItemMetadata>;
4
5
  export declare function updateItem(fileId: string, data: Blob): Promise<StorageItemMetadata>;
5
6
  export declare function getItemMetadata(fileId: string): Promise<StorageItemMetadata>;
package/dist/client.js CHANGED
@@ -22,7 +22,7 @@ async function _upload(method, url, data) {
22
22
  json.modifiedAt = new Date(json.modifiedAt);
23
23
  return json;
24
24
  }
25
- function _parse(result) {
25
+ export function parseItem(result) {
26
26
  result.createdAt = new Date(result.createdAt);
27
27
  result.modifiedAt = new Date(result.modifiedAt);
28
28
  if (result.trashedAt)
@@ -30,19 +30,19 @@ function _parse(result) {
30
30
  return result;
31
31
  }
32
32
  export async function uploadItem(file) {
33
- return _parse(await _upload('PUT', '/raw/storage', file));
33
+ return parseItem(await _upload('PUT', '/raw/storage', file));
34
34
  }
35
35
  export async function updateItem(fileId, data) {
36
- return _parse(await _upload('POST', '/raw/storage/' + fileId, data));
36
+ return parseItem(await _upload('POST', '/raw/storage/' + fileId, data));
37
37
  }
38
38
  export async function getItemMetadata(fileId) {
39
39
  const result = await fetchAPI('GET', 'storage/item/:id', undefined, fileId);
40
- return _parse(result);
40
+ return parseItem(result);
41
41
  }
42
42
  export async function getDirectoryMetadata(parentId) {
43
43
  const result = await fetchAPI('GET', 'storage/directory/:id', undefined, parentId);
44
44
  for (const item of result)
45
- _parse(item);
45
+ parseItem(item);
46
46
  return result;
47
47
  }
48
48
  export async function downloadItem(fileId) {
@@ -55,15 +55,15 @@ export async function downloadItem(fileId) {
55
55
  }
56
56
  export async function updateItemMetadata(fileId, metadata) {
57
57
  const result = await fetchAPI('PATCH', 'storage/item/:id', metadata, fileId);
58
- return _parse(result);
58
+ return parseItem(result);
59
59
  }
60
60
  export async function deleteItem(fileId) {
61
61
  const result = await fetchAPI('DELETE', 'storage/item/:id', undefined, fileId);
62
- return _parse(result);
62
+ return parseItem(result);
63
63
  }
64
64
  export async function getUserFiles(userId) {
65
65
  const result = await fetchAPI('GET', 'users/:id/storage', undefined, userId);
66
66
  for (const item of result.items)
67
- _parse(item);
67
+ parseItem(item);
68
68
  return result;
69
69
  }
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,6 +1,6 @@
1
1
  import { getSessionAndUser } from '@axium/server/auth';
2
2
  import { addConfigDefaults, config } from '@axium/server/config';
3
- import { connect, database } from '@axium/server/database';
3
+ import { connect, database, expectedTypes } from '@axium/server/database';
4
4
  import { dirs } from '@axium/server/io';
5
5
  import { checkAuth, getToken, parseBody, withError } from '@axium/server/requests';
6
6
  import { addRoute } from '@axium/server/routes';
@@ -12,6 +12,22 @@ import { join } from 'node:path/posix';
12
12
  import * as z from 'zod';
13
13
  import { StorageItemUpdate } from './common.js';
14
14
  import './polyfills.js';
15
+ expectedTypes.storage = {
16
+ createdAt: { type: 'timestamptz', required: true, hasDefault: true },
17
+ hash: { type: 'bytea', required: true },
18
+ id: { type: 'uuid', required: true, hasDefault: true },
19
+ immutable: { type: 'bool', required: true, hasDefault: true },
20
+ modifiedAt: { type: 'timestamptz', required: true, hasDefault: true },
21
+ name: { type: 'text' },
22
+ parentId: { type: 'uuid' },
23
+ restricted: { type: 'bool', required: true, hasDefault: true },
24
+ size: { type: 'int4', required: true },
25
+ trashedAt: { type: 'timestampz' },
26
+ type: { type: 'text', required: true },
27
+ userId: { type: 'uuid', required: true, hasDefault: true },
28
+ publicPermission: { type: 'int4', required: true, hasDefault: true },
29
+ metadata: { type: 'jsonb', required: true, hasDefault: true },
30
+ };
15
31
  const defaultCASMime = [/video\/.*/, /audio\/.*/];
16
32
  addConfigDefaults({
17
33
  storage: {
@@ -34,7 +50,7 @@ addConfigDefaults({
34
50
  export function parseItem(item) {
35
51
  return {
36
52
  ...item,
37
- hash: item.hash.toHex(),
53
+ metadata: item.metadata,
38
54
  dataURL: `/raw/storage/${item.id}`,
39
55
  };
40
56
  }
@@ -53,9 +69,17 @@ export async function currentUsage(userId) {
53
69
  }
54
70
  export async function get(itemId) {
55
71
  connect();
56
- const result = await database.selectFrom('storage').where('id', '=', itemId).selectAll().executeTakeFirstOrThrow();
72
+ const result = await database
73
+ .selectFrom('storage')
74
+ .where('id', '=', itemId)
75
+ .selectAll()
76
+ .select(withEncodedHash)
77
+ .executeTakeFirstOrThrow();
57
78
  return parseItem(result);
58
79
  }
80
+ export function withEncodedHash(eb) {
81
+ return eb.fn('encode', ['hash', eb.val('hex')]).as('hash');
82
+ }
59
83
  let _getLimits = null;
60
84
  /**
61
85
  * Define the handler to get limits for a user externally.
@@ -94,8 +118,8 @@ addRoute({
94
118
  error(404, 'Item not found');
95
119
  await checkAuth(event, item.userId);
96
120
  const values = {};
97
- if ('restrict' in body)
98
- values.restricted = body.restrict;
121
+ if ('publicPermission' in body)
122
+ values.publicPermission = body.publicPermission;
99
123
  if ('trash' in body)
100
124
  values.trashedAt = body.trash ? new Date() : null;
101
125
  if ('owner' in body)
@@ -109,6 +133,7 @@ addRoute({
109
133
  .where('id', '=', itemId)
110
134
  .set(values)
111
135
  .returningAll()
136
+ .returning(withEncodedHash)
112
137
  .executeTakeFirstOrThrow()
113
138
  .catch(withError('Could not update item')));
114
139
  },
@@ -154,6 +179,7 @@ addRoute({
154
179
  .where('parentId', '=', itemId)
155
180
  .where('trashedAt', '!=', null)
156
181
  .selectAll()
182
+ .select(withEncodedHash)
157
183
  .execute();
158
184
  return items.map(parseItem);
159
185
  },
@@ -202,6 +228,7 @@ addRoute({
202
228
  .insertInto('storage')
203
229
  .values({ userId: userId, hash, name, size, type, immutable: useCAS, parentId })
204
230
  .returningAll()
231
+ .returning(withEncodedHash)
205
232
  .executeTakeFirstOrThrow()
206
233
  .catch(withError('Could not create item'));
207
234
  const path = join(config.storage.data, result.id);
@@ -256,7 +283,7 @@ addRoute({
256
283
  error(403, 'Item is immutable');
257
284
  if (item.trashedAt)
258
285
  error(410, 'Trashed items can not be changed');
259
- if (item.restricted && item.userId != accessor.id)
286
+ if (item.userId != accessor.id)
260
287
  error(403, 'Item editing is restricted to the owner');
261
288
  const type = event.request.headers.get('content-type') || 'application/octet-stream';
262
289
  // @todo: add this to the audit log
@@ -281,6 +308,7 @@ addRoute({
281
308
  .where('id', '=', itemId)
282
309
  .set({ size, modifiedAt: new Date(), hash })
283
310
  .returningAll()
311
+ .returning(withEncodedHash)
284
312
  .executeTakeFirstOrThrow()
285
313
  .catch(withError('Could not update item'));
286
314
  await writeFile(join(config.storage.data, result.id), content).catch(withError('Could not write'));
@@ -304,7 +332,7 @@ addRoute({
304
332
  const userId = event.params.id;
305
333
  await checkAuth(event, userId);
306
334
  const [items, usage, limits] = await Promise.all([
307
- database.selectFrom('storage').where('userId', '=', userId).selectAll().execute(),
335
+ database.selectFrom('storage').where('userId', '=', userId).selectAll().select(withEncodedHash).execute(),
308
336
  currentUsage(userId),
309
337
  getLimits(userId),
310
338
  ]).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.4",
3
+ "version": "0.2.0",
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.17.0",
37
37
  "@sveltejs/kit": "^2.23.0",
38
38
  "utilium": "^2.3.8"
39
39
  },