@rebasepro/server-core 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e
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/src/collections/CollectionRegistry.d.ts +8 -0
- package/dist/common/src/util/entities.d.ts +22 -0
- package/dist/common/src/util/relations.d.ts +14 -4
- package/dist/common/src/util/resolutions.d.ts +1 -1
- package/dist/index-DXVBFp5V.js +37 -0
- package/dist/index-DXVBFp5V.js.map +1 -0
- package/dist/index.es.js +49931 -55
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +28475 -34569
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/openapi-generator.d.ts +15 -1
- package/dist/server-core/src/api/types.d.ts +1 -1
- package/dist/server-core/src/auth/admin-routes.d.ts +10 -1
- package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
- package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
- package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
- package/dist/server-core/src/auth/google-oauth.d.ts +5 -11
- package/dist/server-core/src/auth/index.d.ts +13 -2
- package/dist/server-core/src/auth/interfaces.d.ts +45 -6
- package/dist/server-core/src/auth/jwt.d.ts +1 -0
- package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
- package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
- package/dist/server-core/src/auth/middleware.d.ts +36 -11
- package/dist/server-core/src/auth/rate-limiter.d.ts +2 -2
- package/dist/server-core/src/auth/routes.d.ts +13 -3
- package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
- package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
- package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
- package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
- package/dist/server-core/src/cron/cron-store.d.ts +32 -0
- package/dist/server-core/src/cron/index.d.ts +6 -0
- package/dist/server-core/src/email/index.d.ts +2 -2
- package/dist/server-core/src/email/templates.d.ts +9 -0
- package/dist/server-core/src/email/types.d.ts +20 -23
- package/dist/server-core/src/history/history-routes.d.ts +2 -2
- package/dist/server-core/src/index.d.ts +5 -0
- package/dist/server-core/src/init.d.ts +112 -2
- package/dist/server-core/src/singleton.d.ts +35 -0
- package/dist/server-core/src/storage/LocalStorageController.d.ts +9 -9
- package/dist/server-core/src/storage/S3StorageController.d.ts +8 -8
- package/dist/server-core/src/storage/index.d.ts +18 -11
- package/dist/server-core/src/storage/routes.d.ts +3 -3
- package/dist/server-core/src/storage/types.d.ts +28 -16
- package/dist/server-core/src/utils/dev-port.d.ts +35 -0
- package/dist/server-core/src/utils/logger.d.ts +31 -0
- package/dist/server-core/src/utils/request-logger.d.ts +19 -0
- package/dist/types/src/controllers/auth.d.ts +2 -0
- package/dist/types/src/controllers/client.d.ts +119 -7
- package/dist/types/src/controllers/collection_registry.d.ts +4 -3
- package/dist/types/src/controllers/customization_controller.d.ts +7 -1
- package/dist/types/src/controllers/data.d.ts +34 -7
- package/dist/types/src/controllers/data_driver.d.ts +20 -28
- package/dist/types/src/controllers/database_admin.d.ts +2 -2
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +1 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
- package/dist/types/src/controllers/navigation.d.ts +5 -5
- package/dist/types/src/controllers/registry.d.ts +6 -3
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
- package/dist/types/src/controllers/storage.d.ts +24 -26
- package/dist/types/src/rebase_context.d.ts +8 -4
- package/dist/types/src/types/backend.d.ts +4 -1
- package/dist/types/src/types/builders.d.ts +5 -4
- package/dist/types/src/types/chips.d.ts +1 -1
- package/dist/types/src/types/collections.d.ts +169 -125
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +1 -1
- package/dist/types/src/types/entity_actions.d.ts +8 -8
- package/dist/types/src/types/entity_callbacks.d.ts +15 -15
- package/dist/types/src/types/entity_link_builder.d.ts +1 -1
- package/dist/types/src/types/entity_overrides.d.ts +2 -1
- package/dist/types/src/types/entity_views.d.ts +8 -8
- package/dist/types/src/types/export_import.d.ts +3 -3
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/plugins.d.ts +72 -18
- package/dist/types/src/types/properties.d.ts +118 -33
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/slots.d.ts +30 -6
- package/dist/types/src/types/translations.d.ts +44 -0
- package/dist/types/src/types/user_management_delegate.d.ts +1 -0
- package/package.json +84 -84
- package/scratch.ts +6 -5
- package/src/api/ast-schema-editor.ts +11 -11
- package/src/api/collections_for_test/callbacks_test_collection.ts +5 -2
- package/src/api/errors.ts +38 -14
- package/src/api/graphql/graphql-schema-generator.ts +5 -3
- package/src/api/openapi-generator.ts +613 -58
- package/src/api/rest/api-generator.ts +17 -11
- package/src/api/rest/query-parser.ts +1 -1
- package/src/api/schema-editor-routes.ts +2 -0
- package/src/api/server.ts +8 -5
- package/src/api/types.ts +1 -1
- package/src/auth/admin-routes.ts +58 -17
- package/src/auth/apple-oauth.ts +130 -0
- package/src/auth/bitbucket-oauth.ts +82 -0
- package/src/auth/discord-oauth.ts +83 -0
- package/src/auth/facebook-oauth.ts +72 -0
- package/src/auth/github-oauth.ts +110 -0
- package/src/auth/gitlab-oauth.ts +70 -0
- package/src/auth/google-oauth.ts +34 -46
- package/src/auth/index.ts +15 -2
- package/src/auth/interfaces.ts +54 -7
- package/src/auth/jwt.ts +22 -5
- package/src/auth/linkedin-oauth.ts +81 -0
- package/src/auth/microsoft-oauth.ts +88 -0
- package/src/auth/middleware.ts +194 -45
- package/src/auth/password.ts +3 -1
- package/src/auth/rate-limiter.ts +4 -4
- package/src/auth/routes.ts +197 -139
- package/src/auth/slack-oauth.ts +71 -0
- package/src/auth/spotify-oauth.ts +67 -0
- package/src/auth/twitter-oauth.ts +120 -0
- package/src/collections/loader.ts +6 -6
- package/src/cron/cron-loader.ts +89 -0
- package/src/cron/cron-routes.test.ts +265 -0
- package/src/cron/cron-routes.ts +85 -0
- package/src/cron/cron-scheduler.test.ts +421 -0
- package/src/cron/cron-scheduler.ts +413 -0
- package/src/cron/cron-store.ts +163 -0
- package/src/cron/index.ts +6 -0
- package/src/db/interfaces.ts +2 -2
- package/src/email/index.ts +3 -2
- package/src/email/smtp-email-service.ts +5 -2
- package/src/email/templates.ts +93 -6
- package/src/email/types.ts +17 -24
- package/src/functions/function-loader.ts +34 -6
- package/src/functions/function-routes.ts +2 -2
- package/src/history/history-routes.ts +4 -3
- package/src/index.ts +10 -0
- package/src/init.ts +475 -57
- package/src/services/driver-registry.ts +1 -1
- package/src/singleton.test.ts +28 -0
- package/src/singleton.ts +70 -0
- package/src/storage/LocalStorageController.ts +50 -53
- package/src/storage/S3StorageController.ts +49 -46
- package/src/storage/index.ts +27 -16
- package/src/storage/routes.ts +76 -59
- package/src/storage/storage-registry.ts +5 -5
- package/src/storage/types.ts +44 -32
- package/src/types/index.ts +4 -4
- package/src/utils/dev-port.ts +176 -0
- package/src/utils/logger.ts +143 -0
- package/src/utils/logging.ts +5 -2
- package/src/utils/request-logger.ts +66 -0
- package/test/admin-routes.test.ts +114 -65
- package/test/api-generator.test.ts +73 -30
- package/test/ast-schema-editor.test.ts +5 -3
- package/test/auth-middleware-hono.test.ts +268 -33
- package/test/auth-routes.test.ts +325 -146
- package/test/driver-registry.test.ts +4 -2
- package/test/error-propagation.test.ts +226 -0
- package/test/errors.test.ts +12 -7
- package/test/jwt-security.test.ts +18 -9
- package/test/jwt.test.ts +26 -13
- package/test/middleware.test.ts +13 -8
- package/test/query-parser.test.ts +13 -8
- package/test/rate-limiter.test.ts +7 -7
- package/test/safe-compare.test.ts +66 -0
- package/test/singleton.test.ts +59 -0
- package/test/storage-local.test.ts +36 -43
- package/test/storage-registry.test.ts +38 -36
- package/test/storage-routes.test.ts +9 -5
- package/test/storage-s3.test.ts +51 -48
- package/test.ts +6 -0
- package/tsconfig.json +1 -1
- package/vite.config.ts +7 -5
- package/dist/index-BeMqpmfQ.js +0 -239
- package/dist/index-BeMqpmfQ.js.map +0 -1
- package/dist/index-bl4J3lNb.js +0 -55823
- package/dist/index-bl4J3lNb.js.map +0 -1
- package/vite.config.ts.timestamp-1775065397568-8a853255edf6e.mjs +0 -46
package/src/storage/index.ts
CHANGED
|
@@ -1,32 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Storage module for Rebase backend
|
|
3
3
|
*
|
|
4
|
-
* Provides file storage
|
|
5
|
-
* - Local filesystem
|
|
6
|
-
* - S3-compatible
|
|
4
|
+
* Provides pluggable file storage with two built-in providers:
|
|
5
|
+
* - **Local filesystem** — zero config, great for dev and single-server deployments.
|
|
6
|
+
* - **S3-compatible** — works with AWS S3, Cloudflare R2, MinIO, Hetzner Object Storage,
|
|
7
|
+
* Backblaze B2, DigitalOcean Spaces, and GCS (via S3 interop).
|
|
8
|
+
*
|
|
9
|
+
* For other providers (native GCS SDK, Azure Blob, etc.), implement the
|
|
10
|
+
* `StorageController` interface and pass the instance directly to the `storage` config.
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
|
-
export * from
|
|
10
|
-
export { LocalStorageController } from
|
|
11
|
-
export { S3StorageController } from
|
|
12
|
-
export { createStorageRoutes } from
|
|
13
|
-
export type { StorageRoutesConfig } from
|
|
14
|
-
export * from
|
|
13
|
+
export * from "./types";
|
|
14
|
+
export { LocalStorageController } from "./LocalStorageController";
|
|
15
|
+
export { S3StorageController } from "./S3StorageController";
|
|
16
|
+
export { createStorageRoutes } from "./routes";
|
|
17
|
+
export type { StorageRoutesConfig } from "./routes";
|
|
18
|
+
export * from "./storage-registry";
|
|
15
19
|
|
|
16
|
-
import { BackendStorageConfig, StorageController } from
|
|
17
|
-
import { LocalStorageController } from
|
|
18
|
-
import { S3StorageController } from
|
|
20
|
+
import { BackendStorageConfig, StorageController } from "./types";
|
|
21
|
+
import { LocalStorageController } from "./LocalStorageController";
|
|
22
|
+
import { S3StorageController } from "./S3StorageController";
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
|
-
*
|
|
25
|
+
* Create a storage controller from a config object.
|
|
26
|
+
*
|
|
27
|
+
* For custom providers, implement `StorageController` directly instead
|
|
28
|
+
* of going through this factory.
|
|
22
29
|
*/
|
|
23
30
|
export function createStorageController(config: BackendStorageConfig): StorageController {
|
|
24
31
|
switch (config.type) {
|
|
25
|
-
case
|
|
32
|
+
case "local":
|
|
26
33
|
return new LocalStorageController(config);
|
|
27
|
-
case
|
|
34
|
+
case "s3":
|
|
28
35
|
return new S3StorageController(config);
|
|
29
36
|
default:
|
|
30
|
-
throw new Error(
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Unknown storage type: ${(config as Record<string, unknown>).type}. ` +
|
|
39
|
+
"Built-in types: local, s3. " +
|
|
40
|
+
"For other providers, implement the StorageController interface directly."
|
|
41
|
+
);
|
|
31
42
|
}
|
|
32
43
|
}
|
package/src/storage/routes.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Storage REST API routes using Hono
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Hono } from
|
|
6
|
-
import * as fs from
|
|
7
|
-
import { StorageController } from
|
|
8
|
-
import { LocalStorageController } from
|
|
9
|
-
import { requireAuth as jwtRequireAuth, optionalAuth } from
|
|
10
|
-
import { ApiError } from
|
|
11
|
-
import { HonoEnv } from
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import { StorageController } from "./types";
|
|
8
|
+
import { LocalStorageController } from "./LocalStorageController";
|
|
9
|
+
import { requireAuth as jwtRequireAuth, optionalAuth } from "../auth/middleware";
|
|
10
|
+
import { ApiError, errorHandler } from "../api/errors";
|
|
11
|
+
import { HonoEnv } from "../api/types";
|
|
12
12
|
|
|
13
13
|
export interface StorageRoutesConfig {
|
|
14
14
|
controller: StorageController;
|
|
@@ -34,11 +34,11 @@ export interface StorageRoutesConfig {
|
|
|
34
34
|
* plus one character for the trailing `/` to obtain `default/file.jpg`.
|
|
35
35
|
*/
|
|
36
36
|
export function extractWildcardPath(c: { req: { path: string; routePath: string } }): string {
|
|
37
|
-
const routePath = c.req.routePath;
|
|
38
|
-
const prefix = routePath.replace(
|
|
39
|
-
const fullPath = c.req.path;
|
|
37
|
+
const routePath = c.req.routePath; // e.g. "/api/storage/metadata/*"
|
|
38
|
+
const prefix = routePath.replace("/*", ""); // e.g. "/api/storage/metadata"
|
|
39
|
+
const fullPath = c.req.path; // e.g. "/api/storage/metadata/default/file.jpg"
|
|
40
40
|
const idx = fullPath.indexOf(prefix);
|
|
41
|
-
if (idx < 0) return
|
|
41
|
+
if (idx < 0) return "";
|
|
42
42
|
// +1 to skip the '/' after the prefix
|
|
43
43
|
return fullPath.substring(idx + prefix.length + 1);
|
|
44
44
|
}
|
|
@@ -48,6 +48,7 @@ export function extractWildcardPath(c: { req: { path: string; routePath: string
|
|
|
48
48
|
*/
|
|
49
49
|
export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv> {
|
|
50
50
|
const router = new Hono<HonoEnv>();
|
|
51
|
+
router.onError(errorHandler);
|
|
51
52
|
const { controller, requireAuth = true, publicRead = false } = config;
|
|
52
53
|
|
|
53
54
|
// Use actual JWT auth middleware from auth module
|
|
@@ -60,19 +61,19 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
|
|
|
60
61
|
* Parse bucket and path from a combined file path.
|
|
61
62
|
*/
|
|
62
63
|
const parseBucketAndPath = (filePath: string): { bucket: string; resolvedPath: string } => {
|
|
63
|
-
const parts = filePath.split(
|
|
64
|
+
const parts = filePath.split("/");
|
|
64
65
|
|
|
65
66
|
// Only recognize 'default' as an explicit bucket prefix
|
|
66
|
-
if (parts.length > 1 && parts[0].toLowerCase() ===
|
|
67
|
+
if (parts.length > 1 && parts[0].toLowerCase() === "default") {
|
|
67
68
|
return {
|
|
68
|
-
bucket:
|
|
69
|
-
resolvedPath: parts.slice(1).join(
|
|
69
|
+
bucket: "default",
|
|
70
|
+
resolvedPath: parts.slice(1).join("/")
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
// All other paths use 'default' bucket with the full path
|
|
74
75
|
return {
|
|
75
|
-
bucket:
|
|
76
|
+
bucket: "default",
|
|
76
77
|
resolvedPath: filePath
|
|
77
78
|
};
|
|
78
79
|
};
|
|
@@ -82,34 +83,48 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
|
|
|
82
83
|
* Body: multipart/form-data with 'file' field
|
|
83
84
|
* Request body can also contain metadata keys 'metadata_*'
|
|
84
85
|
*/
|
|
85
|
-
router.post(
|
|
86
|
+
router.post("/upload", writeAuthMiddleware, async (c) => {
|
|
86
87
|
const body = await c.req.parseBody();
|
|
87
|
-
const uploadedFile = body[
|
|
88
|
+
const uploadedFile = body["file"];
|
|
88
89
|
|
|
89
|
-
if (!uploadedFile || typeof uploadedFile ===
|
|
90
|
-
throw ApiError.badRequest(
|
|
90
|
+
if (!uploadedFile || typeof uploadedFile === "string") {
|
|
91
|
+
throw ApiError.badRequest("No file provided");
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
const
|
|
94
|
-
const bucket = typeof body[
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
const key = typeof body["key"] === "string" ? body["key"] : "";
|
|
95
|
+
const bucket = typeof body["bucket"] === "string" ? body["bucket"] : undefined;
|
|
96
|
+
|
|
97
|
+
// Backward compatibility support for older clients sending path and fileName
|
|
98
|
+
const legacyPath = typeof body["path"] === "string" ? body["path"] : "";
|
|
99
|
+
const legacyFileName = typeof body["fileName"] === "string" ? body["fileName"] : undefined;
|
|
100
|
+
|
|
101
|
+
let finalKey = key;
|
|
102
|
+
if (!finalKey) {
|
|
103
|
+
if (legacyPath || legacyFileName) {
|
|
104
|
+
const parts = [];
|
|
105
|
+
if (legacyPath) parts.push(legacyPath);
|
|
106
|
+
if (legacyFileName) {
|
|
107
|
+
parts.push(legacyFileName);
|
|
108
|
+
} else {
|
|
109
|
+
parts.push(uploadedFile.name || "unnamed");
|
|
110
|
+
}
|
|
111
|
+
finalKey = parts.join("/");
|
|
112
|
+
} else {
|
|
113
|
+
finalKey = uploadedFile.name || "unnamed";
|
|
114
|
+
}
|
|
99
115
|
}
|
|
100
116
|
|
|
101
117
|
// Extract custom metadata from request body
|
|
102
118
|
const metadata: Record<string, unknown> = {};
|
|
103
|
-
for (const [
|
|
104
|
-
if (
|
|
105
|
-
metadata[
|
|
119
|
+
for (const [k, value] of Object.entries(body)) {
|
|
120
|
+
if (k.startsWith("metadata_")) {
|
|
121
|
+
metadata[k.replace("metadata_", "")] = value;
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
124
|
|
|
109
|
-
const result = await controller.
|
|
125
|
+
const result = await controller.putObject({
|
|
110
126
|
file: uploadedFile,
|
|
111
|
-
|
|
112
|
-
path: storagePath,
|
|
127
|
+
key: finalKey,
|
|
113
128
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
114
129
|
bucket
|
|
115
130
|
});
|
|
@@ -124,16 +139,16 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
|
|
|
124
139
|
* GET /file/* - Download/serve a file
|
|
125
140
|
* Path: /file/{bucket}/{path} or /file/{path}
|
|
126
141
|
*/
|
|
127
|
-
router.get(
|
|
142
|
+
router.get("/file/*", readAuthMiddleware, async (c) => {
|
|
128
143
|
const rawPath = extractWildcardPath(c);
|
|
129
144
|
if (!rawPath) {
|
|
130
|
-
throw ApiError.notFound(
|
|
145
|
+
throw ApiError.notFound("File not found");
|
|
131
146
|
}
|
|
132
147
|
|
|
133
148
|
const filePath = decodeURIComponent(rawPath);
|
|
134
149
|
|
|
135
|
-
// For local storage, serve the file directly
|
|
136
|
-
if (controller.getType() ===
|
|
150
|
+
// For local storage, serve the file directly from disk
|
|
151
|
+
if (controller.getType() === "local") {
|
|
137
152
|
const localController = controller as LocalStorageController;
|
|
138
153
|
const { bucket, resolvedPath } = parseBucketAndPath(filePath);
|
|
139
154
|
|
|
@@ -141,31 +156,31 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
|
|
|
141
156
|
|
|
142
157
|
// Check if file exists
|
|
143
158
|
if (!fs.existsSync(absolutePath)) {
|
|
144
|
-
throw ApiError.notFound(
|
|
159
|
+
throw ApiError.notFound("File not found");
|
|
145
160
|
}
|
|
146
161
|
|
|
147
162
|
// Get content type from metadata or infer from extension
|
|
148
|
-
let contentType =
|
|
163
|
+
let contentType = "application/octet-stream";
|
|
149
164
|
const metadataPath = `${absolutePath}.metadata.json`;
|
|
150
165
|
if (fs.existsSync(metadataPath)) {
|
|
151
166
|
try {
|
|
152
|
-
const metadata = JSON.parse(fs.readFileSync(metadataPath,
|
|
167
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
|
|
153
168
|
contentType = metadata.contentType || contentType;
|
|
154
169
|
} catch {
|
|
155
170
|
// Ignore metadata errors
|
|
156
171
|
}
|
|
157
172
|
}
|
|
158
173
|
|
|
159
|
-
c.header(
|
|
174
|
+
c.header("Content-Type", contentType);
|
|
160
175
|
// In a better scenario, we should pipe the stream instead of reading whole file
|
|
161
176
|
const fileContent = fs.readFileSync(absolutePath);
|
|
162
|
-
return c.body(new Uint8Array(fileContent));
|
|
177
|
+
return c.body(new Uint8Array(fileContent));
|
|
163
178
|
}
|
|
164
179
|
|
|
165
|
-
// For
|
|
166
|
-
const downloadConfig = await controller.
|
|
180
|
+
// For remote storage (S3, GCS, etc.), redirect to a signed URL
|
|
181
|
+
const downloadConfig = await controller.getSignedUrl(filePath);
|
|
167
182
|
if (downloadConfig.fileNotFound || !downloadConfig.url) {
|
|
168
|
-
throw ApiError.notFound(
|
|
183
|
+
throw ApiError.notFound("File not found");
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
return c.redirect(downloadConfig.url);
|
|
@@ -174,7 +189,7 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
|
|
|
174
189
|
/**
|
|
175
190
|
* GET /metadata/* - Get file metadata
|
|
176
191
|
*/
|
|
177
|
-
router.get(
|
|
192
|
+
router.get("/metadata/*", readAuthMiddleware, async (c) => {
|
|
178
193
|
const rawPath = extractWildcardPath(c);
|
|
179
194
|
if (!rawPath) {
|
|
180
195
|
return c.json({
|
|
@@ -187,10 +202,10 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
|
|
|
187
202
|
const filePath = decodeURIComponent(rawPath);
|
|
188
203
|
const { bucket, resolvedPath } = parseBucketAndPath(filePath);
|
|
189
204
|
|
|
190
|
-
const downloadConfig = await controller.
|
|
205
|
+
const downloadConfig = await controller.getSignedUrl(resolvedPath, bucket);
|
|
191
206
|
|
|
192
207
|
if (downloadConfig.fileNotFound) {
|
|
193
|
-
throw ApiError.notFound(
|
|
208
|
+
throw ApiError.notFound("File not found");
|
|
194
209
|
}
|
|
195
210
|
|
|
196
211
|
return c.json({
|
|
@@ -202,34 +217,36 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
|
|
|
202
217
|
/**
|
|
203
218
|
* DELETE /file/* - Delete a file
|
|
204
219
|
*/
|
|
205
|
-
router.delete(
|
|
220
|
+
router.delete("/file/*", writeAuthMiddleware, async (c) => {
|
|
206
221
|
const rawPath = extractWildcardPath(c);
|
|
207
222
|
if (!rawPath) {
|
|
208
|
-
return c.json({ success: true,
|
|
223
|
+
return c.json({ success: true,
|
|
224
|
+
message: "No file to delete" });
|
|
209
225
|
}
|
|
210
226
|
|
|
211
227
|
const filePath = decodeURIComponent(rawPath);
|
|
212
228
|
const { bucket, resolvedPath } = parseBucketAndPath(filePath);
|
|
213
229
|
|
|
214
|
-
await controller.
|
|
230
|
+
await controller.deleteObject(resolvedPath, bucket);
|
|
215
231
|
|
|
216
232
|
return c.json({
|
|
217
233
|
success: true,
|
|
218
|
-
message:
|
|
234
|
+
message: "File deleted"
|
|
219
235
|
});
|
|
220
236
|
});
|
|
221
237
|
|
|
222
238
|
/**
|
|
223
239
|
* GET /list - List files in a path
|
|
224
240
|
*/
|
|
225
|
-
router.get(
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
241
|
+
router.get("/list", writeAuthMiddleware, async (c) => {
|
|
242
|
+
// Fallback to path for backward compatibility
|
|
243
|
+
const storagePrefix = c.req.query("prefix") || c.req.query("path") || "";
|
|
244
|
+
const bucket = c.req.query("bucket");
|
|
245
|
+
const maxResults = c.req.query("maxResults");
|
|
246
|
+
const pageToken = c.req.query("pageToken");
|
|
247
|
+
|
|
248
|
+
const result = await controller.listObjects(
|
|
249
|
+
storagePrefix,
|
|
233
250
|
{
|
|
234
251
|
bucket,
|
|
235
252
|
maxResults: maxResults ? parseInt(maxResults, 10) : undefined,
|
|
@@ -121,7 +121,7 @@ export class DefaultStorageRegistry implements StorageRegistry {
|
|
|
121
121
|
const controller = this.controllers.get(DEFAULT_STORAGE_ID);
|
|
122
122
|
if (!controller) {
|
|
123
123
|
throw new Error(
|
|
124
|
-
|
|
124
|
+
"[StorageRegistry] No default storage registered. " +
|
|
125
125
|
`Register one with id "${DEFAULT_STORAGE_ID}" or pass a single StorageController.`
|
|
126
126
|
);
|
|
127
127
|
}
|
|
@@ -178,10 +178,10 @@ function isStorageController(obj: unknown): obj is StorageController {
|
|
|
178
178
|
const controller = obj as StorageController;
|
|
179
179
|
// Check for required StorageController properties
|
|
180
180
|
return (
|
|
181
|
-
typeof controller.
|
|
182
|
-
typeof controller.
|
|
183
|
-
typeof controller.
|
|
184
|
-
typeof controller.
|
|
181
|
+
typeof controller.putObject === "function" &&
|
|
182
|
+
typeof controller.getSignedUrl === "function" &&
|
|
183
|
+
typeof controller.deleteObject === "function" &&
|
|
184
|
+
typeof controller.listObjects === "function" &&
|
|
185
185
|
typeof controller.getType === "function"
|
|
186
186
|
);
|
|
187
187
|
}
|
package/src/storage/types.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { StorageSource, UploadFileProps, UploadFileResult, DownloadConfig, Stora
|
|
|
8
8
|
* Local filesystem storage configuration
|
|
9
9
|
*/
|
|
10
10
|
export interface LocalStorageConfig {
|
|
11
|
-
type:
|
|
11
|
+
type: "local";
|
|
12
12
|
/** Base directory for file storage (e.g., './uploads') */
|
|
13
13
|
basePath: string;
|
|
14
14
|
/** Maximum file size in bytes (default: 50MB) */
|
|
@@ -23,12 +23,12 @@ export interface LocalStorageConfig {
|
|
|
23
23
|
* S3-compatible storage configuration (works with AWS S3 and MinIO)
|
|
24
24
|
*/
|
|
25
25
|
export interface S3StorageConfig {
|
|
26
|
-
type:
|
|
26
|
+
type: "s3";
|
|
27
27
|
/** S3 bucket name */
|
|
28
28
|
bucket: string;
|
|
29
29
|
/** AWS region (e.g., 'us-east-1') */
|
|
30
30
|
region?: string;
|
|
31
|
-
/** Custom endpoint URL (required for MinIO) */
|
|
31
|
+
/** Custom endpoint URL (required for MinIO, Cloudflare R2, Hetzner Object Storage) */
|
|
32
32
|
endpoint?: string;
|
|
33
33
|
/** AWS access key ID */
|
|
34
34
|
accessKeyId: string;
|
|
@@ -45,7 +45,16 @@ export interface S3StorageConfig {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Storage configuration
|
|
48
|
+
* Storage configuration — local filesystem or S3-compatible.
|
|
49
|
+
*
|
|
50
|
+
* **Built-in providers:**
|
|
51
|
+
* - `local` — Zero-config filesystem storage. Great for dev and single-server deployments (Hetzner, bare metal).
|
|
52
|
+
* - `s3` — Any S3-compatible provider. AWS S3, Cloudflare R2, MinIO, Hetzner Object Storage,
|
|
53
|
+
* Backblaze B2, DigitalOcean Spaces, and even GCS (via its S3-compatible interoperability API).
|
|
54
|
+
*
|
|
55
|
+
* **Custom providers:**
|
|
56
|
+
* For cloud-native storage (GCS, Azure Blob, etc.), implement the `StorageController`
|
|
57
|
+
* interface and pass the instance directly to the `storage` config.
|
|
49
58
|
*/
|
|
50
59
|
export type BackendStorageConfig = LocalStorageConfig | S3StorageConfig;
|
|
51
60
|
|
|
@@ -54,38 +63,41 @@ export type BackendStorageConfig = LocalStorageConfig | S3StorageConfig;
|
|
|
54
63
|
*/
|
|
55
64
|
export interface StorageController {
|
|
56
65
|
/**
|
|
57
|
-
* Upload
|
|
66
|
+
* Upload an object
|
|
58
67
|
*/
|
|
59
|
-
|
|
68
|
+
putObject(props: UploadFileProps): Promise<UploadFileResult>;
|
|
60
69
|
|
|
61
70
|
/**
|
|
62
|
-
* Get a download URL for
|
|
71
|
+
* Get a download URL (signed URL equivalent) for an object
|
|
63
72
|
*/
|
|
64
|
-
|
|
73
|
+
getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
|
|
65
74
|
|
|
66
75
|
/**
|
|
67
|
-
* Get
|
|
76
|
+
* Get object as a File
|
|
68
77
|
*/
|
|
69
|
-
|
|
78
|
+
getObject(key: string, bucket?: string): Promise<File | null>;
|
|
70
79
|
|
|
71
80
|
/**
|
|
72
|
-
* Delete
|
|
81
|
+
* Delete an object
|
|
73
82
|
*/
|
|
74
|
-
|
|
83
|
+
deleteObject(key: string, bucket?: string): Promise<void>;
|
|
75
84
|
|
|
76
85
|
/**
|
|
77
|
-
* List
|
|
86
|
+
* List objects in a prefix
|
|
78
87
|
*/
|
|
79
|
-
|
|
88
|
+
listObjects(prefix: string, options?: {
|
|
80
89
|
bucket?: string;
|
|
81
90
|
maxResults?: number;
|
|
82
91
|
pageToken?: string;
|
|
83
92
|
}): Promise<StorageListResult>;
|
|
84
93
|
|
|
85
94
|
/**
|
|
86
|
-
* Get the storage
|
|
95
|
+
* Get the storage provider identifier.
|
|
96
|
+
*
|
|
97
|
+
* Built-in values are `'local'` and `'s3'`. Custom implementations
|
|
98
|
+
* should return their own identifier (e.g. `'gcs'`, `'azure'`).
|
|
87
99
|
*/
|
|
88
|
-
getType():
|
|
100
|
+
getType(): string;
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
/**
|
|
@@ -97,26 +109,26 @@ export const DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
|
97
109
|
* Common image MIME types
|
|
98
110
|
*/
|
|
99
111
|
export const IMAGE_MIME_TYPES = [
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
"image/jpeg",
|
|
113
|
+
"image/png",
|
|
114
|
+
"image/gif",
|
|
115
|
+
"image/webp",
|
|
116
|
+
"image/svg+xml",
|
|
117
|
+
"image/bmp",
|
|
118
|
+
"image/tiff"
|
|
107
119
|
];
|
|
108
120
|
|
|
109
121
|
/**
|
|
110
122
|
* Common document MIME types
|
|
111
123
|
*/
|
|
112
124
|
export const DOCUMENT_MIME_TYPES = [
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
"application/pdf",
|
|
126
|
+
"application/msword",
|
|
127
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
128
|
+
"application/vnd.ms-excel",
|
|
129
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
130
|
+
"application/vnd.ms-powerpoint",
|
|
131
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
132
|
+
"text/plain",
|
|
133
|
+
"text/csv"
|
|
122
134
|
];
|
package/src/types/index.ts
CHANGED