@open-mercato/onboarding 0.4.2-canary-c02407ff85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.mjs +62 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/onboarding/acl.js +11 -0
- package/dist/modules/onboarding/acl.js.map +7 -0
- package/dist/modules/onboarding/api/get/onboarding/verify.js +208 -0
- package/dist/modules/onboarding/api/get/onboarding/verify.js.map +7 -0
- package/dist/modules/onboarding/api/post/onboarding.js +193 -0
- package/dist/modules/onboarding/api/post/onboarding.js.map +7 -0
- package/dist/modules/onboarding/data/entities.js +84 -0
- package/dist/modules/onboarding/data/entities.js.map +7 -0
- package/dist/modules/onboarding/data/validators.js +27 -0
- package/dist/modules/onboarding/data/validators.js.map +7 -0
- package/dist/modules/onboarding/emails/AdminNotificationEmail.js +18 -0
- package/dist/modules/onboarding/emails/AdminNotificationEmail.js.map +7 -0
- package/dist/modules/onboarding/emails/VerificationEmail.js +36 -0
- package/dist/modules/onboarding/emails/VerificationEmail.js.map +7 -0
- package/dist/modules/onboarding/frontend/onboarding/page.js +279 -0
- package/dist/modules/onboarding/frontend/onboarding/page.js.map +7 -0
- package/dist/modules/onboarding/index.js +14 -0
- package/dist/modules/onboarding/index.js.map +7 -0
- package/dist/modules/onboarding/lib/service.js +83 -0
- package/dist/modules/onboarding/lib/service.js.map +7 -0
- package/dist/modules/onboarding/migrations/Migration20260112142945.js +12 -0
- package/dist/modules/onboarding/migrations/Migration20260112142945.js.map +7 -0
- package/generated/entities/onboarding_request/index.ts +19 -0
- package/generated/entities.ids.generated.ts +11 -0
- package/generated/entity-fields-registry.ts +11 -0
- package/jest.config.cjs +19 -0
- package/package.json +83 -0
- package/src/index.ts +2 -0
- package/src/modules/onboarding/acl.ts +7 -0
- package/src/modules/onboarding/api/get/onboarding/verify.ts +224 -0
- package/src/modules/onboarding/api/post/onboarding.ts +210 -0
- package/src/modules/onboarding/data/entities.ts +67 -0
- package/src/modules/onboarding/data/validators.ts +27 -0
- package/src/modules/onboarding/emails/AdminNotificationEmail.tsx +32 -0
- package/src/modules/onboarding/emails/VerificationEmail.tsx +54 -0
- package/src/modules/onboarding/frontend/onboarding/page.tsx +305 -0
- package/src/modules/onboarding/i18n/de.json +49 -0
- package/src/modules/onboarding/i18n/en.json +49 -0
- package/src/modules/onboarding/i18n/es.json +49 -0
- package/src/modules/onboarding/i18n/pl.json +49 -0
- package/src/modules/onboarding/index.ts +12 -0
- package/src/modules/onboarding/lib/service.ts +90 -0
- package/src/modules/onboarding/migrations/.snapshot-open-mercato.json +230 -0
- package/src/modules/onboarding/migrations/Migration20260112142945.ts +11 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
package/build.mjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as esbuild from 'esbuild'
|
|
2
|
+
import { glob } from 'glob'
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
|
|
6
|
+
const entryPoints = await glob('src/**/*.{ts,tsx}', {
|
|
7
|
+
ignore: ['**/__tests__/**', '**/*.test.ts', '**/*.test.tsx']
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
// Plugin to add .js extension to relative imports
|
|
11
|
+
const addJsExtension = {
|
|
12
|
+
name: 'add-js-extension',
|
|
13
|
+
setup(build) {
|
|
14
|
+
build.onEnd(async (result) => {
|
|
15
|
+
if (result.errors.length > 0) return
|
|
16
|
+
const outputFiles = await glob('dist/**/*.js')
|
|
17
|
+
for (const file of outputFiles) {
|
|
18
|
+
const fileDir = dirname(file)
|
|
19
|
+
let content = readFileSync(file, 'utf-8')
|
|
20
|
+
// Add .js to relative imports that don't have an extension
|
|
21
|
+
content = content.replace(
|
|
22
|
+
/from\s+["'](\.[^"']+)["']/g,
|
|
23
|
+
(match, path) => {
|
|
24
|
+
if (path.endsWith('.js') || path.endsWith('.json')) return match
|
|
25
|
+
// Check if it's a directory with index.js
|
|
26
|
+
const resolvedPath = join(fileDir, path)
|
|
27
|
+
if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
|
|
28
|
+
return `from "${path}/index.js"`
|
|
29
|
+
}
|
|
30
|
+
return `from "${path}.js"`
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
content = content.replace(
|
|
34
|
+
/import\s*\(\s*["'](\.[^"']+)["']\s*\)/g,
|
|
35
|
+
(match, path) => {
|
|
36
|
+
if (path.endsWith('.js') || path.endsWith('.json')) return match
|
|
37
|
+
// Check if it's a directory with index.js
|
|
38
|
+
const resolvedPath = join(fileDir, path)
|
|
39
|
+
if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
|
|
40
|
+
return `import("${path}/index.js")`
|
|
41
|
+
}
|
|
42
|
+
return `import("${path}.js")`
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
writeFileSync(file, content)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await esbuild.build({
|
|
52
|
+
entryPoints,
|
|
53
|
+
outdir: 'dist',
|
|
54
|
+
format: 'esm',
|
|
55
|
+
platform: 'node',
|
|
56
|
+
target: 'node18',
|
|
57
|
+
sourcemap: true,
|
|
58
|
+
jsx: 'automatic',
|
|
59
|
+
plugins: [addJsExtension],
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
console.log('onboarding built successfully')
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const features = [
|
|
2
|
+
{ id: "onboarding.access", title: "Access onboarding flow", module: "onboarding" },
|
|
3
|
+
{ id: "onboarding.submit", title: "Submit onboarding request", module: "onboarding" },
|
|
4
|
+
{ id: "onboarding.verify", title: "Verify onboarding request", module: "onboarding" }
|
|
5
|
+
];
|
|
6
|
+
var acl_default = features;
|
|
7
|
+
export {
|
|
8
|
+
acl_default as default,
|
|
9
|
+
features
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=acl.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/onboarding/acl.ts"],
|
|
4
|
+
"sourcesContent": ["export const features = [\n { id: 'onboarding.access', title: 'Access onboarding flow', module: 'onboarding' },\n { id: 'onboarding.submit', title: 'Submit onboarding request', module: 'onboarding' },\n { id: 'onboarding.verify', title: 'Verify onboarding request', module: 'onboarding' },\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,qBAAqB,OAAO,0BAA0B,QAAQ,aAAa;AAAA,EACjF,EAAE,IAAI,qBAAqB,OAAO,6BAA6B,QAAQ,aAAa;AAAA,EACpF,EAAE,IAAI,qBAAqB,OAAO,6BAA6B,QAAQ,aAAa;AACtF;AAEA,IAAO,cAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { onboardingVerifySchema } from "@open-mercato/onboarding/modules/onboarding/data/validators";
|
|
5
|
+
import { OnboardingService } from "@open-mercato/onboarding/modules/onboarding/lib/service";
|
|
6
|
+
import { setupInitialTenant } from "@open-mercato/core/modules/auth/lib/setup-app";
|
|
7
|
+
import {
|
|
8
|
+
seedCustomerDictionaries,
|
|
9
|
+
seedCustomerExamples,
|
|
10
|
+
seedCurrencyDictionary
|
|
11
|
+
} from "@open-mercato/core/modules/customers/cli";
|
|
12
|
+
import { seedDashboardDefaultsForTenant } from "@open-mercato/core/modules/dashboards/cli";
|
|
13
|
+
import { signJwt } from "@open-mercato/shared/lib/auth/jwt";
|
|
14
|
+
import { reindexEntity } from "@open-mercato/core/modules/query_index/lib/reindexer";
|
|
15
|
+
import { purgeIndexScope } from "@open-mercato/core/modules/query_index/lib/purge";
|
|
16
|
+
import { refreshCoverageSnapshot } from "@open-mercato/core/modules/query_index/lib/coverage";
|
|
17
|
+
import { flattenSystemEntityIds } from "@open-mercato/shared/lib/entities/system-entities";
|
|
18
|
+
import { getEntityIds } from "@open-mercato/shared/lib/encryption/entityIds";
|
|
19
|
+
const metadata = {
|
|
20
|
+
GET: {
|
|
21
|
+
requireAuth: false
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function redirectWithStatus(baseUrl, status) {
|
|
25
|
+
return NextResponse.redirect(`${baseUrl}/onboarding?status=${encodeURIComponent(status)}`);
|
|
26
|
+
}
|
|
27
|
+
async function GET(req) {
|
|
28
|
+
const url = new URL(req.url);
|
|
29
|
+
const baseUrl = process.env.APP_URL || `${url.protocol}//${url.host}`;
|
|
30
|
+
const token = url.searchParams.get("token") ?? "";
|
|
31
|
+
const parsed = onboardingVerifySchema.safeParse({ token });
|
|
32
|
+
if (!parsed.success) {
|
|
33
|
+
return redirectWithStatus(baseUrl, "invalid");
|
|
34
|
+
}
|
|
35
|
+
const container = await createRequestContainer();
|
|
36
|
+
const em = container.resolve("em");
|
|
37
|
+
const service = new OnboardingService(em);
|
|
38
|
+
const request = await service.findPendingByToken(parsed.data.token);
|
|
39
|
+
if (!request) {
|
|
40
|
+
return redirectWithStatus(baseUrl, "invalid");
|
|
41
|
+
}
|
|
42
|
+
if (!request.passwordHash) {
|
|
43
|
+
console.error("[onboarding.verify] missing password hash for request", request.id);
|
|
44
|
+
return redirectWithStatus(baseUrl, "error");
|
|
45
|
+
}
|
|
46
|
+
let tenantId = null;
|
|
47
|
+
let organizationId = null;
|
|
48
|
+
let userId = null;
|
|
49
|
+
try {
|
|
50
|
+
const setupResult = await setupInitialTenant(em, {
|
|
51
|
+
orgName: request.organizationName,
|
|
52
|
+
includeDerivedUsers: false,
|
|
53
|
+
failIfUserExists: true,
|
|
54
|
+
primaryUserRoles: ["admin"],
|
|
55
|
+
includeSuperadminRole: false,
|
|
56
|
+
primaryUser: {
|
|
57
|
+
email: request.email,
|
|
58
|
+
firstName: request.firstName,
|
|
59
|
+
lastName: request.lastName,
|
|
60
|
+
displayName: `${request.firstName} ${request.lastName}`.trim(),
|
|
61
|
+
hashedPassword: request.passwordHash,
|
|
62
|
+
confirm: true
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
tenantId = String(setupResult.tenantId);
|
|
66
|
+
organizationId = String(setupResult.organizationId);
|
|
67
|
+
const mainUserSnapshot = setupResult.users.find((entry) => entry.user.email === request.email);
|
|
68
|
+
if (!mainUserSnapshot) throw new Error("USER_NOT_CREATED");
|
|
69
|
+
const user = mainUserSnapshot.user;
|
|
70
|
+
const resolvedUserId = String(user.id);
|
|
71
|
+
userId = resolvedUserId;
|
|
72
|
+
await seedCustomerDictionaries(em, { tenantId, organizationId });
|
|
73
|
+
await seedCurrencyDictionary(em, { tenantId, organizationId });
|
|
74
|
+
await seedCustomerExamples(em, container, { tenantId, organizationId });
|
|
75
|
+
await seedDashboardDefaultsForTenant(em, { tenantId, organizationId, logger: () => {
|
|
76
|
+
} });
|
|
77
|
+
if (tenantId) {
|
|
78
|
+
let vectorService = null;
|
|
79
|
+
try {
|
|
80
|
+
vectorService = container.resolve("vectorIndexService");
|
|
81
|
+
} catch {
|
|
82
|
+
vectorService = null;
|
|
83
|
+
}
|
|
84
|
+
const coverageRefreshKeys = /* @__PURE__ */ new Set();
|
|
85
|
+
try {
|
|
86
|
+
const allEntities = getEntityIds();
|
|
87
|
+
const entityIds = flattenSystemEntityIds(allEntities);
|
|
88
|
+
for (const entityType of entityIds) {
|
|
89
|
+
try {
|
|
90
|
+
await purgeIndexScope(em, { entityType, tenantId });
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("[onboarding.verify] failed to purge query index scope", { entityType, tenantId, error });
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
await reindexEntity(em, {
|
|
96
|
+
entityType,
|
|
97
|
+
tenantId,
|
|
98
|
+
force: true,
|
|
99
|
+
emitVectorizeEvents: false,
|
|
100
|
+
vectorService: null
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("[onboarding.verify] failed to reindex entity", { entityType, tenantId, error });
|
|
104
|
+
}
|
|
105
|
+
coverageRefreshKeys.add(`${entityType}|${tenantId}|__null__`);
|
|
106
|
+
if (organizationId) coverageRefreshKeys.add(`${entityType}|${tenantId}|${organizationId}`);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("[onboarding.verify] failed to rebuild query indexes", { tenantId, error });
|
|
110
|
+
}
|
|
111
|
+
if (vectorService) {
|
|
112
|
+
try {
|
|
113
|
+
await vectorService.reindexAll({ tenantId, organizationId, purgeFirst: true });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error("[onboarding.verify] failed to rebuild vector indexes", { tenantId, organizationId, error });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (coverageRefreshKeys.size) {
|
|
119
|
+
for (const entry of coverageRefreshKeys) {
|
|
120
|
+
const [entityType, tenantKey, orgKey] = entry.split("|");
|
|
121
|
+
const orgScope = orgKey === "__null__" ? null : orgKey;
|
|
122
|
+
try {
|
|
123
|
+
await refreshCoverageSnapshot(
|
|
124
|
+
em,
|
|
125
|
+
{
|
|
126
|
+
entityType,
|
|
127
|
+
tenantId: tenantKey,
|
|
128
|
+
organizationId: orgScope,
|
|
129
|
+
withDeleted: false
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("[onboarding.verify] failed to refresh coverage snapshot", {
|
|
134
|
+
entityType,
|
|
135
|
+
tenantId: tenantKey,
|
|
136
|
+
organizationId: orgScope,
|
|
137
|
+
error
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const authService = container.resolve("authService");
|
|
144
|
+
await authService.updateLastLoginAt(user);
|
|
145
|
+
const roles = await authService.getUserRoles(user, tenantId);
|
|
146
|
+
const jwt = signJwt({
|
|
147
|
+
sub: String(user.id),
|
|
148
|
+
tenantId,
|
|
149
|
+
orgId: organizationId,
|
|
150
|
+
email: user.email,
|
|
151
|
+
roles
|
|
152
|
+
});
|
|
153
|
+
const response = NextResponse.redirect(`${baseUrl}/backend`);
|
|
154
|
+
response.cookies.set("auth_token", jwt, {
|
|
155
|
+
httpOnly: true,
|
|
156
|
+
sameSite: "lax",
|
|
157
|
+
secure: process.env.NODE_ENV === "production",
|
|
158
|
+
path: "/",
|
|
159
|
+
maxAge: 60 * 60 * 8
|
|
160
|
+
});
|
|
161
|
+
const rememberDays = Number(process.env.REMEMBER_ME_DAYS || "30");
|
|
162
|
+
const expiresAt = new Date(Date.now() + rememberDays * 24 * 60 * 60 * 1e3);
|
|
163
|
+
const session = await authService.createSession(user, expiresAt);
|
|
164
|
+
response.cookies.set("session_token", session.token, {
|
|
165
|
+
httpOnly: true,
|
|
166
|
+
sameSite: "lax",
|
|
167
|
+
secure: process.env.NODE_ENV === "production",
|
|
168
|
+
path: "/",
|
|
169
|
+
expires: expiresAt
|
|
170
|
+
});
|
|
171
|
+
await service.markCompleted(request, { tenantId, organizationId, userId: resolvedUserId });
|
|
172
|
+
return response;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error instanceof Error && error.message === "USER_EXISTS") {
|
|
175
|
+
return redirectWithStatus(baseUrl, "already_exists");
|
|
176
|
+
}
|
|
177
|
+
console.error("[onboarding.verify] failed", error);
|
|
178
|
+
return redirectWithStatus(baseUrl, "error");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
var verify_default = GET;
|
|
182
|
+
const onboardingTag = "Onboarding";
|
|
183
|
+
const onboardingVerifyQuerySchema = z.object({
|
|
184
|
+
token: onboardingVerifySchema.shape.token
|
|
185
|
+
});
|
|
186
|
+
const onboardingVerifyDoc = {
|
|
187
|
+
summary: "Verify onboarding token",
|
|
188
|
+
description: "Validates the onboarding token, provisions the tenant, seeds demo data, and redirects the user to the dashboard.",
|
|
189
|
+
tags: [onboardingTag],
|
|
190
|
+
query: onboardingVerifyQuerySchema,
|
|
191
|
+
responses: [
|
|
192
|
+
{ status: 302, description: "Redirect to onboarding UI or dashboard" }
|
|
193
|
+
]
|
|
194
|
+
};
|
|
195
|
+
const openApi = {
|
|
196
|
+
tag: onboardingTag,
|
|
197
|
+
summary: "Onboarding verification redirect",
|
|
198
|
+
methods: {
|
|
199
|
+
GET: onboardingVerifyDoc
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
export {
|
|
203
|
+
GET,
|
|
204
|
+
verify_default as default,
|
|
205
|
+
metadata,
|
|
206
|
+
openApi
|
|
207
|
+
};
|
|
208
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/onboarding/api/get/onboarding/verify.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { onboardingVerifySchema } from '@open-mercato/onboarding/modules/onboarding/data/validators'\nimport { OnboardingService } from '@open-mercato/onboarding/modules/onboarding/lib/service'\nimport { setupInitialTenant } from '@open-mercato/core/modules/auth/lib/setup-app'\nimport {\n seedCustomerDictionaries,\n seedCustomerExamples,\n seedCurrencyDictionary,\n} from '@open-mercato/core/modules/customers/cli'\nimport { seedDashboardDefaultsForTenant } from '@open-mercato/core/modules/dashboards/cli'\nimport { AuthService } from '@open-mercato/core/modules/auth/services/authService'\nimport { signJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport { reindexEntity } from '@open-mercato/core/modules/query_index/lib/reindexer'\nimport { purgeIndexScope } from '@open-mercato/core/modules/query_index/lib/purge'\nimport { refreshCoverageSnapshot } from '@open-mercato/core/modules/query_index/lib/coverage'\nimport { flattenSystemEntityIds } from '@open-mercato/shared/lib/entities/system-entities'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n GET: {\n requireAuth: false,\n },\n}\n\nfunction redirectWithStatus(baseUrl: string, status: string) {\n return NextResponse.redirect(`${baseUrl}/onboarding?status=${encodeURIComponent(status)}`)\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const baseUrl = process.env.APP_URL || `${url.protocol}//${url.host}`\n const token = url.searchParams.get('token') ?? ''\n const parsed = onboardingVerifySchema.safeParse({ token })\n if (!parsed.success) {\n return redirectWithStatus(baseUrl, 'invalid')\n }\n\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n const service = new OnboardingService(em)\n const request = await service.findPendingByToken(parsed.data.token)\n if (!request) {\n return redirectWithStatus(baseUrl, 'invalid')\n }\n if (!request.passwordHash) {\n console.error('[onboarding.verify] missing password hash for request', request.id)\n return redirectWithStatus(baseUrl, 'error')\n }\n\n let tenantId: string | null = null\n let organizationId: string | null = null\n let userId: string | null = null\n\n try {\n const setupResult = await setupInitialTenant(em, {\n orgName: request.organizationName,\n includeDerivedUsers: false,\n failIfUserExists: true,\n primaryUserRoles: ['admin'],\n includeSuperadminRole: false,\n primaryUser: {\n email: request.email,\n firstName: request.firstName,\n lastName: request.lastName,\n displayName: `${request.firstName} ${request.lastName}`.trim(),\n hashedPassword: request.passwordHash,\n confirm: true,\n },\n })\n\n tenantId = String(setupResult.tenantId)\n organizationId = String(setupResult.organizationId)\n\n const mainUserSnapshot = setupResult.users.find((entry) => entry.user.email === request.email)\n if (!mainUserSnapshot) throw new Error('USER_NOT_CREATED')\n const user = mainUserSnapshot.user\n const resolvedUserId = String(user.id)\n userId = resolvedUserId\n\n await seedCustomerDictionaries(em, { tenantId, organizationId })\n await seedCurrencyDictionary(em, { tenantId, organizationId })\n await seedCustomerExamples(em, container, { tenantId, organizationId })\n await seedDashboardDefaultsForTenant(em, { tenantId, organizationId, logger: () => {} })\n\n if (tenantId) {\n let vectorService: VectorIndexService | null = null\n try {\n vectorService = container.resolve<VectorIndexService>('vectorIndexService')\n } catch {\n vectorService = null\n }\n const coverageRefreshKeys = new Set<string>()\n try {\n const allEntities = getEntityIds()\n const entityIds = flattenSystemEntityIds(allEntities)\n for (const entityType of entityIds) {\n try {\n await purgeIndexScope(em, { entityType, tenantId })\n } catch (error) {\n console.error('[onboarding.verify] failed to purge query index scope', { entityType, tenantId, error })\n }\n try {\n await reindexEntity(em, {\n entityType,\n tenantId,\n force: true,\n emitVectorizeEvents: false,\n vectorService: null,\n })\n } catch (error) {\n console.error('[onboarding.verify] failed to reindex entity', { entityType, tenantId, error })\n }\n coverageRefreshKeys.add(`${entityType}|${tenantId}|__null__`)\n if (organizationId) coverageRefreshKeys.add(`${entityType}|${tenantId}|${organizationId}`)\n }\n } catch (error) {\n console.error('[onboarding.verify] failed to rebuild query indexes', { tenantId, error })\n }\n\n if (vectorService) {\n try {\n await vectorService.reindexAll({ tenantId, organizationId, purgeFirst: true })\n } catch (error) {\n console.error('[onboarding.verify] failed to rebuild vector indexes', { tenantId, organizationId, error })\n }\n }\n\n if (coverageRefreshKeys.size) {\n for (const entry of coverageRefreshKeys) {\n const [entityType, tenantKey, orgKey] = entry.split('|')\n const orgScope = orgKey === '__null__' ? null : orgKey\n try {\n await refreshCoverageSnapshot(\n em,\n {\n entityType,\n tenantId: tenantKey,\n organizationId: orgScope,\n withDeleted: false,\n },\n )\n } catch (error) {\n console.error('[onboarding.verify] failed to refresh coverage snapshot', {\n entityType,\n tenantId: tenantKey,\n organizationId: orgScope,\n error,\n })\n }\n }\n }\n }\n\n const authService = (container.resolve('authService') as AuthService)\n await authService.updateLastLoginAt(user)\n const roles = await authService.getUserRoles(user, tenantId)\n const jwt = signJwt({\n sub: String(user.id),\n tenantId,\n orgId: organizationId,\n email: user.email,\n roles,\n })\n const response = NextResponse.redirect(`${baseUrl}/backend`)\n response.cookies.set('auth_token', jwt, {\n httpOnly: true,\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n maxAge: 60 * 60 * 8,\n })\n\n const rememberDays = Number(process.env.REMEMBER_ME_DAYS || '30')\n const expiresAt = new Date(Date.now() + rememberDays * 24 * 60 * 60 * 1000)\n const session = await authService.createSession(user, expiresAt)\n response.cookies.set('session_token', session.token, {\n httpOnly: true,\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n expires: expiresAt,\n })\n\n await service.markCompleted(request, { tenantId, organizationId, userId: resolvedUserId })\n return response\n } catch (error) {\n if (error instanceof Error && error.message === 'USER_EXISTS') {\n return redirectWithStatus(baseUrl, 'already_exists')\n }\n console.error('[onboarding.verify] failed', error)\n return redirectWithStatus(baseUrl, 'error')\n }\n}\n\nexport default GET\n\nconst onboardingTag = 'Onboarding'\n\nconst onboardingVerifyQuerySchema = z.object({\n token: onboardingVerifySchema.shape.token,\n})\n\nconst onboardingVerifyDoc: OpenApiMethodDoc = {\n summary: 'Verify onboarding token',\n description: 'Validates the onboarding token, provisions the tenant, seeds demo data, and redirects the user to the dashboard.',\n tags: [onboardingTag],\n query: onboardingVerifyQuerySchema,\n responses: [\n { status: 302, description: 'Redirect to onboarding UI or dashboard' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: onboardingTag,\n summary: 'Onboarding verification redirect',\n methods: {\n GET: onboardingVerifyDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { loadDictionary } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { defaultLocale, locales } from "@open-mercato/shared/lib/i18n/config";
|
|
6
|
+
import { createFallbackTranslator } from "@open-mercato/shared/lib/i18n/translate";
|
|
7
|
+
import { sendEmail } from "@open-mercato/shared/lib/email/send";
|
|
8
|
+
import { onboardingStartSchema } from "@open-mercato/onboarding/modules/onboarding/data/validators";
|
|
9
|
+
import { OnboardingService } from "@open-mercato/onboarding/modules/onboarding/lib/service";
|
|
10
|
+
import VerificationEmail from "@open-mercato/onboarding/modules/onboarding/emails/VerificationEmail";
|
|
11
|
+
import AdminNotificationEmail from "@open-mercato/onboarding/modules/onboarding/emails/AdminNotificationEmail";
|
|
12
|
+
import { User } from "@open-mercato/core/modules/auth/data/entities";
|
|
13
|
+
const metadata = {
|
|
14
|
+
POST: {
|
|
15
|
+
requireAuth: false
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
async function POST(req) {
|
|
19
|
+
if (process.env.SELF_SERVICE_ONBOARDING_ENABLED !== "true") {
|
|
20
|
+
return NextResponse.json({ ok: false, error: "Self-service onboarding is disabled." }, { status: 404 });
|
|
21
|
+
}
|
|
22
|
+
let payload;
|
|
23
|
+
try {
|
|
24
|
+
payload = await req.json();
|
|
25
|
+
} catch {
|
|
26
|
+
return NextResponse.json({ ok: false, error: "Invalid payload" }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
const rawLocale = payload && typeof payload === "object" && "locale" in payload && typeof payload.locale === "string" ? payload.locale : null;
|
|
29
|
+
const locale = rawLocale && locales.includes(rawLocale) ? rawLocale : defaultLocale;
|
|
30
|
+
const dict = await loadDictionary(locale);
|
|
31
|
+
const translate = createFallbackTranslator(dict);
|
|
32
|
+
const parsed = onboardingStartSchema.safeParse(payload);
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
const fieldErrors = {};
|
|
35
|
+
for (const issue of parsed.error.issues) {
|
|
36
|
+
const path = issue.path[0];
|
|
37
|
+
if (!path) continue;
|
|
38
|
+
switch (path) {
|
|
39
|
+
case "email":
|
|
40
|
+
fieldErrors.email = translate("onboarding.errors.emailInvalid", "Enter a valid work email.");
|
|
41
|
+
break;
|
|
42
|
+
case "firstName":
|
|
43
|
+
fieldErrors.firstName = translate("onboarding.errors.firstNameRequired", "First name is required.");
|
|
44
|
+
break;
|
|
45
|
+
case "lastName":
|
|
46
|
+
fieldErrors.lastName = translate("onboarding.errors.lastNameRequired", "Last name is required.");
|
|
47
|
+
break;
|
|
48
|
+
case "organizationName":
|
|
49
|
+
fieldErrors.organizationName = translate("onboarding.errors.organizationNameRequired", "Organization name is required.");
|
|
50
|
+
break;
|
|
51
|
+
case "password":
|
|
52
|
+
fieldErrors.password = translate("onboarding.errors.passwordRequired", "Password must be at least 6 characters.");
|
|
53
|
+
break;
|
|
54
|
+
case "confirmPassword":
|
|
55
|
+
fieldErrors.confirmPassword = translate("onboarding.errors.passwordMismatch", "Passwords must match.");
|
|
56
|
+
break;
|
|
57
|
+
case "termsAccepted":
|
|
58
|
+
fieldErrors.termsAccepted = translate("onboarding.form.termsRequired", "Please accept the terms to continue.");
|
|
59
|
+
break;
|
|
60
|
+
default:
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return NextResponse.json({
|
|
65
|
+
ok: false,
|
|
66
|
+
error: translate("onboarding.form.genericError", "Please check the form and try again."),
|
|
67
|
+
fieldErrors
|
|
68
|
+
}, { status: 400 });
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const container = await createRequestContainer();
|
|
72
|
+
const em = container.resolve("em");
|
|
73
|
+
const existingUser = await em.findOne(User, { email: parsed.data.email });
|
|
74
|
+
if (existingUser) {
|
|
75
|
+
const message = translate("onboarding.errors.emailExists", "We already have an account with this email. Try signing in or resetting your password.");
|
|
76
|
+
return NextResponse.json({
|
|
77
|
+
ok: false,
|
|
78
|
+
error: message,
|
|
79
|
+
fieldErrors: { email: message }
|
|
80
|
+
}, { status: 409 });
|
|
81
|
+
}
|
|
82
|
+
const service = new OnboardingService(em);
|
|
83
|
+
let request, token;
|
|
84
|
+
try {
|
|
85
|
+
const result = await service.createOrUpdateRequest(parsed.data);
|
|
86
|
+
request = result.request;
|
|
87
|
+
token = result.token;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err instanceof Error && err.message.startsWith("PENDING_REQUEST:")) {
|
|
90
|
+
const minutes = Number(err.message.split(":")[1] || "10");
|
|
91
|
+
const message = translate("onboarding.errors.pendingRequest", "We already have a pending verification. Please try again in about {minutes} minutes or contact the administrator.", { minutes });
|
|
92
|
+
return NextResponse.json({
|
|
93
|
+
ok: false,
|
|
94
|
+
error: message,
|
|
95
|
+
fieldErrors: { email: message }
|
|
96
|
+
}, { status: 409 });
|
|
97
|
+
}
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
const url = new URL(req.url);
|
|
101
|
+
const baseUrl = process.env.APP_URL || `${url.protocol}//${url.host}`;
|
|
102
|
+
const verifyUrl = `${baseUrl}/api/onboarding/onboarding/verify?token=${token}`;
|
|
103
|
+
const firstName = request.firstName || parsed.data.firstName;
|
|
104
|
+
const subject = translate("onboarding.email.subject", "Confirm your email to finish onboarding");
|
|
105
|
+
const emailCopy = {
|
|
106
|
+
preview: translate("onboarding.email.preview", "Confirm your email to activate your Open Mercato workspace"),
|
|
107
|
+
heading: translate("onboarding.email.heading", "Welcome to Open Mercato"),
|
|
108
|
+
greeting: translate("onboarding.email.greeting", "Hi {firstName},", { firstName }),
|
|
109
|
+
body: translate(
|
|
110
|
+
"onboarding.email.body",
|
|
111
|
+
"We just need to confirm your email address to finish setting up the organization {organizationName}.",
|
|
112
|
+
{ organizationName: request.organizationName }
|
|
113
|
+
),
|
|
114
|
+
cta: translate("onboarding.email.cta", "Confirm email & activate workspace"),
|
|
115
|
+
expiry: translate(
|
|
116
|
+
"onboarding.email.expiry",
|
|
117
|
+
"The link will expire in 24 hours. If you didn't request this, you can safely ignore this message."
|
|
118
|
+
),
|
|
119
|
+
footer: translate("onboarding.email.footer", "Open Mercato \xB7 Tenant onboarding service")
|
|
120
|
+
};
|
|
121
|
+
const emailReact = VerificationEmail({ verifyUrl, copy: emailCopy });
|
|
122
|
+
await sendEmail({ to: request.email, subject, react: emailReact });
|
|
123
|
+
const adminEmail = process.env.ADMIN_EMAIL || "piotr@catchthetornado.com";
|
|
124
|
+
const adminSubject = translate("onboarding.email.adminSubject", "New self-service onboarding request");
|
|
125
|
+
const adminCopy = {
|
|
126
|
+
preview: translate("onboarding.email.adminPreview", "New onboarding request submitted"),
|
|
127
|
+
heading: translate("onboarding.email.adminHeading", "New onboarding request"),
|
|
128
|
+
body: translate("onboarding.email.adminBody", "{firstName} {lastName} ({email}) submitted an onboarding request for {organizationName}.", {
|
|
129
|
+
firstName: request.firstName,
|
|
130
|
+
lastName: request.lastName,
|
|
131
|
+
email: request.email,
|
|
132
|
+
organizationName: request.organizationName
|
|
133
|
+
}),
|
|
134
|
+
footer: translate("onboarding.email.adminFooter", "You can review the tenant after verification is complete.")
|
|
135
|
+
};
|
|
136
|
+
await sendEmail({
|
|
137
|
+
to: adminEmail,
|
|
138
|
+
subject: adminSubject,
|
|
139
|
+
react: AdminNotificationEmail({ copy: adminCopy })
|
|
140
|
+
});
|
|
141
|
+
return NextResponse.json({ ok: true, email: request.email });
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error("[onboarding.start] failed", error);
|
|
144
|
+
return NextResponse.json({
|
|
145
|
+
ok: false,
|
|
146
|
+
error: translate("onboarding.form.genericError", "Something went wrong. Please try again later.")
|
|
147
|
+
}, { status: 500 });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
var onboarding_default = POST;
|
|
151
|
+
const onboardingTag = "Onboarding";
|
|
152
|
+
const onboardingSuccessSchema = z.object({
|
|
153
|
+
ok: z.literal(true),
|
|
154
|
+
email: z.string().email()
|
|
155
|
+
});
|
|
156
|
+
const onboardingErrorSchema = z.object({
|
|
157
|
+
ok: z.literal(false),
|
|
158
|
+
error: z.string(),
|
|
159
|
+
fieldErrors: z.record(z.string(), z.string()).optional()
|
|
160
|
+
});
|
|
161
|
+
const onboardingPostDoc = {
|
|
162
|
+
summary: "Submit onboarding request",
|
|
163
|
+
description: "Accepts a self-service onboarding form submission and triggers email verification.",
|
|
164
|
+
tags: [onboardingTag],
|
|
165
|
+
requestBody: {
|
|
166
|
+
contentType: "application/json",
|
|
167
|
+
schema: onboardingStartSchema,
|
|
168
|
+
description: "Onboarding form payload with contact and organization information."
|
|
169
|
+
},
|
|
170
|
+
responses: [
|
|
171
|
+
{ status: 200, description: "Onboarding request accepted.", schema: onboardingSuccessSchema }
|
|
172
|
+
],
|
|
173
|
+
errors: [
|
|
174
|
+
{ status: 400, description: "Validation failed", schema: onboardingErrorSchema },
|
|
175
|
+
{ status: 404, description: "Self-service onboarding disabled", schema: onboardingErrorSchema },
|
|
176
|
+
{ status: 409, description: "Existing account or pending request", schema: onboardingErrorSchema },
|
|
177
|
+
{ status: 500, description: "Unexpected server error", schema: onboardingErrorSchema }
|
|
178
|
+
]
|
|
179
|
+
};
|
|
180
|
+
const openApi = {
|
|
181
|
+
tag: onboardingTag,
|
|
182
|
+
summary: "Self-service onboarding submission",
|
|
183
|
+
methods: {
|
|
184
|
+
POST: onboardingPostDoc
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
export {
|
|
188
|
+
POST,
|
|
189
|
+
onboarding_default as default,
|
|
190
|
+
metadata,
|
|
191
|
+
openApi
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=onboarding.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/onboarding/api/post/onboarding.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { loadDictionary } from '@open-mercato/shared/lib/i18n/server'\nimport { defaultLocale, locales, type Locale } from '@open-mercato/shared/lib/i18n/config'\nimport { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport { onboardingStartSchema } from '@open-mercato/onboarding/modules/onboarding/data/validators'\nimport { OnboardingService } from '@open-mercato/onboarding/modules/onboarding/lib/service'\nimport VerificationEmail from '@open-mercato/onboarding/modules/onboarding/emails/VerificationEmail'\nimport AdminNotificationEmail from '@open-mercato/onboarding/modules/onboarding/emails/AdminNotificationEmail'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n POST: {\n requireAuth: false,\n },\n}\n\nexport async function POST(req: Request) {\n if (process.env.SELF_SERVICE_ONBOARDING_ENABLED !== 'true') {\n return NextResponse.json({ ok: false, error: 'Self-service onboarding is disabled.' }, { status: 404 })\n }\n let payload: unknown\n try {\n payload = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid payload' }, { status: 400 })\n }\n\n const rawLocale =\n payload && typeof payload === 'object' && 'locale' in payload && typeof (payload as any).locale === 'string'\n ? (payload as any).locale as string\n : null\n const locale: Locale = rawLocale && locales.includes(rawLocale as Locale)\n ? (rawLocale as Locale)\n : defaultLocale\n const dict = await loadDictionary(locale)\n const translate = createFallbackTranslator(dict)\n\n const parsed = onboardingStartSchema.safeParse(payload)\n if (!parsed.success) {\n const fieldErrors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const path = issue.path[0]\n if (!path) continue\n switch (path) {\n case 'email':\n fieldErrors.email = translate('onboarding.errors.emailInvalid', 'Enter a valid work email.')\n break\n case 'firstName':\n fieldErrors.firstName = translate('onboarding.errors.firstNameRequired', 'First name is required.')\n break\n case 'lastName':\n fieldErrors.lastName = translate('onboarding.errors.lastNameRequired', 'Last name is required.')\n break\n case 'organizationName':\n fieldErrors.organizationName = translate('onboarding.errors.organizationNameRequired', 'Organization name is required.')\n break\n case 'password':\n fieldErrors.password = translate('onboarding.errors.passwordRequired', 'Password must be at least 6 characters.')\n break\n case 'confirmPassword':\n fieldErrors.confirmPassword = translate('onboarding.errors.passwordMismatch', 'Passwords must match.')\n break\n case 'termsAccepted':\n fieldErrors.termsAccepted = translate('onboarding.form.termsRequired', 'Please accept the terms to continue.')\n break\n default:\n break\n }\n }\n return NextResponse.json({\n ok: false,\n error: translate('onboarding.form.genericError', 'Please check the form and try again.'),\n fieldErrors,\n }, { status: 400 })\n }\n\n try {\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n\n const existingUser = await em.findOne(User, { email: parsed.data.email })\n if (existingUser) {\n const message = translate('onboarding.errors.emailExists', 'We already have an account with this email. Try signing in or resetting your password.')\n return NextResponse.json({\n ok: false,\n error: message,\n fieldErrors: { email: message },\n }, { status: 409 })\n }\n\n const service = new OnboardingService(em)\n let request, token\n try {\n const result = await service.createOrUpdateRequest(parsed.data)\n request = result.request\n token = result.token\n } catch (err) {\n if (err instanceof Error && err.message.startsWith('PENDING_REQUEST:')) {\n const minutes = Number(err.message.split(':')[1] || '10')\n const message = translate('onboarding.errors.pendingRequest', 'We already have a pending verification. Please try again in about {minutes} minutes or contact the administrator.', { minutes })\n return NextResponse.json({\n ok: false,\n error: message,\n fieldErrors: { email: message },\n }, { status: 409 })\n }\n throw err\n }\n\n const url = new URL(req.url)\n const baseUrl = process.env.APP_URL || `${url.protocol}//${url.host}`\n const verifyUrl = `${baseUrl}/api/onboarding/onboarding/verify?token=${token}`\n\n const firstName = request.firstName || parsed.data.firstName\n const subject = translate('onboarding.email.subject', 'Confirm your email to finish onboarding')\n const emailCopy = {\n preview: translate('onboarding.email.preview', 'Confirm your email to activate your Open Mercato workspace'),\n heading: translate('onboarding.email.heading', 'Welcome to Open Mercato'),\n greeting: translate('onboarding.email.greeting', 'Hi {firstName},', { firstName }),\n body: translate(\n 'onboarding.email.body',\n 'We just need to confirm your email address to finish setting up the organization {organizationName}.',\n { organizationName: request.organizationName },\n ),\n cta: translate('onboarding.email.cta', 'Confirm email & activate workspace'),\n expiry: translate(\n 'onboarding.email.expiry',\n \"The link will expire in 24 hours. If you didn't request this, you can safely ignore this message.\",\n ),\n footer: translate('onboarding.email.footer', 'Open Mercato \u00B7 Tenant onboarding service'),\n }\n const emailReact = VerificationEmail({ verifyUrl, copy: emailCopy })\n await sendEmail({ to: request.email, subject, react: emailReact })\n\n const adminEmail = process.env.ADMIN_EMAIL || 'piotr@catchthetornado.com'\n const adminSubject = translate('onboarding.email.adminSubject', 'New self-service onboarding request')\n const adminCopy = {\n preview: translate('onboarding.email.adminPreview', 'New onboarding request submitted'),\n heading: translate('onboarding.email.adminHeading', 'New onboarding request'),\n body: translate('onboarding.email.adminBody', '{firstName} {lastName} ({email}) submitted an onboarding request for {organizationName}.', {\n firstName: request.firstName,\n lastName: request.lastName,\n email: request.email,\n organizationName: request.organizationName,\n }),\n footer: translate('onboarding.email.adminFooter', 'You can review the tenant after verification is complete.'),\n }\n await sendEmail({\n to: adminEmail,\n subject: adminSubject,\n react: AdminNotificationEmail({ copy: adminCopy }),\n })\n\n return NextResponse.json({ ok: true, email: request.email })\n } catch (error) {\n console.error('[onboarding.start] failed', error)\n return NextResponse.json({\n ok: false,\n error: translate('onboarding.form.genericError', 'Something went wrong. Please try again later.'),\n }, { status: 500 })\n }\n}\n\nexport default POST\n\nconst onboardingTag = 'Onboarding'\n\nconst onboardingSuccessSchema = z.object({\n ok: z.literal(true),\n email: z.string().email(),\n})\n\nconst onboardingErrorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n fieldErrors: z.record(z.string(), z.string()).optional(),\n})\n\nconst onboardingPostDoc: OpenApiMethodDoc = {\n summary: 'Submit onboarding request',\n description: 'Accepts a self-service onboarding form submission and triggers email verification.',\n tags: [onboardingTag],\n requestBody: {\n contentType: 'application/json',\n schema: onboardingStartSchema,\n description: 'Onboarding form payload with contact and organization information.',\n },\n responses: [\n { status: 200, description: 'Onboarding request accepted.', schema: onboardingSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: onboardingErrorSchema },\n { status: 404, description: 'Self-service onboarding disabled', schema: onboardingErrorSchema },\n { status: 409, description: 'Existing account or pending request', schema: onboardingErrorSchema },\n { status: 500, description: 'Unexpected server error', schema: onboardingErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: onboardingTag,\n summary: 'Self-service onboarding submission',\n methods: {\n POST: onboardingPostDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
import { Entity, PrimaryKey, Property, Unique } from "@mikro-orm/core";
|
|
12
|
+
let OnboardingRequest = class {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.status = "pending";
|
|
15
|
+
this.termsAccepted = false;
|
|
16
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
__decorateClass([
|
|
20
|
+
PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
|
|
21
|
+
], OnboardingRequest.prototype, "id", 2);
|
|
22
|
+
__decorateClass([
|
|
23
|
+
Property({ type: "text" })
|
|
24
|
+
], OnboardingRequest.prototype, "email", 2);
|
|
25
|
+
__decorateClass([
|
|
26
|
+
Property({ name: "token_hash", type: "text" })
|
|
27
|
+
], OnboardingRequest.prototype, "tokenHash", 2);
|
|
28
|
+
__decorateClass([
|
|
29
|
+
Property({ type: "text", default: "pending" })
|
|
30
|
+
], OnboardingRequest.prototype, "status", 2);
|
|
31
|
+
__decorateClass([
|
|
32
|
+
Property({ name: "first_name", type: "text" })
|
|
33
|
+
], OnboardingRequest.prototype, "firstName", 2);
|
|
34
|
+
__decorateClass([
|
|
35
|
+
Property({ name: "last_name", type: "text" })
|
|
36
|
+
], OnboardingRequest.prototype, "lastName", 2);
|
|
37
|
+
__decorateClass([
|
|
38
|
+
Property({ name: "organization_name", type: "text" })
|
|
39
|
+
], OnboardingRequest.prototype, "organizationName", 2);
|
|
40
|
+
__decorateClass([
|
|
41
|
+
Property({ type: "text", nullable: true })
|
|
42
|
+
], OnboardingRequest.prototype, "locale", 2);
|
|
43
|
+
__decorateClass([
|
|
44
|
+
Property({ name: "terms_accepted", type: "boolean", default: false })
|
|
45
|
+
], OnboardingRequest.prototype, "termsAccepted", 2);
|
|
46
|
+
__decorateClass([
|
|
47
|
+
Property({ name: "password_hash", type: "text", nullable: true })
|
|
48
|
+
], OnboardingRequest.prototype, "passwordHash", 2);
|
|
49
|
+
__decorateClass([
|
|
50
|
+
Property({ name: "expires_at", type: Date })
|
|
51
|
+
], OnboardingRequest.prototype, "expiresAt", 2);
|
|
52
|
+
__decorateClass([
|
|
53
|
+
Property({ name: "completed_at", type: Date, nullable: true })
|
|
54
|
+
], OnboardingRequest.prototype, "completedAt", 2);
|
|
55
|
+
__decorateClass([
|
|
56
|
+
Property({ name: "tenant_id", type: "uuid", nullable: true })
|
|
57
|
+
], OnboardingRequest.prototype, "tenantId", 2);
|
|
58
|
+
__decorateClass([
|
|
59
|
+
Property({ name: "organization_id", type: "uuid", nullable: true })
|
|
60
|
+
], OnboardingRequest.prototype, "organizationId", 2);
|
|
61
|
+
__decorateClass([
|
|
62
|
+
Property({ name: "user_id", type: "uuid", nullable: true })
|
|
63
|
+
], OnboardingRequest.prototype, "userId", 2);
|
|
64
|
+
__decorateClass([
|
|
65
|
+
Property({ name: "last_email_sent_at", type: Date, nullable: true })
|
|
66
|
+
], OnboardingRequest.prototype, "lastEmailSentAt", 2);
|
|
67
|
+
__decorateClass([
|
|
68
|
+
Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
|
|
69
|
+
], OnboardingRequest.prototype, "createdAt", 2);
|
|
70
|
+
__decorateClass([
|
|
71
|
+
Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date(), nullable: true })
|
|
72
|
+
], OnboardingRequest.prototype, "updatedAt", 2);
|
|
73
|
+
__decorateClass([
|
|
74
|
+
Property({ name: "deleted_at", type: Date, nullable: true })
|
|
75
|
+
], OnboardingRequest.prototype, "deletedAt", 2);
|
|
76
|
+
OnboardingRequest = __decorateClass([
|
|
77
|
+
Entity({ tableName: "onboarding_requests" }),
|
|
78
|
+
Unique({ properties: ["email"] }),
|
|
79
|
+
Unique({ properties: ["tokenHash"] })
|
|
80
|
+
], OnboardingRequest);
|
|
81
|
+
export {
|
|
82
|
+
OnboardingRequest
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=entities.js.map
|