@undefineds.co/xpod 0.1.7 → 0.2.0-preview.2

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.
Files changed (305) hide show
  1. package/README.md +141 -2
  2. package/config/cli.json +9 -71
  3. package/config/cloud.json +34 -7
  4. package/config/local.json +6 -2
  5. package/config/resolver.json +11 -49
  6. package/config/runtime-open.json +22 -0
  7. package/config/xpod.base.json +32 -0
  8. package/config/xpod.cluster.json +2 -44
  9. package/config/xpod.json +5 -2
  10. package/dist/api/auth/AuthContext.d.ts +12 -1
  11. package/dist/api/auth/AuthContext.js +18 -1
  12. package/dist/api/auth/AuthContext.js.map +1 -1
  13. package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
  14. package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
  15. package/dist/api/auth/ServiceTokenAuthenticator.d.ts +18 -0
  16. package/dist/api/auth/ServiceTokenAuthenticator.js +50 -0
  17. package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -0
  18. package/dist/api/auth/index.d.ts +1 -0
  19. package/dist/api/auth/index.js +1 -0
  20. package/dist/api/auth/index.js.map +1 -1
  21. package/dist/api/chatkit/ai-provider.d.ts +0 -10
  22. package/dist/api/chatkit/ai-provider.js +11 -120
  23. package/dist/api/chatkit/ai-provider.js.map +1 -1
  24. package/dist/api/chatkit/default-agent.js +11 -8
  25. package/dist/api/chatkit/default-agent.js.map +1 -1
  26. package/dist/api/chatkit/pod-store.js +19 -3
  27. package/dist/api/chatkit/pod-store.js.map +1 -1
  28. package/dist/api/chatkit/schema.d.ts +9 -3
  29. package/dist/api/chatkit/schema.js +14 -6
  30. package/dist/api/chatkit/schema.js.map +1 -1
  31. package/dist/api/container/business-token.d.ts +9 -0
  32. package/dist/api/container/business-token.js +32 -0
  33. package/dist/api/container/business-token.js.map +1 -0
  34. package/dist/api/container/cloud.js +36 -12
  35. package/dist/api/container/cloud.js.map +1 -1
  36. package/dist/api/container/common.js +12 -5
  37. package/dist/api/container/common.js.map +1 -1
  38. package/dist/api/container/index.js +94 -14
  39. package/dist/api/container/index.js.map +1 -1
  40. package/dist/api/container/local.js +2 -1
  41. package/dist/api/container/local.js.map +1 -1
  42. package/dist/api/container/routes.js +81 -9
  43. package/dist/api/container/routes.js.map +1 -1
  44. package/dist/api/container/types.d.ts +8 -6
  45. package/dist/api/container/types.js.map +1 -1
  46. package/dist/api/handlers/AdminHandler.js +9 -9
  47. package/dist/api/handlers/AdminHandler.js.map +1 -1
  48. package/dist/api/handlers/ApiKeyHandler.js +0 -6
  49. package/dist/api/handlers/ApiKeyHandler.js.map +1 -1
  50. package/dist/api/handlers/EdgeNodeSignalHandler.d.ts +17 -0
  51. package/dist/api/handlers/EdgeNodeSignalHandler.js +171 -0
  52. package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -0
  53. package/dist/api/handlers/PodManagementHandler.d.ts +5 -4
  54. package/dist/api/handlers/PodManagementHandler.js +11 -10
  55. package/dist/api/handlers/PodManagementHandler.js.map +1 -1
  56. package/dist/api/handlers/ProvisionHandler.d.ts +42 -0
  57. package/dist/api/handlers/ProvisionHandler.js +161 -0
  58. package/dist/api/handlers/ProvisionHandler.js.map +1 -0
  59. package/dist/api/handlers/QuotaHandler.d.ts +7 -7
  60. package/dist/api/handlers/QuotaHandler.js +143 -73
  61. package/dist/api/handlers/QuotaHandler.js.map +1 -1
  62. package/dist/api/handlers/SubdomainClientHandler.js +2 -2
  63. package/dist/api/handlers/SubdomainClientHandler.js.map +1 -1
  64. package/dist/api/handlers/SubdomainHandler.js +13 -8
  65. package/dist/api/handlers/SubdomainHandler.js.map +1 -1
  66. package/dist/api/handlers/UsageHandler.d.ts +14 -0
  67. package/dist/api/handlers/UsageHandler.js +123 -0
  68. package/dist/api/handlers/UsageHandler.js.map +1 -0
  69. package/dist/api/handlers/index.d.ts +3 -1
  70. package/dist/api/handlers/index.js +3 -1
  71. package/dist/api/handlers/index.js.map +1 -1
  72. package/dist/api/main.js +18 -0
  73. package/dist/api/main.js.map +1 -1
  74. package/dist/api/middleware/OpenAuthMiddleware.d.ts +12 -0
  75. package/dist/api/middleware/OpenAuthMiddleware.js +27 -0
  76. package/dist/api/middleware/OpenAuthMiddleware.js.map +1 -0
  77. package/dist/api/runtime.d.ts +15 -0
  78. package/dist/api/runtime.js +104 -0
  79. package/dist/api/runtime.js.map +1 -0
  80. package/dist/api/service/VercelChatService.d.ts +16 -7
  81. package/dist/api/service/VercelChatService.js +98 -178
  82. package/dist/api/service/VercelChatService.js.map +1 -1
  83. package/dist/api/store/DrizzleClientCredentialsStore.d.ts +6 -11
  84. package/dist/api/store/DrizzleClientCredentialsStore.js +9 -39
  85. package/dist/api/store/DrizzleClientCredentialsStore.js.map +1 -1
  86. package/dist/authorization/AuthModeSelector.d.ts +10 -0
  87. package/dist/authorization/AuthModeSelector.js +27 -0
  88. package/dist/authorization/AuthModeSelector.js.map +1 -0
  89. package/dist/authorization/AuthModeSelector.jsonld +81 -0
  90. package/dist/cli/commands/account.d.ts +6 -0
  91. package/dist/cli/commands/account.js +119 -0
  92. package/dist/cli/commands/account.js.map +1 -0
  93. package/dist/cli/commands/auth.js +20 -29
  94. package/dist/cli/commands/auth.js.map +1 -1
  95. package/dist/cli/commands/backup.d.ts +15 -0
  96. package/dist/cli/commands/backup.js +286 -0
  97. package/dist/cli/commands/backup.js.map +1 -0
  98. package/dist/cli/commands/config.d.ts +34 -3
  99. package/dist/cli/commands/config.js +195 -258
  100. package/dist/cli/commands/config.js.map +1 -1
  101. package/dist/cli/commands/doctor.d.ts +6 -0
  102. package/dist/cli/commands/doctor.js +94 -0
  103. package/dist/cli/commands/doctor.js.map +1 -0
  104. package/dist/cli/commands/pod.d.ts +6 -0
  105. package/dist/cli/commands/pod.js +124 -0
  106. package/dist/cli/commands/pod.js.map +1 -0
  107. package/dist/cli/commands/start.js +28 -5
  108. package/dist/cli/commands/start.js.map +1 -1
  109. package/dist/cli/index.js +9 -0
  110. package/dist/cli/index.js.map +1 -1
  111. package/dist/cli/lib/credentials-store.d.ts +17 -0
  112. package/dist/cli/lib/credentials-store.js +73 -0
  113. package/dist/cli/lib/credentials-store.js.map +1 -0
  114. package/dist/cli/lib/css-account.d.ts +17 -0
  115. package/dist/cli/lib/css-account.js +56 -0
  116. package/dist/cli/lib/css-account.js.map +1 -1
  117. package/dist/cli/lib/pod-thread-store.d.ts +57 -0
  118. package/dist/cli/lib/pod-thread-store.js +310 -0
  119. package/dist/cli/lib/pod-thread-store.js.map +1 -0
  120. package/dist/cli/lib/solid-auth.d.ts +20 -0
  121. package/dist/cli/lib/solid-auth.js +70 -0
  122. package/dist/cli/lib/solid-auth.js.map +1 -0
  123. package/dist/components/components.jsonld +5 -8
  124. package/dist/components/context.jsonld +114 -244
  125. package/dist/edge/EdgeNodeAgent.js +2 -2
  126. package/dist/edge/EdgeNodeAgent.js.map +1 -1
  127. package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -7
  128. package/dist/edge/EdgeNodeDnsCoordinator.js +31 -41
  129. package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
  130. package/dist/edge/EdgeNodeDnsCoordinator.jsonld +1 -27
  131. package/dist/edge/EdgeNodeModeDetector.d.ts +1 -1
  132. package/dist/edge/EdgeNodeModeDetector.js +9 -11
  133. package/dist/edge/EdgeNodeModeDetector.js.map +1 -1
  134. package/dist/http/ClusterIngressRouter.js +3 -3
  135. package/dist/http/ClusterIngressRouter.js.map +1 -1
  136. package/dist/http/ClusterWebSocketConfigurator.js +2 -2
  137. package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
  138. package/dist/http/PodRoutingHttpHandler.js +2 -2
  139. package/dist/http/PodRoutingHttpHandler.js.map +1 -1
  140. package/dist/http/cluster/PodMigrationHttpHandler.d.ts +1 -1
  141. package/dist/http/cluster/PodMigrationHttpHandler.js +1 -1
  142. package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
  143. package/dist/identity/drizzle/EdgeNodeRepository.d.ts +37 -4
  144. package/dist/identity/drizzle/EdgeNodeRepository.js +120 -128
  145. package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
  146. package/dist/identity/drizzle/ServiceTokenRepository.d.ts +52 -0
  147. package/dist/identity/drizzle/ServiceTokenRepository.js +143 -0
  148. package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -0
  149. package/dist/identity/drizzle/db.d.ts +9 -0
  150. package/dist/identity/drizzle/db.js +208 -1
  151. package/dist/identity/drizzle/db.js.map +1 -1
  152. package/dist/identity/drizzle/schema.pg.d.ts +5 -0
  153. package/dist/identity/drizzle/schema.pg.js +49 -20
  154. package/dist/identity/drizzle/schema.pg.js.map +1 -1
  155. package/dist/identity/drizzle/schema.sqlite.d.ts +332 -57
  156. package/dist/identity/drizzle/schema.sqlite.js +48 -18
  157. package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
  158. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +6 -4
  159. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
  160. package/dist/index.d.ts +6 -9
  161. package/dist/index.js +12 -14
  162. package/dist/index.js.map +1 -1
  163. package/dist/main.js +25 -8
  164. package/dist/main.js.map +1 -1
  165. package/dist/provision/ProvisionCodeCodec.d.ts +39 -0
  166. package/dist/provision/ProvisionCodeCodec.js +65 -0
  167. package/dist/provision/ProvisionCodeCodec.js.map +1 -0
  168. package/dist/provision/ProvisionCodeCodec.jsonld +47 -0
  169. package/dist/provision/ProvisionPodCreator.d.ts +20 -0
  170. package/dist/provision/ProvisionPodCreator.js +84 -0
  171. package/dist/provision/ProvisionPodCreator.js.map +1 -0
  172. package/dist/provision/ProvisionPodCreator.jsonld +118 -0
  173. package/dist/quota/DrizzleQuotaService.d.ts +17 -3
  174. package/dist/quota/DrizzleQuotaService.js +108 -8
  175. package/dist/quota/DrizzleQuotaService.js.map +1 -1
  176. package/dist/quota/DrizzleQuotaService.jsonld +33 -22
  177. package/dist/quota/NoopQuotaService.d.ts +7 -1
  178. package/dist/quota/NoopQuotaService.js +12 -0
  179. package/dist/quota/NoopQuotaService.js.map +1 -1
  180. package/dist/quota/NoopQuotaService.jsonld +24 -0
  181. package/dist/quota/QuotaService.d.ts +17 -0
  182. package/dist/quota/QuotaService.js +5 -0
  183. package/dist/quota/QuotaService.js.map +1 -1
  184. package/dist/quota/QuotaService.jsonld +50 -0
  185. package/dist/runtime/Proxy.d.ts +22 -4
  186. package/dist/runtime/Proxy.js +154 -35
  187. package/dist/runtime/Proxy.js.map +1 -1
  188. package/dist/runtime/XpodRuntime.d.ts +49 -0
  189. package/dist/runtime/XpodRuntime.js +374 -0
  190. package/dist/runtime/XpodRuntime.js.map +1 -0
  191. package/dist/runtime/env-utils.d.ts +2 -0
  192. package/dist/runtime/env-utils.js +55 -0
  193. package/dist/runtime/env-utils.js.map +1 -0
  194. package/dist/runtime/index.d.ts +4 -0
  195. package/dist/runtime/index.js +8 -1
  196. package/dist/runtime/index.js.map +1 -1
  197. package/dist/runtime/socket-fetch.d.ts +1 -0
  198. package/dist/runtime/socket-fetch.js +72 -0
  199. package/dist/runtime/socket-fetch.js.map +1 -0
  200. package/dist/runtime/socket-http.d.ts +1 -0
  201. package/dist/runtime/socket-http.js +142 -0
  202. package/dist/runtime/socket-http.js.map +1 -0
  203. package/dist/runtime/socket-utils.d.ts +2 -0
  204. package/dist/runtime/socket-utils.js +34 -0
  205. package/dist/runtime/socket-utils.js.map +1 -0
  206. package/dist/service/{EdgeNodeHeartbeatService.d.ts → EdgeNodeSignalClient.d.ts} +3 -3
  207. package/dist/service/{EdgeNodeHeartbeatService.js → EdgeNodeSignalClient.js} +4 -4
  208. package/dist/service/EdgeNodeSignalClient.js.map +1 -0
  209. package/dist/service/PodMigrationService.d.ts +1 -2
  210. package/dist/service/PodMigrationService.js +1 -2
  211. package/dist/service/PodMigrationService.js.map +1 -1
  212. package/dist/storage/SparqlUpdateResourceStore.js +1 -1
  213. package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
  214. package/dist/storage/accessors/MinioDataAccessor.d.ts +6 -0
  215. package/dist/storage/accessors/MinioDataAccessor.js +10 -0
  216. package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
  217. package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
  218. package/dist/storage/accessors/MixDataAccessor.d.ts +2 -1
  219. package/dist/storage/accessors/MixDataAccessor.js +12 -1
  220. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  221. package/dist/storage/accessors/MixDataAccessor.jsonld +19 -0
  222. package/dist/storage/locking/UrlAwareRedisLocker.d.ts +18 -0
  223. package/dist/storage/locking/UrlAwareRedisLocker.js +60 -0
  224. package/dist/storage/locking/UrlAwareRedisLocker.js.map +1 -0
  225. package/dist/storage/locking/UrlAwareRedisLocker.jsonld +123 -0
  226. package/dist/storage/quota/UsageRepository.d.ts +41 -8
  227. package/dist/storage/quota/UsageRepository.js +252 -50
  228. package/dist/storage/quota/UsageRepository.js.map +1 -1
  229. package/dist/storage/sparql/ComunicaQuintEngine.d.ts +9 -0
  230. package/dist/storage/sparql/ComunicaQuintEngine.js +50 -9
  231. package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
  232. package/dist/storage/sparql/QueryOptimizer.js +13 -1
  233. package/dist/storage/sparql/QueryOptimizer.js.map +1 -1
  234. package/dist/storage/sparql/QuintQuerySource.d.ts +14 -0
  235. package/dist/storage/sparql/QuintQuerySource.js +152 -1
  236. package/dist/storage/sparql/QuintQuerySource.js.map +1 -1
  237. package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -0
  238. package/dist/storage/sparql/SubgraphQueryEngine.js +6 -2
  239. package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
  240. package/dist/storage/sparql/SubgraphQueryEngine.jsonld +4 -0
  241. package/dist/subdomain/SubdomainClient.d.ts +3 -3
  242. package/dist/subdomain/SubdomainClient.js +1 -1
  243. package/dist/subdomain/SubdomainClient.js.map +1 -1
  244. package/dist/subdomain/SubdomainService.d.ts +15 -16
  245. package/dist/subdomain/SubdomainService.js +80 -54
  246. package/dist/subdomain/SubdomainService.js.map +1 -1
  247. package/dist/subdomain/SubdomainService.jsonld +22 -26
  248. package/dist/supervisor/Supervisor.d.ts +7 -2
  249. package/dist/supervisor/Supervisor.js +33 -1
  250. package/dist/supervisor/Supervisor.js.map +1 -1
  251. package/dist/test-utils/index.d.ts +4 -0
  252. package/dist/test-utils/index.js +8 -0
  253. package/dist/test-utils/index.js.map +1 -0
  254. package/dist/test-utils/no-auth-xpod.d.ts +11 -0
  255. package/dist/test-utils/no-auth-xpod.js +25 -0
  256. package/dist/test-utils/no-auth-xpod.js.map +1 -0
  257. package/dist/test-utils/seed-pod.d.ts +5 -0
  258. package/dist/test-utils/seed-pod.js +61 -0
  259. package/dist/test-utils/seed-pod.js.map +1 -0
  260. package/package.json +23 -5
  261. package/templates/identity/account/create-pod.html.ejs +110 -0
  262. package/templates/main.html.ejs +10 -0
  263. package/dist/api/handlers/DevHandler.d.ts +0 -18
  264. package/dist/api/handlers/DevHandler.js +0 -276
  265. package/dist/api/handlers/DevHandler.js.map +0 -1
  266. package/dist/api/handlers/SignalHandler.d.ts +0 -13
  267. package/dist/api/handlers/SignalHandler.js +0 -122
  268. package/dist/api/handlers/SignalHandler.js.map +0 -1
  269. package/dist/gateway/Proxy.d.ts +0 -24
  270. package/dist/gateway/Proxy.js +0 -209
  271. package/dist/gateway/Proxy.js.map +0 -1
  272. package/dist/gateway/Supervisor.d.ts +0 -2
  273. package/dist/gateway/Supervisor.js +0 -7
  274. package/dist/gateway/Supervisor.js.map +0 -1
  275. package/dist/gateway/port-finder.d.ts +0 -4
  276. package/dist/gateway/port-finder.js +0 -15
  277. package/dist/gateway/port-finder.js.map +0 -1
  278. package/dist/gateway/types.d.ts +0 -1
  279. package/dist/gateway/types.js +0 -3
  280. package/dist/gateway/types.js.map +0 -1
  281. package/dist/http/SignalInterceptHttpHandler.d.ts +0 -24
  282. package/dist/http/SignalInterceptHttpHandler.js +0 -47
  283. package/dist/http/SignalInterceptHttpHandler.js.map +0 -1
  284. package/dist/http/SignalInterceptHttpHandler.jsonld +0 -103
  285. package/dist/http/admin/EdgeNodeSignalHttpHandler.d.ts +0 -71
  286. package/dist/http/admin/EdgeNodeSignalHttpHandler.js +0 -674
  287. package/dist/http/admin/EdgeNodeSignalHttpHandler.js.map +0 -1
  288. package/dist/http/admin/EdgeNodeSignalHttpHandler.jsonld +0 -406
  289. package/dist/http/cluster/PodMigrationHttpHandler.jsonld +0 -169
  290. package/dist/quota/DefaultQuotaService.d.ts +0 -16
  291. package/dist/quota/DefaultQuotaService.js +0 -37
  292. package/dist/quota/DefaultQuotaService.js.map +0 -1
  293. package/dist/quota/DefaultQuotaService.jsonld +0 -85
  294. package/dist/service/EdgeNodeHeartbeatService.js.map +0 -1
  295. package/dist/service/PodMigrationService.jsonld +0 -76
  296. package/dist/storage/MigratableDataAccessor.d.ts +0 -63
  297. package/dist/storage/MigratableDataAccessor.js +0 -11
  298. package/dist/storage/MigratableDataAccessor.js.map +0 -1
  299. package/dist/storage/MigratableDataAccessor.jsonld +0 -60
  300. package/dist/storage/accessors/TieredMinioDataAccessor.d.ts +0 -150
  301. package/dist/storage/accessors/TieredMinioDataAccessor.js +0 -582
  302. package/dist/storage/accessors/TieredMinioDataAccessor.js.map +0 -1
  303. package/dist/storage/accessors/TieredMinioDataAccessor.jsonld +0 -333
  304. package/static/app/assets/index.css +0 -1
  305. 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
