@open-mercato/core 0.6.1-develop.3260.1.dc719cac84 → 0.6.1-develop.3278.1.a4d26475e4

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 (212) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/domain_mapping/index.js +31 -0
  3. package/dist/generated/entities/domain_mapping/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +2 -1
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +16 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/helpers/integration/salesUi.js +18 -17
  9. package/dist/helpers/integration/salesUi.js.map +2 -2
  10. package/dist/modules/customer_accounts/acl.js +2 -1
  11. package/dist/modules/customer_accounts/acl.js.map +2 -2
  12. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js +110 -0
  13. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js.map +7 -0
  14. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js +114 -0
  15. package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js.map +7 -0
  16. package/dist/modules/customer_accounts/api/admin/domain-mappings.js +271 -0
  17. package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +7 -0
  18. package/dist/modules/customer_accounts/api/domain-check.js +70 -0
  19. package/dist/modules/customer_accounts/api/domain-check.js.map +7 -0
  20. package/dist/modules/customer_accounts/api/domain-resolve/all.js +128 -0
  21. package/dist/modules/customer_accounts/api/domain-resolve/all.js.map +7 -0
  22. package/dist/modules/customer_accounts/api/domain-resolve.js +84 -0
  23. package/dist/modules/customer_accounts/api/domain-resolve.js.map +7 -0
  24. package/dist/modules/customer_accounts/api/login.js +14 -3
  25. package/dist/modules/customer_accounts/api/login.js.map +2 -2
  26. package/dist/modules/customer_accounts/api/magic-link/request.js +14 -3
  27. package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
  28. package/dist/modules/customer_accounts/api/password/reset-request.js +14 -3
  29. package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
  30. package/dist/modules/customer_accounts/api/signup.js +32 -5
  31. package/dist/modules/customer_accounts/api/signup.js.map +2 -2
  32. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js +48 -0
  33. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js.map +7 -0
  34. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +43 -0
  35. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +7 -0
  36. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js +53 -0
  37. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js.map +7 -0
  38. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js +69 -0
  39. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js.map +7 -0
  40. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js +31 -0
  41. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js.map +7 -0
  42. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js +147 -0
  43. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js.map +7 -0
  44. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js +39 -0
  45. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js.map +7 -0
  46. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js +60 -0
  47. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js.map +7 -0
  48. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js +11 -0
  49. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js.map +7 -0
  50. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js +1 -0
  51. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js.map +7 -0
  52. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +361 -0
  53. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +7 -0
  54. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js +28 -0
  55. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js.map +7 -0
  56. package/dist/modules/customer_accounts/data/enrichers.js +78 -12
  57. package/dist/modules/customer_accounts/data/enrichers.js.map +2 -2
  58. package/dist/modules/customer_accounts/data/entities.js +85 -2
  59. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  60. package/dist/modules/customer_accounts/data/guards.js +130 -0
  61. package/dist/modules/customer_accounts/data/guards.js.map +7 -0
  62. package/dist/modules/customer_accounts/data/validators.js +19 -0
  63. package/dist/modules/customer_accounts/data/validators.js.map +2 -2
  64. package/dist/modules/customer_accounts/di.js +23 -1
  65. package/dist/modules/customer_accounts/di.js.map +2 -2
  66. package/dist/modules/customer_accounts/events.js +9 -2
  67. package/dist/modules/customer_accounts/events.js.map +2 -2
  68. package/dist/modules/customer_accounts/lib/customerAuthServer.js +27 -7
  69. package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
  70. package/dist/modules/customer_accounts/lib/customerUrl.js +48 -0
  71. package/dist/modules/customer_accounts/lib/customerUrl.js.map +7 -0
  72. package/dist/modules/customer_accounts/lib/hostname.js +55 -0
  73. package/dist/modules/customer_accounts/lib/hostname.js.map +7 -0
  74. package/dist/modules/customer_accounts/lib/platformDomains.js +7 -0
  75. package/dist/modules/customer_accounts/lib/platformDomains.js.map +7 -0
  76. package/dist/modules/customer_accounts/lib/proxyRanges.js +71 -0
  77. package/dist/modules/customer_accounts/lib/proxyRanges.js.map +7 -0
  78. package/dist/modules/customer_accounts/lib/rateLimiter.js +7 -0
  79. package/dist/modules/customer_accounts/lib/rateLimiter.js.map +2 -2
  80. package/dist/modules/customer_accounts/lib/resolveTenantContext.js +63 -0
  81. package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +7 -0
  82. package/dist/modules/customer_accounts/lib/secretCompare.js +13 -0
  83. package/dist/modules/customer_accounts/lib/secretCompare.js.map +7 -0
  84. package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js +37 -0
  85. package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js.map +7 -0
  86. package/dist/modules/customer_accounts/notifications.client.js +76 -0
  87. package/dist/modules/customer_accounts/notifications.client.js.map +2 -2
  88. package/dist/modules/customer_accounts/notifications.js +76 -0
  89. package/dist/modules/customer_accounts/notifications.js.map +2 -2
  90. package/dist/modules/customer_accounts/services/domainMappingService.js +497 -0
  91. package/dist/modules/customer_accounts/services/domainMappingService.js.map +7 -0
  92. package/dist/modules/customer_accounts/setup.js +48 -1
  93. package/dist/modules/customer_accounts/setup.js.map +2 -2
  94. package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js +29 -0
  95. package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js.map +7 -0
  96. package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js +29 -0
  97. package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js.map +7 -0
  98. package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js +30 -0
  99. package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js.map +7 -0
  100. package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js +31 -0
  101. package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js.map +7 -0
  102. package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js +29 -0
  103. package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js.map +7 -0
  104. package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js +67 -0
  105. package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js.map +7 -0
  106. package/dist/modules/customer_accounts/workers/domainVerificationWorker.js +34 -0
  107. package/dist/modules/customer_accounts/workers/domainVerificationWorker.js.map +7 -0
  108. package/dist/modules/customers/api/companies/route.js +4 -9
  109. package/dist/modules/customers/api/companies/route.js.map +2 -2
  110. package/dist/modules/customers/api/deals/route.js +4 -9
  111. package/dist/modules/customers/api/deals/route.js.map +2 -2
  112. package/dist/modules/customers/api/people/route.js +4 -9
  113. package/dist/modules/customers/api/people/route.js.map +2 -2
  114. package/dist/modules/customers/api/utils.js +0 -12
  115. package/dist/modules/customers/api/utils.js.map +2 -2
  116. package/dist/modules/customers/backend/customers/companies/page.js +361 -284
  117. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  118. package/dist/modules/customers/backend/customers/deals/page.js +378 -473
  119. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  120. package/dist/modules/customers/backend/customers/people/page.js +368 -286
  121. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  122. package/dist/modules/customers/components/detail/assignableStaff.js +15 -1
  123. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  124. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +188 -36
  125. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
  126. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +2 -2
  127. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +2 -2
  128. package/dist/modules/customers/widgets/injection-table.js +6 -0
  129. package/dist/modules/customers/widgets/injection-table.js.map +2 -2
  130. package/dist/modules/perspectives/services/perspectiveService.js +16 -2
  131. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  132. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +1 -0
  133. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  134. package/dist/modules/query_index/lib/engine.js +31 -8
  135. package/dist/modules/query_index/lib/engine.js.map +2 -2
  136. package/generated/entities/domain_mapping/index.ts +14 -0
  137. package/generated/entities.ids.generated.ts +2 -1
  138. package/generated/entity-fields-registry.ts +16 -0
  139. package/package.json +5 -5
  140. package/src/helpers/integration/salesUi.ts +27 -23
  141. package/src/modules/customer_accounts/acl.ts +1 -0
  142. package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.ts +119 -0
  143. package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.ts +123 -0
  144. package/src/modules/customer_accounts/api/admin/domain-mappings.ts +303 -0
  145. package/src/modules/customer_accounts/api/domain-check.ts +83 -0
  146. package/src/modules/customer_accounts/api/domain-resolve/all.ts +139 -0
  147. package/src/modules/customer_accounts/api/domain-resolve.ts +91 -0
  148. package/src/modules/customer_accounts/api/login.ts +14 -3
  149. package/src/modules/customer_accounts/api/magic-link/request.ts +14 -3
  150. package/src/modules/customer_accounts/api/password/reset-request.ts +15 -3
  151. package/src/modules/customer_accounts/api/signup.ts +36 -5
  152. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.tsx +70 -0
  153. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +66 -0
  154. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.tsx +78 -0
  155. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.tsx +90 -0
  156. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.tsx +43 -0
  157. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.tsx +178 -0
  158. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.tsx +56 -0
  159. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.tsx +69 -0
  160. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.ts +10 -0
  161. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.ts +31 -0
  162. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.ts +26 -0
  163. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +411 -0
  164. package/src/modules/customer_accounts/data/enrichers.ts +95 -12
  165. package/src/modules/customer_accounts/data/entities.ts +80 -1
  166. package/src/modules/customer_accounts/data/guards.ts +155 -0
  167. package/src/modules/customer_accounts/data/validators.ts +26 -0
  168. package/src/modules/customer_accounts/di.ts +35 -1
  169. package/src/modules/customer_accounts/events.ts +8 -1
  170. package/src/modules/customer_accounts/i18n/de.json +78 -0
  171. package/src/modules/customer_accounts/i18n/en.json +78 -0
  172. package/src/modules/customer_accounts/i18n/es.json +78 -0
  173. package/src/modules/customer_accounts/i18n/pl.json +78 -0
  174. package/src/modules/customer_accounts/lib/customerAuthServer.ts +44 -8
  175. package/src/modules/customer_accounts/lib/customerUrl.ts +72 -0
  176. package/src/modules/customer_accounts/lib/hostname.ts +76 -0
  177. package/src/modules/customer_accounts/lib/platformDomains.ts +14 -0
  178. package/src/modules/customer_accounts/lib/proxyRanges.ts +85 -0
  179. package/src/modules/customer_accounts/lib/rateLimiter.ts +10 -0
  180. package/src/modules/customer_accounts/lib/resolveTenantContext.ts +96 -0
  181. package/src/modules/customer_accounts/lib/secretCompare.ts +20 -0
  182. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +341 -0
  183. package/src/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.ts +38 -0
  184. package/src/modules/customer_accounts/notifications.client.ts +76 -0
  185. package/src/modules/customer_accounts/notifications.ts +76 -0
  186. package/src/modules/customer_accounts/services/domainMappingService.ts +632 -0
  187. package/src/modules/customer_accounts/setup.ts +81 -1
  188. package/src/modules/customer_accounts/subscribers/invalidateDomainCache.ts +35 -0
  189. package/src/modules/customer_accounts/subscribers/notifyDomainActivated.ts +32 -0
  190. package/src/modules/customer_accounts/subscribers/notifyDomainDnsFailed.ts +33 -0
  191. package/src/modules/customer_accounts/subscribers/notifyDomainTlsFailed.ts +34 -0
  192. package/src/modules/customer_accounts/subscribers/notifyDomainVerified.ts +32 -0
  193. package/src/modules/customer_accounts/workers/domainTlsRetryWorker.ts +84 -0
  194. package/src/modules/customer_accounts/workers/domainVerificationWorker.ts +45 -0
  195. package/src/modules/customers/api/companies/route.ts +4 -9
  196. package/src/modules/customers/api/deals/route.ts +4 -9
  197. package/src/modules/customers/api/people/route.ts +4 -9
  198. package/src/modules/customers/api/utils.ts +0 -14
  199. package/src/modules/customers/backend/customers/companies/page.tsx +321 -237
  200. package/src/modules/customers/backend/customers/deals/page.tsx +321 -455
  201. package/src/modules/customers/backend/customers/people/page.tsx +331 -240
  202. package/src/modules/customers/components/detail/assignableStaff.ts +21 -0
  203. package/src/modules/customers/i18n/de.json +31 -0
  204. package/src/modules/customers/i18n/en.json +31 -0
  205. package/src/modules/customers/i18n/es.json +31 -0
  206. package/src/modules/customers/i18n/pl.json +31 -0
  207. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +197 -40
  208. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +13 -7
  209. package/src/modules/customers/widgets/injection-table.ts +6 -0
  210. package/src/modules/perspectives/services/perspectiveService.ts +30 -2
  211. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +1 -0
  212. package/src/modules/query_index/lib/engine.ts +46 -8
