@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,96 @@
1
+ /**
2
+ * Resolve the tenant context for a customer-portal request.
3
+ *
4
+ * - Platform-domain hosts: require `tenantId` in the request body.
5
+ * - Custom-domain hosts: resolve via `domainMappingService.resolveByHostname()`.
6
+ * If the body supplied a different `tenantId`, fail closed (mismatch).
7
+ *
8
+ * This is the single entry point used by all customer-auth routes (login,
9
+ * signup, magic-link, password-reset) so they all behave consistently when
10
+ * the request arrives on a tenant's branded URL.
11
+ *
12
+ * See `.ai/specs/2026-04-08-portal-custom-domain-routing.md` Phase 1.5.
13
+ */
14
+
15
+ import { tryNormalizeHostname } from '@open-mercato/core/modules/customer_accounts/lib/hostname'
16
+ import { platformDomains } from '@open-mercato/core/modules/customer_accounts/lib/platformDomains'
17
+ import { secretEqual } from '@open-mercato/core/modules/customer_accounts/lib/secretCompare'
18
+ import type { AppContainer } from '@open-mercato/shared/lib/di/container'
19
+ import type { DomainMappingService } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'
20
+
21
+ export class TenantResolutionError extends Error {
22
+ constructor(message: string, public readonly status: number) {
23
+ super(message)
24
+ this.name = 'TenantResolutionError'
25
+ }
26
+ }
27
+
28
+ export type ResolvedTenantContext = {
29
+ source: 'body' | 'host'
30
+ tenantId: string
31
+ organizationId: string | null
32
+ hostname: string | null
33
+ }
34
+
35
+ function readForcedHost(req: Request): string | null {
36
+ // Test-only override. The middleware honors `X-Force-Host` only when
37
+ // `NODE_ENV === 'test'` AND `X-Force-Host-Secret` matches; we mirror the
38
+ // same check here so request-scoped helpers behave the same way under tests.
39
+ if (process.env.NODE_ENV !== 'test') return null
40
+ const expected = process.env.FORCE_HOST_SECRET
41
+ if (!expected) return null
42
+ if (!secretEqual(req.headers.get('x-force-host-secret'), expected)) return null
43
+ const host = req.headers.get('x-force-host')
44
+ return host && host.length > 0 ? host : null
45
+ }
46
+
47
+ export async function resolveTenantContext(
48
+ req: Request,
49
+ bodyTenantId: string | null | undefined,
50
+ options?: { container?: AppContainer },
51
+ ): Promise<ResolvedTenantContext> {
52
+ const rawHost = readForcedHost(req) ?? req.headers.get('host')
53
+ const hostname = rawHost ? tryNormalizeHostname(rawHost) : null
54
+ const isPlatform = hostname ? platformDomains().includes(hostname) : true
55
+
56
+ if (isPlatform) {
57
+ if (!bodyTenantId) {
58
+ throw new TenantResolutionError('tenantId is required for platform-domain logins', 400)
59
+ }
60
+ return { source: 'body', tenantId: bodyTenantId, organizationId: null, hostname }
61
+ }
62
+
63
+ // Custom-domain host
64
+ let service: DomainMappingService | null = null
65
+ try {
66
+ if (options?.container) {
67
+ service = options.container.resolve('domainMappingService') as DomainMappingService
68
+ } else {
69
+ const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')
70
+ const container = await createRequestContainer()
71
+ service = container.resolve('domainMappingService') as DomainMappingService
72
+ }
73
+ } catch {
74
+ throw new TenantResolutionError('Custom domain routing is not available on this deployment', 503)
75
+ }
76
+
77
+ if (!hostname) throw new TenantResolutionError('Unable to determine request hostname', 400)
78
+ const resolved = await service.resolveByHostname(hostname)
79
+ if (!resolved || resolved.status !== 'active') {
80
+ throw new TenantResolutionError('This domain is not configured for any active organization', 404)
81
+ }
82
+
83
+ if (bodyTenantId && bodyTenantId !== resolved.tenantId) {
84
+ throw new TenantResolutionError(
85
+ 'tenantId in request body does not match the resolved custom domain',
86
+ 400,
87
+ )
88
+ }
89
+
90
+ return {
91
+ source: 'host',
92
+ tenantId: resolved.tenantId,
93
+ organizationId: resolved.organizationId,
94
+ hostname,
95
+ }
96
+ }
@@ -0,0 +1,20 @@
1
+ // Constant-time secret comparison for header-based shared-secret authentication.
2
+ // Plain `===`/`!==` on strings short-circuits on the first mismatching byte and
3
+ // leaks the matching prefix length through CPU timing. The custom-domain
4
+ // `domain-check` and `domain-resolve` routes guard cross-tenant data with these
5
+ // header secrets, so the comparison MUST be constant-time.
6
+ //
7
+ // `crypto.timingSafeEqual` requires equal-length buffers (it throws otherwise).
8
+ // We always do a length check first; the resulting length leak is negligible
9
+ // compared to the byte-by-byte leak of plain `!==`.
10
+
11
+ import { timingSafeEqual } from 'node:crypto'
12
+
13
+ export function secretEqual(supplied: string | null | undefined, expected: string): boolean {
14
+ if (typeof supplied !== 'string' || supplied.length === 0) return false
15
+ if (typeof expected !== 'string' || expected.length === 0) return false
16
+ const a = Buffer.from(supplied, 'utf8')
17
+ const b = Buffer.from(expected, 'utf8')
18
+ if (a.length !== b.length) return false
19
+ return timingSafeEqual(a, b)
20
+ }
@@ -1826,6 +1826,347 @@
1826
1826
  }
