Domain Model
Cả năm bảng nằm trong schema PostgreSQL
licensingvà được định nghĩa trong@nx/coretạipackages/core/src/models/schemas/licensing/.@nx/licensingchỉ re-export repository.
1. ERD đầy đủ
2. Entities
Policy
| Thuộc tính | Giá trị |
|---|---|
| Table | licensing.Policy |
| Source | packages/core/src/models/schemas/licensing/policy/schema.ts |
| Soft-delete | có (generateCommonColumnDefs) |
| Owner ID column | — (template cấp hệ thống) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
name | jsonb (i18n) | ✓ | — | Tên hiển thị { default, en, vi } |
description | jsonb (i18n) | — | Tuỳ chọn | |
product | text | ✓ | — | Định danh product (có index) |
type | text | ✓ | — | PolicyTypes (xem enum) |
status | text | ✓ | ACTIVATED | PolicyStatuses |
sequence | integer | ✓ | 0 | Thứ tự hiển thị |
duration | jsonb (IDuration) | — | { unit, value }; null = vĩnh viễn | |
activation | jsonb (IActivationConfig) | — | { limit }; null = không giới hạn thiết bị | |
gracePeriod | jsonb (IDuration) | — | Mở rộng cửa sổ xác thực sau khi hết hạn |
Enum type (PolicyTypes):
| Giá trị | Mô tả |
|---|---|
000_TRIAL | Gói dùng thử (free trial seed dùng cái này) |
100_SUBSCRIPTION | Subscription có giới hạn thời gian |
200_PERPETUAL | Không hết hạn (đặt duration: null) |
Enum status (PolicyStatuses): ACTIVATED · DEACTIVATED · ARCHIVED.
Index: IDX trên product, type, status.
PolicyFeature
| Thuộc tính | Giá trị |
|---|---|
| Table | licensing.PolicyFeature |
| Source | packages/core/src/models/schemas/licensing/policy-feature/schema.ts |
| Soft-delete | không (generateTzColumnDefs({ deleted: { enable: false } })) |
| Owner ID column | policyId |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
policyId | text | ✓ | — | FK → Policy.id (cascade delete) |
code | text | ✓ | — | Khoá feature (vd MAX_ALLOCATION_LAYOUTS) |
dataType | text | ✓ | — | boolean / number / text / json (từ generateDataTypeColumnDefs) |
boValue | boolean | — | Giá trị khi dataType=boolean | |
nValue | numeric | — | Giá trị khi dataType=number | |
tValue | text | — | Giá trị khi dataType=text | |
jValue | jsonb | — | Giá trị khi dataType=json | |
name | jsonb (i18n) | ✓ | — | Tên hiển thị |
description | jsonb (i18n) | — | Tuỳ chọn | |
sequence | integer | ✓ | 0 | Thứ tự hiển thị |
status | text | ✓ | ACTIVATED | PolicyFeatureStatuses (ACTIVATED/DEACTIVATED) |
Giá trị đa hình:
LicensingBaseService.DATA_TYPE_RESOLVERSđọc cột khớp vớidataType. Một featureDEACTIVATEDgiải về mặc định theo từng kiểu (false/0/''/null).
Index & ràng buộc: UQ trên (policyId, code); IDX trên policyId, status; FK cascade khi delete.
License
| Thuộc tính | Giá trị |
|---|---|
| Table | licensing.License |
| Source | packages/core/src/models/schemas/licensing/license/schema.ts |
| Soft-delete | có |
| Owner ID column | entityType + entityId (principal đa hình) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
policyId | text | ✓ | — | FK → Policy.id |
key | text | ✓ | — | License key (PREFIX-XXXX-XXXX-XXXX-XXXX) |
name | jsonb (i18n) | ✓ | — | Tên hiển thị |
status | text | ✓ | ACTIVATED | LicenseStatuses (xem enum) |
entityType | text | ✓ | — | LicensePrincipalTypes: Merchant / User |
entityId | text | ✓ | — | Id principal |
certificate | text | — | Envelope cert đã ký mới nhất (base64) | |
override | jsonb | — | { activation, features } — ghi đè mặc định Policy | |
issuedAt | timestamptz | ✓ | now() | — |
startsAt | timestamptz | ✓ | now() | Bắt đầu hiệu lực |
expiresAt | timestamptz | — | null = vĩnh viễn | |
graceExpiresAt | timestamptz | — | Hết cửa sổ grace sau hết hạn | |
lastValidatedAt | timestamptz | — | Cập nhật (fire-and-forget) mỗi lần validate thành công |
Enum status (LicenseStatuses):
| Giá trị | Mô tả |
|---|---|
ACTIVATED | Đang hoạt động và dùng được |
SUSPENDED | Tạm vô hiệu (có thể reinstate) |
EXPIRED | Qua expiresAt + grace (đặt lazy khi validate) |
REVOKED | Chấm dứt vĩnh viễn |
isActive= chỉACTIVATED;isInactive={SUSPENDED, REVOKED, EXPIRED}.
Index & ràng buộc: UQ partial-unique trên key (where deletedAt IS NULL); IDX trên policyId, status, (entityType, entityId), entityId, expiresAt; FK → Policy.id.
Activation
| Thuộc tính | Giá trị |
|---|---|
| Table | licensing.Activation |
| Source | packages/core/src/models/schemas/licensing/activation/schema.ts |
| Soft-delete | có |
| Owner ID column | licenseId |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
licenseId | text | ✓ | — | FK → License.id (cascade delete) |
fingerprint | text | ✓ | — | Fingerprint thiết bị |
label | text | — | Tên thiết bị thân thiện | |
platform | text | — | OS / platform | |
hostname | text | — | Hostname thiết bị | |
ip | text | — | IP thấy gần nhất |
Index & ràng buộc: UQ partial-unique trên (licenseId, fingerprint) (where deletedAt IS NULL) — một activation mỗi thiết bị mỗi license; IDX trên licenseId; FK cascade.
LicenseEvent
| Thuộc tính | Giá trị |
|---|---|
| Table | licensing.LicenseEvent |
| Source | packages/core/src/models/schemas/licensing/license-event/schema.ts |
| Soft-delete | không (audit chỉ-thêm) |
| Owner ID column | licenseId (nullable) |
Trường:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
licenseId | text | — | FK → License.id (ON DELETE SET NULL) | |
event | text | ✓ | — | LicenseEventTypes (created/activated/deactivated/suspended/reinstated/renewed/expired/revoked) |
ip | text | — | — | |
userAgent | text | — | — | |
data | jsonb | ✓ | {} | Payload theo sự kiện |
metadata | jsonb | — | — |
Index: IDX trên licenseId, event; FK ON DELETE SET NULL (event sống sót qua việc xoá license).
3. Bất biến xuyên entity
| Bất biến | Cách thực thi |
|---|---|
Số activation ≤ activation.limit | ActivationService.activate / ValidationService.tryCreateActivation: SELECT License FOR UPDATE → re-COUNT trong tx → rollback nếu >= limit |
Một activation mỗi (licenseId, fingerprint) | Partial-unique index + fast path find-before-create |
| License key duy nhất trong các hàng còn sống | Partial-unique index trên key WHERE deletedAt IS NULL |
Giá trị feature khớp dataType | DATA_TYPE_RESOLVERS đọc cột có kiểu; DEACTIVATED → mặc định theo kiểu |
renew yêu cầu Policy có duration | Lỗi RENEW_PERPETUAL nếu duration là null |
| Chứng chỉ phản ánh trạng thái license hiện tại | Re-publish sau mỗi mutation vòng đời (issue/suspend/reinstate/renew/revoke) và khi lazy expiry |
4. Hành vi Soft-delete
| Entity | Soft-delete | Hard-delete | Ghi chú |
|---|---|---|---|
Policy | có (deletedAt) | qua repo | PolicyController xoá theo status (không hard delete trong app) |
License | có | qua repo | Mặc định đọc deletedAt IS NULL |
Activation | có | cascade từ License (FK) | Deactivate dùng deleteById |
PolicyFeature | không | cascade từ Policy (FK) | Cột deleted bị tắt |
LicenseEvent | không | FK SET NULL khi xoá license | Audit chỉ-thêm |