@axova/shared 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CONFIGURATION_GUIDE.md +1 -0
  2. package/README.md +384 -0
  3. package/SCHEMA_ORGANIZATION.md +209 -0
  4. package/dist/configs/index.d.ts +85 -0
  5. package/dist/configs/index.js +555 -0
  6. package/dist/events/kafka.d.ts +40 -0
  7. package/dist/events/kafka.js +311 -0
  8. package/dist/index.d.ts +13 -0
  9. package/dist/index.js +41 -0
  10. package/dist/interfaces/customer-events.d.ts +85 -0
  11. package/dist/interfaces/customer-events.js +2 -0
  12. package/dist/interfaces/inventory-events.d.ts +453 -0
  13. package/dist/interfaces/inventory-events.js +3 -0
  14. package/dist/interfaces/inventory-types.d.ts +894 -0
  15. package/dist/interfaces/inventory-types.js +3 -0
  16. package/dist/interfaces/order-events.d.ts +320 -0
  17. package/dist/interfaces/order-events.js +3 -0
  18. package/dist/lib/auditLogger.d.ts +162 -0
  19. package/dist/lib/auditLogger.js +626 -0
  20. package/dist/lib/authOrganization.d.ts +24 -0
  21. package/dist/lib/authOrganization.js +110 -0
  22. package/dist/lib/db.d.ts +6 -0
  23. package/dist/lib/db.js +88 -0
  24. package/dist/middleware/serviceAuth.d.ts +60 -0
  25. package/dist/middleware/serviceAuth.js +272 -0
  26. package/dist/middleware/storeOwnership.d.ts +15 -0
  27. package/dist/middleware/storeOwnership.js +156 -0
  28. package/dist/middleware/storeValidationMiddleware.d.ts +44 -0
  29. package/dist/middleware/storeValidationMiddleware.js +180 -0
  30. package/dist/middleware/userAuth.d.ts +27 -0
  31. package/dist/middleware/userAuth.js +218 -0
  32. package/dist/schemas/admin/admin-schema.d.ts +741 -0
  33. package/dist/schemas/admin/admin-schema.js +111 -0
  34. package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +648 -0
  35. package/dist/schemas/ai-moderation/ai-moderation-schema.js +88 -0
  36. package/dist/schemas/common/common-schemas.d.ts +436 -0
  37. package/dist/schemas/common/common-schemas.js +94 -0
  38. package/dist/schemas/compliance/compliance-schema.d.ts +3388 -0
  39. package/dist/schemas/compliance/compliance-schema.js +472 -0
  40. package/dist/schemas/compliance/kyc-schema.d.ts +2642 -0
  41. package/dist/schemas/compliance/kyc-schema.js +361 -0
  42. package/dist/schemas/customer/customer-schema.d.ts +2727 -0
  43. package/dist/schemas/customer/customer-schema.js +399 -0
  44. package/dist/schemas/index.d.ts +27 -0
  45. package/dist/schemas/index.js +138 -0
  46. package/dist/schemas/inventory/inventory-tables.d.ts +9476 -0
  47. package/dist/schemas/inventory/inventory-tables.js +1470 -0
  48. package/dist/schemas/inventory/lot-tables.d.ts +3281 -0
  49. package/dist/schemas/inventory/lot-tables.js +608 -0
  50. package/dist/schemas/order/order-schema.d.ts +5825 -0
  51. package/dist/schemas/order/order-schema.js +954 -0
  52. package/dist/schemas/product/discount-relations.d.ts +15 -0
  53. package/dist/schemas/product/discount-relations.js +34 -0
  54. package/dist/schemas/product/discount-schema.d.ts +1975 -0
  55. package/dist/schemas/product/discount-schema.js +297 -0
  56. package/dist/schemas/product/product-relations.d.ts +41 -0
  57. package/dist/schemas/product/product-relations.js +133 -0
  58. package/dist/schemas/product/product-schema.d.ts +4544 -0
  59. package/dist/schemas/product/product-schema.js +671 -0
  60. package/dist/schemas/store/store-audit-schema.d.ts +4135 -0
  61. package/dist/schemas/store/store-audit-schema.js +556 -0
  62. package/dist/schemas/store/store-schema.d.ts +3100 -0
  63. package/dist/schemas/store/store-schema.js +381 -0
  64. package/dist/schemas/store/store-settings-schema.d.ts +665 -0
  65. package/dist/schemas/store/store-settings-schema.js +141 -0
  66. package/dist/schemas/types.d.ts +50 -0
  67. package/dist/schemas/types.js +3 -0
  68. package/dist/types/events.d.ts +2396 -0
  69. package/dist/types/events.js +505 -0
  70. package/dist/utils/errorHandler.d.ts +12 -0
  71. package/dist/utils/errorHandler.js +36 -0
  72. package/dist/utils/subdomain.d.ts +6 -0
  73. package/dist/utils/subdomain.js +20 -0
  74. package/nul +8 -0
  75. package/package.json +43 -0
  76. package/src/configs/index.ts +654 -0
  77. package/src/events/kafka.ts +429 -0
  78. package/src/index.ts +26 -0
  79. package/src/interfaces/customer-events.ts +106 -0
  80. package/src/interfaces/inventory-events.ts +545 -0
  81. package/src/interfaces/inventory-types.ts +1004 -0
  82. package/src/interfaces/order-events.ts +381 -0
  83. package/src/lib/auditLogger.ts +1117 -0
  84. package/src/lib/authOrganization.ts +153 -0
  85. package/src/lib/db.ts +64 -0
  86. package/src/middleware/serviceAuth.ts +328 -0
  87. package/src/middleware/storeOwnership.ts +199 -0
  88. package/src/middleware/storeValidationMiddleware.ts +247 -0
  89. package/src/middleware/userAuth.ts +248 -0
  90. package/src/schemas/admin/admin-schema.ts +208 -0
  91. package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -0
  92. package/src/schemas/common/common-schemas.ts +108 -0
  93. package/src/schemas/compliance/compliance-schema.ts +927 -0
  94. package/src/schemas/compliance/kyc-schema.ts +649 -0
  95. package/src/schemas/customer/customer-schema.ts +576 -0
  96. package/src/schemas/index.ts +189 -0
  97. package/src/schemas/inventory/inventory-tables.ts +1927 -0
  98. package/src/schemas/inventory/lot-tables.ts +799 -0
  99. package/src/schemas/order/order-schema.ts +1400 -0
  100. package/src/schemas/product/discount-relations.ts +44 -0
  101. package/src/schemas/product/discount-schema.ts +464 -0
  102. package/src/schemas/product/product-relations.ts +187 -0
  103. package/src/schemas/product/product-schema.ts +955 -0
  104. package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
  105. package/src/schemas/store/store-audit-schema.ts +1257 -0
  106. package/src/schemas/store/store-schema.ts +661 -0
  107. package/src/schemas/store/store-settings-schema.ts +231 -0
  108. package/src/schemas/types.ts +67 -0
  109. package/src/types/events.ts +646 -0
  110. package/src/utils/errorHandler.ts +44 -0
  111. package/src/utils/subdomain.ts +19 -0
  112. package/tsconfig.json +21 -0
