@endora-commerce/mod-admin-users 0.100.0
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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/index.d.ts +47 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +44 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/AdminRolesPage.d.ts +3 -0
- package/dist/admin/pages/AdminRolesPage.d.ts.map +1 -0
- package/dist/admin/pages/AdminRolesPage.js +180 -0
- package/dist/admin/pages/AdminRolesPage.js.map +1 -0
- package/dist/admin/pages/AdminUsersPage.d.ts +3 -0
- package/dist/admin/pages/AdminUsersPage.d.ts.map +1 -0
- package/dist/admin/pages/AdminUsersPage.js +151 -0
- package/dist/admin/pages/AdminUsersPage.js.map +1 -0
- package/dist/admin/permission-label.d.ts +26 -0
- package/dist/admin/permission-label.d.ts.map +1 -0
- package/dist/admin/permission-label.js +67 -0
- package/dist/admin/permission-label.js.map +1 -0
- package/dist/backend/cli/create-admin.d.ts +16 -0
- package/dist/backend/cli/create-admin.d.ts.map +1 -0
- package/dist/backend/cli/create-admin.js +101 -0
- package/dist/backend/cli/create-admin.js.map +1 -0
- package/dist/backend/demo/reset.d.ts +15 -0
- package/dist/backend/demo/reset.d.ts.map +1 -0
- package/dist/backend/demo/reset.js +13 -0
- package/dist/backend/demo/reset.js.map +1 -0
- package/dist/backend/demo/rows.d.ts +50 -0
- package/dist/backend/demo/rows.d.ts.map +1 -0
- package/dist/backend/demo/rows.js +73 -0
- package/dist/backend/demo/rows.js.map +1 -0
- package/dist/backend/demo/seed.d.ts +24 -0
- package/dist/backend/demo/seed.d.ts.map +1 -0
- package/dist/backend/demo/seed.js +43 -0
- package/dist/backend/demo/seed.js.map +1 -0
- package/dist/backend/entities/admin-user.entity.d.ts +44 -0
- package/dist/backend/entities/admin-user.entity.d.ts.map +1 -0
- package/dist/backend/entities/admin-user.entity.js +115 -0
- package/dist/backend/entities/admin-user.entity.js.map +1 -0
- package/dist/backend/index.d.ts +77 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +111 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +70 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +50 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +32 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +192 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.impersonation.d.ts +25 -0
- package/dist/backend/routes.impersonation.d.ts.map +1 -0
- package/dist/backend/routes.impersonation.js +108 -0
- package/dist/backend/routes.impersonation.js.map +1 -0
- package/dist/backend/routes.public.d.ts +20 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +75 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/services/admin-auth-service.d.ts +47 -0
- package/dist/backend/services/admin-auth-service.d.ts.map +1 -0
- package/dist/backend/services/admin-auth-service.js +93 -0
- package/dist/backend/services/admin-auth-service.js.map +1 -0
- package/dist/backend/services/admin-user-ports.d.ts +75 -0
- package/dist/backend/services/admin-user-ports.d.ts.map +1 -0
- package/dist/backend/services/admin-user-ports.js +160 -0
- package/dist/backend/services/admin-user-ports.js.map +1 -0
- package/dist/backend/services/admin-user-service.d.ts +134 -0
- package/dist/backend/services/admin-user-service.d.ts.map +1 -0
- package/dist/backend/services/admin-user-service.js +241 -0
- package/dist/backend/services/admin-user-service.js.map +1 -0
- package/dist/backend/services/impersonation-service.d.ts +65 -0
- package/dist/backend/services/impersonation-service.d.ts.map +1 -0
- package/dist/backend/services/impersonation-service.js +103 -0
- package/dist/backend/services/impersonation-service.js.map +1 -0
- package/dist/backend/services/two-factor-enrolments.d.ts +19 -0
- package/dist/backend/services/two-factor-enrolments.d.ts.map +1 -0
- package/dist/backend/services/two-factor-enrolments.js +2 -0
- package/dist/backend/services/two-factor-enrolments.js.map +1 -0
- package/dist/manifest.d.ts +182 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +208 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260425T053028_admin_users_init.d.ts +11 -0
- package/dist/migrations/20260425T053028_admin_users_init.d.ts.map +1 -0
- package/dist/migrations/20260425T053028_admin_users_init.js +51 -0
- package/dist/migrations/20260425T053028_admin_users_init.js.map +1 -0
- package/dist/migrations/20260819T155150_admin_users_fold_email_case.d.ts +55 -0
- package/dist/migrations/20260819T155150_admin_users_fold_email_case.d.ts.map +1 -0
- package/dist/migrations/20260819T155150_admin_users_fold_email_case.js +106 -0
- package/dist/migrations/20260819T155150_admin_users_fold_email_case.js.map +1 -0
- package/dist/migrations/20260825T124801_admin_users_drop_legacy_two_factor_secret.d.ts +25 -0
- package/dist/migrations/20260825T124801_admin_users_drop_legacy_two_factor_secret.d.ts.map +1 -0
- package/dist/migrations/20260825T124801_admin_users_drop_legacy_two_factor_secret.js +29 -0
- package/dist/migrations/20260825T124801_admin_users_drop_legacy_two_factor_secret.js.map +1 -0
- package/dist/migrations/index.d.ts +29 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +33 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/admin_users.md +53 -0
- package/i18n/en.json +5 -0
- package/i18n/pl.json +5 -0
- package/package.json +100 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-label.d.ts","sourceRoot":"","sources":["../../src/admin/permission-label.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAEtF,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,2BAA2B,CAAC;AAEhE,gFAAgF;AAChF,eAAO,MAAM,6BAA6B,SAAS,CAAC;AAEpD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASpF;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAYxE"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving a permission label — feature 091, Phase 3.
|
|
3
|
+
*
|
|
4
|
+
* `/admin-roles` renders one row per grantable code and translates it through
|
|
5
|
+
* `adminRoles.permission.<code>`. That key used to be looked up in exactly one
|
|
6
|
+
* namespace, the synthetic `core` that `_i18n`'s bundle is served under, and
|
|
7
|
+
* that is what made a module owning its own labels impossible: a module's
|
|
8
|
+
* bundle is served under the module's own id, so a label written there was
|
|
9
|
+
* **read by nobody**.
|
|
10
|
+
*
|
|
11
|
+
* It was not hypothetical. `mfa`, `pwa`, `stripe` and `prompt_actions` each
|
|
12
|
+
* shipped their permission labels in their own bundle *and* in `_i18n`'s, and
|
|
13
|
+
* the copy in their own bundle had never rendered — the screen took `core`'s.
|
|
14
|
+
* Four authors did the right thing and the platform silently ignored it.
|
|
15
|
+
*
|
|
16
|
+
* So the lookup is over the merged bundle rather than over one namespace:
|
|
17
|
+
* `core` first, because that is where the legacy block still lives and a
|
|
18
|
+
* migrating label must not flicker between two answers, then the module
|
|
19
|
+
* namespaces. The key carries the permission code, which is unique across the
|
|
20
|
+
* catalogue, so a namespace scan cannot collide two different labels — and
|
|
21
|
+
* which module may write one is a question for the ratchet in
|
|
22
|
+
* `backend/test/helpers/permission-labels.ts`, not for the renderer. Being
|
|
23
|
+
* permissive here and strict there is deliberate: the alternative is resolving
|
|
24
|
+
* by the catalogue row's `module` field, which is a **display grouping** and is
|
|
25
|
+
* `module_lifecycle` for a module whose id is `_lifecycle`.
|
|
26
|
+
*/
|
|
27
|
+
import { resolve } from '@endora-commerce/admin-kit/i18n';
|
|
28
|
+
/** The key `/admin-roles` builds for every catalogue row. */
|
|
29
|
+
export const PERMISSION_LABEL_PREFIX = 'adminRoles.permission.';
|
|
30
|
+
/** The namespace `_i18n`'s bundle is served under — the legacy block's home. */
|
|
31
|
+
export const LEGACY_PERMISSION_LABEL_SCOPE = 'core';
|
|
32
|
+
/**
|
|
33
|
+
* The namespace that carries `key`, preferring the legacy block, or `null`.
|
|
34
|
+
*
|
|
35
|
+
* Sorted, so two bundles that both carried the key would resolve to the same
|
|
36
|
+
* one on every render rather than to whichever the server serialised first.
|
|
37
|
+
*/
|
|
38
|
+
export function scopeCarrying(bundle, key) {
|
|
39
|
+
if (bundle === undefined)
|
|
40
|
+
return null;
|
|
41
|
+
if (bundle[LEGACY_PERMISSION_LABEL_SCOPE]?.[key] != null) {
|
|
42
|
+
return LEGACY_PERMISSION_LABEL_SCOPE;
|
|
43
|
+
}
|
|
44
|
+
const carrying = Object.keys(bundle)
|
|
45
|
+
.filter((scope) => bundle[scope]?.[key] != null)
|
|
46
|
+
.sort();
|
|
47
|
+
return carrying[0] ?? null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The label for one permission code: the operator's language, then English,
|
|
51
|
+
* then the manifest label the API already sent.
|
|
52
|
+
*/
|
|
53
|
+
export function resolvePermissionLabel(args) {
|
|
54
|
+
const key = `${PERMISSION_LABEL_PREFIX}${args.code}`;
|
|
55
|
+
const scope = scopeCarrying(args.bundle, key) ?? scopeCarrying(args.fallbackBundle, key);
|
|
56
|
+
if (scope === null)
|
|
57
|
+
return args.fallbackLabel;
|
|
58
|
+
const result = resolve({
|
|
59
|
+
scope,
|
|
60
|
+
key,
|
|
61
|
+
language: args.language,
|
|
62
|
+
bundle: args.bundle,
|
|
63
|
+
...(args.fallbackBundle !== undefined ? { fallbackBundle: args.fallbackBundle } : {}),
|
|
64
|
+
});
|
|
65
|
+
return result.outcome === 'placeholder' ? args.fallbackLabel : result.value;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=permission-label.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-label.js","sourceRoot":"","sources":["../../src/admin/permission-label.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,iCAAiC,CAAC;AAG1D,6DAA6D;AAC7D,MAAM,CAAC,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;AAEhE,gFAAgF;AAChF,MAAM,CAAC,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAA0B,EAAE,GAAW;IACnE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,MAAM,CAAC,6BAA6B,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QACzD,OAAO,6BAA6B,CAAC;IACvC,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;SACjC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;SAC/C,IAAI,EAAE,CAAC;IACV,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC7B,CAAC;AAWD;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAyB;IAC9D,MAAM,GAAG,GAAG,GAAG,uBAAuB,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACrD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACzF,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,aAAa,CAAC;IAC9C,MAAM,MAAM,GAAG,OAAO,CAAC;QACrB,KAAK;QACL,GAAG;QACH,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtF,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ModuleCliCommandContext } from '@endora-commerce/contracts';
|
|
2
|
+
import { type ModuleContext } from '@endora-commerce/platform/kernel';
|
|
3
|
+
interface ParsedArgs {
|
|
4
|
+
email: string;
|
|
5
|
+
password: string;
|
|
6
|
+
firstName: string;
|
|
7
|
+
lastName: string;
|
|
8
|
+
role: string;
|
|
9
|
+
skipRoleBootstrap: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseArgs(argv: readonly string[]): ParsedArgs | {
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function createAdmin({ ctx, argv, out, err, }: ModuleCliCommandContext<ModuleContext>): Promise<number>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=create-admin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-admin.d.ts","sourceRoot":"","sources":["../../../src/backend/cli/create-admin.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAIhF,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAOD,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CA0BjF;AAED,wBAAsB,WAAW,CAAC,EAChC,GAAG,EACH,IAAI,EACJ,GAAG,EACH,GAAG,GACJ,EAAE,uBAAuB,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAoE1D"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { normalizeEmailAddress } from '@endora-commerce/contracts';
|
|
2
|
+
import { lazyPort } from '@endora-commerce/platform/kernel';
|
|
3
|
+
import { hashPassword } from '@endora-commerce/platform/kernel';
|
|
4
|
+
import { AdminUser } from '../entities/admin-user.entity.js';
|
|
5
|
+
export function parseArgs(argv) {
|
|
6
|
+
const map = new Map();
|
|
7
|
+
let skipRoleBootstrap = false;
|
|
8
|
+
for (const arg of argv) {
|
|
9
|
+
if (arg === '--skip-role-bootstrap') {
|
|
10
|
+
skipRoleBootstrap = true;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (!arg.startsWith('--'))
|
|
14
|
+
continue;
|
|
15
|
+
const eq = arg.indexOf('=');
|
|
16
|
+
if (eq === -1)
|
|
17
|
+
continue;
|
|
18
|
+
map.set(arg.slice(2, eq), arg.slice(eq + 1));
|
|
19
|
+
}
|
|
20
|
+
for (const key of ['email', 'password', 'first-name', 'last-name']) {
|
|
21
|
+
if (!map.get(key)?.trim())
|
|
22
|
+
return { error: `Missing required flag: --${key}=...` };
|
|
23
|
+
}
|
|
24
|
+
const password = map.get('password').trim();
|
|
25
|
+
if (password.length < 12)
|
|
26
|
+
return { error: 'Password must be at least 12 characters.' };
|
|
27
|
+
return {
|
|
28
|
+
email: normalizeEmailAddress(map.get('email').trim()),
|
|
29
|
+
password,
|
|
30
|
+
firstName: map.get('first-name').trim(),
|
|
31
|
+
lastName: map.get('last-name').trim(),
|
|
32
|
+
role: map.get('role')?.trim() || 'platform_admin',
|
|
33
|
+
skipRoleBootstrap,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export async function createAdmin({ ctx, argv, out, err, }) {
|
|
37
|
+
// command-coverage-ignore: the bootstrap CLI that mints the first
|
|
38
|
+
// administrator. It runs with shell access to the deployment and, by
|
|
39
|
+
// construction, before any Admin User exists — so there is no acting
|
|
40
|
+
// principal for the Command Bus to attribute the write to. Every subsequent
|
|
41
|
+
// admin-user write goes through the audited admin_users surface; this one
|
|
42
|
+
// exists so that surface has somebody to sign in to it.
|
|
43
|
+
const args = parseArgs(argv);
|
|
44
|
+
if ('error' in args) {
|
|
45
|
+
err(args.error);
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
const adminRoles = lazyPort(ctx, 'adminRolePort');
|
|
49
|
+
const em = ctx.cradle().emFactory();
|
|
50
|
+
// 1. Resolve or bootstrap the role — two methods of the port `admin_roles`
|
|
51
|
+
// publishes, in place of a `findOne` + `em.create` over its entity.
|
|
52
|
+
let role = await adminRoles.findByCode(args.role);
|
|
53
|
+
if (!role) {
|
|
54
|
+
if (args.skipRoleBootstrap || args.role !== 'platform_admin') {
|
|
55
|
+
err(`Role "${args.role}" not found. Either create it via the admin panel or omit ` +
|
|
56
|
+
`--role to bootstrap the platform_admin role.`);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
role = await adminRoles.upsertByCode({
|
|
60
|
+
code: 'platform_admin',
|
|
61
|
+
name: 'Platform Admin',
|
|
62
|
+
permissions: ['*'],
|
|
63
|
+
requiresTwoFactor: false,
|
|
64
|
+
});
|
|
65
|
+
out(`Created bootstrap role: platform_admin (${role.id})`);
|
|
66
|
+
}
|
|
67
|
+
// 2. Upsert the admin user — this module's own table, written directly for
|
|
68
|
+
// the reason the ignore marker above gives.
|
|
69
|
+
const passwordHash = await hashPassword(args.password);
|
|
70
|
+
const existing = await em.findOne(AdminUser, { email: args.email });
|
|
71
|
+
if (existing) {
|
|
72
|
+
existing.passwordHash = passwordHash;
|
|
73
|
+
existing.firstName = args.firstName;
|
|
74
|
+
existing.lastName = args.lastName;
|
|
75
|
+
existing.adminRoleId = role.id;
|
|
76
|
+
existing.status = 'active';
|
|
77
|
+
existing.deletedAt = null;
|
|
78
|
+
await em.flush();
|
|
79
|
+
out(`Updated existing admin: ${args.email}`);
|
|
80
|
+
out(` id : ${existing.id}`);
|
|
81
|
+
out(` role : ${role.code} (${role.id})`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const created = em.create(AdminUser, {
|
|
85
|
+
email: args.email,
|
|
86
|
+
passwordHash,
|
|
87
|
+
firstName: args.firstName,
|
|
88
|
+
lastName: args.lastName,
|
|
89
|
+
adminRoleId: role.id,
|
|
90
|
+
status: 'active',
|
|
91
|
+
});
|
|
92
|
+
await em.persistAndFlush(created);
|
|
93
|
+
out(`Created admin: ${args.email}`);
|
|
94
|
+
out(` id : ${created.id}`);
|
|
95
|
+
out(` role : ${role.code} (${role.id})`);
|
|
96
|
+
}
|
|
97
|
+
out('');
|
|
98
|
+
out('Sign in at the admin panel with the email + password above.');
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=create-admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-admin.js","sourceRoot":"","sources":["../../../src/backend/cli/create-admin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Withdraw `admin_users`' demo data (feature 113, T222 — contract §2.5).
|
|
3
|
+
*
|
|
4
|
+
* By the fixed addresses `seed` assigns, never by a predicate over the table:
|
|
5
|
+
* an operator's own administrator is indistinguishable from a demo one by every
|
|
6
|
+
* other column, and this is the table the host's `truncate … cascade` emptied
|
|
7
|
+
* outright — including the account a developer had created for themselves.
|
|
8
|
+
*
|
|
9
|
+
* The role each account holds is unassigned first, by the composition: `reset`
|
|
10
|
+
* runs the composition's withdrawal before any module's (§5.5).
|
|
11
|
+
*/
|
|
12
|
+
import type { DemoResetResult, ModuleDemoContext } from '@endora-commerce/contracts';
|
|
13
|
+
import type { ModuleContext } from '@endora-commerce/platform/kernel';
|
|
14
|
+
export declare function resetDemo(context: ModuleDemoContext<ModuleContext>): Promise<DemoResetResult>;
|
|
15
|
+
//# sourceMappingURL=reset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../../src/backend/demo/reset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAStE,wBAAsB,SAAS,CAC7B,OAAO,EAAE,iBAAiB,CAAC,aAAa,CAAC,GACxC,OAAO,CAAC,eAAe,CAAC,CAS1B"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AdminUser } from '../entities/admin-user.entity.js';
|
|
2
|
+
import { DEMO_ADMIN_USER_EMAILS } from './rows.js';
|
|
3
|
+
export async function resetDemo(context) {
|
|
4
|
+
// command-coverage-ignore: the withdrawal half of the demo data above. Same
|
|
5
|
+
// entry point, same `mustBeNonProduction()` guard, same absence of an
|
|
6
|
+
// operator to attribute the write to (contract §2.7).
|
|
7
|
+
const em = context.ctx.cradle().emFactory();
|
|
8
|
+
const removed = await em.nativeDelete(AdminUser, {
|
|
9
|
+
email: { $in: [...DEMO_ADMIN_USER_EMAILS] },
|
|
10
|
+
});
|
|
11
|
+
return { removed: [{ entity: 'AdminUser', count: removed }] };
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=reset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.js","sourceRoot":"","sources":["../../../src/backend/demo/reset.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAMnD,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAyC;IAEzC,4EAA4E;IAC5E,sEAAsE;IACtE,sDAAsD;IACtD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAwB,CAAC,SAAS,EAAE,CAAC;IAClE,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE;QAC/C,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,sBAAsB,CAAC,EAAE;KAC5C,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The administrator accounts this module's demo data creates (feature 113,
|
|
3
|
+
* T222).
|
|
4
|
+
*
|
|
5
|
+
* One place holding the data, imported by both bodies: `seed.ts` creates these
|
|
6
|
+
* accounts and `reset.ts` withdraws exactly them, by the `email` they are keyed
|
|
7
|
+
* on (contract §2.5). The values are the host seed's verbatim, moved rather
|
|
8
|
+
* than rewritten; the frozen copy the parity comparison read them against is
|
|
9
|
+
* deleted with that comparison (T226), and
|
|
10
|
+
* `test/integration/demo/demo-shop.test.ts`' recorded delta is what holds them
|
|
11
|
+
* now.
|
|
12
|
+
*
|
|
13
|
+
* ## The role each account holds is deliberately not here
|
|
14
|
+
*
|
|
15
|
+
* An `admin_users` row carrying an `admin_roles` id is two modules' rows in one
|
|
16
|
+
* statement, so the assignment is a composition step (§5.1). `roleCode` below is
|
|
17
|
+
* **not** written by this body: it records which role the demo shop means each
|
|
18
|
+
* account to hold, so that `reset` and this module's own test can say what the
|
|
19
|
+
* composition is expected to have done. `AdminUser.adminRoleId` is nullable —
|
|
20
|
+
* `[OptionalProps]` names it — which is what makes the split available at all,
|
|
21
|
+
* and it is the difference from the demo *buyer*, whose
|
|
22
|
+
* `customer_accounts.organization_id` is `NOT NULL` and who therefore has to be
|
|
23
|
+
* created by the composition outright.
|
|
24
|
+
*
|
|
25
|
+
* ## The password is a real one and is printed
|
|
26
|
+
*
|
|
27
|
+
* It is reported as a {@link DemoCredential} rather than logged, so the runner
|
|
28
|
+
* formats it once (§3.7). `mustBeNonProduction()` at the entry point is what
|
|
29
|
+
* makes shipping a known password defensible: the command refuses to run
|
|
30
|
+
* against a production database at all.
|
|
31
|
+
*/
|
|
32
|
+
/** One demo administrator, in `AdminUser`'s own field names plus its role. */
|
|
33
|
+
export interface DemoAdminUserRow {
|
|
34
|
+
readonly email: string;
|
|
35
|
+
readonly firstName: string;
|
|
36
|
+
readonly lastName: string;
|
|
37
|
+
readonly status: 'active' | 'inactive';
|
|
38
|
+
/** The `admin_roles` code the composition assigns — never written here. */
|
|
39
|
+
readonly roleCode: string;
|
|
40
|
+
/** How the report announces this account. */
|
|
41
|
+
readonly credentialLabel: string;
|
|
42
|
+
}
|
|
43
|
+
/** The one password every demo administrator signs in with. */
|
|
44
|
+
export declare const DEMO_ADMIN_PASSWORD = "ChangeMe!123";
|
|
45
|
+
/** The platform administrator's address, which the demo's report leads with. */
|
|
46
|
+
export declare const DEMO_ADMIN_EMAIL = "admin@demo.local";
|
|
47
|
+
export declare const DEMO_ADMIN_USERS: readonly DemoAdminUserRow[];
|
|
48
|
+
/** The addresses `reset` withdraws — derived from the rows, never a second list. */
|
|
49
|
+
export declare const DEMO_ADMIN_USER_EMAILS: readonly string[];
|
|
50
|
+
//# sourceMappingURL=rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../../src/backend/demo/rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,eAAO,MAAM,gBAAgB,EAAE,SAAS,gBAAgB,EAkCvD,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAA6C,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The administrator accounts this module's demo data creates (feature 113,
|
|
3
|
+
* T222).
|
|
4
|
+
*
|
|
5
|
+
* One place holding the data, imported by both bodies: `seed.ts` creates these
|
|
6
|
+
* accounts and `reset.ts` withdraws exactly them, by the `email` they are keyed
|
|
7
|
+
* on (contract §2.5). The values are the host seed's verbatim, moved rather
|
|
8
|
+
* than rewritten; the frozen copy the parity comparison read them against is
|
|
9
|
+
* deleted with that comparison (T226), and
|
|
10
|
+
* `test/integration/demo/demo-shop.test.ts`' recorded delta is what holds them
|
|
11
|
+
* now.
|
|
12
|
+
*
|
|
13
|
+
* ## The role each account holds is deliberately not here
|
|
14
|
+
*
|
|
15
|
+
* An `admin_users` row carrying an `admin_roles` id is two modules' rows in one
|
|
16
|
+
* statement, so the assignment is a composition step (§5.1). `roleCode` below is
|
|
17
|
+
* **not** written by this body: it records which role the demo shop means each
|
|
18
|
+
* account to hold, so that `reset` and this module's own test can say what the
|
|
19
|
+
* composition is expected to have done. `AdminUser.adminRoleId` is nullable —
|
|
20
|
+
* `[OptionalProps]` names it — which is what makes the split available at all,
|
|
21
|
+
* and it is the difference from the demo *buyer*, whose
|
|
22
|
+
* `customer_accounts.organization_id` is `NOT NULL` and who therefore has to be
|
|
23
|
+
* created by the composition outright.
|
|
24
|
+
*
|
|
25
|
+
* ## The password is a real one and is printed
|
|
26
|
+
*
|
|
27
|
+
* It is reported as a {@link DemoCredential} rather than logged, so the runner
|
|
28
|
+
* formats it once (§3.7). `mustBeNonProduction()` at the entry point is what
|
|
29
|
+
* makes shipping a known password defensible: the command refuses to run
|
|
30
|
+
* against a production database at all.
|
|
31
|
+
*/
|
|
32
|
+
/** The one password every demo administrator signs in with. */
|
|
33
|
+
export const DEMO_ADMIN_PASSWORD = 'ChangeMe!123';
|
|
34
|
+
/** The platform administrator's address, which the demo's report leads with. */
|
|
35
|
+
export const DEMO_ADMIN_EMAIL = 'admin@demo.local';
|
|
36
|
+
export const DEMO_ADMIN_USERS = [
|
|
37
|
+
{
|
|
38
|
+
email: DEMO_ADMIN_EMAIL,
|
|
39
|
+
firstName: 'Demo',
|
|
40
|
+
lastName: 'Admin',
|
|
41
|
+
status: 'active',
|
|
42
|
+
roleCode: 'platform_admin',
|
|
43
|
+
credentialLabel: 'Platform Administrator',
|
|
44
|
+
},
|
|
45
|
+
// Feature 008 — two accounts on the sales_representative role, so the
|
|
46
|
+
// quickstart can exercise assignment-scoped visibility: one representative
|
|
47
|
+
// sees their own organisations and the other's are invisible to them.
|
|
48
|
+
{
|
|
49
|
+
email: 'sales-rep@demo.local',
|
|
50
|
+
firstName: 'Anna',
|
|
51
|
+
// A Polish surname, and it stays one. `check:default-language-prose` reads
|
|
52
|
+
// a literal as prose at two word tokens, or at one in the prop-default
|
|
53
|
+
// position, and this is one token in neither — measured, not assumed. It is
|
|
54
|
+
// also not prose in the sense the rule is about: a person's name is not a
|
|
55
|
+
// sentence a reader is meant to understand, and translating it would be
|
|
56
|
+
// renaming somebody.
|
|
57
|
+
lastName: 'Wiśniewska',
|
|
58
|
+
status: 'active',
|
|
59
|
+
roleCode: 'sales_representative',
|
|
60
|
+
credentialLabel: 'Sales Representative',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
email: 'sales-rep-other@demo.local',
|
|
64
|
+
firstName: 'Tomasz',
|
|
65
|
+
lastName: 'Nowak',
|
|
66
|
+
status: 'active',
|
|
67
|
+
roleCode: 'sales_representative',
|
|
68
|
+
credentialLabel: 'Sales Rep (other)',
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
/** The addresses `reset` withdraws — derived from the rows, never a second list. */
|
|
72
|
+
export const DEMO_ADMIN_USER_EMAILS = DEMO_ADMIN_USERS.map((row) => row.email);
|
|
73
|
+
//# sourceMappingURL=rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.js","sourceRoot":"","sources":["../../../src/backend/demo/rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAcH,+DAA+D;AAC/D,MAAM,CAAC,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAElD,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAEnD,MAAM,CAAC,MAAM,gBAAgB,GAAgC;IAC3D;QACE,KAAK,EAAE,gBAAgB;QACvB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,OAAO;QACjB,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,gBAAgB;QAC1B,eAAe,EAAE,wBAAwB;KAC1C;IACD,sEAAsE;IACtE,2EAA2E;IAC3E,sEAAsE;IACtE;QACE,KAAK,EAAE,sBAAsB;QAC7B,SAAS,EAAE,MAAM;QACjB,2EAA2E;QAC3E,uEAAuE;QACvE,4EAA4E;QAC5E,0EAA0E;QAC1E,wEAAwE;QACxE,qBAAqB;QACrB,QAAQ,EAAE,YAAY;QACtB,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,sBAAsB;QAChC,eAAe,EAAE,sBAAsB;KACxC;IACD;QACE,KAAK,EAAE,4BAA4B;QACnC,SAAS,EAAE,QAAQ;QACnB,QAAQ,EAAE,OAAO;QACjB,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,sBAAsB;QAChC,eAAe,EAAE,mBAAmB;KACrC;CACF,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,sBAAsB,GAAsB,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `admin_users`' demo data (feature 113, T222 — contract §2).
|
|
3
|
+
*
|
|
4
|
+
* The three administrator accounts the quickstart signs in with, in the module
|
|
5
|
+
* that owns the table. It writes `admin_users` and nothing else (§2.1), reads no
|
|
6
|
+
* other module's table and resolves no port (§2.2), so declaring it added no
|
|
7
|
+
* entry to this module's manifest `dependencies` (§2.3).
|
|
8
|
+
*
|
|
9
|
+
* **It writes no `adminRoleId`, and that is the boundary the batch was cut on.**
|
|
10
|
+
* The host block these rows come from created each account with an
|
|
11
|
+
* `admin_roles` id in hand, which is two modules' rows in one statement — a
|
|
12
|
+
* composition step (§5.1). The column is nullable, so the account is this
|
|
13
|
+
* module's and the assignment is the instance's; the composition runs after
|
|
14
|
+
* every module's `seed` and finds both sides already there. Contrast the demo
|
|
15
|
+
* *buyer*, whose `customer_accounts.organization_id` is `NOT NULL`: there no
|
|
16
|
+
* split exists and the composition creates the row outright.
|
|
17
|
+
*
|
|
18
|
+
* Idempotent by an existence probe on the natural key (§2.4): a second run
|
|
19
|
+
* creates nothing, re-hashes nothing and reports the same count.
|
|
20
|
+
*/
|
|
21
|
+
import type { DemoSeedResult, ModuleDemoContext } from '@endora-commerce/contracts';
|
|
22
|
+
import type { ModuleContext } from '@endora-commerce/platform/kernel';
|
|
23
|
+
export declare function seedDemo(context: ModuleDemoContext<ModuleContext>): Promise<DemoSeedResult>;
|
|
24
|
+
//# sourceMappingURL=seed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../src/backend/demo/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAEV,cAAc,EACd,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAWtE,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,iBAAiB,CAAC,aAAa,CAAC,GACxC,OAAO,CAAC,cAAc,CAAC,CAwCzB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { hashPassword } from '@endora-commerce/platform/kernel';
|
|
2
|
+
import { AdminUser } from '../entities/admin-user.entity.js';
|
|
3
|
+
import { DEMO_ADMIN_PASSWORD, DEMO_ADMIN_USERS } from './rows.js';
|
|
4
|
+
export async function seedDemo(context) {
|
|
5
|
+
// command-coverage-ignore: demo data, reached only by `endora demo seed`,
|
|
6
|
+
// whose entry point calls `mustBeNonProduction()` as its first statement and
|
|
7
|
+
// outside every `try` (contract §2.7, §3.3). The guard is the enforcement:
|
|
8
|
+
// this write has no operator, no tenant and no audit reader, and the command
|
|
9
|
+
// refuses to run against a production database at all — which is also what
|
|
10
|
+
// makes a known password in `rows.ts` defensible.
|
|
11
|
+
const em = context.ctx.cradle().emFactory();
|
|
12
|
+
// One hash for the three accounts, exactly as the host block did: Argon2 is
|
|
13
|
+
// deliberately slow and the demo's password is one value.
|
|
14
|
+
let passwordHash;
|
|
15
|
+
for (const row of DEMO_ADMIN_USERS) {
|
|
16
|
+
const existing = await em.findOne(AdminUser, { email: row.email });
|
|
17
|
+
// Present already: left exactly as it is, password included. An operator
|
|
18
|
+
// may have changed it, and overwriting it here would be this body deciding
|
|
19
|
+
// its own literal outranks their change — `reset`'s question, not `seed`'s.
|
|
20
|
+
if (existing)
|
|
21
|
+
continue;
|
|
22
|
+
passwordHash ??= await hashPassword(DEMO_ADMIN_PASSWORD);
|
|
23
|
+
em.create(AdminUser, {
|
|
24
|
+
email: row.email,
|
|
25
|
+
passwordHash,
|
|
26
|
+
firstName: row.firstName,
|
|
27
|
+
lastName: row.lastName,
|
|
28
|
+
status: row.status,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
await em.flush();
|
|
32
|
+
const credentials = DEMO_ADMIN_USERS.map((row) => ({
|
|
33
|
+
label: row.credentialLabel,
|
|
34
|
+
value: `${row.email} / ${DEMO_ADMIN_PASSWORD}`,
|
|
35
|
+
}));
|
|
36
|
+
// The count is what the demo *holds* after the run, not what this call
|
|
37
|
+
// inserted: seeding twice must report the same counts (`DemoEntityCount`).
|
|
38
|
+
return {
|
|
39
|
+
created: [{ entity: 'AdminUser', count: DEMO_ADMIN_USERS.length }],
|
|
40
|
+
credentials,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=seed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/backend/demo/seed.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAOlE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAyC;IAEzC,0EAA0E;IAC1E,6EAA6E;IAC7E,2EAA2E;IAC3E,6EAA6E;IAC7E,2EAA2E;IAC3E,kDAAkD;IAClD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAwB,CAAC,SAAS,EAAE,CAAC;IAElE,4EAA4E;IAC5E,0DAA0D;IAC1D,IAAI,YAAgC,CAAC;IACrC,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QACnE,yEAAyE;QACzE,2EAA2E;QAC3E,4EAA4E;QAC5E,IAAI,QAAQ;YAAE,SAAS;QACvB,YAAY,KAAK,MAAM,YAAY,CAAC,mBAAmB,CAAC,CAAC;QACzD,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE;YACnB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,YAAY;YACZ,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC,CAAC;IACL,CAAC;IACD,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IAEjB,MAAM,WAAW,GAAqB,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACnE,KAAK,EAAE,GAAG,CAAC,eAAe;QAC1B,KAAK,EAAE,GAAG,GAAG,CAAC,KAAK,MAAM,mBAAmB,EAAE;KAC/C,CAAC,CAAC,CAAC;IAEJ,uEAAuE;IACvE,2EAA2E;IAC3E,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB,CAAC,MAAM,EAAE,CAAC;QAClE,WAAW;KACZ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* AdminUser — a Supplier employee with platform-side access. Distinct entity
|
|
4
|
+
* from CustomerAccount (FR-040 — different login surface, different sessions,
|
|
5
|
+
* own permission matrix via AdminRole).
|
|
6
|
+
*/
|
|
7
|
+
export declare class AdminUser {
|
|
8
|
+
[OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'status' | 'adminRoleId' | 'twoFactorConfirmedAt' | 'lastLoginAt' | 'deletedAt' | 'preferredLanguage';
|
|
9
|
+
id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
passwordHash: string;
|
|
12
|
+
firstName: string;
|
|
13
|
+
lastName: string;
|
|
14
|
+
adminRoleId?: string | null;
|
|
15
|
+
status: 'active' | 'inactive';
|
|
16
|
+
/**
|
|
17
|
+
* Never written, and since 2026-08-28 never **read** either.
|
|
18
|
+
*
|
|
19
|
+
* This table has no writer for it at all, not even a broken one, so the
|
|
20
|
+
* published `twoFactorEnabled` derived from it was a provably constant
|
|
21
|
+
* `false`: `/admin-users` reported no second factor for an administrator who
|
|
22
|
+
* had enrolled an hour earlier. That field is now the live `mfa` enrolment,
|
|
23
|
+
* read through `mfaEnrolmentStatePort`, and nothing in the tree reads this
|
|
24
|
+
* column.
|
|
25
|
+
*
|
|
26
|
+
* It is dead schema kept for one more step: dropping it is a migration on
|
|
27
|
+
* this module's table and is deliberately not folded into the repair of the
|
|
28
|
+
* five derivation sites. Do not read it — the honest answer is `mfa`'s.
|
|
29
|
+
*/
|
|
30
|
+
twoFactorConfirmedAt?: Date | null;
|
|
31
|
+
lastLoginAt?: Date | null;
|
|
32
|
+
/**
|
|
33
|
+
* Admin UI preferred language — feature 019. NULL means "no preference
|
|
34
|
+
* saved", which the resolver treats as the platform default (English).
|
|
35
|
+
* The column is bounded by the `SupportedAdminLanguageSchema` allowlist
|
|
36
|
+
* in `@endora-commerce/contracts/src/admin-i18n.ts`; the validation lives at the
|
|
37
|
+
* service / route boundary, not at the column level.
|
|
38
|
+
*/
|
|
39
|
+
preferredLanguage?: string | null;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
updatedAt: Date;
|
|
42
|
+
deletedAt?: Date | null;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=admin-user.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-user.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/admin-user.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAgC,MAAM,iBAAiB,CAAC;AAI7F;;;;GAIG;AACH,qBAEa,SAAS;IACpB,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,WAAW,GACX,WAAW,GACX,QAAQ,GACR,aAAa,GACb,sBAAsB,GACtB,aAAa,GACb,WAAW,GACX,mBAAmB,CAAC;IAGxB,EAAE,EAAE,MAAM,CAAgB;IAI1B,KAAK,EAAG,MAAM,CAAC;IAGf,YAAY,EAAG,MAAM,CAAC;IAGtB,SAAS,EAAG,MAAM,CAAC;IAGnB,QAAQ,EAAG,MAAM,CAAC;IAIlB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAI5B,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAY;IAEzC;;;;;;;;;;;;;OAaG;IAEH,oBAAoB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAGnC,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAE1B;;;;;;OAMG;IAEH,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGlC,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACzB"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
|
|
11
|
+
import { GlobalEntity } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* AdminUser — a Supplier employee with platform-side access. Distinct entity
|
|
15
|
+
* from CustomerAccount (FR-040 — different login surface, different sessions,
|
|
16
|
+
* own permission matrix via AdminRole).
|
|
17
|
+
*/
|
|
18
|
+
let AdminUser = class AdminUser {
|
|
19
|
+
[OptionalProps];
|
|
20
|
+
id = randomUUID();
|
|
21
|
+
email;
|
|
22
|
+
passwordHash;
|
|
23
|
+
firstName;
|
|
24
|
+
lastName;
|
|
25
|
+
adminRoleId;
|
|
26
|
+
status = 'active';
|
|
27
|
+
/**
|
|
28
|
+
* Never written, and since 2026-08-28 never **read** either.
|
|
29
|
+
*
|
|
30
|
+
* This table has no writer for it at all, not even a broken one, so the
|
|
31
|
+
* published `twoFactorEnabled` derived from it was a provably constant
|
|
32
|
+
* `false`: `/admin-users` reported no second factor for an administrator who
|
|
33
|
+
* had enrolled an hour earlier. That field is now the live `mfa` enrolment,
|
|
34
|
+
* read through `mfaEnrolmentStatePort`, and nothing in the tree reads this
|
|
35
|
+
* column.
|
|
36
|
+
*
|
|
37
|
+
* It is dead schema kept for one more step: dropping it is a migration on
|
|
38
|
+
* this module's table and is deliberately not folded into the repair of the
|
|
39
|
+
* five derivation sites. Do not read it — the honest answer is `mfa`'s.
|
|
40
|
+
*/
|
|
41
|
+
twoFactorConfirmedAt;
|
|
42
|
+
lastLoginAt;
|
|
43
|
+
/**
|
|
44
|
+
* Admin UI preferred language — feature 019. NULL means "no preference
|
|
45
|
+
* saved", which the resolver treats as the platform default (English).
|
|
46
|
+
* The column is bounded by the `SupportedAdminLanguageSchema` allowlist
|
|
47
|
+
* in `@endora-commerce/contracts/src/admin-i18n.ts`; the validation lives at the
|
|
48
|
+
* service / route boundary, not at the column level.
|
|
49
|
+
*/
|
|
50
|
+
preferredLanguage;
|
|
51
|
+
createdAt = new Date();
|
|
52
|
+
updatedAt = new Date();
|
|
53
|
+
deletedAt;
|
|
54
|
+
};
|
|
55
|
+
__decorate([
|
|
56
|
+
PrimaryKey({ type: 'uuid' }),
|
|
57
|
+
__metadata("design:type", String)
|
|
58
|
+
], AdminUser.prototype, "id", void 0);
|
|
59
|
+
__decorate([
|
|
60
|
+
Property({ type: 'string', length: 320 }),
|
|
61
|
+
Unique(),
|
|
62
|
+
__metadata("design:type", String)
|
|
63
|
+
], AdminUser.prototype, "email", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
Property({ type: 'string', length: 512 }),
|
|
66
|
+
__metadata("design:type", String)
|
|
67
|
+
], AdminUser.prototype, "passwordHash", void 0);
|
|
68
|
+
__decorate([
|
|
69
|
+
Property({ type: 'string', length: 120 }),
|
|
70
|
+
__metadata("design:type", String)
|
|
71
|
+
], AdminUser.prototype, "firstName", void 0);
|
|
72
|
+
__decorate([
|
|
73
|
+
Property({ type: 'string', length: 120 }),
|
|
74
|
+
__metadata("design:type", String)
|
|
75
|
+
], AdminUser.prototype, "lastName", void 0);
|
|
76
|
+
__decorate([
|
|
77
|
+
Property({ type: 'uuid', nullable: true }),
|
|
78
|
+
Index(),
|
|
79
|
+
__metadata("design:type", Object)
|
|
80
|
+
], AdminUser.prototype, "adminRoleId", void 0);
|
|
81
|
+
__decorate([
|
|
82
|
+
Property({ type: 'string', length: 16 }),
|
|
83
|
+
Index(),
|
|
84
|
+
__metadata("design:type", String)
|
|
85
|
+
], AdminUser.prototype, "status", void 0);
|
|
86
|
+
__decorate([
|
|
87
|
+
Property({ type: 'datetime', nullable: true }),
|
|
88
|
+
__metadata("design:type", Object)
|
|
89
|
+
], AdminUser.prototype, "twoFactorConfirmedAt", void 0);
|
|
90
|
+
__decorate([
|
|
91
|
+
Property({ type: 'datetime', nullable: true }),
|
|
92
|
+
__metadata("design:type", Object)
|
|
93
|
+
], AdminUser.prototype, "lastLoginAt", void 0);
|
|
94
|
+
__decorate([
|
|
95
|
+
Property({ type: 'string', length: 12, nullable: true }),
|
|
96
|
+
__metadata("design:type", Object)
|
|
97
|
+
], AdminUser.prototype, "preferredLanguage", void 0);
|
|
98
|
+
__decorate([
|
|
99
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
100
|
+
__metadata("design:type", Date)
|
|
101
|
+
], AdminUser.prototype, "createdAt", void 0);
|
|
102
|
+
__decorate([
|
|
103
|
+
Property({ type: 'datetime', onUpdate: () => new Date() }),
|
|
104
|
+
__metadata("design:type", Date)
|
|
105
|
+
], AdminUser.prototype, "updatedAt", void 0);
|
|
106
|
+
__decorate([
|
|
107
|
+
Property({ type: 'datetime', nullable: true }),
|
|
108
|
+
__metadata("design:type", Object)
|
|
109
|
+
], AdminUser.prototype, "deletedAt", void 0);
|
|
110
|
+
AdminUser = __decorate([
|
|
111
|
+
GlobalEntity(),
|
|
112
|
+
Entity({ tableName: 'admin_users' })
|
|
113
|
+
], AdminUser);
|
|
114
|
+
export { AdminUser };
|
|
115
|
+
//# sourceMappingURL=admin-user.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-user.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/admin-user.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;GAIG;AAGI,IAAM,SAAS,GAAf,MAAM,SAAS;IACpB,CAAC,aAAa,CAAC,CASS;IAGxB,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,KAAK,CAAU;IAGf,YAAY,CAAU;IAGtB,SAAS,CAAU;IAGnB,QAAQ,CAAU;IAIlB,WAAW,CAAiB;IAI5B,MAAM,GAA0B,QAAQ,CAAC;IAEzC;;;;;;;;;;;;;OAaG;IAEH,oBAAoB,CAAe;IAGnC,WAAW,CAAe;IAE1B;;;;;;OAMG;IAEH,iBAAiB,CAAiB;IAGlC,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,CAAe;CACzB,CAAA;AA7DC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;qCACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IACzC,MAAM,EAAE;;wCACM;AAGf;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;+CACpB;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;4CACvB;AAGnB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;2CACxB;AAIlB;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1C,KAAK,EAAE;;8CACoB;AAI5B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,KAAK,EAAE;;yCACiC;AAiBzC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uDACZ;AAGnC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CACrB;AAU1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oDACvB;AAGlC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;4CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;4CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;4CACvB;AAzEb,SAAS;IAFrB,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;GACxB,SAAS,CA0ErB"}
|