Files
the_order/packages/database/src/eresidency-applications.ts
defiQUG 2633de4d33 feat(eresidency): Complete eResidency service implementation
- Implement credential revocation endpoint with proper database integration
- Fix database row mapping (snake_case to camelCase) for eResidency applications
- Add missing imports (getRiskAssessmentEngine, VeriffKYCProvider, ComplyAdvantageSanctionsProvider)
- Fix environment variable type checking for Veriff and ComplyAdvantage providers
- Add required 'message' field to notification service calls
- Fix risk assessment type mismatches
- Update audit logging to use 'verified' action type (supported by schema)
- Resolve all TypeScript errors and unused variable warnings
- Add TypeScript ignore comments for placeholder implementations
- Temporarily disable security/detect-non-literal-regexp rule due to ESLint 9 compatibility
- Service now builds successfully with no linter errors

All core functionality implemented:
- Application submission and management
- KYC integration (Veriff placeholder)
- Sanctions screening (ComplyAdvantage placeholder)
- Risk assessment engine
- Credential issuance and revocation
- Reviewer console
- Status endpoints
- Auto-issuance service
2025-11-10 19:43:02 -08:00

434 lines
15 KiB
TypeScript

/**
* eResidency Application Database Operations
*/
import { query } from './client';
import {
type eResidencyApplication,
type eCitizenshipApplication,
ApplicationStatus,
} from '@the-order/schemas';
/**
* Map database row to application object
*/
function mapRowToApplication(row: any): eResidencyApplication {
return {
id: row.id,
applicantDid: row.applicant_did || undefined,
email: row.email,
givenName: row.given_name,
familyName: row.family_name,
dateOfBirth: row.date_of_birth ? (row.date_of_birth instanceof Date ? row.date_of_birth.toISOString().split('T')[0] : row.date_of_birth) : undefined,
nationality: row.nationality || undefined,
phone: row.phone || undefined,
address: row.address ? (typeof row.address === 'string' ? JSON.parse(row.address) : row.address) : undefined,
deviceFingerprint: row.device_fingerprint || undefined,
identityDocument: row.identity_document
? typeof row.identity_document === 'string'
? JSON.parse(row.identity_document)
: row.identity_document
: undefined,
selfieLiveness: row.selfie_liveness
? typeof row.selfie_liveness === 'string'
? JSON.parse(row.selfie_liveness)
: row.selfie_liveness
: undefined,
status: row.status as ApplicationStatus,
submittedAt: row.submitted_at ? (row.submitted_at instanceof Date ? row.submitted_at.toISOString() : row.submitted_at) : undefined,
reviewedAt: row.reviewed_at ? (row.reviewed_at instanceof Date ? row.reviewed_at.toISOString() : row.reviewed_at) : undefined,
reviewedBy: row.reviewed_by || undefined,
rejectionReason: row.rejection_reason || undefined,
kycStatus: row.kyc_status || undefined,
sanctionsStatus: row.sanctions_status || undefined,
pepStatus: row.pep_status || undefined,
riskScore: row.risk_score ? parseFloat(String(row.risk_score)) : undefined,
kycResults: row.kyc_results ? (typeof row.kyc_results === 'string' ? JSON.parse(row.kyc_results) : row.kyc_results) : undefined,
sanctionsResults: row.sanctions_results ? (typeof row.sanctions_results === 'string' ? JSON.parse(row.sanctions_results) : row.sanctions_results) : undefined,
riskAssessment: row.risk_assessment ? (typeof row.risk_assessment === 'string' ? JSON.parse(row.risk_assessment) : row.risk_assessment) : undefined,
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : row.updated_at,
};
}
/**
* Create eResidency application
*/
export async function createEResidencyApplication(
application: Omit<eResidencyApplication, 'id' | 'createdAt' | 'updatedAt'>
): Promise<eResidencyApplication> {
const result = await query<eResidencyApplication>(
`INSERT INTO eresidency_applications
(applicant_did, email, given_name, family_name, date_of_birth, nationality, phone, address,
device_fingerprint, identity_document, selfie_liveness, status, kyc_status, sanctions_status, pep_status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
RETURNING *`,
[
application.applicantDid || null,
application.email,
application.givenName,
application.familyName,
application.dateOfBirth || null,
application.nationality || null,
application.phone || null,
application.address ? JSON.stringify(application.address) : null,
application.deviceFingerprint || null,
application.identityDocument ? JSON.stringify(application.identityDocument) : null,
application.selfieLiveness ? JSON.stringify(application.selfieLiveness) : null,
application.status,
application.kycStatus || null,
application.sanctionsStatus || null,
application.pepStatus || null,
]
);
return mapRowToApplication(result.rows[0]!);
}
/**
* Get eResidency application by ID
*/
export async function getEResidencyApplicationById(id: string): Promise<eResidencyApplication | null> {
const result = await query<eResidencyApplication>(
'SELECT * FROM eresidency_applications WHERE id = $1',
[id]
);
if (!result.rows[0]) {
return null;
}
return mapRowToApplication(result.rows[0]);
}
/**
* Update eResidency application
*/
export async function updateEResidencyApplication(
id: string,
updates: {
status?: ApplicationStatus;
kycStatus?: 'pending' | 'passed' | 'failed' | 'requires_edd';
sanctionsStatus?: 'pending' | 'clear' | 'flag';
pepStatus?: 'pending' | 'clear' | 'flag';
riskScore?: number;
kycResults?: unknown;
sanctionsResults?: unknown;
riskAssessment?: unknown;
reviewedAt?: string;
reviewedBy?: string;
rejectionReason?: string;
}
): Promise<eResidencyApplication> {
const fields: string[] = [];
const values: unknown[] = [];
let paramIndex = 1;
if (updates.status !== undefined) {
fields.push(`status = $${paramIndex++}`);
values.push(updates.status);
}
if (updates.kycStatus !== undefined) {
fields.push(`kyc_status = $${paramIndex++}`);
values.push(updates.kycStatus);
}
if (updates.sanctionsStatus !== undefined) {
fields.push(`sanctions_status = $${paramIndex++}`);
values.push(updates.sanctionsStatus);
}
if (updates.pepStatus !== undefined) {
fields.push(`pep_status = $${paramIndex++}`);
values.push(updates.pepStatus);
}
if (updates.riskScore !== undefined) {
fields.push(`risk_score = $${paramIndex++}`);
values.push(updates.riskScore);
}
if (updates.kycResults !== undefined) {
fields.push(`kyc_results = $${paramIndex++}`);
values.push(JSON.stringify(updates.kycResults));
}
if (updates.sanctionsResults !== undefined) {
fields.push(`sanctions_results = $${paramIndex++}`);
values.push(JSON.stringify(updates.sanctionsResults));
}
if (updates.riskAssessment !== undefined) {
fields.push(`risk_assessment = $${paramIndex++}`);
values.push(JSON.stringify(updates.riskAssessment));
}
if (updates.reviewedAt !== undefined) {
fields.push(`reviewed_at = $${paramIndex++}`);
values.push(updates.reviewedAt);
}
if (updates.reviewedBy !== undefined) {
fields.push(`reviewed_by = $${paramIndex++}`);
values.push(updates.reviewedBy);
}
if (updates.rejectionReason !== undefined) {
fields.push(`rejection_reason = $${paramIndex++}`);
values.push(updates.rejectionReason);
}
fields.push(`updated_at = NOW()`);
values.push(id);
const result = await query<eResidencyApplication>(
`UPDATE eresidency_applications SET ${fields.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
values
);
return mapRowToApplication(result.rows[0]!);
}
/**
* Get review queue
*/
export async function getReviewQueue(filters: {
riskBand?: 'low' | 'medium' | 'high';
status?: ApplicationStatus;
assignedTo?: string;
limit?: number;
offset?: number;
}): Promise<{ applications: eResidencyApplication[]; total: number }> {
const conditions: string[] = [];
const params: unknown[] = [];
let paramIndex = 1;
if (filters.riskBand) {
// Map risk band to risk score range
const riskRanges: Record<'low' | 'medium' | 'high', [number, number]> = {
low: [0, 0.3],
medium: [0.3, 0.8],
high: [0.8, 1.0],
};
const [min, max] = riskRanges[filters.riskBand];
conditions.push(`risk_score >= $${paramIndex++} AND risk_score < $${paramIndex++}`);
params.push(min, max);
}
if (filters.status) {
conditions.push(`status = $${paramIndex++}`);
params.push(filters.status);
}
if (filters.assignedTo) {
conditions.push(`reviewed_by = $${paramIndex++}`);
params.push(filters.assignedTo);
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
const limit = filters.limit || 50;
const offset = filters.offset || 0;
// Get total count
const countResult = await query<{ count: string }>(
`SELECT COUNT(*) as count FROM eresidency_applications ${whereClause}`,
params
);
const total = parseInt(countResult.rows[0]?.count || '0', 10);
// Get applications
const result = await query<eResidencyApplication>(
`SELECT * FROM eresidency_applications
${whereClause}
ORDER BY created_at DESC
LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
[...params, limit, offset]
);
const applications = result.rows.map((row) => mapRowToApplication(row));
return { applications, total };
}
/**
* Create eCitizenship application
*/
export async function createECitizenshipApplication(
application: Omit<eCitizenshipApplication, 'id' | 'createdAt' | 'updatedAt'>
): Promise<eCitizenshipApplication> {
const result = await query<any>(
`INSERT INTO ecitizenship_applications
(applicant_did, resident_did, residency_tenure, sponsor_did, service_merit, video_interview,
background_attestations, oath_ceremony, status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING *`,
[
application.applicantDid,
application.residentDid,
application.residencyTenure,
application.sponsorDid || null,
application.serviceMerit ? JSON.stringify(application.serviceMerit) : null,
application.videoInterview ? JSON.stringify(application.videoInterview) : null,
application.backgroundAttestations ? JSON.stringify(application.backgroundAttestations) : null,
application.oathCeremony ? JSON.stringify(application.oathCeremony) : null,
application.status,
]
);
const row: any = result.rows[0]!;
return {
id: row.id,
applicantDid: row.applicant_did,
residentDid: row.resident_did,
residencyTenure: row.residency_tenure || undefined,
sponsorDid: row.sponsor_did || undefined,
serviceMerit: row.service_merit
? typeof row.service_merit === 'string'
? JSON.parse(row.service_merit)
: row.service_merit
: undefined,
videoInterview: row.video_interview
? typeof row.video_interview === 'string'
? JSON.parse(row.video_interview)
: row.video_interview
: undefined,
backgroundAttestations: row.background_attestations
? typeof row.background_attestations === 'string'
? JSON.parse(row.background_attestations)
: row.background_attestations
: undefined,
oathCeremony: row.oath_ceremony
? typeof row.oath_ceremony === 'string'
? JSON.parse(row.oath_ceremony)
: row.oath_ceremony
: undefined,
status: row.status as ApplicationStatus,
submittedAt: row.submitted_at ? (row.submitted_at instanceof Date ? row.submitted_at.toISOString() : row.submitted_at) : undefined,
reviewedAt: row.reviewed_at ? (row.reviewed_at instanceof Date ? row.reviewed_at.toISOString() : row.reviewed_at) : undefined,
reviewedBy: row.reviewed_by || undefined,
rejectionReason: row.rejection_reason || undefined,
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : row.updated_at,
};
}
/**
* Get eCitizenship application by ID
*/
export async function getECitizenshipApplicationById(id: string): Promise<eCitizenshipApplication | null> {
const result = await query<any>(
'SELECT * FROM ecitizenship_applications WHERE id = $1',
[id]
);
if (!result.rows[0]) {
return null;
}
const row: any = result.rows[0]!;
return {
id: row.id,
applicantDid: row.applicant_did,
residentDid: row.resident_did,
residencyTenure: row.residency_tenure || undefined,
sponsorDid: row.sponsor_did || undefined,
serviceMerit: row.service_merit
? typeof row.service_merit === 'string'
? JSON.parse(row.service_merit)
: row.service_merit
: undefined,
videoInterview: row.video_interview
? typeof row.video_interview === 'string'
? JSON.parse(row.video_interview)
: row.video_interview
: undefined,
backgroundAttestations: row.background_attestations
? typeof row.background_attestations === 'string'
? JSON.parse(row.background_attestations)
: row.background_attestations
: undefined,
oathCeremony: row.oath_ceremony
? typeof row.oath_ceremony === 'string'
? JSON.parse(row.oath_ceremony)
: row.oath_ceremony
: undefined,
status: row.status as ApplicationStatus,
submittedAt: row.submitted_at ? (row.submitted_at instanceof Date ? row.submitted_at.toISOString() : row.submitted_at) : undefined,
reviewedAt: row.reviewed_at ? (row.reviewed_at instanceof Date ? row.reviewed_at.toISOString() : row.reviewed_at) : undefined,
reviewedBy: row.reviewed_by || undefined,
rejectionReason: row.rejection_reason || undefined,
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : row.updated_at,
};
}
/**
* Update eCitizenship application
*/
export async function updateECitizenshipApplication(
id: string,
updates: {
status?: ApplicationStatus;
reviewedAt?: string;
reviewedBy?: string;
rejectionReason?: string;
}
): Promise<eCitizenshipApplication> {
const fields: string[] = [];
const values: unknown[] = [];
let paramIndex = 1;
if (updates.status !== undefined) {
fields.push(`status = $${paramIndex++}`);
values.push(updates.status);
}
if (updates.reviewedAt !== undefined) {
fields.push(`reviewed_at = $${paramIndex++}`);
values.push(updates.reviewedAt);
}
if (updates.reviewedBy !== undefined) {
fields.push(`reviewed_by = $${paramIndex++}`);
values.push(updates.reviewedBy);
}
if (updates.rejectionReason !== undefined) {
fields.push(`rejection_reason = $${paramIndex++}`);
values.push(updates.rejectionReason);
}
fields.push(`updated_at = NOW()`);
values.push(id);
const result = await query<eCitizenshipApplication>(
`UPDATE ecitizenship_applications SET ${fields.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
values
);
const row: any = result.rows[0]!;
return {
id: row.id,
applicantDid: row.applicant_did,
residentDid: row.resident_did,
residencyTenure: row.residency_tenure || undefined,
sponsorDid: row.sponsor_did || undefined,
serviceMerit: row.service_merit
? typeof row.service_merit === 'string'
? JSON.parse(row.service_merit)
: row.service_merit
: undefined,
videoInterview: row.video_interview
? typeof row.video_interview === 'string'
? JSON.parse(row.video_interview)
: row.video_interview
: undefined,
backgroundAttestations: row.background_attestations
? typeof row.background_attestations === 'string'
? JSON.parse(row.background_attestations)
: row.background_attestations
: undefined,
oathCeremony: row.oath_ceremony
? typeof row.oath_ceremony === 'string'
? JSON.parse(row.oath_ceremony)
: row.oath_ceremony
: undefined,
status: row.status as ApplicationStatus,
submittedAt: row.submitted_at ? (row.submitted_at instanceof Date ? row.submitted_at.toISOString() : row.submitted_at) : undefined,
reviewedAt: row.reviewed_at ? (row.reviewed_at instanceof Date ? row.reviewed_at.toISOString() : row.reviewed_at) : undefined,
reviewedBy: row.reviewed_by || undefined,
rejectionReason: row.rejection_reason || undefined,
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : row.updated_at,
};
}