1827
1827
  },
1828
1828
  "nativeEnums": {}
1829
+ },
1830
+ {
1831
+ "columns": {
1832
+ "id": {
1833
+ "name": "id",
1834
+ "type": "uuid",
1835
+ "unsigned": false,
1836
+ "autoincrement": false,
1837
+ "primary": true,
1838
+ "nullable": false,
1839
+ "unique": false,
1840
+ "length": null,
1841
+ "precision": null,
1842
+ "scale": null,
1843
+ "default": "gen_random_uuid()",
1844
+ "comment": null,
1845
+ "enumItems": [],
1846
+ "mappedType": "uuid"
1847
+ },
1848
+ "hostname": {
1849
+ "name": "hostname",
1850
+ "type": "text",
1851
+ "unsigned": false,
1852
+ "autoincrement": false,
1853
+ "primary": false,
1854
+ "nullable": false,
1855
+ "unique": true,
1856
+ "length": null,
1857
+ "precision": null,
1858
+ "scale": null,
1859
+ "default": null,
1860
+ "comment": null,
1861
+ "enumItems": [],
1862
+ "mappedType": "text"
1863
+ },
1864
+ "tenant_id": {
1865
+ "name": "tenant_id",
1866
+ "type": "uuid",
1867
+ "unsigned": false,
1868
+ "autoincrement": false,
1869
+ "primary": false,
1870
+ "nullable": false,
1871
+ "unique": false,
1872
+ "length": null,
1873
+ "precision": null,
1874
+ "scale": null,
1875
+ "default": null,
1876
+ "comment": null,
1877
+ "enumItems": [],
1878
+ "mappedType": "uuid"
1879
+ },
1880
+ "organization_id": {
1881
+ "name": "organization_id",
1882
+ "type": "uuid",
1883
+ "unsigned": false,
1884
+ "autoincrement": false,
1885
+ "primary": false,
1886
+ "nullable": false,
1887
+ "unique": false,
1888
+ "length": null,
1889
+ "precision": null,
1890
+ "scale": null,
1891
+ "default": null,
1892
+ "comment": null,
1893
+ "enumItems": [],
1894
+ "mappedType": "uuid"
1895
+ },
1896
+ "replaces_domain_id": {
1897
+ "name": "replaces_domain_id",
1898
+ "type": "uuid",
1899
+ "unsigned": false,
1900
+ "autoincrement": false,
1901
+ "primary": false,
1902
+ "nullable": true,
1903
+ "unique": false,
1904
+ "length": null,
1905
+ "precision": null,
1906
+ "scale": null,
1907
+ "default": null,
1908
+ "comment": null,
1909
+ "enumItems": [],
1910
+ "mappedType": "uuid"
1911
+ },
1912
+ "provider": {
1913
+ "name": "provider",
1914
+ "type": "text",
1915
+ "unsigned": false,
1916
+ "autoincrement": false,
1917
+ "primary": false,
1918
+ "nullable": false,
1919
+ "unique": false,
1920
+ "length": null,
1921
+ "precision": null,
1922
+ "scale": null,
1923
+ "default": "'traefik'",
1924
+ "comment": null,
1925
+ "enumItems": [
1926
+ "traefik"
1927
+ ],
1928
+ "mappedType": "enum"
1929
+ },
1930
+ "status": {
1931
+ "name": "status",
1932
+ "type": "text",
1933
+ "unsigned": false,
1934
+ "autoincrement": false,
1935
+ "primary": false,
1936
+ "nullable": false,
1937
+ "unique": false,
1938
+ "length": null,
1939
+ "precision": null,
1940
+ "scale": null,
1941
+ "default": "'pending'",
1942
+ "comment": null,
1943
+ "enumItems": [
1944
+ "pending",
1945
+ "verified",
1946
+ "active",
1947
+ "dns_failed",
1948
+ "tls_failed"
1949
+ ],
1950
+ "mappedType": "enum"
1951
+ },
1952
+ "verified_at": {
1953
+ "name": "verified_at",
1954
+ "type": "timestamptz",
1955
+ "unsigned": false,
1956
+ "autoincrement": false,
1957
+ "primary": false,
1958
+ "nullable": true,
1959
+ "unique": false,
1960
+ "length": 6,
1961
+ "precision": null,
1962
+ "scale": null,
1963
+ "default": null,
1964
+ "comment": null,
1965
+ "enumItems": [],
1966
+ "mappedType": "datetime"
1967
+ },
1968
+ "last_dns_check_at": {
1969
+ "name": "last_dns_check_at",
1970
+ "type": "timestamptz",
1971
+ "unsigned": false,
1972
+ "autoincrement": false,
1973
+ "primary": false,
1974
+ "nullable": true,
1975
+ "unique": false,
1976
+ "length": 6,
1977
+ "precision": null,
1978
+ "scale": null,
1979
+ "default": null,
1980
+ "comment": null,
1981
+ "enumItems": [],
1982
+ "mappedType": "datetime"
1983
+ },
1984
+ "dns_failure_reason": {
1985
+ "name": "dns_failure_reason",
1986
+ "type": "text",
1987
+ "unsigned": false,
1988
+ "autoincrement": false,
1989
+ "primary": false,
1990
+ "nullable": true,
1991
+ "unique": false,
1992
+ "length": 500,
1993
+ "precision": null,
1994
+ "scale": null,
1995
+ "default": null,
1996
+ "comment": null,
1997
+ "enumItems": [],
1998
+ "mappedType": "text"
1999
+ },
2000
+ "tls_failure_reason": {
2001
+ "name": "tls_failure_reason",
2002
+ "type": "text",
2003
+ "unsigned": false,
2004
+ "autoincrement": false,
2005
+ "primary": false,
2006
+ "nullable": true,
2007
+ "unique": false,
2008
+ "length": 500,
2009
+ "precision": null,
2010
+ "scale": null,
2011
+ "default": null,
2012
+ "comment": null,
2013
+ "enumItems": [],
2014
+ "mappedType": "text"
2015
+ },
2016
+ "tls_retry_count": {
2017
+ "name": "tls_retry_count",
2018
+ "type": "int",
2019
+ "unsigned": false,
2020
+ "autoincrement": false,
2021
+ "primary": false,
2022
+ "nullable": false,
2023
+ "unique": false,
2024
+ "length": null,
2025
+ "precision": null,
2026
+ "scale": null,
2027
+ "default": "0",
2028
+ "comment": null,
2029
+ "enumItems": [],
2030
+ "mappedType": "integer"
2031
+ },
2032
+ "created_at": {
2033
+ "name": "created_at",
2034
+ "type": "timestamptz",
2035
+ "unsigned": false,
2036
+ "autoincrement": false,
2037
+ "primary": false,
2038
+ "nullable": false,
2039
+ "unique": false,
2040
+ "length": 6,
2041
+ "precision": null,
2042
+ "scale": null,
2043
+ "default": null,
2044
+ "comment": null,
2045
+ "enumItems": [],
2046
+ "mappedType": "datetime"
2047
+ },
2048
+ "updated_at": {
2049
+ "name": "updated_at",
2050
+ "type": "timestamptz",
2051
+ "unsigned": false,
2052
+ "autoincrement": false,
2053
+ "primary": false,
2054
+ "nullable": true,
2055
+ "unique": false,
2056
+ "length": 6,
2057
+ "precision": null,
2058
+ "scale": null,
2059
+ "default": null,
2060
+ "comment": null,
2061
+ "enumItems": [],
2062
+ "mappedType": "datetime"
2063
+ }
2064
+ },
2065
+ "name": "domain_mappings",
2066
+ "schema": "public",
2067
+ "indexes": [
2068
+ {
2069
+ "columnNames": [
2070
+ "hostname"
2071
+ ],
2072
+ "composite": false,
2073
+ "constraint": true,
2074
+ "keyName": "domain_mappings_hostname_unique",
2075
+ "primary": false,
2076
+ "unique": true
2077
+ },
2078
+ {
2079
+ "columnNames": [
2080
+ "organization_id"
2081
+ ],
2082
+ "composite": false,
2083
+ "constraint": false,
2084
+ "keyName": "domain_mappings_organization_id_idx",
2085
+ "primary": false,
2086
+ "unique": false
2087
+ },
2088
+ {
2089
+ "columnNames": [],
2090
+ "composite": false,
2091
+ "constraint": false,
2092
+ "keyName": "domain_mappings_pending_tls_idx",
2093
+ "primary": false,
2094
+ "unique": false,
2095
+ "expression": "create index \"domain_mappings_pending_tls_idx\" on \"domain_mappings\" (\"status\", \"updated_at\") where \"status\" in ('verified', 'tls_failed')"
2096
+ },
2097
+ {
2098
+ "columnNames": [],
2099
+ "composite": false,
2100
+ "constraint": false,
2101
+ "keyName": "domain_mappings_pending_verification_idx",
2102
+ "primary": false,
2103
+ "unique": false,
2104
+ "expression": "create index \"domain_mappings_pending_verification_idx\" on \"domain_mappings\" (\"status\", \"last_dns_check_at\") where \"status\" in ('pending', 'dns_failed')"
2105
+ },
2106
+ {
2107
+ "columnNames": [
2108
+ "id"
2109
+ ],
2110
+ "composite": false,
2111
+ "constraint": true,
2112
+ "keyName": "domain_mappings_pkey",
2113
+ "primary": true,
2114
+ "unique": true
2115
+ },
2116
+ {
2117
+ "columnNames": [],
2118
+ "composite": false,
2119
+ "constraint": true,
2120
+ "keyName": "domain_mappings_replaces_domain_id_unique",
2121
+ "primary": false,
2122
+ "unique": true,
2123
+ "expression": "create unique index \"domain_mappings_replaces_domain_id_unique\" on \"domain_mappings\" (\"replaces_domain_id\") where \"replaces_domain_id\" is not null"
2124
+ },
2125
+ {
2126
+ "columnNames": [
2127
+ "tenant_id"
2128
+ ],
2129
+ "composite": false,
2130
+ "constraint": false,
2131
+ "keyName": "domain_mappings_tenant_id_idx",
2132
+ "primary": false,
2133
+ "unique": false
2134
+ }
2135
+ ],
2136
+ "checks": [
2137
+ {
2138
+ "name": "domain_mappings_hostname_normalized_chk",
2139
+ "expression": "\"hostname\" = lower(\"hostname\") and \"hostname\" not like '%.'",
2140
+ "definition": "check (\"hostname\" = lower(\"hostname\") and \"hostname\" not like '%.')"
2141
+ },
2142
+ {
2143
+ "name": "domain_mappings_provider_check",
2144
+ "expression": "\"provider\" in ('traefik')",
2145
+ "definition": "check (\"provider\" in ('traefik'))",
2146
+ "columnName": "provider"
2147
+ },
2148
+ {
2149
+ "name": "domain_mappings_status_check",
2150
+ "expression": "\"status\" in ('pending', 'verified', 'active', 'dns_failed', 'tls_failed')",
2151
+ "definition": "check (\"status\" in ('pending', 'verified', 'active', 'dns_failed', 'tls_failed'))",
2152
+ "columnName": "status"
2153
+ }
2154
+ ],
2155
+ "foreignKeys": {
2156
+ "domain_mappings_replaces_domain_id_foreign": {
2157
+ "columnNames": [
2158
+ "replaces_domain_id"
2159
+ ],
2160
+ "constraintName": "domain_mappings_replaces_domain_id_foreign",
2161
+ "localTableName": "public.domain_mappings",
2162
+ "referencedColumnNames": [
2163
+ "id"
2164
+ ],
2165
+ "referencedTableName": "public.domain_mappings",
2166
+ "deleteRule": "set null"
2167
+ }
2168
+ },
2169
+ "nativeEnums": {}
1829
2170
  }
