@undefineds.co/xpod 0.2.15 → 0.2.20

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 (37) hide show
  1. package/config/cloud.json +2 -2
  2. package/config/xpod.cluster.json +2 -2
  3. package/dist/api/container/cloud.js +6 -1
  4. package/dist/api/container/cloud.js.map +1 -1
  5. package/dist/api/container/routes.js +7 -1
  6. package/dist/api/container/routes.js.map +1 -1
  7. package/dist/api/container/types.d.ts +2 -0
  8. package/dist/api/container/types.js.map +1 -1
  9. package/dist/api/handlers/ProvisionHandler.d.ts +2 -0
  10. package/dist/api/handlers/ProvisionHandler.js +128 -3
  11. package/dist/api/handlers/ProvisionHandler.js.map +1 -1
  12. package/dist/api/handlers/WebIdProfileHandler.d.ts +2 -0
  13. package/dist/api/handlers/WebIdProfileHandler.js +61 -2
  14. package/dist/api/handlers/WebIdProfileHandler.js.map +1 -1
  15. package/dist/components/components.jsonld +2 -0
  16. package/dist/components/context.jsonld +42 -0
  17. package/dist/identity/drizzle/PodLookupRepository.d.ts +4 -0
  18. package/dist/identity/drizzle/PodLookupRepository.js +7 -0
  19. package/dist/identity/drizzle/PodLookupRepository.js.map +1 -1
  20. package/dist/identity/drizzle/WebIdProfileRepository.d.ts +8 -5
  21. package/dist/identity/drizzle/WebIdProfileRepository.js +12 -2
  22. package/dist/identity/drizzle/WebIdProfileRepository.js.map +1 -1
  23. package/dist/identity/drizzle/WebIdProfileRepository.jsonld +108 -0
  24. package/dist/identity/oidc/LoopbackClientIdAdapterFactory.d.ts +7 -0
  25. package/dist/identity/oidc/LoopbackClientIdAdapterFactory.js +48 -0
  26. package/dist/identity/oidc/LoopbackClientIdAdapterFactory.js.map +1 -0
  27. package/dist/identity/oidc/LoopbackClientIdAdapterFactory.jsonld +49 -0
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.js +6 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/provision/ProvisionPodCreator.d.ts +4 -0
  32. package/dist/provision/ProvisionPodCreator.js +33 -2
  33. package/dist/provision/ProvisionPodCreator.js.map +1 -1
  34. package/dist/provision/ProvisionPodCreator.jsonld +23 -0
  35. package/package.json +13 -5
  36. package/static/app/assets/index.css +1 -1
  37. package/static/app/assets/main.js +15 -15