@@ -0,0 +1,110 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import {
6
+ validateCrudMutationGuard,
7
+ runCrudMutationGuardAfterSuccess
8
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
9
+ const FEATURE = "customer_accounts.domain.manage";
10
+ const metadata = {
11
+ POST: { requireAuth: true, requireFeatures: [FEATURE] }
12
+ };
13
+ async function POST(req, ctx) {
14
+ const auth = await getAuthFromRequest(req);
15
+ if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
16
+ const container = await createRequestContainer();
17
+ const rbac = container.resolve("rbacService");
18
+ const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {
19
+ tenantId: auth.tenantId,
20
+ organizationId: auth.orgId
21
+ });
22
+ if (!allowed) return NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 });
23
+ const { id } = await ctx.params;
24
+ const service = container.resolve("domainMappingService");
25
+ const record = await service.findById(id, { tenantId: auth.tenantId });
26
+ if (!record) return NextResponse.json({ ok: false, error: "Not found" }, { status: 404 });
27
+ const guardResult = await validateCrudMutationGuard(container, {
28
+ tenantId: auth.tenantId,
29
+ organizationId: record.organizationId,
30
+ userId: auth.sub,
31
+ resourceKind: "customer_accounts.domain_mapping",
32
+ resourceId: id,
33
+ operation: "update",
34
+ requestMethod: req.method,
35
+ requestHeaders: req.headers
36
+ });
37
+ if (guardResult && !guardResult.ok) {
38
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
39
+ }
40
+ let updated;
41
+ try {
42
+ updated = await service.healthCheck(id);
43
+ } catch (err) {
44
+ return NextResponse.json(
45
+ { ok: false, error: err instanceof Error ? err.message : "Health check failed" },
46
+ { status: 500 }
47
+ );
48
+ }
49
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
50
+ await runCrudMutationGuardAfterSuccess(container, {
51
+ tenantId: auth.tenantId,
52
+ organizationId: record.organizationId,
53
+ userId: auth.sub,
54
+ resourceKind: "customer_accounts.domain_mapping",
55
+ resourceId: id,
56
+ operation: "update",
57
+ requestMethod: req.method,
58
+ requestHeaders: req.headers,
59
+ metadata: guardResult.metadata ?? null
60
+ });
61
+ }
62
+ return NextResponse.json({
63
+ ok: true,
64
+ domainMapping: {
65
+ id: updated.id,
66
+ hostname: updated.hostname,
67
+ status: updated.status,
68
+ tlsRetryCount: updated.tlsRetryCount,
69
+ tlsFailureReason: updated.tlsFailureReason ?? null
70
+ }
71
+ });
72
+ }
73
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
74
+ const openApi = {
75
+ tag: "CustomerAccounts",
76
+ summary: "TLS health check for a custom domain mapping",
77
+ methods: {
78
+ POST: {
79
+ summary: "Trigger TLS health check",
80
+ description: "Runs an HTTPS probe to verify Traefik has provisioned a certificate. On success: transitions the mapping to active.",
81
+ responses: [
82
+ {
83
+ status: 200,
84
+ description: "OK",
85
+ schema: z.object({
86
+ ok: z.literal(true),
87
+ domainMapping: z.object({
88
+ id: z.string().uuid(),
89
+ hostname: z.string(),
90
+ status: z.string(),
91
+ tlsRetryCount: z.number().int().nonnegative(),
92
+ tlsFailureReason: z.string().nullable()
93
+ })
94
+ })
95
+ }
96
+ ],
97
+ errors: [
98
+ { status: 401, description: "Unauthorized", schema: errorSchema },
99
+ { status: 403, description: "Forbidden", schema: errorSchema },
100
+ { status: 404, description: "Not found", schema: errorSchema }
101
+ ]
102
+ }
103
+ }
104
+ };
105
+ export {
106
+ POST,
107
+ metadata,
108
+ openApi
109
+ };
110
+ //# sourceMappingURL=health-check.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/customer_accounts/api/admin/domain-mappings/%5Bid%5D/health-check.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n validateCrudMutationGuard,\n runCrudMutationGuardAfterSuccess,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { DomainMappingService } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'\n\nconst FEATURE = 'customer_accounts.domain.manage'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nexport async function POST(req: Request, ctx: { params: Promise<{ id: string }> }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const rbac = container.resolve('rbacService') as RbacService\n const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!allowed) return NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 })\n\n const { id } = await ctx.params\n const service = container.resolve('domainMappingService') as DomainMappingService\n const record = await service.findById(id, { tenantId: auth.tenantId })\n if (!record) return NextResponse.json({ ok: false, error: 'Not found' }, { status: 404 })\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: record.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let updated\n try {\n updated = await service.healthCheck(id)\n } catch (err) {\n return NextResponse.json(\n { ok: false, error: err instanceof Error ? err.message : 'Health check failed' },\n { status: 500 },\n )\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: record.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n ok: true,\n domainMapping: {\n id: updated.id,\n hostname: updated.hostname,\n status: updated.status,\n tlsRetryCount: updated.tlsRetryCount,\n tlsFailureReason: updated.tlsFailureReason ?? null,\n },\n })\n}\n\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CustomerAccounts',\n summary: 'TLS health check for a custom domain mapping',\n methods: {\n POST: {\n summary: 'Trigger TLS health check',\n description:\n 'Runs an HTTPS probe to verify Traefik has provisioned a certificate. On success: transitions the mapping to active.',\n responses: [\n {\n status: 200,\n description: 'OK',\n schema: z.object({\n ok: z.literal(true),\n domainMapping: z.object({\n id: z.string().uuid(),\n hostname: z.string(),\n status: z.string(),\n tlsRetryCount: z.number().int().nonnegative(),\n tlsFailureReason: z.string().nullable(),\n }),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n { status: 404, description: 'Not found', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,114 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import {
6
+ validateCrudMutationGuard,
7
+ runCrudMutationGuardAfterSuccess
8
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
9
+ const FEATURE = "customer_accounts.domain.manage";
10
+ const metadata = {
11
+ POST: { requireAuth: true, requireFeatures: [FEATURE] }
12
+ };
13
+ async function POST(req, ctx) {
14
+ const auth = await getAuthFromRequest(req);
15
+ if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
16
+ const container = await createRequestContainer();
17
+ const rbac = container.resolve("rbacService");
18
+ const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {
19
+ tenantId: auth.tenantId,
20
+ organizationId: auth.orgId
21
+ });
22
+ if (!allowed) return NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 });
23
+ const { id } = await ctx.params;
24
+ const service = container.resolve("domainMappingService");
25
+ const record = await service.findById(id, { tenantId: auth.tenantId });
26
+ if (!record) return NextResponse.json({ ok: false, error: "Not found" }, { status: 404 });
27
+ const guardResult = await validateCrudMutationGuard(container, {
28
+ tenantId: auth.tenantId,
29
+ organizationId: record.organizationId,
30
+ userId: auth.sub,
31
+ resourceKind: "customer_accounts.domain_mapping",
32
+ resourceId: id,
33
+ operation: "update",
34
+ requestMethod: req.method,
35
+ requestHeaders: req.headers
36
+ });
37
+ if (guardResult && !guardResult.ok) {
38
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
39
+ }
40
+ let result;
41
+ try {
42
+ result = await service.verify(id);
43
+ } catch (err) {
44
+ return NextResponse.json(
45
+ { ok: false, error: err instanceof Error ? err.message : "DNS verification failed" },
46
+ { status: 500 }
47
+ );
48
+ }
49
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
50
+ await runCrudMutationGuardAfterSuccess(container, {
51
+ tenantId: auth.tenantId,
52
+ organizationId: record.organizationId,
53
+ userId: auth.sub,
54
+ resourceKind: "customer_accounts.domain_mapping",
55
+ resourceId: id,
56
+ operation: "update",
57
+ requestMethod: req.method,
58
+ requestHeaders: req.headers,
59
+ metadata: guardResult.metadata ?? null
60
+ });
61
+ }
62
+ return NextResponse.json({
63
+ ok: true,
64
+ domainMapping: {
65
+ id: result.domainMapping.id,
66
+ hostname: result.domainMapping.hostname,
67
+ status: result.domainMapping.status,
68
+ verifiedAt: result.domainMapping.verifiedAt?.toISOString() ?? null,
69
+ lastDnsCheckAt: result.domainMapping.lastDnsCheckAt?.toISOString() ?? null,
70
+ dnsFailureReason: result.domainMapping.dnsFailureReason ?? null
71
+ },
72
+ diagnostics: result.diagnostics ?? null
73
+ });
74
+ }
75
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
76
+ const openApi = {
77
+ tag: "CustomerAccounts",
78
+ summary: "Verify a custom domain mapping (DNS check)",
79
+ methods: {
80
+ POST: {
81
+ summary: "Trigger DNS verification",
82
+ description: "Runs DNS verification (CNAME \u2192 A \u2192 reverse-resolve fallback) for the domain mapping. Returns diagnostics on failure.",
83
+ responses: [
84
+ {
85
+ status: 200,
86
+ description: "OK \u2014 see status",
87
+ schema: z.object({
88
+ ok: z.literal(true),
89
+ domainMapping: z.object({
90
+ id: z.string().uuid(),
91
+ hostname: z.string(),
92
+ status: z.string(),
93
+ verifiedAt: z.string().nullable(),
94
+ lastDnsCheckAt: z.string().nullable(),
95
+ dnsFailureReason: z.string().nullable()
96
+ }),
97
+ diagnostics: z.unknown().nullable()
98
+ })
99
+ }
100
+ ],
101
+ errors: [
102
+ { status: 401, description: "Unauthorized", schema: errorSchema },
103
+ { status: 403, description: "Forbidden", schema: errorSchema },
104
+ { status: 404, description: "Not found", schema: errorSchema }
105
+ ]
106
+ }
107
+ }
108
+ };
109
+ export {
110
+ POST,
111
+ metadata,
112
+ openApi
113
+ };
114
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/customer_accounts/api/admin/domain-mappings/%5Bid%5D/verify.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n validateCrudMutationGuard,\n runCrudMutationGuardAfterSuccess,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { DomainMappingService } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'\n\nconst FEATURE = 'customer_accounts.domain.manage'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nexport async function POST(req: Request, ctx: { params: Promise<{ id: string }> }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const rbac = container.resolve('rbacService') as RbacService\n const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!allowed) return NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 })\n\n const { id } = await ctx.params\n const service = container.resolve('domainMappingService') as DomainMappingService\n const record = await service.findById(id, { tenantId: auth.tenantId })\n if (!record) return NextResponse.json({ ok: false, error: 'Not found' }, { status: 404 })\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: record.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let result\n try {\n result = await service.verify(id)\n } catch (err) {\n return NextResponse.json(\n { ok: false, error: err instanceof Error ? err.message : 'DNS verification failed' },\n { status: 500 },\n )\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: record.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n ok: true,\n domainMapping: {\n id: result.domainMapping.id,\n hostname: result.domainMapping.hostname,\n status: result.domainMapping.status,\n verifiedAt: result.domainMapping.verifiedAt?.toISOString() ?? null,\n lastDnsCheckAt: result.domainMapping.lastDnsCheckAt?.toISOString() ?? null,\n dnsFailureReason: result.domainMapping.dnsFailureReason ?? null,\n },\n diagnostics: result.diagnostics ?? null,\n })\n}\n\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CustomerAccounts',\n summary: 'Verify a custom domain mapping (DNS check)',\n methods: {\n POST: {\n summary: 'Trigger DNS verification',\n description:\n 'Runs DNS verification (CNAME \u2192 A \u2192 reverse-resolve fallback) for the domain mapping. Returns diagnostics on failure.',\n responses: [\n {\n status: 200,\n description: 'OK \u2014 see status',\n schema: z.object({\n ok: z.literal(true),\n domainMapping: z.object({\n id: z.string().uuid(),\n hostname: z.string(),\n status: z.string(),\n verifiedAt: z.string().nullable(),\n lastDnsCheckAt: z.string().nullable(),\n dnsFailureReason: z.string().nullable(),\n }),\n diagnostics: z.unknown().nullable(),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n { status: 404, description: 'Not found', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,271 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { UniqueConstraintViolationException } from "@mikro-orm/core";
4
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
6
+ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
7
+ import {
8
+ validateCrudMutationGuard,
9
+ runCrudMutationGuardAfterSuccess
10
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
11
+ import { registerDomainSchema } from "@open-mercato/core/modules/customer_accounts/data/validators";
12
+ import { DomainMapping } from "@open-mercato/core/modules/customer_accounts/data/entities";
13
+ const FEATURE = "customer_accounts.domain.manage";
14
+ const metadata = {
15
+ GET: { requireAuth: true, requireFeatures: [FEATURE] },
16
+ POST: { requireAuth: true, requireFeatures: [FEATURE] },
17
+ DELETE: { requireAuth: true, requireFeatures: [FEATURE] }
18
+ };
19
+ function isUniqueViolation(error) {
20
+ if (error instanceof UniqueConstraintViolationException) return true;
21
+ if (!error || typeof error !== "object") return false;
22
+ const code = error.code;
23
+ if (code === "23505") return true;
24
+ const messageRaw = error.message;
25
+ const message = typeof messageRaw === "string" ? messageRaw : "";
26
+ return message.toLowerCase().includes("duplicate key");
27
+ }
28
+ function serializeRecord(record) {
29
+ return {
30
+ id: record.id,
31
+ hostname: record.hostname,
32
+ organizationId: record.organizationId,
33
+ tenantId: record.tenantId,
34
+ provider: record.provider,
35
+ status: record.status,
36
+ verifiedAt: record.verifiedAt?.toISOString() ?? null,
37
+ lastDnsCheckAt: record.lastDnsCheckAt?.toISOString() ?? null,
38
+ dnsFailureReason: record.dnsFailureReason ?? null,
39
+ tlsFailureReason: record.tlsFailureReason ?? null,
40
+ tlsRetryCount: record.tlsRetryCount,
41
+ cnameTarget: process.env.CUSTOM_DOMAIN_CNAME_TARGET ?? null,
42
+ aRecordTarget: process.env.CUSTOM_DOMAIN_A_RECORD_TARGET ?? null,
43
+ createdAt: record.createdAt.toISOString(),
44
+ updatedAt: record.updatedAt?.toISOString() ?? null
45
+ };
46
+ }
47
+ async function GET(req) {
48
+ const auth = await getAuthFromRequest(req);
49
+ if (!auth) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
50
+ const container = await createRequestContainer();
51
+ const rbac = container.resolve("rbacService");
52
+ const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {
53
+ tenantId: auth.tenantId,
54
+ organizationId: auth.orgId
55
+ });
56
+ if (!allowed) return NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 });
57
+ const url = new URL(req.url);
58
+ const orgFilter = url.searchParams.get("organizationId");
59
+ const em = container.resolve("em");
60
+ const where = { tenantId: auth.tenantId };
61
+ if (orgFilter) where.organizationId = orgFilter;
62
+ const records = await em.find(DomainMapping, where, { orderBy: { createdAt: "desc" } });
63
+ return NextResponse.json({
64
+ ok: true,
65
+ domainMappings: records.map(serializeRecord),
66
+ config: {
67
+ cnameTarget: process.env.CUSTOM_DOMAIN_CNAME_TARGET ?? null,
68
+ aRecordTarget: process.env.CUSTOM_DOMAIN_A_RECORD_TARGET ?? null
69
+ }
70
+ });
71
+ }
72
+ async function POST(req) {
73
+ const auth = await getAuthFromRequest(req);
74
+ if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
75
+ const container = await createRequestContainer();
76
+ const rbac = container.resolve("rbacService");
77
+ const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {
78
+ tenantId: auth.tenantId,
79
+ organizationId: auth.orgId
80
+ });
81
+ if (!allowed) return NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 });
82
+ const body = await readJsonSafe(req, {});
83
+ const parsed = registerDomainSchema.safeParse(body);
84
+ if (!parsed.success) {
85
+ return NextResponse.json(
86
+ { ok: false, error: "Invalid request", issues: parsed.error.flatten() },
87
+ { status: 400 }
88
+ );
89
+ }
90
+ const guardResult = await validateCrudMutationGuard(container, {
91
+ tenantId: auth.tenantId,
92
+ organizationId: parsed.data.organizationId,
93
+ userId: auth.sub,
94
+ resourceKind: "customer_accounts.domain_mapping",
95
+ resourceId: parsed.data.organizationId,
96
+ operation: "create",
97
+ requestMethod: req.method,
98
+ requestHeaders: req.headers,
99
+ mutationPayload: parsed.data
100
+ });
101
+ if (guardResult && !guardResult.ok) {
102
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
103
+ }
104
+ const service = container.resolve("domainMappingService");
105
+ let entity;
106
+ try {
107
+ entity = await service.register({
108
+ hostname: parsed.data.hostname,
109
+ organizationId: parsed.data.organizationId,
110
+ tenantId: auth.tenantId,
111
+ replacesDomainId: parsed.data.replacesDomainId
112
+ });
113
+ } catch (err) {
114
+ if (isUniqueViolation(err)) {
115
+ return NextResponse.json(
116
+ { ok: false, error: "This domain is not available. Please choose a different hostname." },
117
+ { status: 409 }
118
+ );
119
+ }
120
+ const message = err instanceof Error ? err.message : "Failed to register domain";
121
+ return NextResponse.json({ ok: false, error: message }, { status: 500 });
122
+ }
123
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
124
+ await runCrudMutationGuardAfterSuccess(container, {
125
+ tenantId: auth.tenantId,
126
+ organizationId: parsed.data.organizationId,
127
+ userId: auth.sub,
128
+ resourceKind: "customer_accounts.domain_mapping",
129
+ resourceId: entity.id,
130
+ operation: "create",
131
+ requestMethod: req.method,
132
+ requestHeaders: req.headers,
133
+ metadata: guardResult.metadata ?? null
134
+ });
135
+ }
136
+ return NextResponse.json({ ok: true, domainMapping: serializeRecord(entity) }, { status: 201 });
137
+ }
138
+ async function DELETE(req) {
139
+ const auth = await getAuthFromRequest(req);
140
+ if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
141
+ const container = await createRequestContainer();
142
+ const rbac = container.resolve("rbacService");
143
+ const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {
144
+ tenantId: auth.tenantId,
145
+ organizationId: auth.orgId
146
+ });
147
+ if (!allowed) return NextResponse.json({ ok: false, error: "Forbidden" }, { status: 403 });
148
+ const url = new URL(req.url);
149
+ const id = url.searchParams.get("id");
150
+ if (!id) return NextResponse.json({ ok: false, error: "id is required" }, { status: 400 });
151
+ const service = container.resolve("domainMappingService");
152
+ const existing = await service.findById(id, { tenantId: auth.tenantId });
153
+ if (!existing) return NextResponse.json({ ok: false, error: "Not found" }, { status: 404 });
154
+ const guardResult = await validateCrudMutationGuard(container, {
155
+ tenantId: auth.tenantId,
156
+ organizationId: existing.organizationId,
157
+ userId: auth.sub,
158
+ resourceKind: "customer_accounts.domain_mapping",
159
+ resourceId: id,
160
+ operation: "delete",
161
+ requestMethod: req.method,
162
+ requestHeaders: req.headers
163
+ });
164
+ if (guardResult && !guardResult.ok) {
165
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
166
+ }
167
+ await service.remove(id, { tenantId: auth.tenantId });
168
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
169
+ await runCrudMutationGuardAfterSuccess(container, {
170
+ tenantId: auth.tenantId,
171
+ organizationId: existing.organizationId,
172
+ userId: auth.sub,
173
+ resourceKind: "customer_accounts.domain_mapping",
174
+ resourceId: id,
175
+ operation: "delete",
176
+ requestMethod: req.method,
177
+ requestHeaders: req.headers,
178
+ metadata: guardResult.metadata ?? null
179
+ });
180
+ }
181
+ return NextResponse.json({ ok: true });
182
+ }
183
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
184
+ const domainMappingSchema = z.object({
185
+ id: z.string().uuid(),
186
+ hostname: z.string(),
187
+ organizationId: z.string().uuid(),
188
+ tenantId: z.string().uuid(),
189
+ provider: z.literal("traefik"),
190
+ status: z.enum(["pending", "verified", "active", "dns_failed", "tls_failed"]),
191
+ verifiedAt: z.string().nullable(),
192
+ lastDnsCheckAt: z.string().nullable(),
193
+ dnsFailureReason: z.string().nullable(),
194
+ tlsFailureReason: z.string().nullable(),
195
+ tlsRetryCount: z.number().int().nonnegative(),
196
+ cnameTarget: z.string().nullable(),
197
+ aRecordTarget: z.string().nullable(),
198
+ createdAt: z.string(),
199
+ updatedAt: z.string().nullable()
200
+ });
201
+ const openApi = {
202
+ tag: "CustomerAccounts",
203
+ summary: "Custom portal domain mappings (admin)",
204
+ methods: {
205
+ GET: {
206
+ summary: "List domain mappings",
207
+ description: "Returns all custom-domain mappings for the current tenant, optionally filtered by organization.",
208
+ responses: [
209
+ {
210
+ status: 200,
211
+ description: "OK",
212
+ schema: z.object({
213
+ ok: z.literal(true),
214
+ domainMappings: z.array(domainMappingSchema),
215
+ config: z.object({
216
+ cnameTarget: z.string().nullable(),
217
+ aRecordTarget: z.string().nullable()
218
+ })
219
+ })
220
+ }
221
+ ],
222
+ errors: [
223
+ { status: 401, description: "Unauthorized", schema: errorSchema },
224
+ { status: 403, description: "Forbidden", schema: errorSchema }
225
+ ]
226
+ },
227
+ POST: {
228
+ summary: "Register a custom domain",
229
+ description: "Registers a new custom domain mapping for an organization. Verifies via DNS asynchronously.",
230
+ requestBody: {
231
+ contentType: "application/json",
232
+ schema: z.object({
233
+ hostname: z.string(),
234
+ organizationId: z.string().uuid(),
235
+ replacesDomainId: z.string().uuid().optional()
236
+ })
237
+ },
238
+ responses: [
239
+ {
240
+ status: 201,
241
+ description: "Created",
242
+ schema: z.object({ ok: z.literal(true), domainMapping: domainMappingSchema })
243
+ }
244
+ ],
245
+ errors: [
246
+ { status: 400, description: "Validation error", schema: errorSchema },
247
+ { status: 401, description: "Unauthorized", schema: errorSchema },
248
+ { status: 403, description: "Forbidden", schema: errorSchema },
249
+ { status: 409, description: "Conflict", schema: errorSchema }
250
+ ]
251
+ },
252
+ DELETE: {
253
+ summary: "Remove a custom domain",
254
+ description: "Removes the domain mapping identified by ?id=. Cache and Traefik routing drain within TTL.",
255
+ responses: [{ status: 200, description: "OK", schema: z.object({ ok: z.literal(true) }) }],
256
+ errors: [
257
+ { status: 400, description: "Bad request", schema: errorSchema },
258
+ { status: 401, description: "Unauthorized", schema: errorSchema },
259
+ { status: 403, description: "Forbidden", schema: errorSchema }
260
+ ]
261
+ }
262
+ }
263
+ };
264
+ export {
265
+ DELETE,
266
+ GET,
267
+ POST,
268
+ metadata,
269
+ openApi
270
+ };
271
+ //# sourceMappingURL=domain-mappings.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/customer_accounts/api/admin/domain-mappings.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { UniqueConstraintViolationException } from '@mikro-orm/core'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n validateCrudMutationGuard,\n runCrudMutationGuardAfterSuccess,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { registerDomainSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport {\n DomainMappingService,\n type ResolveResult,\n} from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'\nimport { DomainMapping } from '@open-mercato/core/modules/customer_accounts/data/entities'\n\nconst FEATURE = 'customer_accounts.domain.manage'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [FEATURE] },\n POST: { requireAuth: true, requireFeatures: [FEATURE] },\n DELETE: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nfunction isUniqueViolation(error: unknown): boolean {\n if (error instanceof UniqueConstraintViolationException) return true\n if (!error || typeof error !== 'object') return false\n const code = (error as { code?: string }).code\n if (code === '23505') return true\n const messageRaw = (error as { message?: string }).message\n const message = typeof messageRaw === 'string' ? messageRaw : ''\n return message.toLowerCase().includes('duplicate key')\n}\n\nfunction serializeRecord(record: DomainMapping) {\n return {\n id: record.id,\n hostname: record.hostname,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n provider: record.provider,\n status: record.status,\n verifiedAt: record.verifiedAt?.toISOString() ?? null,\n lastDnsCheckAt: record.lastDnsCheckAt?.toISOString() ?? null,\n dnsFailureReason: record.dnsFailureReason ?? null,\n tlsFailureReason: record.tlsFailureReason ?? null,\n tlsRetryCount: record.tlsRetryCount,\n cnameTarget: process.env.CUSTOM_DOMAIN_CNAME_TARGET ?? null,\n aRecordTarget: process.env.CUSTOM_DOMAIN_A_RECORD_TARGET ?? null,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt?.toISOString() ?? null,\n }\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ ok: false, error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const rbac = container.resolve('rbacService') as RbacService\n const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!allowed) return NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 })\n\n const url = new URL(req.url)\n const orgFilter = url.searchParams.get('organizationId')\n\n const em = container.resolve('em') as EntityManager\n const where: Record<string, unknown> = { tenantId: auth.tenantId }\n if (orgFilter) where.organizationId = orgFilter\n const records = await em.find(DomainMapping, where as never, { orderBy: { createdAt: 'desc' } })\n\n return NextResponse.json({\n ok: true,\n domainMappings: records.map(serializeRecord),\n config: {\n cnameTarget: process.env.CUSTOM_DOMAIN_CNAME_TARGET ?? null,\n aRecordTarget: process.env.CUSTOM_DOMAIN_A_RECORD_TARGET ?? null,\n },\n })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const rbac = container.resolve('rbacService') as RbacService\n const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!allowed) return NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 })\n\n const body = await readJsonSafe(req, {})\n const parsed = registerDomainSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n { ok: false, error: 'Invalid request', issues: parsed.error.flatten() },\n { status: 400 },\n )\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: parsed.data.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: parsed.data.organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const service = container.resolve('domainMappingService') as DomainMappingService\n\n let entity: DomainMapping\n try {\n // service.register normalizes hostname internally \u2014 guards may have\n // returned modifiedPayload but the typed runner doesn't expose it,\n // so we rely on the service for the canonical form.\n entity = await service.register({\n hostname: parsed.data.hostname,\n organizationId: parsed.data.organizationId,\n tenantId: auth.tenantId,\n replacesDomainId: parsed.data.replacesDomainId,\n })\n } catch (err: unknown) {\n if (isUniqueViolation(err)) {\n return NextResponse.json(\n { ok: false, error: 'This domain is not available. Please choose a different hostname.' },\n { status: 409 },\n )\n }\n const message = err instanceof Error ? err.message : 'Failed to register domain'\n return NextResponse.json({ ok: false, error: message }, { status: 500 })\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: parsed.data.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: entity.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true, domainMapping: serializeRecord(entity) }, { status: 201 })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ ok: false, error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const rbac = container.resolve('rbacService') as RbacService\n const allowed = await rbac.userHasAllFeatures(auth.sub, [FEATURE], {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!allowed) return NextResponse.json({ ok: false, error: 'Forbidden' }, { status: 403 })\n\n const url = new URL(req.url)\n const id = url.searchParams.get('id')\n if (!id) return NextResponse.json({ ok: false, error: 'id is required' }, { status: 400 })\n\n const service = container.resolve('domainMappingService') as DomainMappingService\n const existing = await service.findById(id, { tenantId: auth.tenantId })\n if (!existing) return NextResponse.json({ ok: false, error: 'Not found' }, { status: 404 })\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: existing.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n await service.remove(id, { tenantId: auth.tenantId })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: existing.organizationId,\n userId: auth.sub,\n resourceKind: 'customer_accounts.domain_mapping',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n}\n\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\nconst domainMappingSchema = z.object({\n id: z.string().uuid(),\n hostname: z.string(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n provider: z.literal('traefik'),\n status: z.enum(['pending', 'verified', 'active', 'dns_failed', 'tls_failed']),\n verifiedAt: z.string().nullable(),\n lastDnsCheckAt: z.string().nullable(),\n dnsFailureReason: z.string().nullable(),\n tlsFailureReason: z.string().nullable(),\n tlsRetryCount: z.number().int().nonnegative(),\n cnameTarget: z.string().nullable(),\n aRecordTarget: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CustomerAccounts',\n summary: 'Custom portal domain mappings (admin)',\n methods: {\n GET: {\n summary: 'List domain mappings',\n description: 'Returns all custom-domain mappings for the current tenant, optionally filtered by organization.',\n responses: [\n {\n status: 200,\n description: 'OK',\n schema: z.object({\n ok: z.literal(true),\n domainMappings: z.array(domainMappingSchema),\n config: z.object({\n cnameTarget: z.string().nullable(),\n aRecordTarget: z.string().nullable(),\n }),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Register a custom domain',\n description: 'Registers a new custom domain mapping for an organization. Verifies via DNS asynchronously.',\n requestBody: {\n contentType: 'application/json',\n schema: z.object({\n hostname: z.string(),\n organizationId: z.string().uuid(),\n replacesDomainId: z.string().uuid().optional(),\n }),\n },\n responses: [\n {\n status: 201,\n description: 'Created',\n schema: z.object({ ok: z.literal(true), domainMapping: domainMappingSchema }),\n },\n ],\n errors: [\n { status: 400, description: 'Validation error', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n { status: 409, description: 'Conflict', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Remove a custom domain',\n description: 'Removes the domain mapping identified by ?id=. Cache and Traefik routing drain within TTL.',\n responses: [{ status: 200, description: 'OK', schema: z.object({ ok: z.literal(true) }) }],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n ],\n },\n },\n}\n\n// Re-export resolve type so tests can import without indirect lookup\nexport type { ResolveResult }\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0CAA0C;AAGnD,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,4BAA4B;AAKrC,SAAS,qBAAqB;AAE9B,MAAM,UAAU;AAET,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,OAAO,EAAE;AAAA,EACrD,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,OAAO,EAAE;AAAA,EACtD,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,OAAO,EAAE;AAC1D;AAEA,SAAS,kBAAkB,OAAyB;AAClD,MAAI,iBAAiB,mCAAoC,QAAO;AAChE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,OAAQ,MAA4B;AAC1C,MAAI,SAAS,QAAS,QAAO;AAC7B,QAAM,aAAc,MAA+B;AACnD,QAAM,UAAU,OAAO,eAAe,WAAW,aAAa;AAC9D,SAAO,QAAQ,YAAY,EAAE,SAAS,eAAe;AACvD;AAEA,SAAS,gBAAgB,QAAuB;AAC9C,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,UAAU,OAAO;AAAA,IACjB,gBAAgB,OAAO;AAAA,IACvB,UAAU,OAAO;AAAA,IACjB,UAAU,OAAO;AAAA,IACjB,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO,YAAY,YAAY,KAAK;AAAA,IAChD,gBAAgB,OAAO,gBAAgB,YAAY,KAAK;AAAA,IACxD,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,eAAe,OAAO;AAAA,IACtB,aAAa,QAAQ,IAAI,8BAA8B;AAAA,IACvD,eAAe,QAAQ,IAAI,iCAAiC;AAAA,IAC5D,WAAW,OAAO,UAAU,YAAY;AAAA,IACxC,WAAW,OAAO,WAAW,YAAY,KAAK;AAAA,EAChD;AACF;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEzF,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,UAAU,QAAQ,aAAa;AAC5C,QAAM,UAAU,MAAM,KAAK,mBAAmB,KAAK,KAAK,CAAC,OAAO,GAAG;AAAA,IACjE,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,EACvB,CAAC;AACD,MAAI,CAAC,QAAS,QAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEzF,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,YAAY,IAAI,aAAa,IAAI,gBAAgB;AAEvD,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,QAAiC,EAAE,UAAU,KAAK,SAAS;AACjE,MAAI,UAAW,OAAM,iBAAiB;AACtC,QAAM,UAAU,MAAM,GAAG,KAAK,eAAe,OAAgB,EAAE,SAAS,EAAE,WAAW,OAAO,EAAE,CAAC;AAE/F,SAAO,aAAa,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ,gBAAgB,QAAQ,IAAI,eAAe;AAAA,IAC3C,QAAQ;AAAA,MACN,aAAa,QAAQ,IAAI,8BAA8B;AAAA,MACvD,eAAe,QAAQ,IAAI,iCAAiC;AAAA,IAC9D;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,SAAU,QAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE3G,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,UAAU,QAAQ,aAAa;AAC5C,QAAM,UAAU,MAAM,KAAK,mBAAmB,KAAK,KAAK,CAAC,OAAO,GAAG;AAAA,IACjE,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,EACvB,CAAC;AACD,MAAI,CAAC,QAAS,QAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEzF,QAAM,OAAO,MAAM,aAAa,KAAK,CAAC,CAAC;AACvC,QAAM,SAAS,qBAAqB,UAAU,IAAI;AAClD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,OAAO,mBAAmB,QAAQ,OAAO,MAAM,QAAQ,EAAE;AAAA,MACtE,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,IAC7D,UAAU,KAAK;AAAA,IACf,gBAAgB,OAAO,KAAK;AAAA,IAC5B,QAAQ,KAAK;AAAA,IACb,cAAc;AAAA,IACd,YAAY,OAAO,KAAK;AAAA,IACxB,WAAW;AAAA,IACX,eAAe,IAAI;AAAA,IACnB,gBAAgB,IAAI;AAAA,IACpB,iBAAiB,OAAO;AAAA,EAC1B,CAAC;AACD,MAAI,eAAe,CAAC,YAAY,IAAI;AAClC,WAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,EAC3E;AAEA,QAAM,UAAU,UAAU,QAAQ,sBAAsB;AAExD,MAAI;AACJ,MAAI;AAIF,aAAS,MAAM,QAAQ,SAAS;AAAA,MAC9B,UAAU,OAAO,KAAK;AAAA,MACtB,gBAAgB,OAAO,KAAK;AAAA,MAC5B,UAAU,KAAK;AAAA,MACf,kBAAkB,OAAO,KAAK;AAAA,IAChC,CAAC;AAAA,EACH,SAAS,KAAc;AACrB,QAAI,kBAAkB,GAAG,GAAG;AAC1B,aAAO,aAAa;AAAA,QAClB,EAAE,IAAI,OAAO,OAAO,oEAAoE;AAAA,QACxF,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AACA,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzE;AAEA,MAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,UAAM,iCAAiC,WAAW;AAAA,MAChD,UAAU,KAAK;AAAA,MACf,gBAAgB,OAAO,KAAK;AAAA,MAC5B,QAAQ,KAAK;AAAA,MACb,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,UAAU,YAAY,YAAY;AAAA,IACpC,CAAC;AAAA,EACH;AAEA,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,eAAe,gBAAgB,MAAM,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAChG;AAEA,eAAsB,OAAO,KAAc;AACzC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,SAAU,QAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE3G,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,UAAU,QAAQ,aAAa;AAC5C,QAAM,UAAU,MAAM,KAAK,mBAAmB,KAAK,KAAK,CAAC,OAAO,GAAG;AAAA,IACjE,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,EACvB,CAAC;AACD,MAAI,CAAC,QAAS,QAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEzF,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,KAAK,IAAI,aAAa,IAAI,IAAI;AACpC,MAAI,CAAC,GAAI,QAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEzF,QAAM,UAAU,UAAU,QAAQ,sBAAsB;AACxD,QAAM,WAAW,MAAM,QAAQ,SAAS,IAAI,EAAE,UAAU,KAAK,SAAS,CAAC;AACvE,MAAI,CAAC,SAAU,QAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE1F,QAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,IAC7D,UAAU,KAAK;AAAA,IACf,gBAAgB,SAAS;AAAA,IACzB,QAAQ,KAAK;AAAA,IACb,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,eAAe,IAAI;AAAA,IACnB,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACD,MAAI,eAAe,CAAC,YAAY,IAAI;AAClC,WAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,EAC3E;AAEA,QAAM,QAAQ,OAAO,IAAI,EAAE,UAAU,KAAK,SAAS,CAAC;AAEpD,MAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,UAAM,iCAAiC,WAAW;AAAA,MAChD,UAAU,KAAK;AAAA,MACf,gBAAgB,SAAS;AAAA,MACzB,QAAQ,KAAK;AAAA,MACb,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,UAAU,YAAY,YAAY;AAAA,IACpC,CAAC;AAAA,EACH;AAEA,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;AACxE,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,UAAU,EAAE,OAAO;AAAA,EACnB,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,UAAU,EAAE,QAAQ,SAAS;AAAA,EAC7B,QAAQ,EAAE,KAAK,CAAC,WAAW,YAAY,UAAU,cAAc,YAAY,CAAC;AAAA,EAC5E,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,EACpC,kBAAkB,EAAE,OAAO,EAAE,SAAS;AAAA,EACtC,kBAAkB,EAAE,OAAO,EAAE,SAAS;AAAA,EACtC,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC5C,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,WAAW,EAAE,OAAO;AAAA,EACpB,WAAW,EAAE,OAAO,EAAE,SAAS;AACjC,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO;AAAA,YACf,IAAI,EAAE,QAAQ,IAAI;AAAA,YAClB,gBAAgB,EAAE,MAAM,mBAAmB;AAAA,YAC3C,QAAQ,EAAE,OAAO;AAAA,cACf,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,cACjC,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,YACrC,CAAC;AAAA,UACH,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,YAAY;AAAA,MAC/D;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ,EAAE,OAAO;AAAA,UACf,UAAU,EAAE,OAAO;AAAA,UACnB,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAAA,UAChC,kBAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,QAC/C,CAAC;AAAA,MACH;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,GAAG,eAAe,oBAAoB,CAAC;AAAA,QAC9E;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,YAAY;AAAA,QACpE,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,YAAY;AAAA,QAC7D,EAAE,QAAQ,KAAK,aAAa,YAAY,QAAQ,YAAY;AAAA,MAC9D;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,MAAM,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;AAAA,MACzF,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,eAAe,QAAQ,YAAY;AAAA,QAC/D,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,YAAY;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,70 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { tryNormalizeHostname } from "@open-mercato/core/modules/customer_accounts/lib/hostname";
5
+ import { secretEqual } from "@open-mercato/core/modules/customer_accounts/lib/secretCompare";
6
+ const ORIGIN_HEADER_NAME = process.env.CUSTOMER_DOMAIN_ORIGIN_HEADER ?? "X-Open-Mercato-Origin";
7
+ const metadata = {
8
+ GET: { requireAuth: false }
9
+ };
10
+ function withOriginHeader(response) {
11
+ response.headers.set(ORIGIN_HEADER_NAME, "1");
12
+ return response;
13
+ }
14
+ function unauthorized(message) {
15
+ return withOriginHeader(NextResponse.json({ ok: false, error: message }, { status: 403 }));
16
+ }
17
+ function notFound() {
18
+ return withOriginHeader(NextResponse.json({ ok: false, error: "Not found" }, { status: 404 }));
19
+ }
20
+ function ok() {
21
+ return withOriginHeader(NextResponse.json({ ok: true }));
22
+ }
23
+ async function GET(req) {
24
+ const expected = process.env.DOMAIN_CHECK_SECRET;
25
+ if (!expected) {
26
+ return withOriginHeader(
27
+ NextResponse.json(
28
+ { ok: false, error: "DOMAIN_CHECK_SECRET is not configured on the server" },
29
+ { status: 503 }
30
+ )
31
+ );
32
+ }
33
+ const supplied = req.headers.get("x-domain-check-secret");
34
+ if (!secretEqual(supplied, expected)) return unauthorized("Forbidden");
35
+ const url = new URL(req.url);
36
+ const rawHost = url.searchParams.get("host") ?? req.headers.get("x-forwarded-host");
37
+ if (!rawHost) return notFound();
38
+ const hostname = tryNormalizeHostname(rawHost);
39
+ if (!hostname) return notFound();
40
+ const container = await createRequestContainer();
41
+ const service = container.resolve("domainMappingService");
42
+ const result = await service.isAllowedForTls(hostname);
43
+ if (!result) return notFound();
44
+ return ok();
45
+ }
46
+ const errorSchema = z.object({ ok: z.literal(false), error: z.string() });
47
+ const openApi = {
48
+ tag: "CustomerAccounts",
49
+ summary: "Traefik domain-check gate",
50
+ methods: {
51
+ GET: {
52
+ summary: "Verify a hostname is allowed for TLS provisioning",
53
+ description: "Called by Traefik before issuing a Let's Encrypt certificate. Requires the X-Domain-Check-Secret header to match DOMAIN_CHECK_SECRET.",
54
+ responses: [
55
+ { status: 200, description: "Hostname allowed", schema: z.object({ ok: z.literal(true) }) }
56
+ ],
57
+ errors: [
58
+ { status: 403, description: "Forbidden", schema: errorSchema },
59
+ { status: 404, description: "Hostname not registered or not yet verified", schema: errorSchema },
60
+ { status: 503, description: "Server misconfiguration", schema: errorSchema }
61
+ ]
62
+ }
63
+ }
64
+ };
65
+ export {
66
+ GET,
67
+ metadata,
68
+ openApi
69
+ };
70
+ //# sourceMappingURL=domain-check.js.map