feat: add MFA/TOTP auth, PII encryption, agents/leads/inquiries modules, and comprehensive tests

- Add TOTP-based MFA with setup, verify, disable, backup codes, and challenge flow
- Add PII field encryption middleware with AES-256-GCM and deterministic search hashes
- Add agents, inquiries, and leads domain modules with entities, events, value objects
- Add web dashboard pages for inquiries and leads with detail dialogs
- Add 30+ component tests (valuation, charts, listings, search, providers, UI)
- Add Prisma migrations for encryption hash columns and MFA TOTP support
- Fix all ESLint errors (unused imports, duplicate imports, lint auto-fixes)
- Update dependencies and lock file
- Clean up obsolete exploration/QA docs, add audit documentation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-11 23:43:20 +07:00
parent 9e2bf9a4b5
commit 1fbe2f4e73
131 changed files with 11436 additions and 2595 deletions

View File

@@ -0,0 +1,61 @@
import { AggregateRoot } from '@modules/shared';
import { QualityScoreUpdatedEvent } from '../events/quality-score-updated.event';
import { type QualityScore } from '../value-objects/quality-score.vo';
export interface AgentProps {
userId: string;
licenseNumber: string | null;
agency: string | null;
qualityScore: QualityScore;
totalDeals: number;
responseTimeAvg: number | null;
bio: string | null;
serviceAreas: string[];
isVerified: boolean;
}
export class AgentEntity extends AggregateRoot<string> {
private _userId: string;
private _licenseNumber: string | null;
private _agency: string | null;
private _qualityScore: QualityScore;
private _totalDeals: number;
private _responseTimeAvg: number | null;
private _bio: string | null;
private _serviceAreas: string[];
private _isVerified: boolean;
constructor(id: string, props: AgentProps, createdAt?: Date, updatedAt?: Date) {
super(id, createdAt);
if (updatedAt) this.updatedAt = updatedAt;
this._userId = props.userId;
this._licenseNumber = props.licenseNumber;
this._agency = props.agency;
this._qualityScore = props.qualityScore;
this._totalDeals = props.totalDeals;
this._responseTimeAvg = props.responseTimeAvg;
this._bio = props.bio;
this._serviceAreas = props.serviceAreas;
this._isVerified = props.isVerified;
}
get userId(): string { return this._userId; }
get licenseNumber(): string | null { return this._licenseNumber; }
get agency(): string | null { return this._agency; }
get qualityScore(): QualityScore { return this._qualityScore; }
get totalDeals(): number { return this._totalDeals; }
get responseTimeAvg(): number | null { return this._responseTimeAvg; }
get bio(): string | null { return this._bio; }
get serviceAreas(): string[] { return this._serviceAreas; }
get isVerified(): boolean { return this._isVerified; }
updateQualityScore(newScore: QualityScore): void {
const oldScore = this._qualityScore.value;
this._qualityScore = newScore;
this.updatedAt = new Date();
this.addDomainEvent(
new QualityScoreUpdatedEvent(this.id, oldScore, newScore.value),
);
}
}