@rebasepro/server-core 0.7.0 → 0.9.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/README.md +2 -2
- package/dist/api/ast-schema-editor.d.ts +21 -0
- package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
- package/dist/api/errors.d.ts +53 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/logs-routes.d.ts +37 -0
- package/dist/api/openapi-generator.d.ts +16 -0
- package/dist/api/rest/api-generator.d.ts +68 -0
- package/{src/api/rest/index.ts → dist/api/rest/index.d.ts} +0 -1
- package/dist/api/rest/query-parser.d.ts +6 -0
- package/dist/api/schema-editor-routes.d.ts +3 -0
- package/dist/api/types.d.ts +56 -0
- package/dist/auth/adapter-middleware.d.ts +43 -0
- package/dist/auth/admin-roles-route.d.ts +18 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/auth/admin-users-route.d.ts +28 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/{src/auth/api-keys/api-key-permission-guard.ts → dist/auth/api-keys/api-key-permission-guard.d.ts} +2 -34
- package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/auth/api-keys/api-key-store.d.ts +35 -0
- package/{src/auth/api-keys/api-key-types.ts → dist/auth/api-keys/api-key-types.d.ts} +0 -6
- package/{src/auth/api-keys/index.ts → dist/auth/api-keys/index.d.ts} +2 -22
- package/dist/auth/apple-oauth.d.ts +30 -0
- package/{src/auth/auth-hooks.ts → dist/auth/auth-hooks.d.ts} +15 -73
- package/dist/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/auth/builtin-auth-adapter.d.ts +55 -0
- package/dist/auth/cookie-utils.d.ts +32 -0
- package/{src/auth/crypto-utils.ts → dist/auth/crypto-utils.d.ts} +1 -16
- package/dist/auth/custom-auth-adapter.d.ts +39 -0
- package/dist/auth/discord-oauth.d.ts +14 -0
- package/dist/auth/facebook-oauth.d.ts +14 -0
- package/dist/auth/github-oauth.d.ts +15 -0
- package/dist/auth/gitlab-oauth.d.ts +13 -0
- package/dist/auth/google-oauth.d.ts +47 -0
- package/{src/auth/index.ts → dist/auth/index.d.ts} +4 -22
- package/{src/auth/interfaces.ts → dist/auth/interfaces.d.ts} +20 -89
- package/dist/auth/jwt.d.ts +67 -0
- package/dist/auth/linkedin-oauth.d.ts +18 -0
- package/dist/auth/magic-link-routes.d.ts +32 -0
- package/dist/auth/mfa-crypto.d.ts +23 -0
- package/dist/auth/mfa-routes.d.ts +7 -0
- package/dist/auth/mfa.d.ts +49 -0
- package/dist/auth/microsoft-oauth.d.ts +16 -0
- package/dist/auth/middleware.d.ts +158 -0
- package/dist/auth/password.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/rls-scope.d.ts +31 -0
- package/dist/auth/routes.d.ts +66 -0
- package/dist/auth/session-routes.d.ts +29 -0
- package/dist/auth/slack-oauth.d.ts +12 -0
- package/dist/auth/spotify-oauth.d.ts +12 -0
- package/dist/auth/twitter-oauth.d.ts +18 -0
- package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
- package/dist/collections/loader.d.ts +5 -0
- package/dist/cron/cron-loader.d.ts +18 -0
- package/dist/cron/cron-routes.d.ts +14 -0
- package/dist/cron/cron-scheduler.d.ts +106 -0
- package/dist/cron/cron-store.d.ts +32 -0
- package/dist/cron/define-cron.d.ts +28 -0
- package/{src/cron/index.ts → dist/cron/index.d.ts} +1 -0
- package/dist/db/interfaces.d.ts +18 -0
- package/dist/dynamic-import-Dvh-K5fl.js +22 -0
- package/dist/dynamic-import-Dvh-K5fl.js.map +1 -0
- package/dist/email/index.d.ts +6 -0
- package/dist/email/smtp-email-service.d.ts +30 -0
- package/dist/email/templates.d.ts +50 -0
- package/{src/email/types.ts → dist/email/types.d.ts} +40 -36
- package/dist/env.d.ts +102 -0
- package/dist/functions/define-function.d.ts +55 -0
- package/dist/functions/function-loader.d.ts +18 -0
- package/dist/functions/function-routes.d.ts +10 -0
- package/{src/functions/index.ts → dist/functions/index.d.ts} +2 -0
- package/dist/history/history-routes.d.ts +23 -0
- package/{src/history/index.ts → dist/history/index.d.ts} +0 -1
- package/dist/index.d.ts +35 -0
- package/dist/index.es.js +18184 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +48629 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/init/docs.d.ts +4 -0
- package/dist/init/health.d.ts +2 -0
- package/dist/init/middlewares.d.ts +10 -0
- package/dist/init/shutdown.d.ts +65 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/init.d.ts +344 -0
- package/dist/jwt-BwIn8xmk.js +4200 -0
- package/dist/jwt-BwIn8xmk.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-DnYXB-Wd.js +162 -0
- package/dist/ms-DnYXB-Wd.js.map +1 -0
- package/dist/openapi-generator-Z9oYWLf_.js +586 -0
- package/dist/openapi-generator-Z9oYWLf_.js.map +1 -0
- package/dist/schema-editor-routes-D3ef8zu1.js +219 -0
- package/dist/schema-editor-routes-D3ef8zu1.js.map +1 -0
- package/dist/serve-spa.d.ts +36 -0
- package/dist/services/driver-registry.d.ts +78 -0
- package/dist/services/routed-realtime-service.d.ts +43 -0
- package/dist/services/webhook-service.d.ts +29 -0
- package/dist/singleton.d.ts +51 -0
- package/dist/src-B4OLmNVa.js +437 -0
- package/dist/src-B4OLmNVa.js.map +1 -0
- package/dist/src-B5WkP1Rv.js +24778 -0
- package/dist/src-B5WkP1Rv.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +43 -0
- package/dist/storage/LocalStorageController.d.ts +46 -0
- package/dist/storage/S3StorageController.d.ts +40 -0
- package/dist/storage/image-transform.d.ts +50 -0
- package/{src/storage/index.ts → dist/storage/index.d.ts} +6 -21
- package/dist/storage/routes.d.ts +70 -0
- package/dist/storage/storage-registry.d.ts +78 -0
- package/dist/storage/tus-handler.d.ts +53 -0
- package/{src/storage/types.ts → dist/storage/types.d.ts} +32 -38
- package/dist/types/index.d.ts +11 -0
- package/dist/utils/dev-port.d.ts +45 -0
- package/dist/utils/dynamic-import.d.ts +25 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logging.d.ts +9 -0
- package/dist/utils/request-id.d.ts +4 -0
- package/dist/utils/request-logger.d.ts +19 -0
- package/{src/utils/sql.ts → dist/utils/sql.d.ts} +5 -16
- package/package.json +49 -22
- package/coverage/clover.xml +0 -3739
- package/coverage/coverage-final.json +0 -31
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -266
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +0 -952
- package/coverage/lcov-report/src/api/errors.ts.html +0 -472
- package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +0 -1069
- package/coverage/lcov-report/src/api/graphql/index.html +0 -116
- package/coverage/lcov-report/src/api/index.html +0 -176
- package/coverage/lcov-report/src/api/openapi-generator.ts.html +0 -565
- package/coverage/lcov-report/src/api/rest/api-generator.ts.html +0 -994
- package/coverage/lcov-report/src/api/rest/index.html +0 -131
- package/coverage/lcov-report/src/api/rest/query-parser.ts.html +0 -550
- package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +0 -202
- package/coverage/lcov-report/src/api/server.ts.html +0 -823
- package/coverage/lcov-report/src/auth/admin-routes.ts.html +0 -973
- package/coverage/lcov-report/src/auth/index.html +0 -176
- package/coverage/lcov-report/src/auth/jwt.ts.html +0 -574
- package/coverage/lcov-report/src/auth/middleware.ts.html +0 -745
- package/coverage/lcov-report/src/auth/password.ts.html +0 -310
- package/coverage/lcov-report/src/auth/services.ts.html +0 -2074
- package/coverage/lcov-report/src/collections/index.html +0 -116
- package/coverage/lcov-report/src/collections/loader.ts.html +0 -232
- package/coverage/lcov-report/src/db/auth-schema.ts.html +0 -523
- package/coverage/lcov-report/src/db/data-transformer.ts.html +0 -1753
- package/coverage/lcov-report/src/db/entityService.ts.html +0 -700
- package/coverage/lcov-report/src/db/index.html +0 -146
- package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +0 -4048
- package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +0 -883
- package/coverage/lcov-report/src/db/services/RelationService.ts.html +0 -3121
- package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +0 -442
- package/coverage/lcov-report/src/db/services/index.html +0 -176
- package/coverage/lcov-report/src/db/services/index.ts.html +0 -124
- package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +0 -1960
- package/coverage/lcov-report/src/index.html +0 -116
- package/coverage/lcov-report/src/services/driver-registry.ts.html +0 -631
- package/coverage/lcov-report/src/services/index.html +0 -131
- package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +0 -3025
- package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +0 -1189
- package/coverage/lcov-report/src/storage/S3StorageController.ts.html +0 -970
- package/coverage/lcov-report/src/storage/index.html +0 -161
- package/coverage/lcov-report/src/storage/storage-registry.ts.html +0 -646
- package/coverage/lcov-report/src/storage/types.ts.html +0 -451
- package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +0 -3082
- package/coverage/lcov-report/src/utils/index.html +0 -116
- package/coverage/lcov.info +0 -7179
- package/jest.config.cjs +0 -19
- package/src/api/ast-schema-editor.ts +0 -289
- package/src/api/collections_for_test/callbacks_test_collection.ts +0 -61
- package/src/api/errors.ts +0 -278
- package/src/api/graphql/graphql-schema-generator.ts +0 -383
- package/src/api/graphql/index.ts +0 -2
- package/src/api/index.ts +0 -11
- package/src/api/logs-routes.ts +0 -132
- package/src/api/openapi-generator.ts +0 -727
- package/src/api/rest/api-generator-count.test.ts +0 -126
- package/src/api/rest/api-generator.ts +0 -747
- package/src/api/rest/query-parser.ts +0 -300
- package/src/api/schema-editor-routes.ts +0 -41
- package/src/api/server.ts +0 -271
- package/src/api/types.ts +0 -104
- package/src/auth/adapter-middleware.ts +0 -114
- package/src/auth/admin-roles-route.ts +0 -36
- package/src/auth/admin-user-ops.ts +0 -236
- package/src/auth/admin-users-route.ts +0 -302
- package/src/auth/api-keys/api-key-middleware.ts +0 -133
- package/src/auth/api-keys/api-key-routes.ts +0 -193
- package/src/auth/api-keys/api-key-store.ts +0 -340
- package/src/auth/apple-oauth.ts +0 -122
- package/src/auth/bitbucket-oauth.ts +0 -84
- package/src/auth/builtin-auth-adapter.ts +0 -380
- package/src/auth/custom-auth-adapter.ts +0 -86
- package/src/auth/discord-oauth.ts +0 -85
- package/src/auth/facebook-oauth.ts +0 -74
- package/src/auth/github-oauth.ts +0 -112
- package/src/auth/gitlab-oauth.ts +0 -72
- package/src/auth/google-oauth.ts +0 -223
- package/src/auth/jwt.ts +0 -208
- package/src/auth/linkedin-oauth.ts +0 -83
- package/src/auth/magic-link-routes.ts +0 -167
- package/src/auth/mfa-crypto.ts +0 -91
- package/src/auth/mfa-routes.ts +0 -323
- package/src/auth/mfa.ts +0 -161
- package/src/auth/microsoft-oauth.ts +0 -90
- package/src/auth/middleware.ts +0 -369
- package/src/auth/password.ts +0 -77
- package/src/auth/rate-limiter.ts +0 -225
- package/src/auth/reset-password-admin.ts +0 -160
- package/src/auth/rls-scope.ts +0 -58
- package/src/auth/routes.ts +0 -802
- package/src/auth/session-routes.ts +0 -353
- package/src/auth/slack-oauth.ts +0 -73
- package/src/auth/spotify-oauth.ts +0 -69
- package/src/auth/twitter-oauth.ts +0 -128
- package/src/collections/BackendCollectionRegistry.ts +0 -20
- package/src/collections/loader.ts +0 -50
- package/src/cron/cron-loader.ts +0 -84
- package/src/cron/cron-routes.test.ts +0 -265
- package/src/cron/cron-routes.ts +0 -85
- package/src/cron/cron-scheduler.test.ts +0 -559
- package/src/cron/cron-scheduler.ts +0 -581
- package/src/cron/cron-store.ts +0 -166
- package/src/db/interfaces.ts +0 -60
- package/src/email/index.ts +0 -19
- package/src/email/smtp-email-service.ts +0 -123
- package/src/email/templates.ts +0 -470
- package/src/env.ts +0 -227
- package/src/functions/function-loader.ts +0 -116
- package/src/functions/function-routes.ts +0 -31
- package/src/history/history-routes.ts +0 -129
- package/src/index.ts +0 -70
- package/src/init/docs.ts +0 -47
- package/src/init/health.ts +0 -37
- package/src/init/middlewares.ts +0 -61
- package/src/init/shutdown.ts +0 -56
- package/src/init/storage.ts +0 -57
- package/src/init.ts +0 -1041
- package/src/serve-spa.ts +0 -89
- package/src/services/driver-registry.ts +0 -183
- package/src/services/routed-realtime-service.ts +0 -113
- package/src/services/webhook-service.ts +0 -155
- package/src/singleton.ts +0 -70
- package/src/storage/LocalStorageController.ts +0 -389
- package/src/storage/S3StorageController.ts +0 -300
- package/src/storage/image-transform.ts +0 -218
- package/src/storage/routes.ts +0 -368
- package/src/storage/storage-registry.ts +0 -188
- package/src/storage/tus-handler.ts +0 -315
- package/src/types/index.ts +0 -27
- package/src/utils/dev-port.ts +0 -196
- package/src/utils/logger.ts +0 -143
- package/src/utils/logging.ts +0 -40
- package/src/utils/request-id.ts +0 -40
- package/src/utils/request-logger.ts +0 -72
- package/test/api-generator.test.ts +0 -588
- package/test/api-key-permission-guard.test.ts +0 -144
- package/test/ast-schema-editor.test.ts +0 -89
- package/test/auth-middleware-hono.test.ts +0 -556
- package/test/auth-routes.test.ts +0 -1098
- package/test/backend-hooks-data.test.ts +0 -477
- package/test/custom-auth-adapter.test.ts +0 -189
- package/test/driver-registry.test.ts +0 -282
- package/test/email-templates.test.ts +0 -174
- package/test/env.test.ts +0 -128
- package/test/error-propagation.test.ts +0 -226
- package/test/errors-hono.test.ts +0 -133
- package/test/errors.test.ts +0 -155
- package/test/function-loader.test.ts +0 -127
- package/test/graphql-schema-generator.test.ts +0 -554
- package/test/jwt-security.test.ts +0 -182
- package/test/jwt.test.ts +0 -326
- package/test/mfa.test.ts +0 -197
- package/test/middleware.test.ts +0 -305
- package/test/multi-datasource-routing.test.ts +0 -113
- package/test/password.test.ts +0 -165
- package/test/query-parser.test.ts +0 -233
- package/test/rate-limiter.test.ts +0 -102
- package/test/reset-password-admin.test.ts +0 -113
- package/test/routed-realtime-service.test.ts +0 -86
- package/test/safe-compare.test.ts +0 -66
- package/test/singleton.test.ts +0 -59
- package/test/smtp-email-service.test.ts +0 -169
- package/test/storage-local.test.ts +0 -271
- package/test/storage-registry.test.ts +0 -282
- package/test/storage-routes.test.ts +0 -222
- package/test/storage-s3.test.ts +0 -304
- package/test/transform-auth-response.test.ts +0 -305
- package/test/webhook-service.test.ts +0 -260
- package/tsconfig.json +0 -54
- package/tsconfig.prod.json +0 -23
- package/vite.config.ts +0 -86
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Transformation Service
|
|
3
|
-
*
|
|
4
|
-
* Provides on-the-fly image resize, crop, format conversion, and quality
|
|
5
|
-
* adjustment using the `sharp` library. Results are cached in an LRU
|
|
6
|
-
* in-memory cache to avoid redundant processing.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
-
let sharpFactory: ((input: Buffer | Uint8Array) => any) | undefined;
|
|
11
|
-
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
-
async function getSharp(): Promise<(input: Buffer | Uint8Array) => any> {
|
|
14
|
-
if (!sharpFactory) {
|
|
15
|
-
try {
|
|
16
|
-
const mod = await import("sharp");
|
|
17
|
-
sharpFactory = mod.default;
|
|
18
|
-
} catch (err) {
|
|
19
|
-
throw new Error("Failed to load optional 'sharp' dependency for image transformation.");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if (!sharpFactory) {
|
|
23
|
-
throw new Error("Failed to load optional 'sharp' dependency for image transformation.");
|
|
24
|
-
}
|
|
25
|
-
return sharpFactory;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Options that can be specified via query parameters. */
|
|
29
|
-
export interface ImageTransformOptions {
|
|
30
|
-
width?: number;
|
|
31
|
-
height?: number;
|
|
32
|
-
quality?: number;
|
|
33
|
-
format?: "webp" | "avif" | "jpeg" | "png";
|
|
34
|
-
fit?: "cover" | "contain" | "fill" | "inside" | "outside";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Maximum dimension allowed (prevents abuse). */
|
|
38
|
-
const MAX_DIMENSION = 4096;
|
|
39
|
-
/** Maximum quality value. */
|
|
40
|
-
const MAX_QUALITY = 100;
|
|
41
|
-
/** Minimum quality value. */
|
|
42
|
-
const MIN_QUALITY = 1;
|
|
43
|
-
|
|
44
|
-
const VALID_FORMATS = new Set(["webp", "avif", "jpeg", "png"]);
|
|
45
|
-
const VALID_FITS = new Set(["cover", "contain", "fill", "inside", "outside"]);
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Parse transform options from URL query parameters.
|
|
49
|
-
* Returns `null` when no transformation is requested.
|
|
50
|
-
*/
|
|
51
|
-
export function parseTransformOptions(query: Record<string, string>): ImageTransformOptions | null {
|
|
52
|
-
const opts: ImageTransformOptions = {};
|
|
53
|
-
let hasTransform = false;
|
|
54
|
-
|
|
55
|
-
if (query.width) {
|
|
56
|
-
const w = parseInt(query.width, 10);
|
|
57
|
-
if (!Number.isNaN(w) && w > 0) {
|
|
58
|
-
opts.width = Math.min(w, MAX_DIMENSION);
|
|
59
|
-
hasTransform = true;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (query.height) {
|
|
64
|
-
const h = parseInt(query.height, 10);
|
|
65
|
-
if (!Number.isNaN(h) && h > 0) {
|
|
66
|
-
opts.height = Math.min(h, MAX_DIMENSION);
|
|
67
|
-
hasTransform = true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (query.quality) {
|
|
72
|
-
const q = parseInt(query.quality, 10);
|
|
73
|
-
if (!Number.isNaN(q)) {
|
|
74
|
-
opts.quality = Math.min(Math.max(q, MIN_QUALITY), MAX_QUALITY);
|
|
75
|
-
hasTransform = true;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (query.format && VALID_FORMATS.has(query.format)) {
|
|
80
|
-
opts.format = query.format as ImageTransformOptions["format"];
|
|
81
|
-
hasTransform = true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (query.fit && VALID_FITS.has(query.fit)) {
|
|
85
|
-
opts.fit = query.fit as ImageTransformOptions["fit"];
|
|
86
|
-
hasTransform = true;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return hasTransform ? opts : null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** MIME types that can be used as a Content-Type header. */
|
|
93
|
-
const FORMAT_CONTENT_TYPES: Record<string, string> = {
|
|
94
|
-
webp: "image/webp",
|
|
95
|
-
avif: "image/avif",
|
|
96
|
-
jpeg: "image/jpeg",
|
|
97
|
-
png: "image/png"
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/** Check whether a content type is a transformable image. */
|
|
101
|
-
export function isTransformableImage(contentType: string): boolean {
|
|
102
|
-
return (
|
|
103
|
-
contentType.startsWith("image/") &&
|
|
104
|
-
!contentType.includes("svg") &&
|
|
105
|
-
!contentType.includes("gif")
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Apply image transformations and return the result buffer + content type.
|
|
111
|
-
*/
|
|
112
|
-
export async function transformImage(
|
|
113
|
-
buffer: Buffer | Uint8Array,
|
|
114
|
-
options: ImageTransformOptions
|
|
115
|
-
): Promise<{ data: Buffer; contentType: string }> {
|
|
116
|
-
const sharp = await getSharp();
|
|
117
|
-
let pipeline = sharp(buffer);
|
|
118
|
-
|
|
119
|
-
if (options.width || options.height) {
|
|
120
|
-
pipeline = pipeline.resize({
|
|
121
|
-
width: options.width,
|
|
122
|
-
height: options.height,
|
|
123
|
-
fit: options.fit || "cover",
|
|
124
|
-
withoutEnlargement: true
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const format = options.format || "webp";
|
|
129
|
-
const quality = options.quality || 80;
|
|
130
|
-
|
|
131
|
-
switch (format) {
|
|
132
|
-
case "webp":
|
|
133
|
-
pipeline = pipeline.webp({ quality });
|
|
134
|
-
break;
|
|
135
|
-
case "avif":
|
|
136
|
-
pipeline = pipeline.avif({ quality });
|
|
137
|
-
break;
|
|
138
|
-
case "jpeg":
|
|
139
|
-
pipeline = pipeline.jpeg({ quality });
|
|
140
|
-
break;
|
|
141
|
-
case "png":
|
|
142
|
-
pipeline = pipeline.png({ quality });
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const data = await pipeline.toBuffer();
|
|
147
|
-
return { data,
|
|
148
|
-
contentType: FORMAT_CONTENT_TYPES[format] };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
// LRU Transform Cache
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
|
|
155
|
-
interface CacheEntry {
|
|
156
|
-
data: Buffer;
|
|
157
|
-
contentType: string;
|
|
158
|
-
timestamp: number;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Simple LRU cache for transformed images.
|
|
163
|
-
*
|
|
164
|
-
* Entries expire after `maxAgeMs` (default: 1 hour) and the cache
|
|
165
|
-
* evicts the oldest entry when `maxEntries` is exceeded.
|
|
166
|
-
*/
|
|
167
|
-
export class TransformCache {
|
|
168
|
-
private cache = new Map<string, CacheEntry>();
|
|
169
|
-
private readonly maxEntries: number;
|
|
170
|
-
private readonly maxAgeMs: number;
|
|
171
|
-
private readonly maxTotalBytes: number;
|
|
172
|
-
private totalBytes = 0;
|
|
173
|
-
|
|
174
|
-
constructor(maxEntries = 500, maxAgeMs = 3_600_000, maxTotalBytes = 256 * 1024 * 1024) {
|
|
175
|
-
this.maxEntries = maxEntries;
|
|
176
|
-
this.maxAgeMs = maxAgeMs;
|
|
177
|
-
this.maxTotalBytes = maxTotalBytes;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** Build a deterministic cache key from file key + transform options. */
|
|
181
|
-
buildKey(fileKey: string, options: ImageTransformOptions): string {
|
|
182
|
-
return `${fileKey}::${JSON.stringify(options)}`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
get(cacheKey: string): { data: Buffer; contentType: string } | null {
|
|
186
|
-
const entry = this.cache.get(cacheKey);
|
|
187
|
-
if (!entry) return null;
|
|
188
|
-
if (Date.now() - entry.timestamp > this.maxAgeMs) {
|
|
189
|
-
this.totalBytes -= entry.data.length;
|
|
190
|
-
this.cache.delete(cacheKey);
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
// Move to end (most recently used)
|
|
194
|
-
this.cache.delete(cacheKey);
|
|
195
|
-
this.cache.set(cacheKey, entry);
|
|
196
|
-
return { data: entry.data,
|
|
197
|
-
contentType: entry.contentType };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
set(cacheKey: string, data: Buffer, contentType: string): void {
|
|
201
|
-
// Evict oldest entries while over capacity (entry count or total bytes)
|
|
202
|
-
while (
|
|
203
|
-
(this.cache.size >= this.maxEntries || this.totalBytes + data.length > this.maxTotalBytes)
|
|
204
|
-
&& this.cache.size > 0
|
|
205
|
-
) {
|
|
206
|
-
const oldest = this.cache.keys().next().value;
|
|
207
|
-
if (oldest !== undefined) {
|
|
208
|
-
const evicted = this.cache.get(oldest);
|
|
209
|
-
if (evicted) this.totalBytes -= evicted.data.length;
|
|
210
|
-
this.cache.delete(oldest);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
this.totalBytes += data.length;
|
|
214
|
-
this.cache.set(cacheKey, { data,
|
|
215
|
-
contentType,
|
|
216
|
-
timestamp: Date.now() });
|
|
217
|
-
}
|
|
218
|
-
}
|
package/src/storage/routes.ts
DELETED
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storage REST API routes using Hono
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Hono } from "hono";
|
|
6
|
-
import fs from "node:fs";
|
|
7
|
-
import fsp from "node:fs/promises";
|
|
8
|
-
import { StorageController } from "./types";
|
|
9
|
-
import { LocalStorageController } from "./LocalStorageController";
|
|
10
|
-
import { requireAuth as jwtRequireAuth, optionalAuth } from "../auth/middleware";
|
|
11
|
-
import { ApiError, errorHandler } from "../api/errors";
|
|
12
|
-
import { HonoEnv } from "../api/types";
|
|
13
|
-
import { parseTransformOptions, transformImage, isTransformableImage, TransformCache } from "./image-transform";
|
|
14
|
-
import { TusHandler } from "./tus-handler";
|
|
15
|
-
|
|
16
|
-
/** Shared image transform cache (LRU, 500 entries, 1 hour TTL). */
|
|
17
|
-
const transformCache = new TransformCache();
|
|
18
|
-
|
|
19
|
-
export interface StorageRoutesConfig {
|
|
20
|
-
controller: StorageController;
|
|
21
|
-
/** Base path for storage routes (default: '/api/storage') */
|
|
22
|
-
basePath?: string;
|
|
23
|
-
/** Require authentication for write operations (default: true) */
|
|
24
|
-
requireAuth?: boolean;
|
|
25
|
-
/** Allow unauthenticated read access to stored files (default: false).
|
|
26
|
-
* When false and requireAuth is true, reads also require authentication. */
|
|
27
|
-
publicRead?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Extract the wildcard portion of a route path from the full request path.
|
|
32
|
-
*
|
|
33
|
-
* Hono's `c.req.param('*')` does not work reliably in sub-routers mounted
|
|
34
|
-
* via `app.route(prefix, subRouter)`. Instead we derive the wildcard value
|
|
35
|
-
* from the fully-resolved `c.req.path` and `c.req.routePath`.
|
|
36
|
-
*
|
|
37
|
-
* For a route `/metadata/*` mounted at `/api/storage`, a request to
|
|
38
|
-
* `/api/storage/metadata/default/file.jpg` yields routePath
|
|
39
|
-
* `/api/storage/metadata/*`. We strip the prefix (everything before `/*`)
|
|
40
|
-
* plus one character for the trailing `/` to obtain `default/file.jpg`.
|
|
41
|
-
*/
|
|
42
|
-
export function extractWildcardPath(c: { req: { path: string; routePath: string } }): string {
|
|
43
|
-
const routePath = c.req.routePath; // e.g. "/api/storage/metadata/*"
|
|
44
|
-
const prefix = routePath.replace("/*", ""); // e.g. "/api/storage/metadata"
|
|
45
|
-
const fullPath = c.req.path; // e.g. "/api/storage/metadata/default/file.jpg"
|
|
46
|
-
const idx = fullPath.indexOf(prefix);
|
|
47
|
-
if (idx < 0) return "";
|
|
48
|
-
// +1 to skip the '/' after the prefix
|
|
49
|
-
return fullPath.substring(idx + prefix.length + 1);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Sanitize a user-supplied storage key to prevent path traversal and other attacks.
|
|
54
|
-
* Removes null bytes, ../ sequences, leading slashes, and limits length.
|
|
55
|
-
*/
|
|
56
|
-
function sanitizeStorageKey(key: string): string {
|
|
57
|
-
let sanitized = key;
|
|
58
|
-
// Remove null bytes
|
|
59
|
-
sanitized = sanitized.replace(/\0/g, "");
|
|
60
|
-
// Remove ../ sequences (and ..\ on Windows)
|
|
61
|
-
sanitized = sanitized.replace(/\.\.\/|\.\.\\/g, "");
|
|
62
|
-
// Remove leading slashes
|
|
63
|
-
sanitized = sanitized.replace(/^\/+/, "");
|
|
64
|
-
// Limit length
|
|
65
|
-
sanitized = sanitized.slice(0, 1024);
|
|
66
|
-
return sanitized;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Create storage REST API routes
|
|
71
|
-
*/
|
|
72
|
-
export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv> {
|
|
73
|
-
const router = new Hono<HonoEnv>();
|
|
74
|
-
router.onError(errorHandler);
|
|
75
|
-
const { controller, requireAuth = true, publicRead = false } = config;
|
|
76
|
-
|
|
77
|
-
// Use actual JWT auth middleware from auth module
|
|
78
|
-
const writeAuthMiddleware = requireAuth ? jwtRequireAuth : optionalAuth;
|
|
79
|
-
|
|
80
|
-
// For read operations: respect publicRead config.
|
|
81
|
-
const readAuthMiddleware = (publicRead || !requireAuth) ? optionalAuth : jwtRequireAuth;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Parse bucket and path from a combined file path.
|
|
85
|
-
*/
|
|
86
|
-
const parseBucketAndPath = (filePath: string): { bucket: string; resolvedPath: string } => {
|
|
87
|
-
const parts = filePath.split("/");
|
|
88
|
-
|
|
89
|
-
// Only recognize 'default' as an explicit bucket prefix
|
|
90
|
-
if (parts.length > 1 && parts[0].toLowerCase() === "default") {
|
|
91
|
-
return {
|
|
92
|
-
bucket: "default",
|
|
93
|
-
resolvedPath: parts.slice(1).join("/")
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// All other paths use 'default' bucket with the full path
|
|
98
|
-
return {
|
|
99
|
-
bucket: "default",
|
|
100
|
-
resolvedPath: filePath
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* POST /upload - Upload a file
|
|
106
|
-
* Body: multipart/form-data with 'file' field
|
|
107
|
-
* Request body can also contain metadata keys 'metadata_*'
|
|
108
|
-
*/
|
|
109
|
-
router.post("/upload", writeAuthMiddleware, async (c) => {
|
|
110
|
-
const body = await c.req.parseBody();
|
|
111
|
-
const uploadedFile = body["file"];
|
|
112
|
-
|
|
113
|
-
if (!uploadedFile || typeof uploadedFile === "string") {
|
|
114
|
-
throw ApiError.badRequest("No file provided");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const key = typeof body["key"] === "string" ? body["key"] : "";
|
|
118
|
-
const bucket = typeof body["bucket"] === "string" ? body["bucket"] : undefined;
|
|
119
|
-
|
|
120
|
-
const finalKey = sanitizeStorageKey(key || uploadedFile.name || "unnamed");
|
|
121
|
-
|
|
122
|
-
// Extract custom metadata from request body
|
|
123
|
-
const metadata: Record<string, unknown> = {};
|
|
124
|
-
for (const [k, value] of Object.entries(body)) {
|
|
125
|
-
if (k.startsWith("metadata_")) {
|
|
126
|
-
metadata[k.replace("metadata_", "")] = value;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const result = await controller.putObject({
|
|
131
|
-
file: uploadedFile,
|
|
132
|
-
key: finalKey,
|
|
133
|
-
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
134
|
-
bucket
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
return c.json({
|
|
138
|
-
success: true,
|
|
139
|
-
data: result
|
|
140
|
-
}, 201);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* GET /file/* - Download/serve a file
|
|
145
|
-
* Path: /file/{bucket}/{path} or /file/{path}
|
|
146
|
-
*/
|
|
147
|
-
router.get("/file/*", readAuthMiddleware, async (c) => {
|
|
148
|
-
// Allow cross-origin loading so admin frontends on different
|
|
149
|
-
// ports (dev) or domains (CDN) can render images via <img>.
|
|
150
|
-
c.header("Cross-Origin-Resource-Policy", "cross-origin");
|
|
151
|
-
|
|
152
|
-
const rawPath = extractWildcardPath(c);
|
|
153
|
-
if (!rawPath) {
|
|
154
|
-
throw ApiError.notFound("File not found");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const filePath = decodeURIComponent(rawPath);
|
|
158
|
-
|
|
159
|
-
// Parse image transform query params (e.g. ?width=300&format=webp)
|
|
160
|
-
const transformOpts = parseTransformOptions(c.req.query() as Record<string, string>);
|
|
161
|
-
|
|
162
|
-
// For local storage, serve the file directly from disk
|
|
163
|
-
if (controller.getType() === "local") {
|
|
164
|
-
const localController = controller as LocalStorageController;
|
|
165
|
-
const { bucket, resolvedPath } = parseBucketAndPath(filePath);
|
|
166
|
-
|
|
167
|
-
const absolutePath = localController.getAbsolutePath(resolvedPath, bucket);
|
|
168
|
-
|
|
169
|
-
// Check if file exists
|
|
170
|
-
try {
|
|
171
|
-
await fsp.access(absolutePath);
|
|
172
|
-
} catch {
|
|
173
|
-
throw ApiError.notFound("File not found");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Get content type from metadata or infer from extension
|
|
177
|
-
let contentType = "application/octet-stream";
|
|
178
|
-
const metadataPath = `${absolutePath}.metadata.json`;
|
|
179
|
-
try {
|
|
180
|
-
const metadataRaw = await fsp.readFile(metadataPath, "utf-8");
|
|
181
|
-
const metadata = JSON.parse(metadataRaw);
|
|
182
|
-
contentType = metadata.contentType || contentType;
|
|
183
|
-
} catch {
|
|
184
|
-
// Ignore metadata errors (file may not exist)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const fileContent = await fsp.readFile(absolutePath);
|
|
188
|
-
|
|
189
|
-
// Apply image transforms if requested and the file is a transformable image
|
|
190
|
-
if (transformOpts && isTransformableImage(contentType)) {
|
|
191
|
-
const cacheKey = transformCache.buildKey(filePath, transformOpts);
|
|
192
|
-
let cached = transformCache.get(cacheKey);
|
|
193
|
-
if (!cached) {
|
|
194
|
-
cached = await transformImage(Buffer.from(fileContent), transformOpts);
|
|
195
|
-
transformCache.set(cacheKey, cached.data, cached.contentType);
|
|
196
|
-
}
|
|
197
|
-
c.header("Content-Type", cached.contentType);
|
|
198
|
-
c.header("Cache-Control", "public, max-age=31536000, immutable");
|
|
199
|
-
return c.body(new Uint8Array(cached.data));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
c.header("Content-Type", contentType);
|
|
203
|
-
return c.body(new Uint8Array(fileContent));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// For remote storage (S3, GCS, etc.), proxy the file through the backend.
|
|
207
|
-
// We avoid redirecting to signed URLs because:
|
|
208
|
-
// 1. Mixed-content (HTTPS page → HTTP MinIO) is blocked by browsers
|
|
209
|
-
// 2. Internal IPs / VPC endpoints are unreachable from the browser
|
|
210
|
-
const { bucket: parsedBucket, resolvedPath: parsedPath } = parseBucketAndPath(filePath);
|
|
211
|
-
const fileObject = await controller.getObject(parsedPath, parsedBucket);
|
|
212
|
-
if (!fileObject) {
|
|
213
|
-
throw ApiError.notFound("File not found");
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const remoteContentType = fileObject.type || "application/octet-stream";
|
|
217
|
-
|
|
218
|
-
// Apply image transforms for remote storage too
|
|
219
|
-
if (transformOpts && isTransformableImage(remoteContentType)) {
|
|
220
|
-
const cacheKey = transformCache.buildKey(filePath, transformOpts);
|
|
221
|
-
let cached = transformCache.get(cacheKey);
|
|
222
|
-
if (!cached) {
|
|
223
|
-
const buf = Buffer.from(await fileObject.arrayBuffer());
|
|
224
|
-
cached = await transformImage(buf, transformOpts);
|
|
225
|
-
transformCache.set(cacheKey, cached.data, cached.contentType);
|
|
226
|
-
}
|
|
227
|
-
c.header("Content-Type", cached.contentType);
|
|
228
|
-
c.header("Cache-Control", "public, max-age=31536000, immutable");
|
|
229
|
-
return c.body(new Uint8Array(cached.data));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
c.header("Content-Type", remoteContentType);
|
|
233
|
-
c.header("Cache-Control", "public, max-age=3600, immutable");
|
|
234
|
-
const buf = await fileObject.arrayBuffer();
|
|
235
|
-
return c.body(new Uint8Array(buf));
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* GET /metadata/* - Get file metadata
|
|
240
|
-
*/
|
|
241
|
-
router.get("/metadata/*", readAuthMiddleware, async (c) => {
|
|
242
|
-
const rawPath = extractWildcardPath(c);
|
|
243
|
-
if (!rawPath) {
|
|
244
|
-
return c.json({
|
|
245
|
-
success: true,
|
|
246
|
-
data: null,
|
|
247
|
-
fileNotFound: true
|
|
248
|
-
}, 404);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const filePath = decodeURIComponent(rawPath);
|
|
252
|
-
const { bucket, resolvedPath } = parseBucketAndPath(filePath);
|
|
253
|
-
|
|
254
|
-
const downloadConfig = await controller.getSignedUrl(resolvedPath, bucket);
|
|
255
|
-
|
|
256
|
-
if (downloadConfig.fileNotFound) {
|
|
257
|
-
throw ApiError.notFound("File not found");
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return c.json({
|
|
261
|
-
success: true,
|
|
262
|
-
data: downloadConfig.metadata
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* DELETE /file/* - Delete a file
|
|
268
|
-
*/
|
|
269
|
-
router.delete("/file/*", writeAuthMiddleware, async (c) => {
|
|
270
|
-
const rawPath = extractWildcardPath(c);
|
|
271
|
-
if (!rawPath) {
|
|
272
|
-
return c.json({ success: true,
|
|
273
|
-
message: "No file to delete" });
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const filePath = decodeURIComponent(rawPath);
|
|
277
|
-
const { bucket, resolvedPath } = parseBucketAndPath(filePath);
|
|
278
|
-
|
|
279
|
-
await controller.deleteObject(resolvedPath, bucket);
|
|
280
|
-
|
|
281
|
-
return c.json({
|
|
282
|
-
success: true,
|
|
283
|
-
message: "File deleted"
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* GET /list - List files in a path
|
|
289
|
-
*/
|
|
290
|
-
router.get("/list", writeAuthMiddleware, async (c) => {
|
|
291
|
-
// Fallback to path for backward compatibility
|
|
292
|
-
const storagePrefix = c.req.query("prefix") || c.req.query("path") || "";
|
|
293
|
-
const bucket = c.req.query("bucket");
|
|
294
|
-
const maxResults = c.req.query("maxResults");
|
|
295
|
-
const pageToken = c.req.query("pageToken");
|
|
296
|
-
|
|
297
|
-
const result = await controller.listObjects(
|
|
298
|
-
storagePrefix,
|
|
299
|
-
{
|
|
300
|
-
bucket: bucket ?? (controller.getType() === "local" ? "default" : undefined),
|
|
301
|
-
maxResults: maxResults ? parseInt(maxResults, 10) : undefined,
|
|
302
|
-
pageToken
|
|
303
|
-
}
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
return c.json({
|
|
307
|
-
success: true,
|
|
308
|
-
data: result
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* POST /folder - Create a new folder
|
|
314
|
-
* Body: { path: string, bucket?: string }
|
|
315
|
-
*/
|
|
316
|
-
router.post("/folder", writeAuthMiddleware, async (c) => {
|
|
317
|
-
const body = await c.req.json();
|
|
318
|
-
const folderPath = body.path;
|
|
319
|
-
|
|
320
|
-
if (!folderPath || typeof folderPath !== "string") {
|
|
321
|
-
throw ApiError.badRequest("Folder path is required");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const { bucket, resolvedPath } = parseBucketAndPath(folderPath);
|
|
325
|
-
|
|
326
|
-
if (!resolvedPath || resolvedPath.trim() === "") {
|
|
327
|
-
throw ApiError.badRequest("Invalid folder path");
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (controller.getType() === "local") {
|
|
331
|
-
// For local storage, create the directory
|
|
332
|
-
const localController = controller as LocalStorageController;
|
|
333
|
-
const absolutePath = localController.getAbsolutePath(resolvedPath, bucket);
|
|
334
|
-
fs.mkdirSync(absolutePath, { recursive: true });
|
|
335
|
-
} else {
|
|
336
|
-
// For S3-compatible storage, create a zero-byte marker object with trailing slash
|
|
337
|
-
const key = resolvedPath.endsWith("/") ? resolvedPath : resolvedPath + "/";
|
|
338
|
-
const emptyFile = new File([], key, { type: "application/x-directory" });
|
|
339
|
-
await controller.putObject({
|
|
340
|
-
file: emptyFile,
|
|
341
|
-
key
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return c.json({
|
|
346
|
-
success: true,
|
|
347
|
-
message: "Folder created"
|
|
348
|
-
}, 201);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
// -----------------------------------------------------------------------
|
|
352
|
-
// TUS Resumable Uploads
|
|
353
|
-
// -----------------------------------------------------------------------
|
|
354
|
-
|
|
355
|
-
const tusBaseDir = controller.getType() === "local"
|
|
356
|
-
? (controller as LocalStorageController).getBasePath()
|
|
357
|
-
: (process.env.STORAGE_PATH || "./uploads");
|
|
358
|
-
const tusHandler = new TusHandler(tusBaseDir, controller);
|
|
359
|
-
tusHandler.startCleanup();
|
|
360
|
-
|
|
361
|
-
router.options("/tus", (_c) => tusHandler.options());
|
|
362
|
-
router.post("/tus", writeAuthMiddleware, async (c) => tusHandler.create(c));
|
|
363
|
-
router.get("/tus/:id", readAuthMiddleware, (c) => tusHandler.head(c, c.req.param("id")));
|
|
364
|
-
router.patch("/tus/:id", writeAuthMiddleware, async (c) => tusHandler.patch(c, c.req.param("id")));
|
|
365
|
-
router.delete("/tus/:id", writeAuthMiddleware, async (c) => tusHandler.delete(c, c.req.param("id")));
|
|
366
|
-
|
|
367
|
-
return router;
|
|
368
|
-
}
|