@undefineds.co/xpod 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -3
- package/config/cli.json +9 -71
- package/config/cloud.json +34 -7
- package/config/local.json +6 -2
- package/config/resolver.json +11 -49
- package/config/runtime-open.json +22 -0
- package/config/xpod.base.json +32 -0
- package/config/xpod.cluster.json +2 -44
- package/config/xpod.json +5 -2
- package/dist/agents/AgentExecutorFactory.js +1 -1
- package/dist/agents/AgentExecutorFactory.js.map +1 -1
- package/dist/agents/AgentManager.js +1 -1
- package/dist/agents/AgentManager.js.map +1 -1
- package/dist/agents/config/agent-meta-schema.d.ts +7 -7
- package/dist/agents/config/agent-meta-schema.js +1 -1
- package/dist/agents/config/agent-meta-schema.js.map +1 -1
- package/dist/agents/config/resolve.js +1 -1
- package/dist/agents/config/resolve.js.map +1 -1
- package/dist/agents/schema/agent-config.d.ts +18 -18
- package/dist/agents/schema/agent-config.js +1 -1
- package/dist/agents/schema/agent-config.js.map +1 -1
- package/dist/agents/schema/tables.d.ts +8 -8
- package/dist/agents/schema/tables.js +1 -1
- package/dist/agents/schema/tables.js.map +1 -1
- package/dist/ai/schema/config.d.ts +7 -7
- package/dist/ai/schema/config.js +1 -1
- package/dist/ai/schema/config.js.map +1 -1
- package/dist/ai/schema/model.d.ts +13 -13
- package/dist/ai/schema/model.js +1 -1
- package/dist/ai/schema/model.js.map +1 -1
- package/dist/ai/schema/provider.d.ts +7 -7
- package/dist/ai/schema/provider.js +1 -1
- package/dist/ai/schema/provider.js.map +1 -1
- package/dist/ai/schema/vector-store.d.ts +17 -17
- package/dist/ai/schema/vector-store.js +1 -1
- package/dist/ai/schema/vector-store.js.map +1 -1
- package/dist/ai/service/CredentialReaderImpl.js +1 -1
- package/dist/ai/service/CredentialReaderImpl.js.map +1 -1
- package/dist/ai/service/DefaultAiConfigService.js.map +1 -1
- package/dist/api/ApiServer.d.ts +3 -1
- package/dist/api/ApiServer.js +14 -1
- package/dist/api/ApiServer.js.map +1 -1
- package/dist/api/auth/AuthContext.d.ts +12 -1
- package/dist/api/auth/AuthContext.js +18 -1
- package/dist/api/auth/AuthContext.js.map +1 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
- package/dist/api/auth/ServiceTokenAuthenticator.d.ts +18 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js +50 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -0
- package/dist/api/auth/index.d.ts +1 -0
- package/dist/api/auth/index.js +1 -0
- package/dist/api/auth/index.js.map +1 -1
- package/dist/api/chatkit/ai-provider.d.ts +0 -10
- package/dist/api/chatkit/ai-provider.js +11 -120
- package/dist/api/chatkit/ai-provider.js.map +1 -1
- package/dist/api/chatkit/default-agent.js +11 -8
- package/dist/api/chatkit/default-agent.js.map +1 -1
- package/dist/api/chatkit/pod-store.d.ts +6 -0
- package/dist/api/chatkit/pod-store.js +103 -36
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/schema.d.ts +32 -26
- package/dist/api/chatkit/schema.js +16 -8
- package/dist/api/chatkit/schema.js.map +1 -1
- package/dist/api/container/business-token.d.ts +9 -0
- package/dist/api/container/business-token.js +32 -0
- package/dist/api/container/business-token.js.map +1 -0
- package/dist/api/container/cloud.js +36 -12
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/common.js +13 -5
- package/dist/api/container/common.js.map +1 -1
- package/dist/api/container/index.js +94 -14
- package/dist/api/container/index.js.map +1 -1
- package/dist/api/container/local.js +2 -1
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +81 -9
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +8 -6
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/AdminHandler.js +9 -9
- package/dist/api/handlers/AdminHandler.js.map +1 -1
- package/dist/api/handlers/ApiKeyHandler.js +0 -6
- package/dist/api/handlers/ApiKeyHandler.js.map +1 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.d.ts +17 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js +171 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -0
- package/dist/api/handlers/PodManagementHandler.d.ts +5 -4
- package/dist/api/handlers/PodManagementHandler.js +11 -10
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/ProvisionHandler.d.ts +42 -0
- package/dist/api/handlers/ProvisionHandler.js +161 -0
- package/dist/api/handlers/ProvisionHandler.js.map +1 -0
- package/dist/api/handlers/QuotaHandler.d.ts +7 -7
- package/dist/api/handlers/QuotaHandler.js +143 -73
- package/dist/api/handlers/QuotaHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainClientHandler.js +2 -2
- package/dist/api/handlers/SubdomainClientHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainHandler.js +13 -8
- package/dist/api/handlers/SubdomainHandler.js.map +1 -1
- package/dist/api/handlers/UsageHandler.d.ts +14 -0
- package/dist/api/handlers/UsageHandler.js +123 -0
- package/dist/api/handlers/UsageHandler.js.map +1 -0
- package/dist/api/handlers/index.d.ts +3 -1
- package/dist/api/handlers/index.js +3 -1
- package/dist/api/handlers/index.js.map +1 -1
- package/dist/api/main.js +18 -0
- package/dist/api/main.js.map +1 -1
- package/dist/api/middleware/OpenAuthMiddleware.d.ts +12 -0
- package/dist/api/middleware/OpenAuthMiddleware.js +27 -0
- package/dist/api/middleware/OpenAuthMiddleware.js.map +1 -0
- package/dist/api/runtime.d.ts +15 -0
- package/dist/api/runtime.js +125 -0
- package/dist/api/runtime.js.map +1 -0
- package/dist/api/service/VectorStoreService.js +1 -1
- package/dist/api/service/VectorStoreService.js.map +1 -1
- package/dist/api/service/VercelChatService.d.ts +16 -7
- package/dist/api/service/VercelChatService.js +98 -178
- package/dist/api/service/VercelChatService.js.map +1 -1
- package/dist/api/store/DrizzleClientCredentialsStore.d.ts +6 -11
- package/dist/api/store/DrizzleClientCredentialsStore.js +9 -39
- package/dist/api/store/DrizzleClientCredentialsStore.js.map +1 -1
- package/dist/authorization/AuthModeSelector.d.ts +10 -0
- package/dist/authorization/AuthModeSelector.js +27 -0
- package/dist/authorization/AuthModeSelector.js.map +1 -0
- package/dist/authorization/AuthModeSelector.jsonld +81 -0
- package/dist/cli/commands/account.d.ts +6 -0
- package/dist/cli/commands/account.js +119 -0
- package/dist/cli/commands/account.js.map +1 -0
- package/dist/cli/commands/auth.js +20 -29
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/backup.d.ts +15 -0
- package/dist/cli/commands/backup.js +286 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/config.d.ts +34 -3
- package/dist/cli/commands/config.js +195 -258
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.js +94 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/pod.d.ts +6 -0
- package/dist/cli/commands/pod.js +124 -0
- package/dist/cli/commands/pod.js.map +1 -0
- package/dist/cli/commands/start.js +28 -5
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/lib/credentials-store.d.ts +17 -0
- package/dist/cli/lib/credentials-store.js +73 -0
- package/dist/cli/lib/credentials-store.js.map +1 -0
- package/dist/cli/lib/css-account.d.ts +17 -0
- package/dist/cli/lib/css-account.js +56 -0
- package/dist/cli/lib/css-account.js.map +1 -1
- package/dist/cli/lib/pod-thread-store.d.ts +57 -0
- package/dist/cli/lib/pod-thread-store.js +310 -0
- package/dist/cli/lib/pod-thread-store.js.map +1 -0
- package/dist/cli/lib/solid-auth.d.ts +20 -0
- package/dist/cli/lib/solid-auth.js +70 -0
- package/dist/cli/lib/solid-auth.js.map +1 -0
- package/dist/components/components.jsonld +5 -8
- package/dist/components/context.jsonld +114 -244
- package/dist/credential/schema/tables.d.ts +14 -14
- package/dist/credential/schema/tables.js +1 -1
- package/dist/credential/schema/tables.js.map +1 -1
- package/dist/edge/EdgeNodeAgent.js +2 -2
- package/dist/edge/EdgeNodeAgent.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -7
- package/dist/edge/EdgeNodeDnsCoordinator.js +31 -41
- package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.jsonld +1 -27
- package/dist/edge/EdgeNodeModeDetector.d.ts +1 -1
- package/dist/edge/EdgeNodeModeDetector.js +9 -11
- package/dist/edge/EdgeNodeModeDetector.js.map +1 -1
- package/dist/http/ClusterIngressRouter.js +3 -3
- package/dist/http/ClusterIngressRouter.js.map +1 -1
- package/dist/http/ClusterWebSocketConfigurator.js +2 -2
- package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
- package/dist/http/PodRoutingHttpHandler.js +2 -2
- package/dist/http/PodRoutingHttpHandler.js.map +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.d.ts +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/EdgeNodeRepository.d.ts +37 -4
- package/dist/identity/drizzle/EdgeNodeRepository.js +120 -128
- package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
- package/dist/identity/drizzle/ServiceTokenRepository.d.ts +52 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js +142 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -0
- package/dist/identity/drizzle/db.d.ts +9 -0
- package/dist/identity/drizzle/db.js +235 -3
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +5 -0
- package/dist/identity/drizzle/schema.pg.js +49 -20
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +332 -57
- package/dist/identity/drizzle/schema.sqlite.js +48 -18
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +6 -4
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.js +12 -14
- package/dist/index.js.map +1 -1
- package/dist/main.js +25 -8
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionCodeCodec.d.ts +39 -0
- package/dist/provision/ProvisionCodeCodec.js +65 -0
- package/dist/provision/ProvisionCodeCodec.js.map +1 -0
- package/dist/provision/ProvisionCodeCodec.jsonld +47 -0
- package/dist/provision/ProvisionPodCreator.d.ts +20 -0
- package/dist/provision/ProvisionPodCreator.js +84 -0
- package/dist/provision/ProvisionPodCreator.js.map +1 -0
- package/dist/provision/ProvisionPodCreator.jsonld +118 -0
- package/dist/quota/DrizzleQuotaService.d.ts +17 -3
- package/dist/quota/DrizzleQuotaService.js +108 -8
- package/dist/quota/DrizzleQuotaService.js.map +1 -1
- package/dist/quota/DrizzleQuotaService.jsonld +33 -22
- package/dist/quota/NoopQuotaService.d.ts +7 -1
- package/dist/quota/NoopQuotaService.js +12 -0
- package/dist/quota/NoopQuotaService.js.map +1 -1
- package/dist/quota/NoopQuotaService.jsonld +24 -0
- package/dist/quota/QuotaService.d.ts +17 -0
- package/dist/quota/QuotaService.js +5 -0
- package/dist/quota/QuotaService.js.map +1 -1
- package/dist/quota/QuotaService.jsonld +50 -0
- package/dist/runtime/Proxy.d.ts +22 -4
- package/dist/runtime/Proxy.js +154 -35
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/runtime/XpodRuntime.d.ts +49 -0
- package/dist/runtime/XpodRuntime.js +374 -0
- package/dist/runtime/XpodRuntime.js.map +1 -0
- package/dist/runtime/env-utils.d.ts +2 -0
- package/dist/runtime/env-utils.js +55 -0
- package/dist/runtime/env-utils.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +8 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/socket-fetch.d.ts +1 -0
- package/dist/runtime/socket-fetch.js +72 -0
- package/dist/runtime/socket-fetch.js.map +1 -0
- package/dist/runtime/socket-http.d.ts +1 -0
- package/dist/runtime/socket-http.js +142 -0
- package/dist/runtime/socket-http.js.map +1 -0
- package/dist/runtime/socket-utils.d.ts +2 -0
- package/dist/runtime/socket-utils.js +34 -0
- package/dist/runtime/socket-utils.js.map +1 -0
- package/dist/service/{EdgeNodeHeartbeatService.d.ts → EdgeNodeSignalClient.d.ts} +3 -3
- package/dist/service/{EdgeNodeHeartbeatService.js → EdgeNodeSignalClient.js} +4 -4
- package/dist/service/EdgeNodeSignalClient.js.map +1 -0
- package/dist/service/PodMigrationService.d.ts +1 -2
- package/dist/service/PodMigrationService.js +1 -2
- package/dist/service/PodMigrationService.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.d.ts +6 -0
- package/dist/storage/accessors/MinioDataAccessor.js +10 -0
- package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +2 -1
- package/dist/storage/accessors/MixDataAccessor.js +12 -1
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +19 -0
- package/dist/storage/locking/UrlAwareRedisLocker.d.ts +18 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js +60 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js.map +1 -0
- package/dist/storage/locking/UrlAwareRedisLocker.jsonld +123 -0
- package/dist/storage/quota/UsageRepository.d.ts +41 -8
- package/dist/storage/quota/UsageRepository.js +252 -50
- package/dist/storage/quota/UsageRepository.js.map +1 -1
- package/dist/storage/sparql/ComunicaQuintEngine.d.ts +9 -0
- package/dist/storage/sparql/ComunicaQuintEngine.js +50 -9
- package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
- package/dist/storage/sparql/QueryOptimizer.js +13 -1
- package/dist/storage/sparql/QueryOptimizer.js.map +1 -1
- package/dist/storage/sparql/QuintQuerySource.d.ts +14 -0
- package/dist/storage/sparql/QuintQuerySource.js +152 -1
- package/dist/storage/sparql/QuintQuerySource.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -0
- package/dist/storage/sparql/SubgraphQueryEngine.js +6 -2
- package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.jsonld +4 -0
- package/dist/subdomain/SubdomainClient.d.ts +3 -3
- package/dist/subdomain/SubdomainClient.js +1 -1
- package/dist/subdomain/SubdomainClient.js.map +1 -1
- package/dist/subdomain/SubdomainService.d.ts +15 -16
- package/dist/subdomain/SubdomainService.js +80 -54
- package/dist/subdomain/SubdomainService.js.map +1 -1
- package/dist/subdomain/SubdomainService.jsonld +22 -26
- package/dist/supervisor/Supervisor.d.ts +7 -2
- package/dist/supervisor/Supervisor.js +33 -1
- package/dist/supervisor/Supervisor.js.map +1 -1
- package/dist/task/DrizzleTaskQueue.d.ts +1 -1
- package/dist/task/DrizzleTaskQueue.js +1 -1
- package/dist/task/DrizzleTaskQueue.js.map +1 -1
- package/dist/task/schema.d.ts +10 -10
- package/dist/task/schema.js +1 -1
- package/dist/task/schema.js.map +1 -1
- package/dist/test-utils/index.d.ts +4 -0
- package/dist/test-utils/index.js +8 -0
- package/dist/test-utils/index.js.map +1 -0
- package/dist/test-utils/no-auth-xpod.d.ts +11 -0
- package/dist/test-utils/no-auth-xpod.js +25 -0
- package/dist/test-utils/no-auth-xpod.js.map +1 -0
- package/dist/test-utils/seed-pod.d.ts +5 -0
- package/dist/test-utils/seed-pod.js +61 -0
- package/dist/test-utils/seed-pod.js.map +1 -0
- package/package.json +38 -10
- package/templates/identity/account/create-pod.html.ejs +110 -0
- package/templates/main.html.ejs +10 -0
- package/dist/api/handlers/DevHandler.d.ts +0 -18
- package/dist/api/handlers/DevHandler.js +0 -276
- package/dist/api/handlers/DevHandler.js.map +0 -1
- package/dist/api/handlers/SignalHandler.d.ts +0 -13
- package/dist/api/handlers/SignalHandler.js +0 -122
- package/dist/api/handlers/SignalHandler.js.map +0 -1
- package/dist/gateway/Proxy.d.ts +0 -24
- package/dist/gateway/Proxy.js +0 -209
- package/dist/gateway/Proxy.js.map +0 -1
- package/dist/gateway/Supervisor.d.ts +0 -2
- package/dist/gateway/Supervisor.js +0 -7
- package/dist/gateway/Supervisor.js.map +0 -1
- package/dist/gateway/port-finder.d.ts +0 -4
- package/dist/gateway/port-finder.js +0 -15
- package/dist/gateway/port-finder.js.map +0 -1
- package/dist/gateway/types.d.ts +0 -1
- package/dist/gateway/types.js +0 -3
- package/dist/gateway/types.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.d.ts +0 -24
- package/dist/http/SignalInterceptHttpHandler.js +0 -47
- package/dist/http/SignalInterceptHttpHandler.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.jsonld +0 -103
- package/dist/http/admin/EdgeNodeSignalHttpHandler.d.ts +0 -71
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js +0 -674
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js.map +0 -1
- package/dist/http/admin/EdgeNodeSignalHttpHandler.jsonld +0 -406
- package/dist/http/cluster/PodMigrationHttpHandler.jsonld +0 -169
- package/dist/quota/DefaultQuotaService.d.ts +0 -16
- package/dist/quota/DefaultQuotaService.js +0 -37
- package/dist/quota/DefaultQuotaService.js.map +0 -1
- package/dist/quota/DefaultQuotaService.jsonld +0 -85
- package/dist/service/EdgeNodeHeartbeatService.js.map +0 -1
- package/dist/service/PodMigrationService.jsonld +0 -76
- package/dist/storage/MigratableDataAccessor.d.ts +0 -63
- package/dist/storage/MigratableDataAccessor.js +0 -11
- package/dist/storage/MigratableDataAccessor.js.map +0 -1
- package/dist/storage/MigratableDataAccessor.jsonld +0 -60
- package/dist/storage/accessors/TieredMinioDataAccessor.d.ts +0 -150
- package/dist/storage/accessors/TieredMinioDataAccessor.js +0 -582
- package/dist/storage/accessors/TieredMinioDataAccessor.js.map +0 -1
- package/dist/storage/accessors/TieredMinioDataAccessor.jsonld +0 -333
- package/static/app/assets/index.css +0 -1
- package/static/app/assets/main.js +0 -11
|
@@ -2,31 +2,46 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerQuotaRoutes = registerQuotaRoutes;
|
|
4
4
|
const global_logger_factory_1 = require("global-logger-factory");
|
|
5
|
+
const AuthContext_1 = require("../auth/AuthContext");
|
|
5
6
|
/**
|
|
6
7
|
* Handler for quota management API
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Supports four resource types: storage, bandwidth, compute, token.
|
|
10
|
+
* Requires ServiceAuthContext with 'quota:write' scope for mutations.
|
|
10
11
|
*
|
|
11
|
-
* GET /v1/quota/accounts/:accountId - Get account quota
|
|
12
|
+
* GET /v1/quota/accounts/:accountId - Get account quota + usage
|
|
12
13
|
* PUT /v1/quota/accounts/:accountId - Set account quota
|
|
13
|
-
* DELETE /v1/quota/accounts/:accountId - Clear account quota
|
|
14
|
-
* GET /v1/quota/pods/:podId - Get pod quota
|
|
14
|
+
* DELETE /v1/quota/accounts/:accountId - Clear account quota (revert to defaults)
|
|
15
|
+
* GET /v1/quota/pods/:podId - Get pod quota + usage
|
|
15
16
|
* PUT /v1/quota/pods/:podId - Set pod quota
|
|
16
17
|
* DELETE /v1/quota/pods/:podId - Clear pod quota
|
|
17
18
|
*/
|
|
18
19
|
function registerQuotaRoutes(server, options) {
|
|
19
20
|
const logger = (0, global_logger_factory_1.getLoggerFor)('QuotaHandler');
|
|
20
|
-
const { quotaService,
|
|
21
|
-
// GET /
|
|
21
|
+
const { quotaService, usageRepo } = options;
|
|
22
|
+
// GET /v1/quota/accounts/:accountId
|
|
22
23
|
server.get('/v1/quota/accounts/:accountId', async (request, response, params) => {
|
|
23
24
|
const accountId = decodeURIComponent(params.accountId);
|
|
24
25
|
try {
|
|
25
|
-
const
|
|
26
|
+
const quota = await quotaService.getAccountQuota(accountId);
|
|
27
|
+
const usage = await usageRepo.getAccountUsage(accountId);
|
|
26
28
|
sendJson(response, 200, {
|
|
27
|
-
type: 'account',
|
|
28
29
|
accountId,
|
|
29
|
-
|
|
30
|
+
quota: {
|
|
31
|
+
storageLimitBytes: quota.storageLimitBytes,
|
|
32
|
+
bandwidthLimitBps: quota.bandwidthLimitBps,
|
|
33
|
+
computeLimitSeconds: quota.computeLimitSeconds,
|
|
34
|
+
tokenLimitMonthly: quota.tokenLimitMonthly,
|
|
35
|
+
},
|
|
36
|
+
usage: {
|
|
37
|
+
storageBytes: usage?.storageBytes ?? 0,
|
|
38
|
+
ingressBytes: usage?.ingressBytes ?? 0,
|
|
39
|
+
egressBytes: usage?.egressBytes ?? 0,
|
|
40
|
+
computeSeconds: usage?.computeSeconds ?? 0,
|
|
41
|
+
tokensUsed: usage?.tokensUsed ?? 0,
|
|
42
|
+
periodStart: usage?.periodStart ? new Date(usage.periodStart * 1000).toISOString() : null,
|
|
43
|
+
},
|
|
44
|
+
source: hasCustomQuota(usage) ? 'custom' : 'default',
|
|
30
45
|
});
|
|
31
46
|
}
|
|
32
47
|
catch (error) {
|
|
@@ -34,8 +49,11 @@ function registerQuotaRoutes(server, options) {
|
|
|
34
49
|
sendJson(response, 500, { error: 'Failed to get quota' });
|
|
35
50
|
}
|
|
36
51
|
});
|
|
37
|
-
// PUT /
|
|
52
|
+
// PUT /v1/quota/accounts/:accountId
|
|
38
53
|
server.put('/v1/quota/accounts/:accountId', async (request, response, params) => {
|
|
54
|
+
if (!requireScope(request, response, 'quota:write')) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
39
57
|
const accountId = decodeURIComponent(params.accountId);
|
|
40
58
|
const body = await readJsonBody(request);
|
|
41
59
|
if (!body || typeof body !== 'object') {
|
|
@@ -43,24 +61,19 @@ function registerQuotaRoutes(server, options) {
|
|
|
43
61
|
return;
|
|
44
62
|
}
|
|
45
63
|
const payload = body;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
const quota = extractQuota(payload.quotaLimit);
|
|
51
|
-
if (quota === undefined) {
|
|
52
|
-
sendJson(response, 400, { error: 'quotaLimit must be a non-negative number or null' });
|
|
64
|
+
const partial = extractQuotaFields(payload);
|
|
65
|
+
if (!partial) {
|
|
66
|
+
sendJson(response, 400, { error: 'Body must include at least one quota field (storageLimitBytes, bandwidthLimitBps, computeLimitSeconds, tokenLimitMonthly)' });
|
|
53
67
|
return;
|
|
54
68
|
}
|
|
55
69
|
try {
|
|
56
|
-
await quotaService.
|
|
57
|
-
const latest = await quotaService.
|
|
58
|
-
logger.info(`Set account ${accountId} quota
|
|
70
|
+
await quotaService.setAccountQuota(accountId, partial);
|
|
71
|
+
const latest = await quotaService.getAccountQuota(accountId);
|
|
72
|
+
logger.info(`Set account ${accountId} quota: ${JSON.stringify(partial)}`);
|
|
59
73
|
sendJson(response, 200, {
|
|
60
74
|
status: 'updated',
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
quotaLimit: latest ?? null,
|
|
75
|
+
accountId,
|
|
76
|
+
quota: latest,
|
|
64
77
|
});
|
|
65
78
|
}
|
|
66
79
|
catch (error) {
|
|
@@ -68,16 +81,18 @@ function registerQuotaRoutes(server, options) {
|
|
|
68
81
|
sendJson(response, 500, { error: 'Failed to set quota' });
|
|
69
82
|
}
|
|
70
83
|
});
|
|
71
|
-
// DELETE /
|
|
84
|
+
// DELETE /v1/quota/accounts/:accountId
|
|
72
85
|
server.delete('/v1/quota/accounts/:accountId', async (request, response, params) => {
|
|
86
|
+
if (!requireScope(request, response, 'quota:write')) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
73
89
|
const accountId = decodeURIComponent(params.accountId);
|
|
74
90
|
try {
|
|
75
|
-
await quotaService.
|
|
91
|
+
await quotaService.clearAccountQuota(accountId);
|
|
76
92
|
logger.info(`Cleared account ${accountId} quota`);
|
|
77
93
|
sendJson(response, 200, {
|
|
78
94
|
status: 'cleared',
|
|
79
|
-
|
|
80
|
-
targetId: accountId,
|
|
95
|
+
accountId,
|
|
81
96
|
});
|
|
82
97
|
}
|
|
83
98
|
catch (error) {
|
|
@@ -85,22 +100,30 @@ function registerQuotaRoutes(server, options) {
|
|
|
85
100
|
sendJson(response, 500, { error: 'Failed to clear quota' });
|
|
86
101
|
}
|
|
87
102
|
});
|
|
88
|
-
// GET /
|
|
103
|
+
// GET /v1/quota/pods/:podId
|
|
89
104
|
server.get('/v1/quota/pods/:podId', async (request, response, params) => {
|
|
90
105
|
const podId = decodeURIComponent(params.podId);
|
|
91
106
|
try {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
sendJson(response, 404, { error: 'Pod not found' });
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const limit = await quotaService.getPodLimit(podId);
|
|
107
|
+
const quota = await quotaService.getPodQuota(podId);
|
|
108
|
+
const usage = await usageRepo.getPodUsage(podId);
|
|
98
109
|
sendJson(response, 200, {
|
|
99
|
-
type: 'pod',
|
|
100
110
|
podId,
|
|
101
|
-
accountId:
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
accountId: usage?.accountId ?? null,
|
|
112
|
+
quota: {
|
|
113
|
+
storageLimitBytes: quota.storageLimitBytes,
|
|
114
|
+
bandwidthLimitBps: quota.bandwidthLimitBps,
|
|
115
|
+
computeLimitSeconds: quota.computeLimitSeconds,
|
|
116
|
+
tokenLimitMonthly: quota.tokenLimitMonthly,
|
|
117
|
+
},
|
|
118
|
+
usage: {
|
|
119
|
+
storageBytes: usage?.storageBytes ?? 0,
|
|
120
|
+
ingressBytes: usage?.ingressBytes ?? 0,
|
|
121
|
+
egressBytes: usage?.egressBytes ?? 0,
|
|
122
|
+
computeSeconds: usage?.computeSeconds ?? 0,
|
|
123
|
+
tokensUsed: usage?.tokensUsed ?? 0,
|
|
124
|
+
periodStart: usage?.periodStart ? new Date(usage.periodStart * 1000).toISOString() : null,
|
|
125
|
+
},
|
|
126
|
+
source: hasCustomQuota(usage) ? 'custom' : 'default',
|
|
104
127
|
});
|
|
105
128
|
}
|
|
106
129
|
catch (error) {
|
|
@@ -108,8 +131,11 @@ function registerQuotaRoutes(server, options) {
|
|
|
108
131
|
sendJson(response, 500, { error: 'Failed to get quota' });
|
|
109
132
|
}
|
|
110
133
|
});
|
|
111
|
-
// PUT /
|
|
134
|
+
// PUT /v1/quota/pods/:podId
|
|
112
135
|
server.put('/v1/quota/pods/:podId', async (request, response, params) => {
|
|
136
|
+
if (!requireScope(request, response, 'quota:write')) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
113
139
|
const podId = decodeURIComponent(params.podId);
|
|
114
140
|
const body = await readJsonBody(request);
|
|
115
141
|
if (!body || typeof body !== 'object') {
|
|
@@ -117,29 +143,19 @@ function registerQuotaRoutes(server, options) {
|
|
|
117
143
|
return;
|
|
118
144
|
}
|
|
119
145
|
const payload = body;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
const quota = extractQuota(payload.quotaLimit);
|
|
125
|
-
if (quota === undefined) {
|
|
126
|
-
sendJson(response, 400, { error: 'quotaLimit must be a non-negative number or null' });
|
|
146
|
+
const partial = extractQuotaFields(payload);
|
|
147
|
+
if (!partial) {
|
|
148
|
+
sendJson(response, 400, { error: 'Body must include at least one quota field' });
|
|
127
149
|
return;
|
|
128
150
|
}
|
|
129
151
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
await quotaService.setPodLimit(podId, quota);
|
|
136
|
-
const latest = await quotaService.getPodLimit(podId);
|
|
137
|
-
logger.info(`Set pod ${podId} quota to ${quota}`);
|
|
152
|
+
await quotaService.setPodQuota(podId, partial);
|
|
153
|
+
const latest = await quotaService.getPodQuota(podId);
|
|
154
|
+
logger.info(`Set pod ${podId} quota: ${JSON.stringify(partial)}`);
|
|
138
155
|
sendJson(response, 200, {
|
|
139
156
|
status: 'updated',
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
quotaLimit: latest ?? null,
|
|
157
|
+
podId,
|
|
158
|
+
quota: latest,
|
|
143
159
|
});
|
|
144
160
|
}
|
|
145
161
|
catch (error) {
|
|
@@ -147,21 +163,18 @@ function registerQuotaRoutes(server, options) {
|
|
|
147
163
|
sendJson(response, 500, { error: 'Failed to set quota' });
|
|
148
164
|
}
|
|
149
165
|
});
|
|
150
|
-
// DELETE /
|
|
166
|
+
// DELETE /v1/quota/pods/:podId
|
|
151
167
|
server.delete('/v1/quota/pods/:podId', async (request, response, params) => {
|
|
168
|
+
if (!requireScope(request, response, 'quota:write')) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
152
171
|
const podId = decodeURIComponent(params.podId);
|
|
153
172
|
try {
|
|
154
|
-
|
|
155
|
-
if (!podInfo) {
|
|
156
|
-
sendJson(response, 404, { error: 'Pod not found' });
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
await quotaService.setPodLimit(podId, null);
|
|
173
|
+
await quotaService.clearPodQuota(podId);
|
|
160
174
|
logger.info(`Cleared pod ${podId} quota`);
|
|
161
175
|
sendJson(response, 200, {
|
|
162
176
|
status: 'cleared',
|
|
163
|
-
|
|
164
|
-
targetId: podId,
|
|
177
|
+
podId,
|
|
165
178
|
});
|
|
166
179
|
}
|
|
167
180
|
catch (error) {
|
|
@@ -170,14 +183,71 @@ function registerQuotaRoutes(server, options) {
|
|
|
170
183
|
}
|
|
171
184
|
});
|
|
172
185
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Check if the request has the required scope. Sends 403 if not.
|
|
188
|
+
*/
|
|
189
|
+
function requireScope(request, response, scope) {
|
|
190
|
+
if (!request.auth) {
|
|
191
|
+
sendJson(response, 401, { error: 'Authentication required' });
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
// Service tokens need explicit scope; Solid users with admin role can also access
|
|
195
|
+
if (request.auth.type === 'service') {
|
|
196
|
+
if (!(0, AuthContext_1.hasScope)(request.auth, scope)) {
|
|
197
|
+
sendJson(response, 403, { error: `Missing required scope: ${scope}` });
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
// Allow Solid auth (for admin users) - actual admin check can be added later
|
|
203
|
+
if (request.auth.type === 'solid') {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
sendJson(response, 403, { error: 'Insufficient permissions' });
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
const QUOTA_FIELDS = ['storageLimitBytes', 'bandwidthLimitBps', 'computeLimitSeconds', 'tokenLimitMonthly'];
|
|
210
|
+
function extractQuotaFields(payload) {
|
|
211
|
+
const result = {};
|
|
212
|
+
let hasField = false;
|
|
213
|
+
for (const field of QUOTA_FIELDS) {
|
|
214
|
+
if (Object.prototype.hasOwnProperty.call(payload, field)) {
|
|
215
|
+
const value = payload[field];
|
|
216
|
+
if (value === null) {
|
|
217
|
+
result[field] = null;
|
|
218
|
+
hasField = true;
|
|
219
|
+
}
|
|
220
|
+
else if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
|
|
221
|
+
result[field] = value;
|
|
222
|
+
hasField = true;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
return undefined; // Invalid value
|
|
226
|
+
}
|
|
227
|
+
}
|
|
176
228
|
}
|
|
177
|
-
|
|
178
|
-
|
|
229
|
+
// Backward compat: support legacy 'quotaLimit' field
|
|
230
|
+
if (!hasField && Object.prototype.hasOwnProperty.call(payload, 'quotaLimit')) {
|
|
231
|
+
const value = payload.quotaLimit;
|
|
232
|
+
if (value === null) {
|
|
233
|
+
result.storageLimitBytes = null;
|
|
234
|
+
hasField = true;
|
|
235
|
+
}
|
|
236
|
+
else if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
|
|
237
|
+
result.storageLimitBytes = value;
|
|
238
|
+
hasField = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return hasField ? result : undefined;
|
|
242
|
+
}
|
|
243
|
+
function hasCustomQuota(usage) {
|
|
244
|
+
if (!usage) {
|
|
245
|
+
return false;
|
|
179
246
|
}
|
|
180
|
-
return
|
|
247
|
+
return typeof usage.storageLimitBytes === 'number'
|
|
248
|
+
|| typeof usage.bandwidthLimitBps === 'number'
|
|
249
|
+
|| typeof usage.computeLimitSeconds === 'number'
|
|
250
|
+
|| typeof usage.tokenLimitMonthly === 'number';
|
|
181
251
|
}
|
|
182
252
|
async function readJsonBody(request) {
|
|
183
253
|
return new Promise((resolve, reject) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/QuotaHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { QuotaService } from '../../quota/QuotaService';\nimport type { AccountRepository } from '../../identity/drizzle/AccountRepository';\n\nexport interface QuotaHandlerOptions {\n quotaService: QuotaService;\n accountRepo: AccountRepository;\n}\n\n/**\n * Handler for quota management API\n * \n * These endpoints are for internal billing system use.\n * They require authentication via client credentials.\n * \n * GET /v1/quota/accounts/:accountId - Get account quota\n * PUT /v1/quota/accounts/:accountId - Set account quota\n * DELETE /v1/quota/accounts/:accountId - Clear account quota\n * GET /v1/quota/pods/:podId - Get pod quota\n * PUT /v1/quota/pods/:podId - Set pod quota\n * DELETE /v1/quota/pods/:podId - Clear pod quota\n */\nexport function registerQuotaRoutes(server: ApiServer, options: QuotaHandlerOptions): void {\n const logger = getLoggerFor('QuotaHandler');\n const { quotaService, accountRepo } = options;\n\n // GET /api/quota/accounts/:accountId\n server.get('/v1/quota/accounts/:accountId', async (request, response, params) => {\n const accountId = decodeURIComponent(params.accountId);\n\n try {\n const limit = await quotaService.getAccountLimit(accountId);\n sendJson(response, 200, {\n type: 'account',\n accountId,\n quotaLimit: limit ?? null,\n });\n } catch (error) {\n logger.error(`Failed to get account quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to get quota' });\n }\n });\n\n // PUT /api/quota/accounts/:accountId\n server.put('/v1/quota/accounts/:accountId', async (request, response, params) => {\n const accountId = decodeURIComponent(params.accountId);\n const body = await readJsonBody(request);\n\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n if (!Object.prototype.hasOwnProperty.call(payload, 'quotaLimit')) {\n sendJson(response, 400, { error: 'Body must include quotaLimit' });\n return;\n }\n\n const quota = extractQuota(payload.quotaLimit);\n if (quota === undefined) {\n sendJson(response, 400, { error: 'quotaLimit must be a non-negative number or null' });\n return;\n }\n\n try {\n await quotaService.setAccountLimit(accountId, quota);\n const latest = await quotaService.getAccountLimit(accountId);\n \n logger.info(`Set account ${accountId} quota to ${quota}`);\n \n sendJson(response, 200, {\n status: 'updated',\n targetType: 'account',\n targetId: accountId,\n quotaLimit: latest ?? null,\n });\n } catch (error) {\n logger.error(`Failed to set account quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to set quota' });\n }\n });\n\n // DELETE /api/quota/accounts/:accountId\n server.delete('/v1/quota/accounts/:accountId', async (request, response, params) => {\n const accountId = decodeURIComponent(params.accountId);\n\n try {\n await quotaService.setAccountLimit(accountId, null);\n \n logger.info(`Cleared account ${accountId} quota`);\n \n sendJson(response, 200, {\n status: 'cleared',\n targetType: 'account',\n targetId: accountId,\n });\n } catch (error) {\n logger.error(`Failed to clear account quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to clear quota' });\n }\n });\n\n // GET /api/quota/pods/:podId\n server.get('/v1/quota/pods/:podId', async (request, response, params) => {\n const podId = decodeURIComponent(params.podId);\n\n try {\n const podInfo = await accountRepo.getPodInfo(podId);\n if (!podInfo) {\n sendJson(response, 404, { error: 'Pod not found' });\n return;\n }\n\n const limit = await quotaService.getPodLimit(podId);\n sendJson(response, 200, {\n type: 'pod',\n podId,\n accountId: podInfo.accountId,\n baseUrl: podInfo.baseUrl ?? null,\n quotaLimit: limit ?? null,\n });\n } catch (error) {\n logger.error(`Failed to get pod quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to get quota' });\n }\n });\n\n // PUT /api/quota/pods/:podId\n server.put('/v1/quota/pods/:podId', async (request, response, params) => {\n const podId = decodeURIComponent(params.podId);\n const body = await readJsonBody(request);\n\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n if (!Object.prototype.hasOwnProperty.call(payload, 'quotaLimit')) {\n sendJson(response, 400, { error: 'Body must include quotaLimit' });\n return;\n }\n\n const quota = extractQuota(payload.quotaLimit);\n if (quota === undefined) {\n sendJson(response, 400, { error: 'quotaLimit must be a non-negative number or null' });\n return;\n }\n\n try {\n const podInfo = await accountRepo.getPodInfo(podId);\n if (!podInfo) {\n sendJson(response, 404, { error: 'Pod not found' });\n return;\n }\n\n await quotaService.setPodLimit(podId, quota);\n const latest = await quotaService.getPodLimit(podId);\n \n logger.info(`Set pod ${podId} quota to ${quota}`);\n \n sendJson(response, 200, {\n status: 'updated',\n targetType: 'pod',\n targetId: podId,\n quotaLimit: latest ?? null,\n });\n } catch (error) {\n logger.error(`Failed to set pod quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to set quota' });\n }\n });\n\n // DELETE /api/quota/pods/:podId\n server.delete('/v1/quota/pods/:podId', async (request, response, params) => {\n const podId = decodeURIComponent(params.podId);\n\n try {\n const podInfo = await accountRepo.getPodInfo(podId);\n if (!podInfo) {\n sendJson(response, 404, { error: 'Pod not found' });\n return;\n }\n\n await quotaService.setPodLimit(podId, null);\n \n logger.info(`Cleared pod ${podId} quota`);\n \n sendJson(response, 200, {\n status: 'cleared',\n targetType: 'pod',\n targetId: podId,\n });\n } catch (error) {\n logger.error(`Failed to clear pod quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to clear quota' });\n }\n });\n}\n\nfunction extractQuota(value: unknown): number | null | undefined {\n if (value === null) {\n return null;\n }\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n return undefined;\n }\n return value;\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"QuotaHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/QuotaHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { QuotaService } from '../../quota/QuotaService';\nimport type { UsageRepository } from '../../storage/quota/UsageRepository';\nimport { hasScope } from '../auth/AuthContext';\n\nexport interface QuotaHandlerOptions {\n quotaService: QuotaService;\n usageRepo: UsageRepository;\n}\n\n/**\n * Handler for quota management API\n *\n * Supports four resource types: storage, bandwidth, compute, token.\n * Requires ServiceAuthContext with 'quota:write' scope for mutations.\n *\n * GET /v1/quota/accounts/:accountId - Get account quota + usage\n * PUT /v1/quota/accounts/:accountId - Set account quota\n * DELETE /v1/quota/accounts/:accountId - Clear account quota (revert to defaults)\n * GET /v1/quota/pods/:podId - Get pod quota + usage\n * PUT /v1/quota/pods/:podId - Set pod quota\n * DELETE /v1/quota/pods/:podId - Clear pod quota\n */\nexport function registerQuotaRoutes(server: ApiServer, options: QuotaHandlerOptions): void {\n const logger = getLoggerFor('QuotaHandler');\n const { quotaService, usageRepo } = options;\n\n // GET /v1/quota/accounts/:accountId\n server.get('/v1/quota/accounts/:accountId', async (request, response, params) => {\n const accountId = decodeURIComponent(params.accountId);\n\n try {\n const quota = await quotaService.getAccountQuota(accountId);\n const usage = await usageRepo.getAccountUsage(accountId);\n\n sendJson(response, 200, {\n accountId,\n quota: {\n storageLimitBytes: quota.storageLimitBytes,\n bandwidthLimitBps: quota.bandwidthLimitBps,\n computeLimitSeconds: quota.computeLimitSeconds,\n tokenLimitMonthly: quota.tokenLimitMonthly,\n },\n usage: {\n storageBytes: usage?.storageBytes ?? 0,\n ingressBytes: usage?.ingressBytes ?? 0,\n egressBytes: usage?.egressBytes ?? 0,\n computeSeconds: usage?.computeSeconds ?? 0,\n tokensUsed: usage?.tokensUsed ?? 0,\n periodStart: usage?.periodStart ? new Date(usage.periodStart * 1000).toISOString() : null,\n },\n source: hasCustomQuota(usage) ? 'custom' : 'default',\n });\n } catch (error) {\n logger.error(`Failed to get account quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to get quota' });\n }\n });\n\n // PUT /v1/quota/accounts/:accountId\n server.put('/v1/quota/accounts/:accountId', async (request, response, params) => {\n if (!requireScope(request, response, 'quota:write')) {\n return;\n }\n\n const accountId = decodeURIComponent(params.accountId);\n const body = await readJsonBody(request);\n\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n const partial = extractQuotaFields(payload);\n if (!partial) {\n sendJson(response, 400, { error: 'Body must include at least one quota field (storageLimitBytes, bandwidthLimitBps, computeLimitSeconds, tokenLimitMonthly)' });\n return;\n }\n\n try {\n await quotaService.setAccountQuota(accountId, partial);\n const latest = await quotaService.getAccountQuota(accountId);\n\n logger.info(`Set account ${accountId} quota: ${JSON.stringify(partial)}`);\n\n sendJson(response, 200, {\n status: 'updated',\n accountId,\n quota: latest,\n });\n } catch (error) {\n logger.error(`Failed to set account quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to set quota' });\n }\n });\n\n // DELETE /v1/quota/accounts/:accountId\n server.delete('/v1/quota/accounts/:accountId', async (request, response, params) => {\n if (!requireScope(request, response, 'quota:write')) {\n return;\n }\n\n const accountId = decodeURIComponent(params.accountId);\n\n try {\n await quotaService.clearAccountQuota(accountId);\n\n logger.info(`Cleared account ${accountId} quota`);\n\n sendJson(response, 200, {\n status: 'cleared',\n accountId,\n });\n } catch (error) {\n logger.error(`Failed to clear account quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to clear quota' });\n }\n });\n\n // GET /v1/quota/pods/:podId\n server.get('/v1/quota/pods/:podId', async (request, response, params) => {\n const podId = decodeURIComponent(params.podId);\n\n try {\n const quota = await quotaService.getPodQuota(podId);\n const usage = await usageRepo.getPodUsage(podId);\n\n sendJson(response, 200, {\n podId,\n accountId: usage?.accountId ?? null,\n quota: {\n storageLimitBytes: quota.storageLimitBytes,\n bandwidthLimitBps: quota.bandwidthLimitBps,\n computeLimitSeconds: quota.computeLimitSeconds,\n tokenLimitMonthly: quota.tokenLimitMonthly,\n },\n usage: {\n storageBytes: usage?.storageBytes ?? 0,\n ingressBytes: usage?.ingressBytes ?? 0,\n egressBytes: usage?.egressBytes ?? 0,\n computeSeconds: usage?.computeSeconds ?? 0,\n tokensUsed: usage?.tokensUsed ?? 0,\n periodStart: usage?.periodStart ? new Date(usage.periodStart * 1000).toISOString() : null,\n },\n source: hasCustomQuota(usage) ? 'custom' : 'default',\n });\n } catch (error) {\n logger.error(`Failed to get pod quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to get quota' });\n }\n });\n\n // PUT /v1/quota/pods/:podId\n server.put('/v1/quota/pods/:podId', async (request, response, params) => {\n if (!requireScope(request, response, 'quota:write')) {\n return;\n }\n\n const podId = decodeURIComponent(params.podId);\n const body = await readJsonBody(request);\n\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n const partial = extractQuotaFields(payload);\n if (!partial) {\n sendJson(response, 400, { error: 'Body must include at least one quota field' });\n return;\n }\n\n try {\n await quotaService.setPodQuota(podId, partial);\n const latest = await quotaService.getPodQuota(podId);\n\n logger.info(`Set pod ${podId} quota: ${JSON.stringify(partial)}`);\n\n sendJson(response, 200, {\n status: 'updated',\n podId,\n quota: latest,\n });\n } catch (error) {\n logger.error(`Failed to set pod quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to set quota' });\n }\n });\n\n // DELETE /v1/quota/pods/:podId\n server.delete('/v1/quota/pods/:podId', async (request, response, params) => {\n if (!requireScope(request, response, 'quota:write')) {\n return;\n }\n\n const podId = decodeURIComponent(params.podId);\n\n try {\n await quotaService.clearPodQuota(podId);\n\n logger.info(`Cleared pod ${podId} quota`);\n\n sendJson(response, 200, {\n status: 'cleared',\n podId,\n });\n } catch (error) {\n logger.error(`Failed to clear pod quota: ${error}`);\n sendJson(response, 500, { error: 'Failed to clear quota' });\n }\n });\n}\n\n/**\n * Check if the request has the required scope. Sends 403 if not.\n */\nfunction requireScope(request: AuthenticatedRequest, response: ServerResponse, scope: string): boolean {\n if (!request.auth) {\n sendJson(response, 401, { error: 'Authentication required' });\n return false;\n }\n // Service tokens need explicit scope; Solid users with admin role can also access\n if (request.auth.type === 'service') {\n if (!hasScope(request.auth, scope)) {\n sendJson(response, 403, { error: `Missing required scope: ${scope}` });\n return false;\n }\n return true;\n }\n // Allow Solid auth (for admin users) - actual admin check can be added later\n if (request.auth.type === 'solid') {\n return true;\n }\n sendJson(response, 403, { error: 'Insufficient permissions' });\n return false;\n}\n\nconst QUOTA_FIELDS = ['storageLimitBytes', 'bandwidthLimitBps', 'computeLimitSeconds', 'tokenLimitMonthly'] as const;\n\nfunction extractQuotaFields(payload: Record<string, unknown>): Record<string, number | null> | undefined {\n const result: Record<string, number | null> = {};\n let hasField = false;\n\n for (const field of QUOTA_FIELDS) {\n if (Object.prototype.hasOwnProperty.call(payload, field)) {\n const value = payload[field];\n if (value === null) {\n result[field] = null;\n hasField = true;\n } else if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {\n result[field] = value;\n hasField = true;\n } else {\n return undefined; // Invalid value\n }\n }\n }\n\n // Backward compat: support legacy 'quotaLimit' field\n if (!hasField && Object.prototype.hasOwnProperty.call(payload, 'quotaLimit')) {\n const value = payload.quotaLimit;\n if (value === null) {\n result.storageLimitBytes = null;\n hasField = true;\n } else if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {\n result.storageLimitBytes = value;\n hasField = true;\n }\n }\n\n return hasField ? result : undefined;\n}\n\nfunction hasCustomQuota(usage: { storageLimitBytes?: number | null; bandwidthLimitBps?: number | null; computeLimitSeconds?: number | null; tokenLimitMonthly?: number | null } | undefined): boolean {\n if (!usage) {\n return false;\n }\n return typeof usage.storageLimitBytes === 'number'\n || typeof usage.bandwidthLimitBps === 'number'\n || typeof usage.computeLimitSeconds === 'number'\n || typeof usage.tokenLimitMonthly === 'number';\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
|
|
@@ -74,7 +74,7 @@ function registerSubdomainClientRoutes(server, options) {
|
|
|
74
74
|
sendJson(response, 400, { error: 'Invalid request body' });
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
-
const { subdomain, localPort,
|
|
77
|
+
const { subdomain, localPort, ipv4 } = body;
|
|
78
78
|
if (!subdomain || typeof subdomain !== 'string') {
|
|
79
79
|
sendJson(response, 400, { error: 'Missing "subdomain" field' });
|
|
80
80
|
return;
|
|
@@ -87,7 +87,7 @@ function registerSubdomainClientRoutes(server, options) {
|
|
|
87
87
|
const result = await client.register({
|
|
88
88
|
subdomain,
|
|
89
89
|
localPort,
|
|
90
|
-
|
|
90
|
+
ipv4: typeof ipv4 === 'string' ? ipv4 : undefined,
|
|
91
91
|
});
|
|
92
92
|
sendJson(response, 201, result);
|
|
93
93
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubdomainClientHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/SubdomainClientHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SubdomainClientHandler - Local 模式子域名 API\n * \n * 代理请求到 Cloud API,供本地 UI 调用\n */\n\nimport type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { SubdomainClient } from '../../subdomain/SubdomainClient';\n\nexport interface SubdomainClientHandlerOptions {\n subdomainClient: SubdomainClient;\n}\n\n/**\n * 注册 Local 模式子域名路由\n * \n * 这些路由是本地 UI 调用的入口,内部通过 SubdomainClient 转发到 Cloud\n * \n * GET /v1/subdomain/check?name=xxx - 检查子域名可用性\n * GET /v1/subdomain - 列出子域名\n * GET /v1/subdomain/:name - 获取子域名详情\n * POST /v1/subdomain/register - 注册子域名\n * DELETE /v1/subdomain/:name - 释放子域名\n * POST /v1/subdomain/:name/start - 启动隧道\n * POST /v1/subdomain/:name/stop - 停止隧道\n */\nexport function registerSubdomainClientRoutes(\n server: ApiServer,\n options: SubdomainClientHandlerOptions,\n): void {\n const logger = getLoggerFor('SubdomainClientHandler');\n const client = options.subdomainClient;\n\n // GET /v1/subdomain/check?name=xxx - 检查可用性 (public)\n server.get('/v1/subdomain/check', async (request, response, _params) => {\n const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);\n const name = url.searchParams.get('name');\n\n if (!name) {\n sendJson(response, 400, { error: 'Missing \"name\" query parameter' });\n return;\n }\n\n try {\n const result = await client.checkAvailability(name);\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to check availability: ${error}`);\n sendErrorJson(response, error);\n }\n }, { public: true });\n\n // GET /v1/subdomain - 列出子域名\n server.get('/v1/subdomain', async (_request, response, _params) => {\n try {\n const result = await client.list();\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to list subdomains: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // GET /v1/subdomain/:name - 获取子域名详情\n server.get('/v1/subdomain/:name', async (_request, response, params) => {\n const name = decodeURIComponent(params.name);\n\n try {\n const result = await client.getInfo(name);\n if (!result) {\n sendJson(response, 404, { error: 'Subdomain not found' });\n return;\n }\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to get subdomain info: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // POST /v1/subdomain/register - 注册子域名\n server.post('/v1/subdomain/register', async (request, response, _params) => {\n const body = await readJsonBody(request);\n \n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Invalid request body' });\n return;\n }\n\n const { subdomain, localPort, publicIp } = body as Record<string, unknown>;\n\n if (!subdomain || typeof subdomain !== 'string') {\n sendJson(response, 400, { error: 'Missing \"subdomain\" field' });\n return;\n }\n\n if (!localPort || typeof localPort !== 'number') {\n sendJson(response, 400, { error: 'Missing or invalid \"localPort\" field' });\n return;\n }\n\n try {\n const result = await client.register({\n subdomain,\n localPort,\n publicIp: typeof publicIp === 'string' ? publicIp : undefined,\n });\n sendJson(response, 201, result);\n } catch (error) {\n logger.error(`Failed to register subdomain: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // DELETE /v1/subdomain/:name - 释放子域名\n server.delete('/v1/subdomain/:name', async (_request, response, params) => {\n const name = decodeURIComponent(params.name);\n\n try {\n const result = await client.release(name);\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to release subdomain: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // POST /v1/subdomain/:name/start - 启动隧道\n server.post('/v1/subdomain/:name/start', async (_request, response, params) => {\n const name = decodeURIComponent(params.name);\n\n try {\n const result = await client.startTunnel(name);\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to start tunnel: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // POST /v1/subdomain/:name/stop - 停止隧道\n server.post('/v1/subdomain/:name/stop', async (_request, response, params) => {\n const name = decodeURIComponent(params.name);\n\n try {\n const result = await client.stopTunnel(name);\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to stop tunnel: ${error}`);\n sendErrorJson(response, error);\n }\n });\n}\n\n// ============ Helper Functions ============\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction sendErrorJson(response: ServerResponse, error: unknown): void {\n const status = (error as any)?.status ?? 500;\n const message = error instanceof Error ? error.message : 'Unknown error';\n sendJson(response, status, { error: message });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SubdomainClientHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/SubdomainClientHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SubdomainClientHandler - Local 模式子域名 API\n * \n * 代理请求到 Cloud API,供本地 UI 调用\n */\n\nimport type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { SubdomainClient } from '../../subdomain/SubdomainClient';\n\nexport interface SubdomainClientHandlerOptions {\n subdomainClient: SubdomainClient;\n}\n\n/**\n * 注册 Local 模式子域名路由\n * \n * 这些路由是本地 UI 调用的入口,内部通过 SubdomainClient 转发到 Cloud\n * \n * GET /v1/subdomain/check?name=xxx - 检查子域名可用性\n * GET /v1/subdomain - 列出子域名\n * GET /v1/subdomain/:name - 获取子域名详情\n * POST /v1/subdomain/register - 注册子域名\n * DELETE /v1/subdomain/:name - 释放子域名\n * POST /v1/subdomain/:name/start - 启动隧道\n * POST /v1/subdomain/:name/stop - 停止隧道\n */\nexport function registerSubdomainClientRoutes(\n server: ApiServer,\n options: SubdomainClientHandlerOptions,\n): void {\n const logger = getLoggerFor('SubdomainClientHandler');\n const client = options.subdomainClient;\n\n // GET /v1/subdomain/check?name=xxx - 检查可用性 (public)\n server.get('/v1/subdomain/check', async (request, response, _params) => {\n const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);\n const name = url.searchParams.get('name');\n\n if (!name) {\n sendJson(response, 400, { error: 'Missing \"name\" query parameter' });\n return;\n }\n\n try {\n const result = await client.checkAvailability(name);\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to check availability: ${error}`);\n sendErrorJson(response, error);\n }\n }, { public: true });\n\n // GET /v1/subdomain - 列出子域名\n server.get('/v1/subdomain', async (_request, response, _params) => {\n try {\n const result = await client.list();\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to list subdomains: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // GET /v1/subdomain/:name - 获取子域名详情\n server.get('/v1/subdomain/:name', async (_request, response, params) => {\n const name = decodeURIComponent(params.name);\n\n try {\n const result = await client.getInfo(name);\n if (!result) {\n sendJson(response, 404, { error: 'Subdomain not found' });\n return;\n }\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to get subdomain info: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // POST /v1/subdomain/register - 注册子域名\n server.post('/v1/subdomain/register', async (request, response, _params) => {\n const body = await readJsonBody(request);\n \n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Invalid request body' });\n return;\n }\n\n const { subdomain, localPort, ipv4 } = body as Record<string, unknown>;\n\n if (!subdomain || typeof subdomain !== 'string') {\n sendJson(response, 400, { error: 'Missing \"subdomain\" field' });\n return;\n }\n\n if (!localPort || typeof localPort !== 'number') {\n sendJson(response, 400, { error: 'Missing or invalid \"localPort\" field' });\n return;\n }\n\n try {\n const result = await client.register({\n subdomain,\n localPort,\n ipv4: typeof ipv4 === 'string' ? ipv4 : undefined,\n });\n sendJson(response, 201, result);\n } catch (error) {\n logger.error(`Failed to register subdomain: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // DELETE /v1/subdomain/:name - 释放子域名\n server.delete('/v1/subdomain/:name', async (_request, response, params) => {\n const name = decodeURIComponent(params.name);\n\n try {\n const result = await client.release(name);\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to release subdomain: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // POST /v1/subdomain/:name/start - 启动隧道\n server.post('/v1/subdomain/:name/start', async (_request, response, params) => {\n const name = decodeURIComponent(params.name);\n\n try {\n const result = await client.startTunnel(name);\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to start tunnel: ${error}`);\n sendErrorJson(response, error);\n }\n });\n\n // POST /v1/subdomain/:name/stop - 停止隧道\n server.post('/v1/subdomain/:name/stop', async (_request, response, params) => {\n const name = decodeURIComponent(params.name);\n\n try {\n const result = await client.stopTunnel(name);\n sendJson(response, 200, result);\n } catch (error) {\n logger.error(`Failed to stop tunnel: ${error}`);\n sendErrorJson(response, error);\n }\n });\n}\n\n// ============ Helper Functions ============\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction sendErrorJson(response: ServerResponse, error: unknown): void {\n const status = (error as any)?.status ?? 500;\n const message = error instanceof Error ? error.message : 'Unknown error';\n sendJson(response, status, { error: message });\n}\n"]}
|
|
@@ -60,7 +60,7 @@ function registerSubdomainRoutes(server, options) {
|
|
|
60
60
|
}
|
|
61
61
|
try {
|
|
62
62
|
// Filter registrations by owner
|
|
63
|
-
const allRegistrations = service.getAllRegistrations();
|
|
63
|
+
const allRegistrations = await service.getAllRegistrations();
|
|
64
64
|
const userRegistrations = allRegistrations.filter(r => r.ownerId === webId);
|
|
65
65
|
sendJson(response, 200, {
|
|
66
66
|
registrations: userRegistrations.map(formatRegistration),
|
|
@@ -86,7 +86,7 @@ function registerSubdomainRoutes(server, options) {
|
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
try {
|
|
89
|
-
const registration = service.getRegistration(name);
|
|
89
|
+
const registration = await service.getRegistration(name);
|
|
90
90
|
if (!registration) {
|
|
91
91
|
sendJson(response, 404, { error: 'Subdomain not found' });
|
|
92
92
|
return;
|
|
@@ -122,11 +122,15 @@ function registerSubdomainRoutes(server, options) {
|
|
|
122
122
|
sendJson(response, 400, { error: 'Invalid request body' });
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
|
-
const { subdomain, localPort,
|
|
125
|
+
const { subdomain, nodeId, localPort, ipv4 } = body;
|
|
126
126
|
if (!subdomain || typeof subdomain !== 'string') {
|
|
127
127
|
sendJson(response, 400, { error: 'Missing "subdomain" field' });
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
|
+
if (!nodeId || typeof nodeId !== 'string') {
|
|
131
|
+
sendJson(response, 400, { error: 'Missing "nodeId" field' });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
130
134
|
if (!localPort || typeof localPort !== 'number') {
|
|
131
135
|
sendJson(response, 400, { error: 'Missing or invalid "localPort" field' });
|
|
132
136
|
return;
|
|
@@ -134,8 +138,9 @@ function registerSubdomainRoutes(server, options) {
|
|
|
134
138
|
try {
|
|
135
139
|
const registration = await service.register({
|
|
136
140
|
subdomain,
|
|
141
|
+
nodeId,
|
|
137
142
|
localPort,
|
|
138
|
-
|
|
143
|
+
ipv4: typeof ipv4 === 'string' ? ipv4 : undefined,
|
|
139
144
|
ownerId: webId,
|
|
140
145
|
});
|
|
141
146
|
logger.info(`Registered subdomain: ${registration.fullDomain} for ${webId} (mode: ${registration.mode})`);
|
|
@@ -168,7 +173,7 @@ function registerSubdomainRoutes(server, options) {
|
|
|
168
173
|
return;
|
|
169
174
|
}
|
|
170
175
|
try {
|
|
171
|
-
const registration = service.getRegistration(name);
|
|
176
|
+
const registration = await service.getRegistration(name);
|
|
172
177
|
if (!registration) {
|
|
173
178
|
sendJson(response, 404, { error: 'Subdomain not found' });
|
|
174
179
|
return;
|
|
@@ -205,7 +210,7 @@ function registerSubdomainRoutes(server, options) {
|
|
|
205
210
|
return;
|
|
206
211
|
}
|
|
207
212
|
try {
|
|
208
|
-
const registration = service.getRegistration(name);
|
|
213
|
+
const registration = await service.getRegistration(name);
|
|
209
214
|
if (!registration) {
|
|
210
215
|
sendJson(response, 404, { error: 'Subdomain not found' });
|
|
211
216
|
return;
|
|
@@ -246,7 +251,7 @@ function registerSubdomainRoutes(server, options) {
|
|
|
246
251
|
return;
|
|
247
252
|
}
|
|
248
253
|
try {
|
|
249
|
-
const registration = service.getRegistration(name);
|
|
254
|
+
const registration = await service.getRegistration(name);
|
|
250
255
|
if (!registration) {
|
|
251
256
|
sendJson(response, 404, { error: 'Subdomain not found' });
|
|
252
257
|
return;
|
|
@@ -275,7 +280,7 @@ function formatRegistration(reg) {
|
|
|
275
280
|
subdomain: reg.subdomain,
|
|
276
281
|
fullDomain: reg.fullDomain,
|
|
277
282
|
mode: reg.mode,
|
|
278
|
-
|
|
283
|
+
ipv4: reg.ipv4,
|
|
279
284
|
tunnelProvider: reg.tunnelConfig?.provider,
|
|
280
285
|
tunnelEndpoint: reg.tunnelConfig?.endpoint,
|
|
281
286
|
registeredAt: reg.registeredAt.toISOString(),
|