1830
2171
  ],
1831
2172
  "nativeEnums": {}
@@ -0,0 +1,38 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260430120000_customer_accounts extends Migration {
4
+
5
+ override async up(): Promise<void> {
6
+ this.addSql(`create table "domain_mappings" (
7
+ "id" uuid not null default gen_random_uuid(),
8
+ "hostname" text not null,
9
+ "tenant_id" uuid not null,
10
+ "organization_id" uuid not null,
11
+ "replaces_domain_id" uuid null,
12
+ "provider" text not null default 'traefik',
13
+ "status" text not null default 'pending',
14
+ "verified_at" timestamptz null,
15
+ "last_dns_check_at" timestamptz null,
16
+ "dns_failure_reason" varchar(500) null,
17
+ "tls_failure_reason" varchar(500) null,
18
+ "tls_retry_count" int not null default 0,
19
+ "created_at" timestamptz not null default now(),
20
+ "updated_at" timestamptz null,
21
+ constraint "domain_mappings_pkey" primary key ("id"),
22
+ constraint "domain_mappings_replaces_domain_id_foreign" foreign key ("replaces_domain_id") references "domain_mappings" ("id") on delete set null
23
+ );`);
24
+
25
+ this.addSql(`create unique index "domain_mappings_hostname_unique" on "domain_mappings" ("hostname");`);
26
+ this.addSql(`create index "domain_mappings_organization_id_idx" on "domain_mappings" ("organization_id");`);
27
+ this.addSql(`create index "domain_mappings_tenant_id_idx" on "domain_mappings" ("tenant_id");`);
28
+ this.addSql(`create unique index "domain_mappings_replaces_domain_id_unique" on "domain_mappings" ("replaces_domain_id") where "replaces_domain_id" is not null;`);
29
+ this.addSql(`create index "domain_mappings_pending_verification_idx" on "domain_mappings" ("status", "last_dns_check_at") where "status" in ('pending', 'dns_failed');`);
30
+ this.addSql(`create index "domain_mappings_pending_tls_idx" on "domain_mappings" ("status", "updated_at") where "status" in ('verified', 'tls_failed');`);
31
+ this.addSql(`alter table "domain_mappings" add constraint "domain_mappings_hostname_normalized_chk" check ("hostname" = lower("hostname") and "hostname" not like '%.');`);
32
+ }
33
+
34
+ override async down(): Promise<void> {
35
+ this.addSql(`drop table if exists "domain_mappings" cascade;`);
36
+ }
37
+
38
+ }
@@ -41,6 +41,82 @@ export const customerAccountsNotificationTypes: NotificationTypeDefinition[] = [
41
41
  linkHref: '/backend/customer_accounts/users/{sourceEntityId}',
42
42
  expiresAfterHours: 168,
43
43
  },
