@axium/storage 0.19.1 → 0.20.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/db.json +20 -0
- package/dist/client/cli.js +1 -1
- package/dist/client/local.d.ts +1 -1
- package/dist/common.d.ts +29 -27
- package/dist/common.js +4 -4
- package/dist/server/batch.js +5 -4
- package/dist/server/cli.js +2 -2
- package/dist/server/config.js +1 -1
- package/dist/server/db.js +5 -3
- package/dist/server/hooks.js +1 -1
- package/dist/server/item.d.ts +1 -1
- package/dist/server/item.js +11 -4
- package/dist/server/raw.js +23 -14
- package/lib/Add.svelte +0 -1
- package/lib/List.svelte +48 -5
- package/lib/Preview.svelte +14 -3
- package/lib/Usage.svelte +2 -2
- package/locales/en.json +1 -2
- package/package.json +3 -3
- package/routes/files/usage/+page.svelte +2 -2
package/db.json
CHANGED
|
@@ -79,6 +79,26 @@
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"delta": true,
|
|
85
|
+
"alter_tables": {
|
|
86
|
+
"storage": {
|
|
87
|
+
"add_constraints": {
|
|
88
|
+
"unique_name_parentId": { "type": "unique", "on": ["name", "parentId"] }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"delta": true,
|
|
95
|
+
"alter_tables": {
|
|
96
|
+
"storage": {
|
|
97
|
+
"alter_columns": {
|
|
98
|
+
"size": { "type": "bigint" }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
82
102
|
}
|
|
83
103
|
],
|
|
84
104
|
"wipe": ["storage", "acl.storage"],
|
package/dist/client/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ cli.command('usage')
|
|
|
26
26
|
.action(async () => {
|
|
27
27
|
const { limits, itemCount, usedBytes } = await api.getUserStats(session().userId);
|
|
28
28
|
console.log(`Items: ${itemCount} ${limits.user_items ? ' / ' + limits.user_items : ''}`);
|
|
29
|
-
console.log(`Space: ${formatBytes(usedBytes)} ${limits.user_size ? ' / ' + formatBytes(limits.user_size *
|
|
29
|
+
console.log(`Space: ${formatBytes(usedBytes)} ${limits.user_size ? ' / ' + formatBytes(limits.user_size * 1000000n) : ''}`);
|
|
30
30
|
});
|
|
31
31
|
cli.command('ls')
|
|
32
32
|
.alias('list')
|
package/dist/client/local.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ declare const StorageCache: z.ZodObject<{
|
|
|
14
14
|
name: z.ZodString;
|
|
15
15
|
userId: z.ZodUUID;
|
|
16
16
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
17
|
-
size: z.
|
|
17
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
18
18
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
19
19
|
type: z.ZodString;
|
|
20
20
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
package/dist/common.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const StorageItemMetadata: z.ZodObject<{
|
|
|
23
23
|
name: z.ZodString;
|
|
24
24
|
userId: z.ZodUUID;
|
|
25
25
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
26
|
-
size: z.
|
|
26
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
27
27
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
28
28
|
type: z.ZodString;
|
|
29
29
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -61,16 +61,18 @@ export declare const StorageItemSorting: z.ZodObject<{
|
|
|
61
61
|
descending: z.ZodOptional<z.ZodBoolean>;
|
|
62
62
|
by: z.ZodLiteral<"name" | "createdAt" | "modifiedAt" | "size">;
|
|
63
63
|
}, z.core.$strip>;
|
|
64
|
+
export interface StorageItemSorting extends z.infer<typeof StorageItemSorting> {
|
|
65
|
+
}
|
|
64
66
|
export declare const syncProtocolVersion = 0;
|
|
65
67
|
export declare const StorageLimits: z.ZodObject<{
|
|
66
68
|
item_size: z.ZodInt;
|
|
67
69
|
user_items: z.ZodInt;
|
|
68
|
-
user_size: z.
|
|
70
|
+
user_size: z.ZodCoercedBigInt<unknown>;
|
|
69
71
|
}, z.core.$strip>;
|
|
70
72
|
export interface StorageLimits extends z.infer<typeof StorageLimits> {
|
|
71
73
|
}
|
|
72
74
|
export declare const StorageStats: z.ZodObject<{
|
|
73
|
-
usedBytes: z.
|
|
75
|
+
usedBytes: z.ZodCoercedBigInt<unknown>;
|
|
74
76
|
itemCount: z.ZodInt;
|
|
75
77
|
lastModified: z.ZodCoercedDate<unknown>;
|
|
76
78
|
lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
@@ -81,9 +83,9 @@ export declare const UserStorageInfo: z.ZodObject<{
|
|
|
81
83
|
limits: z.ZodObject<{
|
|
82
84
|
item_size: z.ZodInt;
|
|
83
85
|
user_items: z.ZodInt;
|
|
84
|
-
user_size: z.
|
|
86
|
+
user_size: z.ZodCoercedBigInt<unknown>;
|
|
85
87
|
}, z.core.$strip>;
|
|
86
|
-
usedBytes: z.
|
|
88
|
+
usedBytes: z.ZodCoercedBigInt<unknown>;
|
|
87
89
|
itemCount: z.ZodInt;
|
|
88
90
|
lastModified: z.ZodCoercedDate<unknown>;
|
|
89
91
|
lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
@@ -101,7 +103,7 @@ export declare const UserStorage: z.ZodObject<{
|
|
|
101
103
|
name: z.ZodString;
|
|
102
104
|
userId: z.ZodUUID;
|
|
103
105
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
104
|
-
size: z.
|
|
106
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
105
107
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
106
108
|
type: z.ZodString;
|
|
107
109
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -135,9 +137,9 @@ export declare const UserStorage: z.ZodObject<{
|
|
|
135
137
|
limits: z.ZodObject<{
|
|
136
138
|
item_size: z.ZodInt;
|
|
137
139
|
user_items: z.ZodInt;
|
|
138
|
-
user_size: z.
|
|
140
|
+
user_size: z.ZodCoercedBigInt<unknown>;
|
|
139
141
|
}, z.core.$strip>;
|
|
140
|
-
usedBytes: z.
|
|
142
|
+
usedBytes: z.ZodCoercedBigInt<unknown>;
|
|
141
143
|
itemCount: z.ZodInt;
|
|
142
144
|
lastModified: z.ZodCoercedDate<unknown>;
|
|
143
145
|
lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
@@ -206,7 +208,7 @@ export declare const StorageConfig: z.ZodObject<{
|
|
|
206
208
|
limits: z.ZodObject<{
|
|
207
209
|
item_size: z.ZodInt;
|
|
208
210
|
user_items: z.ZodInt;
|
|
209
|
-
user_size: z.
|
|
211
|
+
user_size: z.ZodCoercedBigInt<unknown>;
|
|
210
212
|
}, z.core.$strip>;
|
|
211
213
|
trash_duration: z.ZodNumber;
|
|
212
214
|
temp_dir: z.ZodString;
|
|
@@ -219,7 +221,7 @@ declare module '@axium/core/plugins' {
|
|
|
219
221
|
}
|
|
220
222
|
export declare const StorageItemInit: z.ZodObject<{
|
|
221
223
|
name: z.ZodString;
|
|
222
|
-
size: z.
|
|
224
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
223
225
|
type: z.ZodString;
|
|
224
226
|
parentId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
225
227
|
hash: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
|
|
@@ -247,7 +249,7 @@ export declare const UploadInitResult: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
247
249
|
name: z.ZodString;
|
|
248
250
|
userId: z.ZodUUID;
|
|
249
251
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
250
|
-
size: z.
|
|
252
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
251
253
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
252
254
|
type: z.ZodString;
|
|
253
255
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -286,9 +288,9 @@ declare const StorageAPI: {
|
|
|
286
288
|
limits: z.ZodObject<{
|
|
287
289
|
item_size: z.ZodInt;
|
|
288
290
|
user_items: z.ZodInt;
|
|
289
|
-
user_size: z.
|
|
291
|
+
user_size: z.ZodCoercedBigInt<unknown>;
|
|
290
292
|
}, z.core.$strip>;
|
|
291
|
-
usedBytes: z.
|
|
293
|
+
usedBytes: z.ZodCoercedBigInt<unknown>;
|
|
292
294
|
itemCount: z.ZodInt;
|
|
293
295
|
lastModified: z.ZodCoercedDate<unknown>;
|
|
294
296
|
lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
@@ -309,7 +311,7 @@ declare const StorageAPI: {
|
|
|
309
311
|
name: z.ZodString;
|
|
310
312
|
userId: z.ZodUUID;
|
|
311
313
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
312
|
-
size: z.
|
|
314
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
313
315
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
314
316
|
type: z.ZodString;
|
|
315
317
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -343,9 +345,9 @@ declare const StorageAPI: {
|
|
|
343
345
|
limits: z.ZodObject<{
|
|
344
346
|
item_size: z.ZodInt;
|
|
345
347
|
user_items: z.ZodInt;
|
|
346
|
-
user_size: z.
|
|
348
|
+
user_size: z.ZodCoercedBigInt<unknown>;
|
|
347
349
|
}, z.core.$strip>;
|
|
348
|
-
usedBytes: z.
|
|
350
|
+
usedBytes: z.ZodCoercedBigInt<unknown>;
|
|
349
351
|
itemCount: z.ZodInt;
|
|
350
352
|
lastModified: z.ZodCoercedDate<unknown>;
|
|
351
353
|
lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
@@ -362,7 +364,7 @@ declare const StorageAPI: {
|
|
|
362
364
|
name: z.ZodString;
|
|
363
365
|
userId: z.ZodUUID;
|
|
364
366
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
365
|
-
size: z.
|
|
367
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
366
368
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
367
369
|
type: z.ZodString;
|
|
368
370
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -405,7 +407,7 @@ declare const StorageAPI: {
|
|
|
405
407
|
name: z.ZodString;
|
|
406
408
|
userId: z.ZodUUID;
|
|
407
409
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
408
|
-
size: z.
|
|
410
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
409
411
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
410
412
|
type: z.ZodString;
|
|
411
413
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -448,7 +450,7 @@ declare const StorageAPI: {
|
|
|
448
450
|
name: z.ZodString;
|
|
449
451
|
userId: z.ZodUUID;
|
|
450
452
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
451
|
-
size: z.
|
|
453
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
452
454
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
453
455
|
type: z.ZodString;
|
|
454
456
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -493,7 +495,7 @@ declare const StorageAPI: {
|
|
|
493
495
|
}, z.core.$strip>;
|
|
494
496
|
readonly PUT: readonly [z.ZodObject<{
|
|
495
497
|
name: z.ZodString;
|
|
496
|
-
size: z.
|
|
498
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
497
499
|
type: z.ZodString;
|
|
498
500
|
parentId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
499
501
|
hash: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
|
|
@@ -518,7 +520,7 @@ declare const StorageAPI: {
|
|
|
518
520
|
name: z.ZodString;
|
|
519
521
|
userId: z.ZodUUID;
|
|
520
522
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
521
|
-
size: z.
|
|
523
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
522
524
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
523
525
|
type: z.ZodString;
|
|
524
526
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -573,7 +575,7 @@ declare const StorageAPI: {
|
|
|
573
575
|
name: z.ZodString;
|
|
574
576
|
userId: z.ZodUUID;
|
|
575
577
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
576
|
-
size: z.
|
|
578
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
577
579
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
578
580
|
type: z.ZodString;
|
|
579
581
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -618,7 +620,7 @@ declare const StorageAPI: {
|
|
|
618
620
|
name: z.ZodString;
|
|
619
621
|
userId: z.ZodUUID;
|
|
620
622
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
621
|
-
size: z.
|
|
623
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
622
624
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
623
625
|
type: z.ZodString;
|
|
624
626
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -659,7 +661,7 @@ declare const StorageAPI: {
|
|
|
659
661
|
name: z.ZodString;
|
|
660
662
|
userId: z.ZodUUID;
|
|
661
663
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
662
|
-
size: z.
|
|
664
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
663
665
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
664
666
|
type: z.ZodString;
|
|
665
667
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -704,7 +706,7 @@ declare const StorageAPI: {
|
|
|
704
706
|
name: z.ZodString;
|
|
705
707
|
userId: z.ZodUUID;
|
|
706
708
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
707
|
-
size: z.
|
|
709
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
708
710
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
709
711
|
type: z.ZodString;
|
|
710
712
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -747,7 +749,7 @@ declare const StorageAPI: {
|
|
|
747
749
|
name: z.ZodString;
|
|
748
750
|
userId: z.ZodUUID;
|
|
749
751
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
750
|
-
size: z.
|
|
752
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
751
753
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
752
754
|
type: z.ZodString;
|
|
753
755
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -790,7 +792,7 @@ declare const StorageAPI: {
|
|
|
790
792
|
name: z.ZodString;
|
|
791
793
|
userId: z.ZodUUID;
|
|
792
794
|
parentId: z.ZodNullable<z.ZodUUID>;
|
|
793
|
-
size: z.
|
|
795
|
+
size: z.ZodCoercedBigInt<unknown>;
|
|
794
796
|
trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
795
797
|
type: z.ZodString;
|
|
796
798
|
metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
package/dist/common.js
CHANGED
|
@@ -26,7 +26,7 @@ export const StorageItemMetadata = z.object({
|
|
|
26
26
|
name: z.string(),
|
|
27
27
|
userId: z.uuid(),
|
|
28
28
|
parentId: z.uuid().nullable(),
|
|
29
|
-
size: z.
|
|
29
|
+
size: z.coerce.bigint().nonnegative(),
|
|
30
30
|
trashedAt: z.coerce.date().nullable(),
|
|
31
31
|
type: z.string(),
|
|
32
32
|
metadata: z.record(z.string(), z.unknown()),
|
|
@@ -44,10 +44,10 @@ export const StorageLimits = z.object({
|
|
|
44
44
|
/** Maximum number of items per user */
|
|
45
45
|
user_items: z.int().nonnegative(),
|
|
46
46
|
/** The maximum storage size per user in MB */
|
|
47
|
-
user_size: z.
|
|
47
|
+
user_size: z.coerce.bigint().nonnegative(),
|
|
48
48
|
});
|
|
49
49
|
export const StorageStats = z.object({
|
|
50
|
-
usedBytes: z.
|
|
50
|
+
usedBytes: z.coerce.bigint().nonnegative(),
|
|
51
51
|
itemCount: z.int().nonnegative(),
|
|
52
52
|
lastModified: z.coerce.date(),
|
|
53
53
|
lastTrashed: z.coerce.date().nullable(),
|
|
@@ -126,7 +126,7 @@ export const StorageConfig = StoragePublicConfig.safeExtend({
|
|
|
126
126
|
setServerConfig('@axium/storage', StorageConfig);
|
|
127
127
|
export const StorageItemInit = z.object({
|
|
128
128
|
name: z.string(),
|
|
129
|
-
size: z.
|
|
129
|
+
size: z.coerce.bigint().nonnegative(),
|
|
130
130
|
type: z.string(),
|
|
131
131
|
parentId: z.uuid().nullish(),
|
|
132
132
|
hash: z.hex().nullish(),
|
package/dist/server/batch.js
CHANGED
|
@@ -25,8 +25,8 @@ addRoute({
|
|
|
25
25
|
const batchHeaderSize = Number(req.headers.get('x-batch-header-size'));
|
|
26
26
|
if (!Number.isSafeInteger(batchHeaderSize) || batchHeaderSize < 2)
|
|
27
27
|
error(400, 'Invalid or missing header, X-Batch-Header-Size');
|
|
28
|
-
const size =
|
|
29
|
-
if (
|
|
28
|
+
const size = BigInt(req.headers.get('content-length') || '-1');
|
|
29
|
+
if (size < 0n)
|
|
30
30
|
error(411, 'Missing or invalid content length header');
|
|
31
31
|
const raw = await req.bytes();
|
|
32
32
|
if (raw.byteLength - batchHeaderSize > size) {
|
|
@@ -77,7 +77,8 @@ addRoute({
|
|
|
77
77
|
acl.check(item.acl, changedIds.has(item.id) ? { write: true } : { manage: true });
|
|
78
78
|
error(403, 'Missing permission for item: ' + item.id);
|
|
79
79
|
}
|
|
80
|
-
if (limits.user_size &&
|
|
80
|
+
if (limits.user_size &&
|
|
81
|
+
(usage.usedBytes + size - items.reduce((sum, item) => sum + item.size, 0n)) / 1000000n >= limits.user_size)
|
|
81
82
|
error(413, 'Not enough space');
|
|
82
83
|
const tx = await database.startTransaction().execute();
|
|
83
84
|
const results = new Map();
|
|
@@ -88,7 +89,7 @@ addRoute({
|
|
|
88
89
|
const result = await tx
|
|
89
90
|
.updateTable('storage')
|
|
90
91
|
.where('id', '=', itemId)
|
|
91
|
-
.set({ size, modifiedAt: new Date(), hash })
|
|
92
|
+
.set({ size: BigInt(size), modifiedAt: new Date(), hash })
|
|
92
93
|
.returningAll()
|
|
93
94
|
.executeTakeFirstOrThrow();
|
|
94
95
|
writeFileSync(join(getConfig('@axium/storage').data, result.id), content);
|
package/dist/server/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ cli.command('usage')
|
|
|
15
15
|
.selectFrom('storage')
|
|
16
16
|
.select(eb => eb.fn.sum('size').as('size'))
|
|
17
17
|
.executeTakeFirstOrThrow();
|
|
18
|
-
console.log(`${items} items totaling ${formatBytes(
|
|
18
|
+
console.log(`${items} items totaling ${formatBytes(BigInt(size))}`);
|
|
19
19
|
});
|
|
20
20
|
const _byteSize = (msg) => (v) => parseByteSize(v) ?? io.exit(msg);
|
|
21
21
|
cli.command('query')
|
|
@@ -48,7 +48,7 @@ cli.command('query')
|
|
|
48
48
|
}
|
|
49
49
|
let validMinSize = false;
|
|
50
50
|
if (opt.minSize !== undefined) {
|
|
51
|
-
if (opt.minSize ==
|
|
51
|
+
if (opt.minSize == 0n)
|
|
52
52
|
io.warn('Minimum size of 0 has no effect, ignoring.');
|
|
53
53
|
else {
|
|
54
54
|
query = query.where('size', '>=', opt.minSize);
|
package/dist/server/config.js
CHANGED
package/dist/server/db.js
CHANGED
|
@@ -25,9 +25,11 @@ export async function getUserStats(userId) {
|
|
|
25
25
|
eb.fn.max('trashedAt').as('lastTrashed'),
|
|
26
26
|
])
|
|
27
27
|
.executeTakeFirstOrThrow();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
return {
|
|
29
|
+
...result,
|
|
30
|
+
usedBytes: BigInt(result.usedBytes || 0n),
|
|
31
|
+
itemCount: Number(result.itemCount),
|
|
32
|
+
};
|
|
31
33
|
}
|
|
32
34
|
export async function get(itemId) {
|
|
33
35
|
const result = await database
|
package/dist/server/hooks.js
CHANGED
|
@@ -12,7 +12,7 @@ export function load() {
|
|
|
12
12
|
export async function statusText() {
|
|
13
13
|
const { storage: items } = await count('storage');
|
|
14
14
|
const size = await getTotalUse();
|
|
15
|
-
return `${items} items totaling ${formatBytes(
|
|
15
|
+
return `${items} items totaling ${formatBytes(size)}`;
|
|
16
16
|
}
|
|
17
17
|
export async function clean(opt) {
|
|
18
18
|
start('Removing expired trash items');
|
package/dist/server/item.d.ts
CHANGED
package/dist/server/item.js
CHANGED
|
@@ -31,11 +31,11 @@ export async function checkNewItem(init, session) {
|
|
|
31
31
|
: null;
|
|
32
32
|
if (parentId)
|
|
33
33
|
await authSessionForItem('storage', parentId, { write: true }, session);
|
|
34
|
-
if (
|
|
34
|
+
if (BigInt(size) < 0n)
|
|
35
35
|
error(411, 'Missing or invalid content length');
|
|
36
36
|
if (limits.user_items && usage.itemCount >= limits.user_items)
|
|
37
37
|
error(409, 'Too many items');
|
|
38
|
-
if (limits.user_size && (usage.usedBytes + size) /
|
|
38
|
+
if (limits.user_size && (usage.usedBytes + size) / 1000000n >= limits.user_size)
|
|
39
39
|
error(413, 'Not enough space');
|
|
40
40
|
if (limits.item_size && size > limits.item_size * 1_000_000)
|
|
41
41
|
error(413, 'File size exceeds maximum size');
|
|
@@ -77,7 +77,14 @@ export async function createNewItem(init, userId, writeContent) {
|
|
|
77
77
|
hash,
|
|
78
78
|
})
|
|
79
79
|
.returningAll()
|
|
80
|
-
.executeTakeFirstOrThrow()
|
|
80
|
+
.executeTakeFirstOrThrow()
|
|
81
|
+
.catch(e => {
|
|
82
|
+
if (!(e instanceof Error))
|
|
83
|
+
throw e;
|
|
84
|
+
if (e.message.includes('unique_name_parentId') && e.message.includes('duplicate'))
|
|
85
|
+
error(409, 'A file with that name already exists in this folder.');
|
|
86
|
+
throw e;
|
|
87
|
+
}));
|
|
81
88
|
const path = join(dataDir, item.id);
|
|
82
89
|
if (existing)
|
|
83
90
|
linkSync(join(dataDir, existing.id), path);
|
|
@@ -113,7 +120,7 @@ export function startUpload(init, session) {
|
|
|
113
120
|
hash: createHash('BLAKE2b512'),
|
|
114
121
|
file,
|
|
115
122
|
fd,
|
|
116
|
-
uploadedBytes:
|
|
123
|
+
uploadedBytes: 0n,
|
|
117
124
|
sessionId: session.id,
|
|
118
125
|
userId: session.userId,
|
|
119
126
|
init,
|
package/dist/server/raw.js
CHANGED
|
@@ -64,6 +64,15 @@ import '../polyfills.js';
|
|
|
64
64
|
import { getLimits } from './config.js';
|
|
65
65
|
import { getUserStats, parseItem } from './db.js';
|
|
66
66
|
import { checkNewItem, createNewItem, requireUpload } from './item.js';
|
|
67
|
+
function contentDispositionFor(name) {
|
|
68
|
+
const fallback = name
|
|
69
|
+
.replace(/[\r\n]/g, '')
|
|
70
|
+
.replace(/[^\x20-\x7E]/g, '_')
|
|
71
|
+
.trim()
|
|
72
|
+
.replace(/[\\"]/g, '\\$&') || 'download';
|
|
73
|
+
const encoded = encodeURIComponent(name.replace(/[\r\n]/g, '')).replace(/['()*]/g, char => '%' + char.charCodeAt(0).toString(16).toUpperCase());
|
|
74
|
+
return `attachment; filename="${fallback}"; filename*=UTF-8''${encoded}`;
|
|
75
|
+
}
|
|
67
76
|
addRoute({
|
|
68
77
|
path: '/raw/storage',
|
|
69
78
|
async PUT(request) {
|
|
@@ -73,7 +82,7 @@ addRoute({
|
|
|
73
82
|
const { userId } = session;
|
|
74
83
|
const name = request.headers.get('x-name'); // checked in `checkNewItem`
|
|
75
84
|
const parentId = request.headers.get('x-parent');
|
|
76
|
-
const size =
|
|
85
|
+
const size = BigInt(request.headers.get('x-size') || -1);
|
|
77
86
|
const type = request.headers.get('content-type') || 'application/octet-stream';
|
|
78
87
|
const content = await request.bytes();
|
|
79
88
|
if (content.byteLength > size) {
|
|
@@ -92,22 +101,22 @@ addRoute({
|
|
|
92
101
|
if (!getConfig('@axium/storage').enabled)
|
|
93
102
|
error(503, 'User storage is disabled');
|
|
94
103
|
const upload = await requireUpload(request);
|
|
95
|
-
const size =
|
|
96
|
-
if (
|
|
104
|
+
const size = BigInt(request.headers.get('content-length') || -1);
|
|
105
|
+
if (size < 0n)
|
|
97
106
|
error(411, 'Missing or invalid content length');
|
|
98
107
|
if (upload.uploadedBytes + size > upload.init.size)
|
|
99
108
|
error(413, 'Upload exceeds allowed size');
|
|
100
109
|
const content = await request.bytes();
|
|
101
|
-
if (content.byteLength != size) {
|
|
110
|
+
if (content.byteLength != Number(size)) {
|
|
102
111
|
await audit('storage_size_mismatch', upload.userId, { item: null });
|
|
103
112
|
error(400, `Content length mismatch: expected ${size}, got ${content.byteLength}`);
|
|
104
113
|
}
|
|
105
|
-
const offset =
|
|
114
|
+
const offset = BigInt(request.headers.get('x-offset') || -1);
|
|
106
115
|
if (offset != upload.uploadedBytes)
|
|
107
116
|
error(400, `Expected offset ${upload.uploadedBytes} but got ${offset}`);
|
|
108
117
|
writeSync(upload.fd, content); // opened with 'a', this appends
|
|
109
118
|
upload.hash.update(content);
|
|
110
|
-
upload.uploadedBytes += size;
|
|
119
|
+
upload.uploadedBytes += BigInt(size);
|
|
111
120
|
if (upload.uploadedBytes != upload.init.size)
|
|
112
121
|
return new Response(null, { status: 204 });
|
|
113
122
|
const hash = upload.hash.digest();
|
|
@@ -149,14 +158,14 @@ addRoute({
|
|
|
149
158
|
const range = request.headers.get('range');
|
|
150
159
|
const fd = openSync(path, 'r');
|
|
151
160
|
const _ = __addDisposableResource(env_1, { [Symbol.dispose]: () => closeSync(fd) }, false);
|
|
152
|
-
let start = 0, end = item.size -
|
|
161
|
+
let start = 0, end = Number(item.size - 1n), length = Number(item.size);
|
|
153
162
|
if (range) {
|
|
154
|
-
const [_start, _end =
|
|
163
|
+
const [_start, _end = end] = range
|
|
155
164
|
.replace(/bytes=/, '')
|
|
156
165
|
.split('-')
|
|
157
166
|
.map(val => (val && Number.isSafeInteger(parseInt(val)) ? parseInt(val) : undefined));
|
|
158
|
-
start = typeof _start == 'number' ? _start : item.size - _end;
|
|
159
|
-
end = typeof _start == 'number' ? _end :
|
|
167
|
+
start = typeof _start == 'number' ? _start : Number(item.size) - _end;
|
|
168
|
+
end = typeof _start == 'number' ? _end : end;
|
|
160
169
|
length = end - start + 1;
|
|
161
170
|
}
|
|
162
171
|
if (start >= item.size || end >= item.size || start > end || start < 0) {
|
|
@@ -168,13 +177,13 @@ addRoute({
|
|
|
168
177
|
const content = new Uint8Array(length);
|
|
169
178
|
readSync(fd, content, 0, length, start);
|
|
170
179
|
return new Response(content, {
|
|
171
|
-
status: length == item.size ? 200 : 206,
|
|
180
|
+
status: BigInt(length) == item.size ? 200 : 206,
|
|
172
181
|
headers: {
|
|
173
182
|
'Content-Range': `bytes ${start}-${end}/${item.size}`,
|
|
174
183
|
'Accept-Ranges': 'bytes',
|
|
175
184
|
'Content-Length': String(length),
|
|
176
185
|
'Content-Type': item.type,
|
|
177
|
-
'Content-Disposition':
|
|
186
|
+
'Content-Disposition': contentDispositionFor(item.name),
|
|
178
187
|
},
|
|
179
188
|
});
|
|
180
189
|
}
|
|
@@ -205,7 +214,7 @@ addRoute({
|
|
|
205
214
|
if (Number.isNaN(size))
|
|
206
215
|
error(411, 'Missing or invalid content length header');
|
|
207
216
|
const [usage, limits] = await Promise.all([getUserStats(item.userId), getLimits(item.userId)]).catch(withError('Could not fetch usage and/or limits'));
|
|
208
|
-
if (limits.user_size && (usage.usedBytes + size - item.size) /
|
|
217
|
+
if (limits.user_size && (usage.usedBytes + BigInt(size) - item.size) / 1000000n >= limits.user_size)
|
|
209
218
|
error(413, 'Not enough space');
|
|
210
219
|
if (limits.item_size && size > limits.item_size * 1_000_000)
|
|
211
220
|
error(413, 'File size exceeds maximum size');
|
|
@@ -220,7 +229,7 @@ addRoute({
|
|
|
220
229
|
const result = await tx
|
|
221
230
|
.updateTable('storage')
|
|
222
231
|
.where('id', '=', itemId)
|
|
223
|
-
.set({ size, modifiedAt: new Date(), hash })
|
|
232
|
+
.set({ size: BigInt(size), modifiedAt: new Date(), hash })
|
|
224
233
|
.returningAll()
|
|
225
234
|
.executeTakeFirstOrThrow();
|
|
226
235
|
writeFileSync(join(getConfig('@axium/storage').data, result.id), content);
|
package/lib/Add.svelte
CHANGED
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
<span class="menu-item" onclick={() => uploadDialog.showModal()}><Icon i="upload" />{text('storage.Add.upload')}</span>
|
|
39
39
|
{@render _item('inode/directory', text('storage.Add.new_folder'))}
|
|
40
40
|
{@render _item('text/plain', text('storage.Add.plain_text'))}
|
|
41
|
-
{@render _item('text/x-uri', text('storage.Add.url'), true)}
|
|
42
41
|
</Popover>
|
|
43
42
|
|
|
44
43
|
<FormDialog
|
package/lib/List.svelte
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
import type { AccessControllable, UserPublic } from '@axium/core';
|
|
8
8
|
import { formatBytes } from '@axium/core/format';
|
|
9
9
|
import { forMime as iconForMime } from '@axium/core/icons';
|
|
10
|
+
import { errorText } from '@axium/core/io';
|
|
10
11
|
import { getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
|
|
11
12
|
import { copyShortURL, formatItemName } from '@axium/storage/client/frontend';
|
|
12
|
-
import type
|
|
13
|
+
import { StorageItemSorting, type StorageItemMetadata } from '@axium/storage/common';
|
|
13
14
|
import Preview from './Preview.svelte';
|
|
14
15
|
|
|
15
16
|
let {
|
|
@@ -23,6 +24,31 @@
|
|
|
23
24
|
const activeItem = $derived(items[activeIndex]);
|
|
24
25
|
const activeItemName = $derived(formatItemName(activeItem));
|
|
25
26
|
const dialogs = $state<Record<string, HTMLDialogElement>>({});
|
|
27
|
+
|
|
28
|
+
const search = new URLSearchParams(location.search);
|
|
29
|
+
let sort = $state<StorageItemSorting | null>();
|
|
30
|
+
try {
|
|
31
|
+
sort = StorageItemSorting.parse({
|
|
32
|
+
by: search.get('sortBy'),
|
|
33
|
+
descending: search.has('descending'),
|
|
34
|
+
});
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.log('Ignoring invalid sorting parameters', errorText(e));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const sortedItems = $derived(
|
|
40
|
+
items
|
|
41
|
+
.map((item, i) => [item, i] as const)
|
|
42
|
+
.toSorted(
|
|
43
|
+
sort
|
|
44
|
+
? ([_a], [_b]) => {
|
|
45
|
+
const [a, b] = sort?.descending ? [_b, _a] : [_a, _b];
|
|
46
|
+
// @ts-expect-error 2362 — `Date`s have a `valueOf` and can be treated like numbers
|
|
47
|
+
return sort.by == 'name' ? a.name.localeCompare(b.name) : a[sort.by] - b[sort.by];
|
|
48
|
+
}
|
|
49
|
+
: undefined
|
|
50
|
+
)
|
|
51
|
+
);
|
|
26
52
|
</script>
|
|
27
53
|
|
|
28
54
|
{#snippet action(name: string, icon: string, i: number, preview: boolean = false)}
|
|
@@ -40,11 +66,19 @@
|
|
|
40
66
|
<div class="list">
|
|
41
67
|
<div class="list-item list-header">
|
|
42
68
|
<span></span>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
69
|
+
{#each [['name', 'storage.generic.name'], ['modifiedAt', 'storage.List.last_modified'], ['size', 'storage.List.size']] as const as [key, translation]}
|
|
70
|
+
<span
|
|
71
|
+
class="header-column"
|
|
72
|
+
onclick={() => (sort = sort?.descending === false ? null : { by: key, descending: !sort?.descending })}
|
|
73
|
+
>
|
|
74
|
+
{#if sort?.by == key}
|
|
75
|
+
<Icon i="sort-{sort.descending ? 'down' : 'up'}" />
|
|
76
|
+
{/if}
|
|
77
|
+
<span>{text(translation)}</span>
|
|
78
|
+
</span>
|
|
79
|
+
{/each}
|
|
46
80
|
</div>
|
|
47
|
-
{#each
|
|
81
|
+
{#each sortedItems as [item, i] (item.id)}
|
|
48
82
|
<div
|
|
49
83
|
class="list-item"
|
|
50
84
|
onclick={async () => {
|
|
@@ -167,6 +201,15 @@
|
|
|
167
201
|
grid-template-columns: 1em 4fr 15em 5em repeat(4, 1em);
|
|
168
202
|
}
|
|
169
203
|
|
|
204
|
+
.header-column {
|
|
205
|
+
display: inline-flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
|
|
208
|
+
&:hover {
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
170
213
|
@media (width < 700px) {
|
|
171
214
|
.item-actions {
|
|
172
215
|
display: none;
|
package/lib/Preview.svelte
CHANGED
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
</div>
|
|
71
71
|
<div class="preview-content">
|
|
72
72
|
{#if item.type.startsWith('image/')}
|
|
73
|
-
<img src={item.dataURL} alt={item.name}
|
|
73
|
+
<img src={item.dataURL} alt={item.name} />
|
|
74
74
|
{:else if item.type.startsWith('audio/')}
|
|
75
75
|
<audio src={item.dataURL} controls></audio>
|
|
76
76
|
{:else if item.type.startsWith('video/')}
|
|
77
|
-
<video src={item.dataURL} controls
|
|
77
|
+
<video src={item.dataURL} controls>
|
|
78
78
|
<track kind="captions" />
|
|
79
79
|
</video>
|
|
80
80
|
{:else if item.type == 'application/pdf'}
|
|
@@ -195,7 +195,12 @@
|
|
|
195
195
|
|
|
196
196
|
.preview-content {
|
|
197
197
|
position: absolute;
|
|
198
|
-
inset: 3em 10em
|
|
198
|
+
inset: 3em 10em 1em;
|
|
199
|
+
display: flex;
|
|
200
|
+
text-align: center;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
flex-direction: column;
|
|
199
204
|
|
|
200
205
|
.full-fill {
|
|
201
206
|
position: absolute;
|
|
@@ -211,6 +216,12 @@
|
|
|
211
216
|
background-color: var(--bg-menu);
|
|
212
217
|
font-family: monospace;
|
|
213
218
|
}
|
|
219
|
+
|
|
220
|
+
img,
|
|
221
|
+
video {
|
|
222
|
+
max-width: 100%;
|
|
223
|
+
max-height: 100%;
|
|
224
|
+
}
|
|
214
225
|
}
|
|
215
226
|
|
|
216
227
|
.no-preview {
|
package/lib/Usage.svelte
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<p>
|
|
16
16
|
<a href="/files/usage">
|
|
17
17
|
<NumberBar
|
|
18
|
-
max={info.limits.user_size && info.limits.user_size *
|
|
18
|
+
max={info.limits.user_size && info.limits.user_size * 1_000_000n}
|
|
19
19
|
value={info.usedBytes}
|
|
20
20
|
text="{formatBytes(info.usedBytes)} {!info.limits.user_size
|
|
21
21
|
? ''
|
|
22
|
-
: '/ ' + formatBytes(info.limits.user_size *
|
|
22
|
+
: '/ ' + formatBytes(info.limits.user_size * 1_000_000n)}"
|
|
23
23
|
/>
|
|
24
24
|
</a>
|
|
25
25
|
</p>
|
package/locales/en.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/storage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev>",
|
|
5
5
|
"description": "User file storage for Axium",
|
|
6
6
|
"funding": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"build": "tsc"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@axium/client": ">=0.19.
|
|
44
|
-
"@axium/core": ">=0.
|
|
43
|
+
"@axium/client": ">=0.19.1",
|
|
44
|
+
"@axium/core": ">=0.22.0",
|
|
45
45
|
"@axium/server": ">=0.39.0",
|
|
46
46
|
"@sveltejs/kit": "^2.27.3",
|
|
47
47
|
"utilium": "^2.6.3"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
let barText = $derived(
|
|
14
14
|
limits.user_size
|
|
15
|
-
? text('page.files.usage.bar_text', { used: formatBytes(usedBytes), total: formatBytes(limits.user_size *
|
|
15
|
+
? text('page.files.usage.bar_text', { used: formatBytes(usedBytes), total: formatBytes(limits.user_size * 1_000_000n) })
|
|
16
16
|
: text('page.files.usage.bar_text_unlimited', { used: formatBytes(usedBytes) })
|
|
17
17
|
);
|
|
18
18
|
</script>
|
|
@@ -23,6 +23,6 @@
|
|
|
23
23
|
|
|
24
24
|
<h2>{text('page.files.usage.heading')}</h2>
|
|
25
25
|
|
|
26
|
-
<p><NumberBar max={limits.user_size *
|
|
26
|
+
<p><NumberBar max={limits.user_size * 1_000_000n} value={usedBytes} text={barText} /></p>
|
|
27
27
|
|
|
28
28
|
<List bind:items emptyText={text('page.files.usage.empty')} user={data.session?.user} />
|