@@ -22,7 +22,7 @@ function registerWebIdProfileRoutes(server, options) {
22
22
  server.get('/:username/profile/card', async (_request, response, params) => {
23
23
  const username = decodeURIComponent(params.username);
24
24
  try {
25
- const profile = await profileRepo.get(username);
25
+ const profile = await resolveProfileWithStorageBackfill(username, options);
26
26
  if (!profile) {
27
27
  sendError(response, 404, 'Profile not found');
28
28
  return;
@@ -97,7 +97,7 @@ function registerWebIdProfileRoutes(server, options) {
97
97
  server.get('/api/v1/identity/:username', async (_request, response, params) => {
98
98
  const username = decodeURIComponent(params.username);
99
99
  try {
100
- const profile = await profileRepo.get(username);
100
+ const profile = await resolveProfileWithStorageBackfill(username, options);
101
101
  if (!profile) {
102
102
  sendError(response, 404, 'Profile not found');
103
103
  return;
@@ -171,6 +171,65 @@ function registerWebIdProfileRoutes(server, options) {
171
171
  });
172
172
  logger.info('WebID Profile routes registered');
173
173
  }
174
+ async function resolveProfileWithStorageBackfill(username, options) {
175
+ const { profileRepo, podLookupRepo } = options;
176
+ const profile = await profileRepo.get(username);
177
+ if (!profile) {
178
+ return null;
179
+ }
180
+ if (profile.storageUrl || !profile.accountId || !podLookupRepo) {
181
+ return profile;
182
+ }
183
+ const pods = await podLookupRepo.listByAccountId(profile.accountId);
184
+ const storageUrl = selectStorageBackfillCandidate(username, pods);
185
+ if (!storageUrl) {
186
+ logger.warn(`Skipped storage backfill for ${username}: no unambiguous pod found for account ${profile.accountId}`);
187
+ return profile;
188
+ }
189
+ try {
190
+ const updated = await profileRepo.updateStorage(username, {
191
+ storageUrl,
192
+ storageMode: profile.storageMode,
193
+ });
194
+ if (updated) {
195
+ logger.info(`Backfilled storage for ${username}: ${storageUrl}`);
196
+ return updated;
197
+ }
198
+ }
199
+ catch (error) {
200
+ logger.warn(`Failed to backfill storage for ${username}: ${error}`);
201
+ }
202
+ return profile;
203
+ }
204
+ function selectStorageBackfillCandidate(username, pods) {
205
+ if (pods.length === 0) {
206
+ return null;
207
+ }
208
+ const exactMatches = pods.filter((pod) => derivePodSlug(pod.baseUrl) === username);
209
+ if (exactMatches.length === 1) {
210
+ return ensureTrailingSlash(exactMatches[0].baseUrl);
211
+ }
212
+ if (exactMatches.length > 1) {
213
+ return null;
214
+ }
215
+ if (pods.length === 1) {
216
+ return ensureTrailingSlash(pods[0].baseUrl);
217
+ }
218
+ return null;
219
+ }
220
+ function derivePodSlug(baseUrl) {
221
+ try {
222
+ const parsed = new URL(baseUrl);
223
+ const [slug] = parsed.pathname.split('/').filter(Boolean);
224
+ return slug || null;
225
+ }
226
+ catch {
227
+ return null;
228
+ }
229
+ }
230
+ function ensureTrailingSlash(url) {
231
+ return url.replace(/\/+$/, '') + '/';
232
+ }
174
233
  async function readJsonBody(request) {
175
234
  return new Promise((resolve, reject) => {
176
235
  let data = '';
@@ -1 +1 @@
1
- {"version":3,"file":"WebIdProfileHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/WebIdProfileHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebID Profile API Handler\n *\n * 提供 WebID Profile 托管服务的 HTTP API\n *\n * GET /{username}/profile/card - 获取 WebID Profile (Turtle 格式)\n * POST /api/v1/identity/{username}/storage - 更新 storage 指针 (需认证)\n */\n\nimport type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { WebIdProfileRepository } from '../../identity/drizzle/WebIdProfileRepository';\n\nconst logger = getLoggerFor('WebIdProfileHandler');\n\nexport interface WebIdProfileHandlerOptions {\n profileRepo: WebIdProfileRepository;\n}\n\nexport function registerWebIdProfileRoutes(\n server: ApiServer,\n options: WebIdProfileHandlerOptions,\n): void {\n const { profileRepo } = options;\n\n /**\n * GET /{username}/profile/card\n *\n * 获取 WebID Profile (Turtle 格式)\n * 这是 Solid 标准的 WebID 端点\n */\n server.get('/:username/profile/card', async (_request, response, params) => {\n const username = decodeURIComponent(params.username);\n\n try {\n const profile = await profileRepo.get(username);\n\n if (!profile) {\n sendError(response, 404, 'Profile not found');\n return;\n }\n\n // 返回 Turtle 格式\n const turtle = profileRepo.generateProfileTurtle(profile);\n\n response.statusCode = 200;\n response.setHeader('Content-Type', 'text/turtle');\n response.setHeader('Link', `<${profile.webidUrl}>; rel=\"describedby\"`);\n response.end(turtle);\n } catch (error) {\n logger.error(`Failed to get profile for ${username}: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n }, { public: true });\n\n /**\n * POST /api/v1/identity/{username}/storage\n *\n * 更新 storage 指针\n * 用于 Local 节点更新其 storage URL\n *\n * Request body:\n * {\n * \"storageUrl\": \"https://alice.undefineds.xyz/\"\n * }\n */\n server.post('/api/v1/identity/:username/storage', async (request, response, params) => {\n const username = decodeURIComponent(params.username);\n\n try {\n const body = await readJsonBody(request);\n const payload = body as { storageUrl?: string; storageMode?: string } | undefined;\n\n if (!payload?.storageUrl) {\n sendError(response, 400, 'storageUrl is required');\n return;\n }\n\n // 验证 URL 格式\n try {\n new URL(payload.storageUrl);\n } catch {\n sendError(response, 400, 'Invalid storageUrl format');\n return;\n }\n\n const profile = await profileRepo.updateStorage(username, {\n storageUrl: payload.storageUrl,\n storageMode: payload.storageMode as 'cloud' | 'local' | 'custom' | undefined,\n });\n\n if (!profile) {\n sendError(response, 404, 'Profile not found');\n return;\n }\n\n logger.info(`Updated storage for ${username}: ${payload.storageUrl}`);\n\n sendJson(response, 200, {\n success: true,\n username,\n storageUrl: profile.storageUrl,\n storageMode: profile.storageMode,\n updatedAt: profile.updatedAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to update storage for ${username}: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * GET /api/v1/identity/{username}\n *\n * 获取 WebID Profile 信息 (JSON 格式)\n */\n server.get('/api/v1/identity/:username', async (_request, response, params) => {\n const username = decodeURIComponent(params.username);\n\n try {\n const profile = await profileRepo.get(username);\n\n if (!profile) {\n sendError(response, 404, 'Profile not found');\n return;\n }\n\n sendJson(response, 200, {\n username: profile.username,\n webidUrl: profile.webidUrl,\n storageUrl: profile.storageUrl,\n storageMode: profile.storageMode,\n oidcIssuer: profile.oidcIssuer,\n createdAt: profile.createdAt.toISOString(),\n updatedAt: profile.updatedAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to get profile for ${username}: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n }, { public: true });\n\n /**\n * POST /api/v1/identity\n *\n * 创建 WebID Profile\n *\n * Request body:\n * {\n * \"username\": \"alice\",\n * \"storageMode\": \"local\", // optional, default: \"cloud\"\n * \"storageUrl\": \"https://alice.undefineds.xyz/\" // optional\n * }\n */\n server.post('/api/v1/identity', async (request, response, _params) => {\n try {\n const body = await readJsonBody(request);\n const payload = body as {\n username?: string;\n storageMode?: string;\n storageUrl?: string;\n accountId?: string;\n } | undefined;\n\n if (!payload?.username) {\n sendError(response, 400, 'username is required');\n return;\n }\n\n // 验证用户名格式\n if (!/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(payload.username)) {\n sendError(response, 400, 'Invalid username format');\n return;\n }\n\n // 检查是否已存在\n const existing = await profileRepo.get(payload.username);\n if (existing) {\n sendError(response, 409, 'Username already taken');\n return;\n }\n\n const profile = await profileRepo.create({\n username: payload.username,\n storageMode: payload.storageMode as 'cloud' | 'local' | 'custom' | undefined,\n storageUrl: payload.storageUrl,\n accountId: payload.accountId,\n });\n\n logger.info(`Created profile for ${payload.username}`);\n\n sendJson(response, 201, {\n success: true,\n username: profile.username,\n webidUrl: profile.webidUrl,\n storageUrl: profile.storageUrl,\n storageMode: profile.storageMode,\n createdAt: profile.createdAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to create profile: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n logger.info('WebID Profile routes registered');\n}\n\nasync function readJsonBody(request: IncomingMessage): 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 sendError(response: ServerResponse, status: number, message: string): void {\n sendJson(response, status, { error: message });\n}\n"]}
1
+ {"version":3,"file":"WebIdProfileHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/WebIdProfileHandler.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAeH,gEA2LC;AAvMD,iEAAqD;AAKrD,MAAM,MAAM,GAAG,IAAA,oCAAY,EAAC,qBAAqB,CAAC,CAAC;AAOnD,SAAgB,0BAA0B,CACxC,MAAiB,EACjB,OAAmC;IAEnC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAEhC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,yBAAyB,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACzE,MAAM,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAErD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,iCAAiC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAE3E,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,mBAAmB,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;YAED,eAAe;YACf,MAAM,MAAM,GAAG,WAAW,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;YAE1D,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;YAC1B,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;YAClD,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,OAAO,CAAC,QAAQ,sBAAsB,CAAC,CAAC;YACvE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,6BAA6B,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;YAChE,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAErB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,CAAC,oCAAoC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACpF,MAAM,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAErD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,IAAiE,CAAC;YAElF,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;gBACzB,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,wBAAwB,CAAC,CAAC;gBACnD,OAAO;YACT,CAAC;YAED,YAAY;YACZ,IAAI,CAAC;gBACH,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,2BAA2B,CAAC,CAAC;gBACtD,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC,QAAQ,EAAE;gBACxD,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,WAAW,EAAE,OAAO,CAAC,WAAuD;aAC7E,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,mBAAmB,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,uBAAuB,QAAQ,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;YAEtE,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,OAAO,EAAE,IAAI;gBACb,QAAQ;gBACR,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,gCAAgC,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;YACnE,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,4BAA4B,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QAC5E,MAAM,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAErD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,iCAAiC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAE3E,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,mBAAmB,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;YAED,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE;gBAC1C,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,6BAA6B,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;YAChE,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAErB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QACnE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,IAKH,CAAC;YAEd,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;gBACvB,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,sBAAsB,CAAC,CAAC;gBACjD,OAAO;YACT,CAAC;YAED,UAAU;YACV,IAAI,CAAC,mCAAmC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAChE,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,yBAAyB,CAAC,CAAC;gBACpD,OAAO;YACT,CAAC;YAED,UAAU;YACV,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACzD,IAAI,QAAQ,EAAE,CAAC;gBACb,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,wBAAwB,CAAC,CAAC;gBACnD,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC;gBACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,WAAW,EAAE,OAAO,CAAC,WAAuD;gBAC5E,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;aAC7B,CAAC,CAAC;YAEH,MAAM,CAAC,IAAI,CAAC,uBAAuB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YAEvD,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,6BAA6B,KAAK,EAAE,CAAC,CAAC;YACnD,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;AACjD,CAAC;AAED,KAAK,UAAU,iCAAiC,CAC9C,QAAgB,EAChB,OAAmC;IAEnC,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,aAAa,EAAE,CAAC;QAC/D,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpE,MAAM,UAAU,GAAG,8BAA8B,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,gCAAgC,QAAQ,0CAA0C,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;QACnH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC,QAAQ,EAAE;YACxD,UAAU;YACV,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC,CAAC,CAAC;QACH,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,0BAA0B,QAAQ,KAAK,UAAU,EAAE,CAAC,CAAC;YACjE,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,kCAAkC,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,8BAA8B,CACrC,QAAgB,EAChB,IAAuB;IAEvB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IACnF,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1D,OAAO,IAAI,IAAI,IAAI,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC;AACvC,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,OAAwB;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACnC,IAAI,IAAI,KAAK,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,CAAC,SAAS,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,SAAS,CAAC,CAAC;YACrB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,QAAwB,EAAE,MAAc,EAAE,IAAa;IACvE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,SAAS,CAAC,QAAwB,EAAE,MAAc,EAAE,OAAe;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/**\n * WebID Profile API Handler\n *\n * 提供 WebID Profile 托管服务的 HTTP API\n *\n * GET /{username}/profile/card - 获取 WebID Profile (Turtle 格式)\n * POST /api/v1/identity/{username}/storage - 更新 storage 指针 (需认证)\n */\n\nimport type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { WebIdProfileRepository } from '../../identity/drizzle/WebIdProfileRepository';\nimport type { PodLookupRepository, PodLookupResult } from '../../identity/drizzle/PodLookupRepository';\n\nconst logger = getLoggerFor('WebIdProfileHandler');\n\nexport interface WebIdProfileHandlerOptions {\n profileRepo: WebIdProfileRepository;\n podLookupRepo?: PodLookupRepository;\n}\n\nexport function registerWebIdProfileRoutes(\n server: ApiServer,\n options: WebIdProfileHandlerOptions,\n): void {\n const { profileRepo } = options;\n\n /**\n * GET /{username}/profile/card\n *\n * 获取 WebID Profile (Turtle 格式)\n * 这是 Solid 标准的 WebID 端点\n */\n server.get('/:username/profile/card', async (_request, response, params) => {\n const username = decodeURIComponent(params.username);\n\n try {\n const profile = await resolveProfileWithStorageBackfill(username, options);\n\n if (!profile) {\n sendError(response, 404, 'Profile not found');\n return;\n }\n\n // 返回 Turtle 格式\n const turtle = profileRepo.generateProfileTurtle(profile);\n\n response.statusCode = 200;\n response.setHeader('Content-Type', 'text/turtle');\n response.setHeader('Link', `<${profile.webidUrl}>; rel=\"describedby\"`);\n response.end(turtle);\n } catch (error) {\n logger.error(`Failed to get profile for ${username}: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n }, { public: true });\n\n /**\n * POST /api/v1/identity/{username}/storage\n *\n * 更新 storage 指针\n * 用于 Local 节点更新其 storage URL\n *\n * Request body:\n * {\n * \"storageUrl\": \"https://alice.undefineds.xyz/\"\n * }\n */\n server.post('/api/v1/identity/:username/storage', async (request, response, params) => {\n const username = decodeURIComponent(params.username);\n\n try {\n const body = await readJsonBody(request);\n const payload = body as { storageUrl?: string; storageMode?: string } | undefined;\n\n if (!payload?.storageUrl) {\n sendError(response, 400, 'storageUrl is required');\n return;\n }\n\n // 验证 URL 格式\n try {\n new URL(payload.storageUrl);\n } catch {\n sendError(response, 400, 'Invalid storageUrl format');\n return;\n }\n\n const profile = await profileRepo.updateStorage(username, {\n storageUrl: payload.storageUrl,\n storageMode: payload.storageMode as 'cloud' | 'local' | 'custom' | undefined,\n });\n\n if (!profile) {\n sendError(response, 404, 'Profile not found');\n return;\n }\n\n logger.info(`Updated storage for ${username}: ${payload.storageUrl}`);\n\n sendJson(response, 200, {\n success: true,\n username,\n storageUrl: profile.storageUrl,\n storageMode: profile.storageMode,\n updatedAt: profile.updatedAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to update storage for ${username}: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * GET /api/v1/identity/{username}\n *\n * 获取 WebID Profile 信息 (JSON 格式)\n */\n server.get('/api/v1/identity/:username', async (_request, response, params) => {\n const username = decodeURIComponent(params.username);\n\n try {\n const profile = await resolveProfileWithStorageBackfill(username, options);\n\n if (!profile) {\n sendError(response, 404, 'Profile not found');\n return;\n }\n\n sendJson(response, 200, {\n username: profile.username,\n webidUrl: profile.webidUrl,\n storageUrl: profile.storageUrl,\n storageMode: profile.storageMode,\n oidcIssuer: profile.oidcIssuer,\n createdAt: profile.createdAt.toISOString(),\n updatedAt: profile.updatedAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to get profile for ${username}: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n }, { public: true });\n\n /**\n * POST /api/v1/identity\n *\n * 创建 WebID Profile\n *\n * Request body:\n * {\n * \"username\": \"alice\",\n * \"storageMode\": \"local\", // optional, default: \"cloud\"\n * \"storageUrl\": \"https://alice.undefineds.xyz/\" // optional\n * }\n */\n server.post('/api/v1/identity', async (request, response, _params) => {\n try {\n const body = await readJsonBody(request);\n const payload = body as {\n username?: string;\n storageMode?: string;\n storageUrl?: string;\n accountId?: string;\n } | undefined;\n\n if (!payload?.username) {\n sendError(response, 400, 'username is required');\n return;\n }\n\n // 验证用户名格式\n if (!/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(payload.username)) {\n sendError(response, 400, 'Invalid username format');\n return;\n }\n\n // 检查是否已存在\n const existing = await profileRepo.get(payload.username);\n if (existing) {\n sendError(response, 409, 'Username already taken');\n return;\n }\n\n const profile = await profileRepo.create({\n username: payload.username,\n storageMode: payload.storageMode as 'cloud' | 'local' | 'custom' | undefined,\n storageUrl: payload.storageUrl,\n accountId: payload.accountId,\n });\n\n logger.info(`Created profile for ${payload.username}`);\n\n sendJson(response, 201, {\n success: true,\n username: profile.username,\n webidUrl: profile.webidUrl,\n storageUrl: profile.storageUrl,\n storageMode: profile.storageMode,\n createdAt: profile.createdAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to create profile: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n logger.info('WebID Profile routes registered');\n}\n\nasync function resolveProfileWithStorageBackfill(\n username: string,\n options: WebIdProfileHandlerOptions,\n) {\n const { profileRepo, podLookupRepo } = options;\n const profile = await profileRepo.get(username);\n if (!profile) {\n return null;\n }\n\n if (profile.storageUrl || !profile.accountId || !podLookupRepo) {\n return profile;\n }\n\n const pods = await podLookupRepo.listByAccountId(profile.accountId);\n const storageUrl = selectStorageBackfillCandidate(username, pods);\n if (!storageUrl) {\n logger.warn(`Skipped storage backfill for ${username}: no unambiguous pod found for account ${profile.accountId}`);\n return profile;\n }\n\n try {\n const updated = await profileRepo.updateStorage(username, {\n storageUrl,\n storageMode: profile.storageMode,\n });\n if (updated) {\n logger.info(`Backfilled storage for ${username}: ${storageUrl}`);\n return updated;\n }\n } catch (error) {\n logger.warn(`Failed to backfill storage for ${username}: ${error}`);\n }\n\n return profile;\n}\n\nfunction selectStorageBackfillCandidate(\n username: string,\n pods: PodLookupResult[],\n): string | null {\n if (pods.length === 0) {\n return null;\n }\n\n const exactMatches = pods.filter((pod) => derivePodSlug(pod.baseUrl) === username);\n if (exactMatches.length === 1) {\n return ensureTrailingSlash(exactMatches[0].baseUrl);\n }\n\n if (exactMatches.length > 1) {\n return null;\n }\n\n if (pods.length === 1) {\n return ensureTrailingSlash(pods[0].baseUrl);\n }\n\n return null;\n}\n\nfunction derivePodSlug(baseUrl: string): string | null {\n try {\n const parsed = new URL(baseUrl);\n const [slug] = parsed.pathname.split('/').filter(Boolean);\n return slug || null;\n } catch {\n return null;\n }\n}\n\nfunction ensureTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '') + '/';\n}\n\nasync function readJsonBody(request: IncomingMessage): 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 sendError(response: ServerResponse, status: number, message: string): void {\n sendJson(response, status, { error: message });\n}\n"]}
@@ -35,6 +35,7 @@
35
35
  "undefineds:dist/http/terminal/TerminalHttpHandler.jsonld",
36
36
  "undefineds:dist/pods/ReservedSuffixIdentifierGenerator.jsonld",
37
37
  "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld",
38
+ "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld",
38
39
  "undefineds:dist/storage/keyvalue/PostgresKeyValueStorage.jsonld",
39
40
  "undefineds:dist/storage/keyvalue/RedisKeyValueStorage.jsonld",
40
41
  "undefineds:dist/storage/keyvalue/SqliteKeyValueStorage.jsonld",
@@ -78,6 +79,7 @@
78
79
  "undefineds:dist/identity/oidc/DisabledIdentityProviderHandler.jsonld",
79
80
  "undefineds:dist/identity/oidc/AutoDetectOidcHandler.jsonld",
80
81
  "undefineds:dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld",
82
+ "undefineds:dist/identity/oidc/LoopbackClientIdAdapterFactory.jsonld",
81
83
  "undefineds:dist/storage/locking/UrlAwareRedisLocker.jsonld",
82
84
  "undefineds:dist/provision/ProvisionPodCreator.jsonld",
83
85
  "undefineds:dist/provision/ProvisionCodeCodec.jsonld",
@@ -775,6 +775,30 @@
775
775
  }
776
776
  }
777
777
  },
778
+ "WebIdProfileRepository": {
779
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository",
780
+ "@prefix": true,
781
+ "@context": {
782
+ "options_baseUrl": {
783
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_baseUrl"
784
+ },
785
+ "options_db": {
786
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_db"
787
+ },
788
+ "options_identityDbUrl": {
789
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_identityDbUrl"
790
+ },
791
+ "baseUrl": {
792
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_baseUrl"
793
+ },
794
+ "db": {
795
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_db"
796
+ },
797
+ "identityDbUrl": {
798
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_identityDbUrl"
799
+ }
800
+ }
801
+ },
778
802
  "PostgresKeyValueStorage": {
779
803
  "@id": "undefineds:dist/storage/keyvalue/PostgresKeyValueStorage.jsonld#PostgresKeyValueStorage",
780
804
  "@prefix": true,
@@ -1782,6 +1806,18 @@
1782
1806
  }
1783
1807
  }
1784
1808
  },
1809
+ "LoopbackClientIdAdapterFactory": {
1810
+ "@id": "undefineds:dist/identity/oidc/LoopbackClientIdAdapterFactory.jsonld#LoopbackClientIdAdapterFactory",
1811
+ "@prefix": true,
1812
+ "@context": {
1813
+ "source": {
1814
+ "@id": "undefineds:dist/identity/oidc/LoopbackClientIdAdapterFactory.jsonld#LoopbackClientIdAdapterFactory_source"
1815
+ },
1816
+ "converter": {
1817
+ "@id": "undefineds:dist/identity/oidc/LoopbackClientIdAdapterFactory.jsonld#LoopbackClientIdAdapterFactory_converter"
1818
+ }
1819
+ }
1820
+ },
1785
1821
  "UrlAwareRedisLocker": {
1786
1822
  "@id": "undefineds:dist/storage/locking/UrlAwareRedisLocker.jsonld#UrlAwareRedisLocker",
1787
1823
  "@prefix": true,
@@ -1825,6 +1861,9 @@
1825
1861
  "args_provisionBaseUrl": {
1826
1862
  "@id": "undefineds:dist/provision/ProvisionPodCreator.jsonld#ProvisionPodCreator_args_provisionBaseUrl"
1827
1863
  },
1864
+ "args_webIdProfileRepo": {
1865
+ "@id": "undefineds:dist/provision/ProvisionPodCreator.jsonld#ProvisionPodCreator_args_webIdProfileRepo"
1866
+ },
1828
1867
  "args_baseUrl": {
1829
1868
  "@id": "undefineds:dist/provision/ProvisionPodCreator.jsonld#ProvisionPodCreator_args_baseUrl"
1830
1869
  },
@@ -1843,6 +1882,9 @@
1843
1882
  "provisionBaseUrl": {
1844
1883
  "@id": "undefineds:dist/provision/ProvisionPodCreator.jsonld#ProvisionPodCreator_args_provisionBaseUrl"
1845
1884
  },
1885
+ "webIdProfileRepo": {
1886
+ "@id": "undefineds:dist/provision/ProvisionPodCreator.jsonld#ProvisionPodCreator_args_webIdProfileRepo"
1887
+ },
1846
1888
  "baseUrl": {
1847
1889
  "@id": "undefineds:dist/provision/ProvisionPodCreator.jsonld#ProvisionPodCreator_args_baseUrl"
1848
1890
  },
@@ -33,6 +33,10 @@ export declare class PodLookupRepository {
33
33
  * Get Pod by ID.
34
34
  */
35
35
  findById(podId: string): Promise<PodLookupResult | undefined>;
36
+ /**
37
+ * List Pods for a specific account.
38
+ */
39
+ listByAccountId(accountId: string): Promise<PodLookupResult[]>;
36
40
  /**
37
41
  * Get migration status for a Pod from identity_pod_usage table.
38
42
  */
@@ -38,6 +38,13 @@ class PodLookupRepository {
38
38
  const pods = await this.getAllPods();
39
39
  return pods.find((p) => p.podId === podId);
40
40
  }
41
+ /**
42
+ * List Pods for a specific account.
43
+ */
44
+ async listByAccountId(accountId) {
45
+ const pods = await this.getAllPods();
46
+ return pods.filter((pod) => pod.accountId === accountId);
47
+ }
41
48
  /**
42
49
  * Get migration status for a Pod from identity_pod_usage table.
43
50
  */
@@ -1 +1 @@
1
- {"version":3,"file":"PodLookupRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/PodLookupRepository.ts"],"names":[],"mappings":"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","sourcesContent":["import { sql } from 'drizzle-orm';\nimport { type IdentityDatabase, executeQuery, executeStatement } from './db';\n\nexport interface PodLookupResult {\n podId: string;\n accountId: string;\n baseUrl: string;\n nodeId?: string;\n edgeNodeId?: string;\n}\n\nexport interface PodMigrationStatus {\n podId: string;\n nodeId?: string;\n migrationStatus?: 'syncing' | 'done' | null;\n migrationTargetNode?: string;\n migrationProgress?: number;\n}\n\ninterface InternalKvRow {\n key?: string;\n value?: string;\n id?: string;\n account_id?: string;\n base_url?: string;\n node_id?: string;\n edge_node_id?: string;\n}\n\n/**\n * Repository for Pod lookup operations.\n *\n * Reads Pod data from CSS's internal_kv table where account data is stored.\n * CSS stores account data at key \"accounts/data/{accountId}\" with Pod info\n * nested in the \"**pod**\" field.\n */\nexport class PodLookupRepository {\n private readonly kvTableName: string;\n private readonly usageTableName: string;\n\n public constructor(\n private readonly db: IdentityDatabase,\n kvTableName?: string,\n ) {\n this.kvTableName = kvTableName ?? 'internal_kv';\n this.usageTableName = 'identity_pod_usage';\n }\n\n /**\n * Find Pod by resource path (matches longest baseUrl prefix).\n */\n public async findByResourceIdentifier(resourcePath: string): Promise<PodLookupResult | undefined> {\n const pods = await this.getAllPods();\n\n let bestMatch: PodLookupResult | undefined;\n let bestLength = 0;\n\n for (const pod of pods) {\n if (resourcePath.startsWith(pod.baseUrl) && pod.baseUrl.length > bestLength) {\n bestMatch = pod;\n bestLength = pod.baseUrl.length;\n }\n }\n\n return bestMatch;\n }\n\n /**\n * Get Pod by ID.\n */\n public async findById(podId: string): Promise<PodLookupResult | undefined> {\n const pods = await this.getAllPods();\n return pods.find((p) => p.podId === podId);\n }\n\n /**\n * Get migration status for a Pod from identity_pod_usage table.\n */\n public async getMigrationStatus(podId: string): Promise<PodMigrationStatus | undefined> {\n try {\n const tableId = sql.identifier([this.usageTableName]);\n const result = await executeQuery<{\n pod_id?: string;\n id?: string;\n node_id?: string | null;\n migration_status?: string | null;\n migration_target_node?: string | null;\n migration_progress?: number | null;\n }>(this.db, sql`\n SELECT pod_id, node_id, migration_status, migration_target_node, migration_progress\n FROM ${tableId}\n WHERE pod_id = ${podId}\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0];\n return {\n podId: row.pod_id ?? row.id ?? podId,\n nodeId: row.node_id ?? undefined,\n migrationStatus: row.migration_status as 'syncing' | 'done' | null | undefined,\n migrationTargetNode: row.migration_target_node ?? undefined,\n migrationProgress: row.migration_progress ?? undefined,\n };\n } catch {\n // Table might not exist.\n return undefined;\n }\n }\n\n /**\n * Set the nodeId for a Pod in identity_pod_usage table.\n */\n public async setNodeId(podId: string, nodeId: string): Promise<void> {\n const tableId = sql.identifier([this.usageTableName]);\n await executeStatement(this.db, sql`\n INSERT INTO ${tableId} (pod_id, account_id, node_id)\n VALUES (${podId}, '', ${nodeId})\n ON CONFLICT (pod_id) DO UPDATE SET node_id = ${nodeId}\n `);\n }\n\n /**\n * Update migration status for a Pod in identity_pod_usage table.\n */\n public async setMigrationStatus(\n podId: string,\n status: 'syncing' | 'done' | null,\n targetNode?: string | null,\n progress?: number | null,\n ): Promise<void> {\n const tableId = sql.identifier([this.usageTableName]);\n await executeStatement(this.db, sql`\n INSERT INTO ${tableId} (pod_id, account_id, migration_status, migration_target_node, migration_progress)\n VALUES (${podId}, '', ${status}, ${targetNode ?? null}, ${progress ?? 0})\n ON CONFLICT (pod_id) DO UPDATE SET\n migration_status = ${status},\n migration_target_node = ${targetNode ?? null},\n migration_progress = ${progress ?? 0}\n `);\n }\n\n /**\n * List all pods.\n */\n public async listAllPods(): Promise<PodLookupResult[]> {\n return this.getAllPods();\n }\n\n /**\n * Extract all pods from CSS's internal_kv storage.\n *\n * It keeps backward compatibility with legacy rows that already expose\n * id/account_id/base_url columns (used by some unit tests and older schemas).\n */\n private async getAllPods(): Promise<PodLookupResult[]> {\n const kvTableId = sql.identifier([this.kvTableName]);\n\n const result = await executeQuery<InternalKvRow>(this.db, sql`\n SELECT key, value FROM ${kvTableId}\n WHERE key LIKE 'accounts/data/%'\n `);\n\n const pods: PodLookupResult[] = [];\n\n for (const row of result.rows) {\n if (row.id && row.account_id && row.base_url) {\n pods.push({\n podId: String(row.id),\n accountId: String(row.account_id),\n baseUrl: String(row.base_url),\n nodeId: row.node_id ? String(row.node_id) : undefined,\n edgeNodeId: row.edge_node_id ? String(row.edge_node_id) : undefined,\n });\n continue;\n }\n\n if (!row.key || row.value === undefined) {\n continue;\n }\n\n try {\n const accountId = row.key.replace('accounts/data/', '');\n const data = typeof row.value === 'string' ? JSON.parse(row.value) : row.value;\n\n const podMap = (data as any)['**pod**'] || (data as any).pod || {};\n\n for (const [podId, podData] of Object.entries(podMap)) {\n const pod = podData as Record<string, unknown>;\n if (pod.baseUrl && typeof pod.baseUrl === 'string') {\n pods.push({\n podId,\n accountId,\n baseUrl: pod.baseUrl,\n nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : undefined,\n edgeNodeId: typeof pod.edgeNodeId === 'string' ? pod.edgeNodeId : undefined,\n });\n }\n }\n } catch {\n // Skip malformed entries.\n }\n }\n\n return pods;\n }\n}\n"]}
1
+ {"version":3,"file":"PodLookupRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/PodLookupRepository.ts"],"names":[],"mappings":"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","sourcesContent":["import { sql } from 'drizzle-orm';\nimport { type IdentityDatabase, executeQuery, executeStatement } from './db';\n\nexport interface PodLookupResult {\n podId: string;\n accountId: string;\n baseUrl: string;\n nodeId?: string;\n edgeNodeId?: string;\n}\n\nexport interface PodMigrationStatus {\n podId: string;\n nodeId?: string;\n migrationStatus?: 'syncing' | 'done' | null;\n migrationTargetNode?: string;\n migrationProgress?: number;\n}\n\ninterface InternalKvRow {\n key?: string;\n value?: string;\n id?: string;\n account_id?: string;\n base_url?: string;\n node_id?: string;\n edge_node_id?: string;\n}\n\n/**\n * Repository for Pod lookup operations.\n *\n * Reads Pod data from CSS's internal_kv table where account data is stored.\n * CSS stores account data at key \"accounts/data/{accountId}\" with Pod info\n * nested in the \"**pod**\" field.\n */\nexport class PodLookupRepository {\n private readonly kvTableName: string;\n private readonly usageTableName: string;\n\n public constructor(\n private readonly db: IdentityDatabase,\n kvTableName?: string,\n ) {\n this.kvTableName = kvTableName ?? 'internal_kv';\n this.usageTableName = 'identity_pod_usage';\n }\n\n /**\n * Find Pod by resource path (matches longest baseUrl prefix).\n */\n public async findByResourceIdentifier(resourcePath: string): Promise<PodLookupResult | undefined> {\n const pods = await this.getAllPods();\n\n let bestMatch: PodLookupResult | undefined;\n let bestLength = 0;\n\n for (const pod of pods) {\n if (resourcePath.startsWith(pod.baseUrl) && pod.baseUrl.length > bestLength) {\n bestMatch = pod;\n bestLength = pod.baseUrl.length;\n }\n }\n\n return bestMatch;\n }\n\n /**\n * Get Pod by ID.\n */\n public async findById(podId: string): Promise<PodLookupResult | undefined> {\n const pods = await this.getAllPods();\n return pods.find((p) => p.podId === podId);\n }\n\n /**\n * List Pods for a specific account.\n */\n public async listByAccountId(accountId: string): Promise<PodLookupResult[]> {\n const pods = await this.getAllPods();\n return pods.filter((pod) => pod.accountId === accountId);\n }\n\n /**\n * Get migration status for a Pod from identity_pod_usage table.\n */\n public async getMigrationStatus(podId: string): Promise<PodMigrationStatus | undefined> {\n try {\n const tableId = sql.identifier([this.usageTableName]);\n const result = await executeQuery<{\n pod_id?: string;\n id?: string;\n node_id?: string | null;\n migration_status?: string | null;\n migration_target_node?: string | null;\n migration_progress?: number | null;\n }>(this.db, sql`\n SELECT pod_id, node_id, migration_status, migration_target_node, migration_progress\n FROM ${tableId}\n WHERE pod_id = ${podId}\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0];\n return {\n podId: row.pod_id ?? row.id ?? podId,\n nodeId: row.node_id ?? undefined,\n migrationStatus: row.migration_status as 'syncing' | 'done' | null | undefined,\n migrationTargetNode: row.migration_target_node ?? undefined,\n migrationProgress: row.migration_progress ?? undefined,\n };\n } catch {\n // Table might not exist.\n return undefined;\n }\n }\n\n /**\n * Set the nodeId for a Pod in identity_pod_usage table.\n */\n public async setNodeId(podId: string, nodeId: string): Promise<void> {\n const tableId = sql.identifier([this.usageTableName]);\n await executeStatement(this.db, sql`\n INSERT INTO ${tableId} (pod_id, account_id, node_id)\n VALUES (${podId}, '', ${nodeId})\n ON CONFLICT (pod_id) DO UPDATE SET node_id = ${nodeId}\n `);\n }\n\n /**\n * Update migration status for a Pod in identity_pod_usage table.\n */\n public async setMigrationStatus(\n podId: string,\n status: 'syncing' | 'done' | null,\n targetNode?: string | null,\n progress?: number | null,\n ): Promise<void> {\n const tableId = sql.identifier([this.usageTableName]);\n await executeStatement(this.db, sql`\n INSERT INTO ${tableId} (pod_id, account_id, migration_status, migration_target_node, migration_progress)\n VALUES (${podId}, '', ${status}, ${targetNode ?? null}, ${progress ?? 0})\n ON CONFLICT (pod_id) DO UPDATE SET\n migration_status = ${status},\n migration_target_node = ${targetNode ?? null},\n migration_progress = ${progress ?? 0}\n `);\n }\n\n /**\n * List all pods.\n */\n public async listAllPods(): Promise<PodLookupResult[]> {\n return this.getAllPods();\n }\n\n /**\n * Extract all pods from CSS's internal_kv storage.\n *\n * It keeps backward compatibility with legacy rows that already expose\n * id/account_id/base_url columns (used by some unit tests and older schemas).\n */\n private async getAllPods(): Promise<PodLookupResult[]> {\n const kvTableId = sql.identifier([this.kvTableName]);\n\n const result = await executeQuery<InternalKvRow>(this.db, sql`\n SELECT key, value FROM ${kvTableId}\n WHERE key LIKE 'accounts/data/%'\n `);\n\n const pods: PodLookupResult[] = [];\n\n for (const row of result.rows) {\n if (row.id && row.account_id && row.base_url) {\n pods.push({\n podId: String(row.id),\n accountId: String(row.account_id),\n baseUrl: String(row.base_url),\n nodeId: row.node_id ? String(row.node_id) : undefined,\n edgeNodeId: row.edge_node_id ? String(row.edge_node_id) : undefined,\n });\n continue;\n }\n\n if (!row.key || row.value === undefined) {\n continue;\n }\n\n try {\n const accountId = row.key.replace('accounts/data/', '');\n const data = typeof row.value === 'string' ? JSON.parse(row.value) : row.value;\n\n const podMap = (data as any)['**pod**'] || (data as any).pod || {};\n\n for (const [podId, podData] of Object.entries(podMap)) {\n const pod = podData as Record<string, unknown>;\n if (pod.baseUrl && typeof pod.baseUrl === 'string') {\n pods.push({\n podId,\n accountId,\n baseUrl: pod.baseUrl,\n nodeId: typeof pod.nodeId === 'string' ? pod.nodeId : undefined,\n edgeNodeId: typeof pod.edgeNodeId === 'string' ? pod.edgeNodeId : undefined,\n });\n }\n }\n } catch {\n // Skip malformed entries.\n }\n }\n\n return pods;\n }\n}\n"]}
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 管理 WebID Profile 的托管,支持身份与存储分离架构
5
5
  */
6
- import type { IdentityDatabase } from './db';
6
+ import { type IdentityDatabase } from './db';
7
7
  export interface WebIdProfile {
8
8
  username: string;
9
9
  webidUrl: string;
@@ -25,12 +25,15 @@ export interface UpdateStorageInput {
25
25
  storageUrl: string;
26
26
  storageMode?: 'cloud' | 'local' | 'custom';
27
27
  }
28
+ export interface WebIdProfileRepositoryOptions {
29
+ baseUrl: string;
30
+ db?: IdentityDatabase;
31
+ identityDbUrl?: string;
32
+ }
28
33
  export declare class WebIdProfileRepository {
29
- private readonly db;
30
34
  private readonly baseUrl;
31
- constructor(db: IdentityDatabase, options: {
32
- baseUrl: string;
33
- });
35
+ private readonly db;
36
+ constructor(options: WebIdProfileRepositoryOptions);
34
37
  /**
35
38
  * 创建 WebID Profile
36
39
  */
@@ -7,11 +7,15 @@
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.WebIdProfileRepository = void 0;
9
9
  const drizzle_orm_1 = require("drizzle-orm");
10
+ const db_1 = require("./db");
10
11
  const global_logger_factory_1 = require("global-logger-factory");
11
12
  const logger = (0, global_logger_factory_1.getLoggerFor)('WebIdProfileRepository');
12
13
  class WebIdProfileRepository {
13
- constructor(db, options) {
14
- this.db = db;
14
+ constructor(options) {
15
+ this.db = options.db ?? getIdentityDatabaseFromOptions(options);
16
+ if (!options.baseUrl) {
17
+ throw new Error('WebIdProfileRepository requires baseUrl.');
18
+ }
15
19
  this.baseUrl = options.baseUrl.replace(/\/$/, '');
16
20
  }
17
21
  /**
@@ -154,4 +158,10 @@ class WebIdProfileRepository {
154
158
  }
155
159
  }
156
160
  exports.WebIdProfileRepository = WebIdProfileRepository;
161
+ function getIdentityDatabaseFromOptions(options) {
162
+ if (options.identityDbUrl) {
163
+ return (0, db_1.getIdentityDatabase)(options.identityDbUrl);
164
+ }
165
+ throw new Error('WebIdProfileRepository requires db or identityDbUrl.');
166
+ }
157
167
  //# sourceMappingURL=WebIdProfileRepository.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WebIdProfileRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/WebIdProfileRepository.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebID Profile Repository\n *\n * 管理 WebID Profile 的托管,支持身份与存储分离架构\n */\n\nimport { eq } from 'drizzle-orm';\nimport type { IdentityDatabase } from './db';\nimport { getLoggerFor } from 'global-logger-factory';\n\nconst logger = getLoggerFor('WebIdProfileRepository');\n\nexport interface WebIdProfile {\n username: string;\n webidUrl: string;\n storageUrl?: string;\n storageMode: 'cloud' | 'local' | 'custom';\n oidcIssuer?: string;\n profileData?: Record<string, unknown>;\n accountId?: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateWebIdProfileInput {\n username: string;\n storageMode?: 'cloud' | 'local' | 'custom';\n storageUrl?: string;\n accountId?: string;\n}\n\nexport interface UpdateStorageInput {\n storageUrl: string;\n storageMode?: 'cloud' | 'local' | 'custom';\n}\n\nexport class WebIdProfileRepository {\n private readonly baseUrl: string;\n\n constructor(\n private readonly db: IdentityDatabase,\n options: { baseUrl: string },\n ) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n }\n\n /**\n * 创建 WebID Profile\n */\n async create(input: CreateWebIdProfileInput): Promise<WebIdProfile> {\n const { username, storageMode = 'cloud', storageUrl, accountId } = input;\n\n // 生成 WebID URL\n const webidUrl = `${this.baseUrl}/${username}/profile/card#me`;\n\n // 默认 storage URL\n const defaultStorageUrl = storageMode === 'cloud'\n ? `${this.baseUrl}/${username}/`\n : storageUrl;\n\n // 生成默认的 Profile 数据\n const profileData = this.generateDefaultProfile(username, webidUrl, defaultStorageUrl);\n\n const now = new Date();\n\n await this.db.insert(this.db.schema.webidProfiles).values({\n username,\n webidUrl,\n storageUrl: defaultStorageUrl,\n storageMode,\n oidcIssuer: this.baseUrl,\n profileData,\n accountId,\n createdAt: now,\n updatedAt: now,\n });\n\n logger.info(`Created WebID profile for ${username}: ${webidUrl}`);\n\n return {\n username,\n webidUrl,\n storageUrl: defaultStorageUrl,\n storageMode,\n oidcIssuer: this.baseUrl,\n profileData,\n accountId,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * 获取 WebID Profile\n */\n async get(username: string): Promise<WebIdProfile | null> {\n const results = await this.db\n .select()\n .from(this.db.schema.webidProfiles)\n .where(eq(this.db.schema.webidProfiles.username, username))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n username: row.username,\n webidUrl: row.webidUrl,\n storageUrl: row.storageUrl ?? undefined,\n storageMode: (row.storageMode as 'cloud' | 'local' | 'custom') ?? 'cloud',\n oidcIssuer: row.oidcIssuer ?? undefined,\n profileData: row.profileData as Record<string, unknown> | undefined,\n accountId: row.accountId ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n }\n\n /**\n * 更新 Storage URL(用于 Local 节点更新指针)\n */\n async updateStorage(username: string, input: UpdateStorageInput): Promise<WebIdProfile | null> {\n const { storageUrl, storageMode } = input;\n\n const existing = await this.get(username);\n if (!existing) {\n return null;\n }\n\n // 更新 Profile 数据中的 storage\n const profileData = {\n ...(existing.profileData ?? {}),\n 'solid:storage': { '@id': storageUrl },\n };\n\n const now = new Date();\n\n await this.db\n .update(this.db.schema.webidProfiles)\n .set({\n storageUrl,\n storageMode: storageMode ?? existing.storageMode,\n profileData,\n updatedAt: now,\n })\n .where(eq(this.db.schema.webidProfiles.username, username));\n\n logger.info(`Updated storage for ${username}: ${storageUrl}`);\n\n return {\n ...existing,\n storageUrl,\n storageMode: storageMode ?? existing.storageMode,\n profileData,\n updatedAt: now,\n };\n }\n\n /**\n * 删除 WebID Profile\n */\n async delete(username: string): Promise<boolean> {\n const result = await this.db\n .delete(this.db.schema.webidProfiles)\n .where(eq(this.db.schema.webidProfiles.username, username));\n\n // drizzle 返回的结果格式因数据库而异\n return true;\n }\n\n /**\n * 生成 WebID Profile 的 Turtle 格式\n */\n generateProfileTurtle(profile: WebIdProfile): string {\n const { webidUrl, storageUrl, oidcIssuer } = profile;\n\n return `@prefix foaf: <http://xmlns.com/foaf/0.1/>.\n@prefix solid: <http://www.w3.org/ns/solid/terms#>.\n\n<${webidUrl}>\n a foaf:Person;\n solid:oidcIssuer <${oidcIssuer}>${storageUrl ? `;\n solid:storage <${storageUrl}>` : ''}.\n`;\n }\n\n /**\n * 生成默认的 Profile 数据(JSON-LD 格式)\n */\n private generateDefaultProfile(\n username: string,\n webidUrl: string,\n storageUrl?: string,\n ): Record<string, unknown> {\n const profile: Record<string, unknown> = {\n '@context': {\n foaf: 'http://xmlns.com/foaf/0.1/',\n solid: 'http://www.w3.org/ns/solid/terms#',\n },\n '@id': webidUrl,\n '@type': 'foaf:Person',\n 'solid:oidcIssuer': { '@id': this.baseUrl },\n };\n\n if (storageUrl) {\n profile['solid:storage'] = { '@id': storageUrl };\n }\n\n return profile;\n }\n}\n"]}
1
+ {"version":3,"file":"WebIdProfileRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/WebIdProfileRepository.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebID Profile Repository\n *\n * 管理 WebID Profile 的托管,支持身份与存储分离架构\n */\n\nimport { eq } from 'drizzle-orm';\nimport { getIdentityDatabase, type IdentityDatabase } from './db';\nimport { getLoggerFor } from 'global-logger-factory';\n\nconst logger = getLoggerFor('WebIdProfileRepository');\n\nexport interface WebIdProfile {\n username: string;\n webidUrl: string;\n storageUrl?: string;\n storageMode: 'cloud' | 'local' | 'custom';\n oidcIssuer?: string;\n profileData?: Record<string, unknown>;\n accountId?: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateWebIdProfileInput {\n username: string;\n storageMode?: 'cloud' | 'local' | 'custom';\n storageUrl?: string;\n accountId?: string;\n}\n\nexport interface UpdateStorageInput {\n storageUrl: string;\n storageMode?: 'cloud' | 'local' | 'custom';\n}\n\nexport interface WebIdProfileRepositoryOptions {\n baseUrl: string;\n db?: IdentityDatabase;\n identityDbUrl?: string;\n}\n\nexport class WebIdProfileRepository {\n private readonly baseUrl: string;\n private readonly db: IdentityDatabase;\n\n public constructor(options: WebIdProfileRepositoryOptions) {\n this.db = options.db ?? getIdentityDatabaseFromOptions(options);\n if (!options.baseUrl) {\n throw new Error('WebIdProfileRepository requires baseUrl.');\n }\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n }\n\n /**\n * 创建 WebID Profile\n */\n async create(input: CreateWebIdProfileInput): Promise<WebIdProfile> {\n const { username, storageMode = 'cloud', storageUrl, accountId } = input;\n\n // 生成 WebID URL\n const webidUrl = `${this.baseUrl}/${username}/profile/card#me`;\n\n // 默认 storage URL\n const defaultStorageUrl = storageMode === 'cloud'\n ? `${this.baseUrl}/${username}/`\n : storageUrl;\n\n // 生成默认的 Profile 数据\n const profileData = this.generateDefaultProfile(username, webidUrl, defaultStorageUrl);\n\n const now = new Date();\n\n await this.db.insert(this.db.schema.webidProfiles).values({\n username,\n webidUrl,\n storageUrl: defaultStorageUrl,\n storageMode,\n oidcIssuer: this.baseUrl,\n profileData,\n accountId,\n createdAt: now,\n updatedAt: now,\n });\n\n logger.info(`Created WebID profile for ${username}: ${webidUrl}`);\n\n return {\n username,\n webidUrl,\n storageUrl: defaultStorageUrl,\n storageMode,\n oidcIssuer: this.baseUrl,\n profileData,\n accountId,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * 获取 WebID Profile\n */\n async get(username: string): Promise<WebIdProfile | null> {\n const results = await this.db\n .select()\n .from(this.db.schema.webidProfiles)\n .where(eq(this.db.schema.webidProfiles.username, username))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n username: row.username,\n webidUrl: row.webidUrl,\n storageUrl: row.storageUrl ?? undefined,\n storageMode: (row.storageMode as 'cloud' | 'local' | 'custom') ?? 'cloud',\n oidcIssuer: row.oidcIssuer ?? undefined,\n profileData: row.profileData as Record<string, unknown> | undefined,\n accountId: row.accountId ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n }\n\n /**\n * 更新 Storage URL(用于 Local 节点更新指针)\n */\n async updateStorage(username: string, input: UpdateStorageInput): Promise<WebIdProfile | null> {\n const { storageUrl, storageMode } = input;\n\n const existing = await this.get(username);\n if (!existing) {\n return null;\n }\n\n // 更新 Profile 数据中的 storage\n const profileData = {\n ...(existing.profileData ?? {}),\n 'solid:storage': { '@id': storageUrl },\n };\n\n const now = new Date();\n\n await this.db\n .update(this.db.schema.webidProfiles)\n .set({\n storageUrl,\n storageMode: storageMode ?? existing.storageMode,\n profileData,\n updatedAt: now,\n })\n .where(eq(this.db.schema.webidProfiles.username, username));\n\n logger.info(`Updated storage for ${username}: ${storageUrl}`);\n\n return {\n ...existing,\n storageUrl,\n storageMode: storageMode ?? existing.storageMode,\n profileData,\n updatedAt: now,\n };\n }\n\n /**\n * 删除 WebID Profile\n */\n async delete(username: string): Promise<boolean> {\n const result = await this.db\n .delete(this.db.schema.webidProfiles)\n .where(eq(this.db.schema.webidProfiles.username, username));\n\n // drizzle 返回的结果格式因数据库而异\n return true;\n }\n\n /**\n * 生成 WebID Profile 的 Turtle 格式\n */\n generateProfileTurtle(profile: WebIdProfile): string {\n const { webidUrl, storageUrl, oidcIssuer } = profile;\n\n return `@prefix foaf: <http://xmlns.com/foaf/0.1/>.\n@prefix solid: <http://www.w3.org/ns/solid/terms#>.\n\n<${webidUrl}>\n a foaf:Person;\n solid:oidcIssuer <${oidcIssuer}>${storageUrl ? `;\n solid:storage <${storageUrl}>` : ''}.\n`;\n }\n\n /**\n * 生成默认的 Profile 数据(JSON-LD 格式)\n */\n private generateDefaultProfile(\n username: string,\n webidUrl: string,\n storageUrl?: string,\n ): Record<string, unknown> {\n const profile: Record<string, unknown> = {\n '@context': {\n foaf: 'http://xmlns.com/foaf/0.1/',\n solid: 'http://www.w3.org/ns/solid/terms#',\n },\n '@id': webidUrl,\n '@type': 'foaf:Person',\n 'solid:oidcIssuer': { '@id': this.baseUrl },\n };\n\n if (storageUrl) {\n profile['solid:storage'] = { '@id': storageUrl };\n }\n\n return profile;\n }\n}\n\nfunction getIdentityDatabaseFromOptions(options: WebIdProfileRepositoryOptions): IdentityDatabase {\n if (options.identityDbUrl) {\n return getIdentityDatabase(options.identityDbUrl);\n }\n\n throw new Error('WebIdProfileRepository requires db or identityDbUrl.');\n}\n"]}
@@ -0,0 +1,108 @@
1
+ {
2
+ "@context": [
3
+ "https://linkedsoftwaredependencies.org/bundles/npm/@undefineds.co/xpod/^0.0.0/components/context.jsonld"
4
+ ],
5
+ "@id": "npmd:@undefineds.co/xpod",
6
+ "components": [
7
+ {
8
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository",
9
+ "@type": "Class",
10
+ "requireElement": "WebIdProfileRepository",
11
+ "parameters": [
12
+ {
13
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_baseUrl",
14
+ "range": "xsd:string"
15
+ },
16
+ {
17
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_db",
18
+ "range": {
19
+ "@type": "ParameterRangeUnion",
20
+ "parameterRangeElements": [
21
+ {
22
+ "@type": "ParameterRangeWildcard"
23
+ },
24
+ {
25
+ "@type": "ParameterRangeUndefined"
26
+ }
27
+ ]
28
+ }
29
+ },
30
+ {
31
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_identityDbUrl",
32
+ "range": {
33
+ "@type": "ParameterRangeUnion",
34
+ "parameterRangeElements": [
35
+ "xsd:string",
36
+ {
37
+ "@type": "ParameterRangeUndefined"
38
+ }
39
+ ]
40
+ }
41
+ }
42
+ ],
43
+ "memberFields": [
44
+ {
45
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_baseUrl",
46
+ "memberFieldName": "baseUrl"
47
+ },
48
+ {
49
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_db",
50
+ "memberFieldName": "db"
51
+ },
52
+ {
53
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_constructor",
54
+ "memberFieldName": "constructor"
55
+ },
56
+ {
57
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_create",
58
+ "memberFieldName": "create"
59
+ },
60
+ {
61
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_get",
62
+ "memberFieldName": "get"
63
+ },
64
+ {
65
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_updateStorage",
66
+ "memberFieldName": "updateStorage"
67
+ },
68
+ {
69
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_delete",
70
+ "memberFieldName": "delete"
71
+ },
72
+ {
73
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_generateProfileTurtle",
74
+ "memberFieldName": "generateProfileTurtle"
75
+ },
76
+ {
77
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository__member_generateDefaultProfile",
78
+ "memberFieldName": "generateDefaultProfile"
79
+ }
80
+ ],
81
+ "constructorArguments": [
82
+ {
83
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options__constructorArgument",
84
+ "fields": [
85
+ {
86
+ "keyRaw": "baseUrl",
87
+ "value": {
88
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_baseUrl"
89
+ }
90
+ },
91
+ {
92
+ "keyRaw": "db",
93
+ "value": {
94
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_db"
95
+ }
96
+ },
97
+ {
98
+ "keyRaw": "identityDbUrl",
99
+ "value": {
100
+ "@id": "undefineds:dist/identity/drizzle/WebIdProfileRepository.jsonld#WebIdProfileRepository_options_identityDbUrl"
101
+ }
102
+ }
103
+ ]
104
+ }
105
+ ]
106
+ }
107
+ ]
108
+ }
@@ -0,0 +1,7 @@
1
+ import type { Adapter } from 'oidc-provider';
2
+ import { ClientIdAdapterFactory, type AdapterFactory, type RepresentationConverter } from '@solid/community-server';
3
+ export declare class LoopbackClientIdAdapterFactory extends ClientIdAdapterFactory {
4
+ private readonly loopbackConverter;
5
+ constructor(source: AdapterFactory, converter: RepresentationConverter);
6
+ createStorageAdapter(name: string): Adapter;
7
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoopbackClientIdAdapterFactory = void 0;
4
+ const community_server_1 = require("@solid/community-server");
5
+ function isLoopbackRedirectUri(value) {
6
+ try {
7
+ const url = new URL(value);
8
+ return url.protocol === 'http:' &&
9
+ (url.hostname === '127.0.0.1' || url.hostname === 'localhost' || url.hostname === '[::1]');
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ function isNativeClientPayload(payload) {
16
+ if (payload.application_type === 'native') {
17
+ return true;
18
+ }
19
+ const redirectUris = Array.isArray(payload.redirect_uris) ? payload.redirect_uris : [];
20
+ return redirectUris.some((uri) => typeof uri === 'string' && isLoopbackRedirectUri(uri));
21
+ }
22
+ class LoopbackClientIdAdapter extends community_server_1.ClientIdAdapter {
23
+ async find(id) {
24
+ const payload = await super.find(id);
25
+ if (!payload) {
26
+ return payload;
27
+ }
28
+ if (isNativeClientPayload(payload)) {
29
+ return {
30
+ ...payload,
31
+ application_type: 'native',
32
+ };
33
+ }
34
+ return payload;
35
+ }
36
+ }
37
+ class LoopbackClientIdAdapterFactory extends community_server_1.ClientIdAdapterFactory {
38
+ constructor(source, converter) {
39
+ super(source, converter);
40
+ this.loopbackConverter = converter;
41
+ }
42
+ createStorageAdapter(name) {
43
+ const adapter = this.source.createStorageAdapter(name);
44
+ return new LoopbackClientIdAdapter(name, adapter, this.loopbackConverter);
45
+ }
46
+ }
47
+ exports.LoopbackClientIdAdapterFactory = LoopbackClientIdAdapterFactory;
48
+ //# sourceMappingURL=LoopbackClientIdAdapterFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoopbackClientIdAdapterFactory.js","sourceRoot":"","sources":["../../../src/identity/oidc/LoopbackClientIdAdapterFactory.ts"],"names":[],"mappings":";;;AACA,8DAKiC;AAEjC,SAAS,qBAAqB,CAAC,KAAa;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO,GAAG,CAAC,QAAQ,KAAK,OAAO;YAC7B,CAAC,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAC/F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,OAAuB;IACpD,IAAI,OAAO,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;IACvF,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,MAAM,uBAAwB,SAAQ,kCAAe;IACnC,KAAK,CAAC,IAAI,CAAC,EAAU;QACnC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,IAAI,qBAAqB,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO;gBACL,GAAG,OAAO;gBACV,gBAAgB,EAAE,QAAQ;aAC3B,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AAED,MAAa,8BAA+B,SAAQ,yCAAsB;IAGxE,YAAmB,MAAsB,EAAE,SAAkC;QAC3E,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACzB,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;IACrC,CAAC;IAEe,oBAAoB,CAAC,IAAY;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACvD,OAAO,IAAI,uBAAuB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC5E,CAAC;CACF;AAZD,wEAYC","sourcesContent":["import type { Adapter, AdapterPayload } from 'oidc-provider';\nimport {\n ClientIdAdapter,\n ClientIdAdapterFactory,\n type AdapterFactory,\n type RepresentationConverter,\n} from '@solid/community-server';\n\nfunction isLoopbackRedirectUri(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === 'http:' &&\n (url.hostname === '127.0.0.1' || url.hostname === 'localhost' || url.hostname === '[::1]');\n } catch {\n return false;\n }\n}\n\nfunction isNativeClientPayload(payload: AdapterPayload): boolean {\n if (payload.application_type === 'native') {\n return true;\n }\n\n const redirectUris = Array.isArray(payload.redirect_uris) ? payload.redirect_uris : [];\n return redirectUris.some((uri) => typeof uri === 'string' && isLoopbackRedirectUri(uri));\n}\n\nclass LoopbackClientIdAdapter extends ClientIdAdapter {\n public override async find(id: string): Promise<AdapterPayload | void> {\n const payload = await super.find(id);\n if (!payload) {\n return payload;\n }\n\n if (isNativeClientPayload(payload)) {\n return {\n ...payload,\n application_type: 'native',\n };\n }\n\n return payload;\n }\n}\n\nexport class LoopbackClientIdAdapterFactory extends ClientIdAdapterFactory {\n private readonly loopbackConverter: RepresentationConverter;\n\n public constructor(source: AdapterFactory, converter: RepresentationConverter) {\n super(source, converter);\n this.loopbackConverter = converter;\n }\n\n public override createStorageAdapter(name: string): Adapter {\n const adapter = this.source.createStorageAdapter(name);\n return new LoopbackClientIdAdapter(name, adapter, this.loopbackConverter);\n }\n}\n"]}