- * These endpoints are for internal billing system use.
9
- * They require authentication via client credentials.
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, accountRepo } = options;
21
- // GET /api/quota/accounts/:accountId
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 limit = await quotaService.getAccountLimit(accountId);
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
- quotaLimit: limit ?? null,
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 /api/quota/accounts/:accountId
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
- if (!Object.prototype.hasOwnProperty.call(payload, 'quotaLimit')) {
47
- sendJson(response, 400, { error: 'Body must include quotaLimit' });
48
- return;
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.setAccountLimit(accountId, quota);
57
- const latest = await quotaService.getAccountLimit(accountId);
58
- logger.info(`Set account ${accountId} quota to ${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
- targetType: 'account',
62
- targetId: accountId,
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 /api/quota/accounts/:accountId
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.setAccountLimit(accountId, null);
91
+ await quotaService.clearAccountQuota(accountId);
76
92
  logger.info(`Cleared account ${accountId} quota`);
77
93
  sendJson(response, 200, {
78
94
  status: 'cleared',
79
- targetType: 'account',
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 /api/quota/pods/:podId
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 podInfo = await accountRepo.getPodInfo(podId);
93
- if (!podInfo) {
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: podInfo.accountId,
102
- baseUrl: podInfo.baseUrl ?? null,
103
- quotaLimit: limit ?? null,
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 /api/quota/pods/:podId
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
- if (!Object.prototype.hasOwnProperty.call(payload, 'quotaLimit')) {
121
- sendJson(response, 400, { error: 'Body must include quotaLimit' });
122
- return;
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
- const podInfo = await accountRepo.getPodInfo(podId);
131
- if (!podInfo) {
132
- sendJson(response, 404, { error: 'Pod not found' });
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
- targetType: 'pod',
141
- targetId: podId,
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 /api/quota/pods/:podId
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
- const podInfo = await accountRepo.getPodInfo(podId);
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
- targetType: 'pod',
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
- function extractQuota(value) {
174
- if (value === null) {
175
- return null;
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
- if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
178
- return undefined;
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 value;
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, publicIp } = body;
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
- publicIp: typeof publicIp === 'string' ? publicIp : undefined,
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, publicIp } = body;
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
- publicIp: typeof publicIp === 'string' ? publicIp : undefined,
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
- publicIp: reg.publicIp,
283
+ ipv4: reg.ipv4,
279
284
  tunnelProvider: reg.tunnelConfig?.provider,
280
285
  tunnelEndpoint: reg.tunnelConfig?.endpoint,
281
286
  registeredAt: reg.registeredAt.toISOString(),