44
+ {
45
+ type: 'customer_accounts.domain_mapping.verified',
46
+ module: 'customer_accounts',
47
+ titleKey: 'customer_accounts.notifications.domain_mapping.verified.title',
48
+ bodyKey: 'customer_accounts.notifications.domain_mapping.verified.body',
49
+ icon: 'check-circle',
50
+ severity: 'success',
51
+ actions: [
52
+ {
53
+ id: 'view',
54
+ labelKey: 'common.view',
55
+ variant: 'outline',
56
+ href: '/backend/customer_accounts/settings/domain',
57
+ icon: 'external-link',
58
+ },
59
+ ],
60
+ linkHref: '/backend/customer_accounts/settings/domain',
61
+ expiresAfterHours: 168,
62
+ },
63
+ {
64
+ type: 'customer_accounts.domain_mapping.activated',
65
+ module: 'customer_accounts',
66
+ titleKey: 'customer_accounts.notifications.domain_mapping.activated.title',
67
+ bodyKey: 'customer_accounts.notifications.domain_mapping.activated.body',
68
+ icon: 'globe',
69
+ severity: 'success',
70
+ actions: [
71
+ {
72
+ id: 'view',
73
+ labelKey: 'common.view',
74
+ variant: 'outline',
75
+ href: '/backend/customer_accounts/settings/domain',
76
+ icon: 'external-link',
77
+ },
78
+ ],
79
+ linkHref: '/backend/customer_accounts/settings/domain',
80
+ expiresAfterHours: 168,
81
+ },
82
+ {
83
+ type: 'customer_accounts.domain_mapping.dns_failed',
84
+ module: 'customer_accounts',
85
+ titleKey: 'customer_accounts.notifications.domain_mapping.dns_failed.title',
86
+ bodyKey: 'customer_accounts.notifications.domain_mapping.dns_failed.body',
87
+ icon: 'alert-triangle',
88
+ severity: 'warning',
89
+ actions: [
90
+ {
91
+ id: 'view',
92
+ labelKey: 'common.view',
93
+ variant: 'outline',
94
+ href: '/backend/customer_accounts/settings/domain',
95
+ icon: 'external-link',
96
+ },
97
+ ],
98
+ linkHref: '/backend/customer_accounts/settings/domain',
99
+ expiresAfterHours: 168,
100
+ },
101
+ {
102
+ type: 'customer_accounts.domain_mapping.tls_failed',
103
+ module: 'customer_accounts',
104
+ titleKey: 'customer_accounts.notifications.domain_mapping.tls_failed.title',
105
+ bodyKey: 'customer_accounts.notifications.domain_mapping.tls_failed.body',
106
+ icon: 'shield-alert',
107
+ severity: 'warning',
108
+ actions: [
109
+ {
110
+ id: 'view',
111
+ labelKey: 'common.view',
112
+ variant: 'outline',
113
+ href: '/backend/customer_accounts/settings/domain',
114
+ icon: 'external-link',
115
+ },
116
+ ],
117
+ linkHref: '/backend/customer_accounts/settings/domain',
118
+ expiresAfterHours: 168,
119
+ },
44
120
  ]