@@ -0,0 +1,649 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import { relations } from "drizzle-orm";
3
+ import {
4
+ boolean,
5
+ decimal,
6
+ index,
7
+ integer,
8
+ jsonb,
9
+ pgTable,
10
+ text,
11
+ timestamp,
12
+ unique,
13
+ varchar,
14
+ } from "drizzle-orm/pg-core";
15
+ import { storeCompliance } from "./compliance-schema";
16
+
17
+ // Store KYC Records - Main table for KYC verification per store
18
+ export const storeKyc = pgTable(
19
+ "store_kyc",
20
+ {
21
+ id: text("id")
22
+ .primaryKey()
23
+ .$defaultFn(() => createId()),
24
+ storeId: text("store_id").notNull(),
25
+ userId: text("user_id").notNull(),
26
+
27
+ // Didit Integration
28
+ diditSessionId: text("didit_session_id"),
29
+ diditWorkflowId: text("didit_workflow_id"),
30
+ diditVerificationUrl: text("didit_verification_url"),
31
+
32
+ // KYC Status
33
+ kycStatus: varchar("kyc_status", { length: 30 })
34
+ .notNull()
35
+ .default("NOT_STARTED")
36
+ .$type<
37
+ | "NOT_STARTED"
38
+ | "IN_PROGRESS"
39
+ | "PENDING_REVIEW"
40
+ | "APPROVED"
41
+ | "REJECTED"
42
+ | "REQUIRES_RESUBMISSION"
43
+ | "EXPIRED"
44
+ | "CANCELLED"
45
+ >(),
46
+
47
+ kycLevel: varchar("kyc_level", { length: 20 })
48
+ .$type<"BASIC" | "INTERMEDIATE" | "ADVANCED" | "ENHANCED">()
49
+ .default("BASIC"),
50
+
51
+ verificationTier: varchar("verification_tier", { length: 30 })
52
+ .$type<
53
+ | "TIER_0_UNVERIFIED"
54
+ | "TIER_1_EMAIL_PHONE"
55
+ | "TIER_2_BASIC_ID"
56
+ | "TIER_3_ENHANCED_ID"
57
+ | "TIER_4_FULL_KYC"
58
+ >()
59
+ .default("TIER_0_UNVERIFIED"),
60
+
61
+ // Verification Types Completed
62
+ verificationsCompleted: jsonb("verifications_completed")
63
+ .$type<{
64
+ idVerification?: boolean;
65
+ faceMatch?: boolean;
66
+ ageVerification?: boolean;
67
+ proofOfAddress?: boolean;
68
+ amlScreening?: boolean;
69
+ databaseValidation?: boolean;
70
+ phoneVerification?: boolean;
71
+ emailVerification?: boolean;
72
+ passiveLiveness?: boolean;
73
+ }>()
74
+ .default({}),
75
+
76
+ // Verification Scores
77
+ verificationScore: decimal("verification_score", {
78
+ precision: 5,
79
+ scale: 2,
80
+ }), // 0-100
81
+ confidenceScore: decimal("confidence_score", { precision: 5, scale: 2 }), // 0-100
82
+ riskScore: decimal("risk_score", { precision: 5, scale: 2 }), // 0-100
83
+
84
+ // Risk Assessment
85
+ riskLevel: varchar("risk_level", { length: 20 })
86
+ .$type<"VERY_LOW" | "LOW" | "MEDIUM" | "HIGH" | "VERY_HIGH" | "CRITICAL">()
87
+ .default("MEDIUM"),
88
+
89
+ riskFlags: jsonb("risk_flags")
90
+ .$type<{
91
+ sanctionsList?: boolean;
92
+ pepList?: boolean;
93
+ adverseMedia?: boolean;
94
+ fraudIndicators?: boolean;
95
+ documentIssues?: boolean;
96
+ faceMismatch?: boolean;
97
+ livenessCheckFailed?: boolean;
98
+ ageRestriction?: boolean;
99
+ jurisdictionRestriction?: boolean;
100
+ }>()
101
+ .default({}),
102
+
103
+ // Personal Information (from Didit)
104
+ personalInfo: jsonb("personal_info")
105
+ .$type<{
106
+ firstName?: string;
107
+ lastName?: string;
108
+ middleName?: string;
109
+ dateOfBirth?: string;
110
+ nationality?: string;
111
+ countryOfResidence?: string;
112
+ gender?: string;
113
+ }>()
114
+ .default({}),
115
+
116
+ // Document Information
117
+ documentInfo: jsonb("document_info")
118
+ .$type<{
119
+ documentType?: string; // PASSPORT, ID_CARD, DRIVER_LICENSE, etc.
120
+ documentNumber?: string;
121
+ issuingCountry?: string;
122
+ issueDate?: string;
123
+ expiryDate?: string;
124
+ documentImages?: string[]; // URLs to encrypted document images
125
+ }>()
126
+ .default({}),
127
+
128
+ // Address Information
129
+ addressInfo: jsonb("address_info")
130
+ .$type<{
131
+ fullAddress?: string;
132
+ street?: string;
133
+ city?: string;
134
+ state?: string;
135
+ postalCode?: string;
136
+ country?: string;
137
+ verificationDate?: string;
138
+ proofOfAddressType?: string; // UTILITY_BILL, BANK_STATEMENT, etc.
139
+ }>()
140
+ .default({}),
141
+
142
+ // Biometric Data (hashed/encrypted references)
143
+ biometricData: jsonb("biometric_data")
144
+ .$type<{
145
+ faceImageUrl?: string;
146
+ faceMatchScore?: number;
147
+ livenessScore?: number;
148
+ livenessCheckPassed?: boolean;
149
+ biometricHash?: string; // For deduplication
150
+ }>()
151
+ .default({}),
152
+
153
+ // AML/Sanctions Screening Results
154
+ amlResults: jsonb("aml_results")
155
+ .$type<{
156
+ screeningDate?: string;
157
+ sanctionsMatch?: boolean;
158
+ pepMatch?: boolean;
159
+ adverseMediaMatch?: boolean;
160
+ matchDetails?: Array<{
161
+ listName: string;
162
+ matchScore: number;
163
+ matchType: string;
164
+ details: string;
165
+ }>;
166
+ clearanceLevel?: "CLEAR" | "REVIEW_REQUIRED" | "REJECTED";
167
+ }>()
168
+ .default({}),
169
+
170
+ // Phone & Email Verification
171
+ contactVerification: jsonb("contact_verification")
172
+ .$type<{
173
+ phoneNumber?: string;
174
+ phoneVerified?: boolean;
175
+ phoneVerifiedAt?: string;
176
+ email?: string;
177
+ emailVerified?: boolean;
178
+ emailVerifiedAt?: string;
179
+ }>()
180
+ .default({}),
181
+
182
+ // Submission & Review Tracking
183
+ submittedAt: timestamp("submitted_at", { withTimezone: true }),
184
+ submittedBy: text("submitted_by"),
185
+ reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
186
+ reviewedBy: text("reviewed_by"),
187
+ reviewerNotes: text("reviewer_notes"),
188
+
189
+ approvedAt: timestamp("approved_at", { withTimezone: true }),
190
+ approvedBy: text("approved_by"),
191
+ rejectedAt: timestamp("rejected_at", { withTimezone: true }),
192
+ rejectedBy: text("rejected_by"),
193
+ rejectionReason: text("rejection_reason"),
194
+
195
+ // Expiry & Renewal
196
+ expiryDate: timestamp("expiry_date", { withTimezone: true }),
197
+ requiresRenewal: boolean("requires_renewal").notNull().default(false),
198
+ lastRenewalDate: timestamp("last_renewal_date", { withTimezone: true }),
199
+ nextRenewalDate: timestamp("next_renewal_date", { withTimezone: true }),
200
+
201
+ // Compliance & Regulations
202
+ complianceChecks: jsonb("compliance_checks")
203
+ .$type<{
204
+ gdprCompliant?: boolean;
205
+ ccpaCompliant?: boolean;
206
+ kycCompliant?: boolean;
207
+ amlCompliant?: boolean;
208
+ ofacCompliant?: boolean;
209
+ jurisdictionCompliant?: boolean;
210
+ complianceNotes?: string;
211
+ }>()
212
+ .default({}),
213
+
214
+ regulatoryStatus: varchar("regulatory_status", { length: 30 })
215
+ .$type<"COMPLIANT" | "PENDING" | "NON_COMPLIANT" | "UNDER_REVIEW">()
216
+ .default("PENDING"),
217
+
218
+ // Verification Attempts & History
219
+ attemptCount: integer("attempt_count").notNull().default(0),
220
+ lastAttemptAt: timestamp("last_attempt_at", { withTimezone: true }),
221
+ maxAttemptsReached: boolean("max_attempts_reached")
222
+ .notNull()
223
+ .default(false),
224
+
225
+ // Metadata & Configuration
226
+ verificationMetadata: jsonb("verification_metadata")
227
+ .$type<{
228
+ ipAddress?: string;
229
+ userAgent?: string;
230
+ deviceId?: string;
231
+ location?: {
232
+ country?: string;
233
+ city?: string;
234
+ coordinates?: [number, number];
235
+ };
236
+ sessionDuration?: number;
237
+ verificationMethod?: string;
238
+ additionalInfo?: Record<string, unknown>;
239
+ }>()
240
+ .default({}),
241
+
242
+ // Internal Notes & Flags
243
+ internalNotes: text("internal_notes"),
244
+ flags: jsonb("flags")
245
+ .$type<{
246
+ manualReviewRequired?: boolean;
247
+ escalated?: boolean;
248
+ fraudSuspected?: boolean;
249
+ documentExpired?: boolean;
250
+ duplicateDetected?: boolean;
251
+ highRisk?: boolean;
252
+ vipCustomer?: boolean;
253
+ }>()
254
+ .default({}),
255
+
256
+ // Store-specific KYC Requirements
257
+ storeKycRequirements: jsonb("store_kyc_requirements")
258
+ .$type<{
259
+ minimumTier?: string;
260
+ requiredVerifications?: string[];
261
+ additionalDocuments?: string[];
262
+ customRequirements?: Record<string, unknown>;
263
+ }>()
264
+ .default({}),
265
+
266
+ createdAt: timestamp("created_at", { withTimezone: true })
267
+ .defaultNow()
268
+ .notNull(),
269
+ updatedAt: timestamp("updated_at", { withTimezone: true })
270
+ .defaultNow()
271
+ .notNull(),
272
+ },
273
+ (table) => ({
274
+ storeIdIndex: index("idx_kyc_store_id").on(table.storeId),
275
+ userIdIndex: index("idx_kyc_user_id").on(table.userId),
276
+ statusIndex: index("idx_kyc_status").on(table.kycStatus),
277
+ diditSessionIndex: index("idx_kyc_didit_session").on(table.diditSessionId),
278
+ tierIndex: index("idx_kyc_tier").on(table.verificationTier),
279
+ riskLevelIndex: index("idx_kyc_risk_level").on(table.riskLevel),
280
+ expiryIndex: index("idx_kyc_expiry").on(
281
+ table.expiryDate,
282
+ table.requiresRenewal,
283
+ ),
284
+ storeUserUnique: unique("unique_store_user_kyc").on(
285
+ table.storeId,
286
+ table.userId,
287
+ ),
288
+ }),
289
+ );
290
+
291
+ // KYC Verification History - Track all verification attempts and changes
292
+ export const kycVerificationHistory = pgTable(
293
+ "kyc_verification_history",
294
+ {
295
+ id: text("id")
296
+ .primaryKey()
297
+ .$defaultFn(() => createId()),
298
+ kycId: text("kyc_id")
299
+ .notNull()
300
+ .references(() => storeKyc.id, { onDelete: "cascade" }),
301
+ storeId: text("store_id").notNull(),
302
+ userId: text("user_id").notNull(),
303
+
304
+ // Event Details
305
+ eventType: varchar("event_type", { length: 50 })
306
+ .notNull()
307
+ .$type<
308
+ | "VERIFICATION_STARTED"
309
+ | "VERIFICATION_SUBMITTED"
310
+ | "STATUS_CHANGED"
311
+ | "DOCUMENT_UPLOADED"
312
+ | "REVIEW_COMPLETED"
313
+ | "APPROVED"
314
+ | "REJECTED"
315
+ | "RESUBMISSION_REQUESTED"
316
+ | "EXPIRED"
317
+ | "RENEWED"
318
+ | "CANCELLED"
319
+ >(),
320
+
321
+ previousStatus: varchar("previous_status", { length: 30 }),
322
+ newStatus: varchar("new_status", { length: 30 }),
323
+
324
+ // Change Details
325
+ changeDetails: jsonb("change_details").$type<{
326
+ field?: string;
327
+ oldValue?: unknown;
328
+ newValue?: unknown;
329
+ reason?: string;
330
+ performedBy?: string;
331
+ metadata?: Record<string, unknown>;
332
+ }>(),
333
+
334
+ diditEventData: jsonb("didit_event_data").$type<Record<string, unknown>>(),
335
+
336
+ performedBy: text("performed_by"),
337
+ performedByType: varchar("performed_by_type", { length: 20 }).$type<
338
+ "SYSTEM" | "USER" | "ADMIN" | "DIDIT_WEBHOOK" | "AUTOMATED_PROCESS"
339
+ >(),
340
+
341
+ notes: text("notes"),
342
+
343
+ createdAt: timestamp("created_at", { withTimezone: true })
344
+ .defaultNow()
345
+ .notNull(),
346
+ },
347
+ (table) => ({
348
+ kycIdIndex: index("idx_kyc_history_kyc_id").on(table.kycId),
349
+ storeIdIndex: index("idx_kyc_history_store_id").on(table.storeId),
350
+ eventTypeIndex: index("idx_kyc_history_event_type").on(table.eventType),
351
+ createdAtIndex: index("idx_kyc_history_created_at").on(table.createdAt),
352
+ }),
353
+ );
354
+
355
+ // KYC Documents - Store references to verification documents
356
+ export const kycDocuments = pgTable(
357
+ "kyc_documents",
358
+ {
359
+ id: text("id")
360
+ .primaryKey()
361
+ .$defaultFn(() => createId()),
362
+ kycId: text("kyc_id")
363
+ .notNull()
364
+ .references(() => storeKyc.id, { onDelete: "cascade" }),
365
+ storeId: text("store_id").notNull(),
366
+ userId: text("user_id").notNull(),
367
+
368
+ // Document Classification
369
+ documentType: varchar("document_type", { length: 50 })
370
+ .notNull()
371
+ .$type<
372
+ | "PASSPORT"
373
+ | "ID_CARD"
374
+ | "DRIVER_LICENSE"
375
+ | "RESIDENCE_PERMIT"
376
+ | "PROOF_OF_ADDRESS"
377
+ | "BANK_STATEMENT"
378
+ | "UTILITY_BILL"
379
+ | "SELFIE"
380
+ | "FACE_PHOTO"
381
+ | "OTHER"
382
+ >(),
383
+
384
+ documentCategory: varchar("document_category", { length: 30 }).$type<
385
+ "IDENTITY" | "ADDRESS" | "BIOMETRIC" | "FINANCIAL" | "ADDITIONAL"
386
+ >(),
387
+
388
+ // Document Details
389
+ fileName: varchar("file_name", { length: 255 }),
390
+ fileUrl: text("file_url").notNull(),
391
+ encryptedFileUrl: text("encrypted_file_url"),
392
+ fileSize: integer("file_size"),
393
+ mimeType: varchar("mime_type", { length: 50 }),
394
+
395
+ // Document Verification
396
+ verificationStatus: varchar("verification_status", { length: 30 })
397
+ .notNull()
398
+ .default("PENDING")
399
+ .$type<
400
+ | "PENDING"
401
+ | "VERIFIED"
402
+ | "REJECTED"
403
+ | "EXPIRED"
404
+ | "REQUIRES_RESUBMISSION"
405
+ >(),
406
+
407
+ verifiedBy: text("verified_by"),
408
+ verifiedAt: timestamp("verified_at", { withTimezone: true }),
409
+ verificationNotes: text("verification_notes"),
410
+
411
+ // Document Metadata
412
+ documentMetadata: jsonb("document_metadata").$type<{
413
+ documentNumber?: string;
414
+ issueDate?: string;
415
+ expiryDate?: string;
416
+ issuingAuthority?: string;
417
+ issuingCountry?: string;
418
+ extractedData?: Record<string, unknown>;
419
+ ocrConfidence?: number;
420
+ }>(),
421
+
422
+ // Security & Compliance
423
+ isEncrypted: boolean("is_encrypted").notNull().default(true),
424
+ encryptionMethod: varchar("encryption_method", { length: 50 }),
425
+ accessLog: jsonb("access_log")
426
+ .$type<
427
+ Array<{
428
+ accessedBy: string;
429
+ accessedAt: string;
430
+ purpose: string;
431
+ ipAddress?: string;
432
+ }>
433
+ >()
434
+ .default([]),
435
+
436
+ // Retention & Deletion
437
+ retentionPeriod: integer("retention_period"), // days
438
+ scheduledDeletionDate: timestamp("scheduled_deletion_date", {
439
+ withTimezone: true,
440
+ }),
441
+ deletedAt: timestamp("deleted_at", { withTimezone: true }),
442
+ deletedBy: text("deleted_by"),
443
+
444
+ uploadedAt: timestamp("uploaded_at", { withTimezone: true }).defaultNow(),
445
+ uploadedBy: text("uploaded_by"),
446
+
447
+ createdAt: timestamp("created_at", { withTimezone: true })
448
+ .defaultNow()
449
+ .notNull(),
450
+ updatedAt: timestamp("updated_at", { withTimezone: true })
451
+ .defaultNow()
452
+ .notNull(),
453
+ },
454
+ (table) => ({
455
+ kycIdIndex: index("idx_kyc_docs_kyc_id").on(table.kycId),
456
+ storeIdIndex: index("idx_kyc_docs_store_id").on(table.storeId),
457
+ documentTypeIndex: index("idx_kyc_docs_type").on(table.documentType),
458
+ verificationStatusIndex: index("idx_kyc_docs_verification_status").on(
459
+ table.verificationStatus,
460
+ ),
461
+ scheduledDeletionIndex: index("idx_kyc_docs_scheduled_deletion").on(
462
+ table.scheduledDeletionDate,
463
+ ),
464
+ }),
465
+ );
466
+
467
+ // KYC Analytics - Track KYC metrics and analytics per store
468
+ export const kycAnalytics = pgTable(
469
+ "kyc_analytics",
470
+ {
471
+ id: text("id")
472
+ .primaryKey()
473
+ .$defaultFn(() => createId()),
474
+ storeId: text("store_id").notNull().unique(),
475
+
476
+ // Verification Statistics
477
+ totalVerifications: integer("total_verifications").notNull().default(0),
478
+ approvedVerifications: integer("approved_verifications")
479
+ .notNull()
480
+ .default(0),
481
+ rejectedVerifications: integer("rejected_verifications")
482
+ .notNull()
483
+ .default(0),
484
+ pendingVerifications: integer("pending_verifications").notNull().default(0),
485
+
486
+ // Success Rates
487
+ approvalRate: decimal("approval_rate", { precision: 5, scale: 2 }), // percentage
488
+ rejectionRate: decimal("rejection_rate", { precision: 5, scale: 2 }),
489
+ averageVerificationTime: integer("average_verification_time"), // minutes
490
+
491
+ // Risk Distribution
492
+ lowRiskCount: integer("low_risk_count").notNull().default(0),
493
+ mediumRiskCount: integer("medium_risk_count").notNull().default(0),
494
+ highRiskCount: integer("high_risk_count").notNull().default(0),
495
+ criticalRiskCount: integer("critical_risk_count").notNull().default(0),
496
+
497
+ // Tier Distribution
498
+ tier0Count: integer("tier0_count").notNull().default(0),
499
+ tier1Count: integer("tier1_count").notNull().default(0),
500
+ tier2Count: integer("tier2_count").notNull().default(0),
501
+ tier3Count: integer("tier3_count").notNull().default(0),
502
+ tier4Count: integer("tier4_count").notNull().default(0),
503
+
504
+ // Document Statistics
505
+ totalDocumentsUploaded: integer("total_documents_uploaded")
506
+ .notNull()
507
+ .default(0),
508
+ averageDocumentsPerVerification: decimal(
509
+ "average_documents_per_verification",
510
+ { precision: 5, scale: 2 },
511
+ ),
512
+
513
+ // Compliance Statistics
514
+ amlScreeningsCompleted: integer("aml_screenings_completed")
515
+ .notNull()
516
+ .default(0),
517
+ sanctionsMatchesFound: integer("sanctions_matches_found")
518
+ .notNull()
519
+ .default(0),
520
+ pepMatchesFound: integer("pep_matches_found").notNull().default(0),
521
+
522
+ // Fraud Indicators
523
+ fraudSuspectedCount: integer("fraud_suspected_count").notNull().default(0),
524
+ duplicateDetectedCount: integer("duplicate_detected_count")
525
+ .notNull()
526
+ .default(0),
527
+
528
+ // Performance Metrics
529
+ averageConfidenceScore: decimal("average_confidence_score", {
530
+ precision: 5,
531
+ scale: 2,
532
+ }),
533
+ averageRiskScore: decimal("average_risk_score", { precision: 5, scale: 2 }),
534
+
535
+ // Time-based Metrics
536
+ verificationsThisMonth: integer("verifications_this_month")
537
+ .notNull()
538
+ .default(0),
539
+ verificationsLastMonth: integer("verifications_last_month")
540
+ .notNull()
541
+ .default(0),
542
+
543
+ lastCalculatedAt: timestamp("last_calculated_at", { withTimezone: true }),
544
+
545
+ createdAt: timestamp("created_at", { withTimezone: true })
546
+ .defaultNow()
547
+ .notNull(),
548
+ updatedAt: timestamp("updated_at", { withTimezone: true })
549
+ .defaultNow()
550
+ .notNull(),
551
+ },
552
+ (table) => ({
553
+ storeIdIndex: index("idx_kyc_analytics_store_id").on(table.storeId),
554
+ }),
555
+ );
556
+
557
+ // KYC Webhooks Log - Track all Didit webhook events
558
+ export const kycWebhooksLog = pgTable(
559
+ "kyc_webhooks_log",
560
+ {
561
+ id: text("id")
562
+ .primaryKey()
563
+ .$defaultFn(() => createId()),
564
+ diditSessionId: text("didit_session_id"),
565
+ storeId: text("store_id"),
566
+ kycId: text("kyc_id").references(() => storeKyc.id),
567
+
568
+ // Webhook Event Details
569
+ eventType: varchar("event_type", { length: 50 })
570
+ .notNull()
571
+ .$type<"status.updated" | "data.updated" | "session.completed">(),
572
+
573
+ webhookPayload: jsonb("webhook_payload")
574
+ .notNull()
575
+ .$type<Record<string, unknown>>(),
576
+
577
+ // Processing Status
578
+ processingStatus: varchar("processing_status", { length: 30 })
579
+ .notNull()
580
+ .default("PENDING")
581
+ .$type<"PENDING" | "PROCESSING" | "PROCESSED" | "FAILED" | "RETRYING">(),
582
+
583
+ processedAt: timestamp("processed_at", { withTimezone: true }),
584
+ failureReason: text("failure_reason"),
585
+ retryCount: integer("retry_count").notNull().default(0),
586
+ maxRetriesReached: boolean("max_retries_reached").notNull().default(false),
587
+
588
+ // Request Details
589
+ requestHeaders: jsonb("request_headers").$type<Record<string, string>>(),
590
+ requestIp: varchar("request_ip", { length: 45 }),
591
+ signatureValid: boolean("signature_valid"),
592
+
593
+ createdAt: timestamp("created_at", { withTimezone: true })
594
+ .defaultNow()
595
+ .notNull(),
596
+ },
597
+ (table) => ({
598
+ sessionIdIndex: index("idx_kyc_webhooks_session_id").on(
599
+ table.diditSessionId,
600
+ ),
601
+ kycIdIndex: index("idx_kyc_webhooks_kyc_id").on(table.kycId),
602
+ processingStatusIndex: index("idx_kyc_webhooks_processing_status").on(
603
+ table.processingStatus,
604
+ ),
605
+ createdAtIndex: index("idx_kyc_webhooks_created_at").on(table.createdAt),
606
+ }),
607
+ );
608
+
609
+ // Relations
610
+ export const storeKycRelations = relations(storeKyc, ({ one, many }) => ({
611
+ compliance: one(storeCompliance, {
612
+ fields: [storeKyc.storeId],
613
+ references: [storeCompliance.storeId],
614
+ }),
615
+ history: many(kycVerificationHistory),
616
+ documents: many(kycDocuments),
617
+ webhooks: many(kycWebhooksLog),
618
+ }));
619
+
620
+ export const kycVerificationHistoryRelations = relations(
621
+ kycVerificationHistory,
622
+ ({ one }) => ({
623
+ kyc: one(storeKyc, {
624
+ fields: [kycVerificationHistory.kycId],
625
+ references: [storeKyc.id],
626
+ }),
627
+ }),
628
+ );
629
+
630
+ export const kycDocumentsRelations = relations(kycDocuments, ({ one }) => ({
631
+ kyc: one(storeKyc, {
632
+ fields: [kycDocuments.kycId],
633
+ references: [storeKyc.id],
634
+ }),
635
+ }));
636
+
637
+ export const kycWebhooksLogRelations = relations(kycWebhooksLog, ({ one }) => ({
638
+ kyc: one(storeKyc, {
639
+ fields: [kycWebhooksLog.kycId],
640
+ references: [storeKyc.id],
641
+ }),
642
+ }));
643
+
644
+ export const kycAnalyticsRelations = relations(kycAnalytics, ({ one }) => ({
645
+ compliance: one(storeCompliance, {
646
+ fields: [kycAnalytics.storeId],
647
+ references: [storeCompliance.storeId],
648
+ }),
649
+ }));