@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 +4 -5
- package/dist/common.js +1 -1
- package/dist/plugin.js +4 -3
- package/dist/server.d.ts +11 -4
- package/dist/server.js +45 -36
- package/package.json +3 -3
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
|
-
|
|
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
|
-
|
|
60
|
-
restricted: boolean;
|
|
59
|
+
publicPermission: number;
|
|
61
60
|
size: number;
|
|
62
61
|
trashedAt: Date | null;
|
|
63
62
|
type: string;
|
|
64
|
-
|
|
63
|
+
metadata?: T;
|
|
65
64
|
}
|
package/dist/common.js
CHANGED
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('
|
|
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
|
-
|
|
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']>
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 ('
|
|
98
|
-
values.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
36
|
-
"@axium/server": ">=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
|
},
|