45
121
 
46
122
  export default customerAccountsNotificationTypes
@@ -39,6 +39,82 @@ export const notificationTypes: NotificationTypeDefinition[] = [
39
39
  linkHref: '/backend/customer_accounts/users/{sourceEntityId}',
40
40
  expiresAfterHours: 168,
41
41
  },
42
+ {
43
+ type: 'customer_accounts.domain_mapping.verified',
44
+ module: 'customer_accounts',
45
+ titleKey: 'customer_accounts.notifications.domain_mapping.verified.title',
46
+ bodyKey: 'customer_accounts.notifications.domain_mapping.verified.body',
47
+ icon: 'check-circle',
48
+ severity: 'success',
49
+ actions: [
50
+ {
51
+ id: 'view',
52
+ labelKey: 'common.view',
53
+ variant: 'outline',
54
+ href: '/backend/customer_accounts/settings/domain',
55
+ icon: 'external-link',
56
+ },
57
+ ],
58
+ linkHref: '/backend/customer_accounts/settings/domain',
59
+ expiresAfterHours: 168,
60
+ },
61
+ {
62
+ type: 'customer_accounts.domain_mapping.activated',
63
+ module: 'customer_accounts',
64
+ titleKey: 'customer_accounts.notifications.domain_mapping.activated.title',
65
+ bodyKey: 'customer_accounts.notifications.domain_mapping.activated.body',
66
+ icon: 'globe',
67
+ severity: 'success',
68
+ actions: [
69
+ {
70
+ id: 'view',
71
+ labelKey: 'common.view',
72
+ variant: 'outline',
73
+ href: '/backend/customer_accounts/settings/domain',
74
+ icon: 'external-link',
75
+ },
76
+ ],
77
+ linkHref: '/backend/customer_accounts/settings/domain',
78
+ expiresAfterHours: 168,
79
+ },
80
+ {
81
+ type: 'customer_accounts.domain_mapping.dns_failed',
82
+ module: 'customer_accounts',
83
+ titleKey: 'customer_accounts.notifications.domain_mapping.dns_failed.title',
84
+ bodyKey: 'customer_accounts.notifications.domain_mapping.dns_failed.body',
85
+ icon: 'alert-triangle',
86
+ severity: 'warning',
87
+ actions: [
88
+ {
89
+ id: 'view',
90
+ labelKey: 'common.view',
91
+ variant: 'outline',
92
+ href: '/backend/customer_accounts/settings/domain',
93
+ icon: 'external-link',
94
+ },
95
+ ],
96
+ linkHref: '/backend/customer_accounts/settings/domain',
97
+ expiresAfterHours: 168,
98
+ },
99
+ {
100
+ type: 'customer_accounts.domain_mapping.tls_failed',
101
+ module: 'customer_accounts',
102
+ titleKey: 'customer_accounts.notifications.domain_mapping.tls_failed.title',
103
+ bodyKey: 'customer_accounts.notifications.domain_mapping.tls_failed.body',
104
+ icon: 'shield-alert',
105
+ severity: 'warning',
106
+ actions: [
107
+ {
108
+ id: 'view',
109
+ labelKey: 'common.view',
110
+ variant: 'outline',
111
+ href: '/backend/customer_accounts/settings/domain',
112
+ icon: 'external-link',
113
+ },
114
+ ],
115
+ linkHref: '/backend/customer_accounts/settings/domain',
116
+ expiresAfterHours: 168,
117
+ },
42
118
  ]
43
119
 
44
120
  export default notificationTypes