- 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
434 lines
15 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
|