Domain Model
Schemas in
@nx/core/src/models/schemas/identity/(and sharedpublic/configuration/). Numeric/text columns follow IGNIS conventions.
1. Full ERD
2. Common Columns
Every entity adds these via generateCommonColumnDefs():
| Column | Type |
|---|---|
id | text (PK, Snowflake) |
createdAt / modifiedAt | timestamptz |
createdBy / modifiedBy | text |
deletedAt | timestamptz (soft-delete) |
metadata | jsonb |
status | text (when using …WithStatusDefs) |
3. Entities
3.1 User
| Property | Value |
|---|---|
| Table | User |
| Soft-delete | yes |
| Field | Type | Required | Description |
|---|---|---|---|
username | text | Display username (also stored as identifier) | |
status | text | ✓ | ACTIVATED / DEACTIVATED / LOCKED |
lastLoginAt | timestamptz | Updated by signIn |
3.2 UserIdentifier
| Field | Type | Required | Description |
|---|---|---|---|
userId | text | ✓ | FK |
identifier | text | ✓ | The login value (email, phone, username, etc.) |
scheme | text | ✓ | USERNAME / EMAIL / PHONE_NUMBER / USER_NUMBER / NX_AUTH |
verified | boolean | ✓ | Default false; set to true after OTP confirmation |
Unique constraint: partial unique on (scheme, identifier) WHERE deletedAt IS NULL — a value is unique per scheme.
3.3 UserCredential
| Field | Type | Required | Description |
|---|---|---|---|
userId | text | ✓ | FK |
scheme | text | ✓ | BASIC (password) / TWO_FA / OAUTH / OAUTH2 |
credential | text | ✓ | Hashed via Bun.password (or external token for OAUTH) |
3.4 UserProfile
| Field | Type | Required | Description |
|---|---|---|---|
userId | text | ✓ | FK (1:1) |
firstName / lastName | text | — | |
birthday | timestamptz | — | |
locale | text | en / vi (drives mail/SMS template selection) |
3.5 UserConfiguration
Per-user feature flags / settings as kv pairs in jsonb.
3.6 Role
| Field | Type | Required | Description |
|---|---|---|---|
identifier | text | ✓ | Unique slug (SUPER_ADMIN, etc.) |
name | i18n jsonb | ✓ | Display |
description | i18n jsonb | — | |
priority | int | ✓ | Range 101–499 for custom; system roles use 500–1000 |
type | text | ✓ | SYSTEM / CUSTOM / UNKNOWN |
status | text | ✓ | ACTIVATED / DEACTIVATED |
System roles (immutable, seeded): SUPER_ADMIN(1000), ADMIN(500), OPERATOR(600), OWNER(500), CASHIER(110), EMPLOYEE(100), CUSTOMER(10), GUEST(1).
3.7 Permission
| Field | Type | Required | Description |
|---|---|---|---|
code | text | ✓ | <Resource>.<action> (e.g. User.create) |
resource | text | ✓ | Authorization subject |
action | text | ✓ | find / create / updateById / deleteById / domain action |
name / description | i18n jsonb | — |
3.8 PolicyDefinition (RBAC edge)
| Field | Type | Required | Description |
|---|---|---|---|
variant | text | ✓ | GROUP (subject ↔ subject) / PERMISSION (role ↔ permission) |
subjectType | text | ✓ | User / Role / Permission |
subjectId | text | ✓ | FK target |
targetType | text | ✓ | Role / Permission / User / Organizer / Merchant |
targetId | text | ✓ | FK target |
scope | text | SYSTEM / ORGANIZER / MERCHANT (PolicyDomains) |
Common edges:
| Edge type | Variant | Subject | Target |
|---|---|---|---|
| User → Role | GROUP | User | Role |
| User → Organizer | GROUP | User | Organizer |
| User → Merchant | GROUP | User | Merchant |
| Role → Permission | PERMISSION | Role | Permission |
3.9 Customer
User extension with sales-context fields (managed in identity but read by sale).
| Field | Type | Required | Description |
|---|---|---|---|
userId | text | optional FK (some customers are guests, no User) | |
name | text | ✓ | Display |
phone / email | text | Contact | |
merchantId | text | ✓ | Owner |
pointBalance | decimal(15,4) | ✓ | Loyalty points (default 0; written by sale) |
3.10 Employee
| Field | Type | Required | Description |
|---|---|---|---|
userId | text | ✓ | FK to User |
merchantId | text | Employer (or organizerId) | |
organizerId | text | Alternative employer | |
position | text | Job title | |
status | text | ✓ | ACTIVATED / DEACTIVATED |
3.11 Configuration (shared)
| Field | Type | Required | Description |
|---|---|---|---|
code | text | ✓ | Unique per (group, principalId, principalType, environment) partial |
group | text | ✓ | INTEGRATION / MAIL / SMS / OTP / etc. |
principalType / principalId | text | When per-merchant (e.g., SMS provider per merchant) | |
environment | text | DEVELOPMENT / PRODUCTION | |
tValue | text | JSON value (encrypted for credentials) | |
dataType | text | TEXT / JSON / BOOLEAN | |
credential | text | Encrypted (AES-256-GCM via CryptoUtility); hidden from CRUD reads |
Identity reads mail/SMS/OTP configs from this table; payment writes encrypted provider credentials.
4. Status Enums
4.1 UserIdentifierSchemes
| Value | Description |
|---|---|
USERNAME | Display username |
EMAIL | Email address |
PHONE_NUMBER | E.164 phone |
USER_NUMBER | Internal numeric id |
NX_AUTH | NX-Auth federation |
4.2 UserCredentialSchemes
| Value | Use |
|---|---|
BASIC | Password (Bun.password hash) |
TWO_FA | Second factor token (placeholder) |
OAUTH | OAuth1 federation (declared, no provider) |
OAUTH2 | OAuth2 federation (declared, no provider) |
4.3 RoleTypes
| Value | Code |
|---|---|
SYSTEM | seeded, immutable |
CUSTOM | merchant-defined |
UNKNOWN | fallback |
4.4 PolicyDomains (scope field)
| Value | Use |
|---|---|
SYSTEM | global edges |
ORGANIZER | scoped to an organizer |
MERCHANT | scoped to a merchant |
4.5 OTP namespaces
| Namespace | Use |
|---|---|
verify-email | Register email |
verify-phone | Register phone |
forgot-password | Password reset |
phone-auth | Phone-only sign-in (when applicable) |
add-phone / add-email | Link to authenticated account |
4.6 OTP defaults
| Setting | Value |
|---|---|
CODE_LENGTH | 6 |
EXPIRY | 5–15 min depending on flow |
MAX_ATTEMPTS | 5 |
LOCKOUT | 10–15 min |
RESEND_COOLDOWN | 60s |
MAX_RESENDS_PER_DAY | 5 |
5. Cross-entity Invariants
| Invariant | Enforcement |
|---|---|
UserIdentifier(scheme, value) is unique system-wide (per scheme) | Partial unique index |
UserCredential.scheme=BASIC is hashed (never plaintext) | AuthenticationService.signUp + changePassword always hash |
Role.priority is unique within type | Service validation in RoleService |
System roles (type=SYSTEM) cannot be deleted/modified except via migration | Service guard |
PolicyDefinition cycles (User → Role → User) are forbidden | Service-level acyclicity check |
OTP daily quota per (namespace, identifierValue) | Redis counter {ns}:daily:{value} |
JWT payload always includes userId, roles, organizers, merchants | AuthenticationService.generateToken |
6. Soft-delete Behavior
| Entity | Soft-delete | Notes |
|---|---|---|
| All identity entities | ✓ | deletedAt marker; reads default IS NULL |
Configuration | ✓ | But seeded rows are typically restored on migration re-run |