@axova/shared 1.0.1 → 1.0.2

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 (31) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +0 -2
  3. package/dist/middleware/storeOwnership.js +3 -22
  4. package/dist/middleware/storeValidationMiddleware.js +39 -16
  5. package/dist/schemas/admin/admin-schema.d.ts +2 -2
  6. package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +2 -2
  7. package/dist/schemas/index.d.ts +0 -28
  8. package/dist/schemas/index.js +3 -125
  9. package/dist/utils/subdomain.d.ts +1 -1
  10. package/dist/utils/subdomain.js +15 -10
  11. package/package.json +1 -1
  12. package/src/index.ts +0 -3
  13. package/src/middleware/storeOwnership.ts +3 -21
  14. package/src/middleware/storeValidationMiddleware.ts +50 -17
  15. package/src/schemas/index.ts +5 -194
  16. package/src/utils/subdomain.ts +15 -11
  17. package/src/schemas/compliance/compliance-schema.ts +0 -927
  18. package/src/schemas/compliance/kyc-schema.ts +0 -649
  19. package/src/schemas/customer/customer-schema.ts +0 -576
  20. package/src/schemas/inventory/inventory-tables.ts +0 -1927
  21. package/src/schemas/inventory/lot-tables.ts +0 -799
  22. package/src/schemas/order/order-schema.ts +0 -1400
  23. package/src/schemas/product/discount-relations.ts +0 -44
  24. package/src/schemas/product/discount-schema.ts +0 -464
  25. package/src/schemas/product/product-relations.ts +0 -187
  26. package/src/schemas/product/product-schema.ts +0 -955
  27. package/src/schemas/store/ethiopian_business_api.md.resolved +0 -212
  28. package/src/schemas/store/store-audit-schema.ts +0 -1257
  29. package/src/schemas/store/store-schema.ts +0 -661
  30. package/src/schemas/store/store-settings-schema.ts +0 -231
  31. package/src/schemas/store/storefront-config-schema.ts +0 -604
@@ -1,576 +0,0 @@
1
- import { createId } from "@paralleldrive/cuid2";
2
- import {
3
- boolean,
4
- decimal,
5
- index,
6
- integer,
7
- jsonb,
8
- pgTable,
9
- text,
10
- timestamp,
11
- varchar,
12
- } from "drizzle-orm/pg-core";
13
-
14
- // Customers Table - Core customer information
15
- export const customers = pgTable(
16
- "customers",
17
- {
18
- id: text("id")
19
- .primaryKey()
20
- .$defaultFn(() => createId()),
21
-
22
- // Basic Info
23
- userId: text("user_id").notNull().unique(), // Links to auth system
24
- email: varchar("email", { length: 255 }).notNull().unique(),
25
- firstName: varchar("first_name", { length: 100 }).notNull(),
26
- lastName: varchar("last_name", { length: 100 }).notNull(),
27
- phoneNumber: varchar("phone_number", { length: 20 }),
28
- dateOfBirth: timestamp("date_of_birth", { withTimezone: true }),
29
-
30
- // Customer Status
31
- status: varchar("status", { length: 20 })
32
- .notNull()
33
- .default("ACTIVE")
34
- .$type<"ACTIVE" | "INACTIVE" | "SUSPENDED" | "BANNED">(),
35
-
36
- customerType: varchar("customer_type", { length: 30 })
37
- .notNull()
38
- .default("RETAIL")
39
- .$type<"RETAIL" | "WHOLESALE" | "VIP" | "BUSINESS">(),
40
-
41
- // Customer Source and Registration
42
- registrationSource: varchar("registration_source", { length: 30 })
43
- .notNull()
44
- .default("ONLINE")
45
- .$type<"ONLINE" | "WALK_IN" | "PHONE" | "REFERRAL" | "SOCIAL_MEDIA">(),
46
-
47
- storeLocationId: text("store_location_id"), // For walk-in customers
48
-
49
- // Profile Information
50
- gender: varchar("gender", { length: 20 }).$type<
51
- "MALE" | "FEMALE" | "OTHER" | "PREFER_NOT_TO_SAY"
52
- >(),
53
- profilePicture: text("profile_picture"),
54
- bio: text("bio"),
55
-
56
- // Preferences
57
- preferredLanguage: varchar("preferred_language", { length: 10 })
58
- .notNull()
59
- .default("en"),
60
- preferredCurrency: varchar("preferred_currency", { length: 3 })
61
- .notNull()
62
- .default("USD"),
63
- timezone: varchar("timezone", { length: 50 }).notNull().default("UTC"),
64
-
65
- // Communication Preferences
66
- marketingConsent: boolean("marketing_consent").notNull().default(false),
67
- emailNotifications: boolean("email_notifications").notNull().default(true),
68
- smsNotifications: boolean("sms_notifications").notNull().default(false),
69
- pushNotifications: boolean("push_notifications").notNull().default(true),
70
-
71
- // Purchase Metrics
72
- totalOrders: integer("total_orders").notNull().default(0),
73
- totalSpent: decimal("total_spent", { precision: 12, scale: 2 })
74
- .notNull()
75
- .default("0.00"),
76
- averageOrderValue: decimal("average_order_value", {
77
- precision: 12,
78
- scale: 2,
79
- })
80
- .notNull()
81
- .default("0.00"),
82
-
83
- // Interaction Metrics
84
- totalVisits: integer("total_visits").notNull().default(0),
85
- totalInteractions: integer("total_interactions").notNull().default(0),
86
-
87
- // Tracking
88
- lastActiveAt: timestamp("last_active_at", { withTimezone: true }),
89
- lastOrderAt: timestamp("last_order_at", { withTimezone: true }),
90
- firstOrderAt: timestamp("first_order_at", { withTimezone: true }),
91
-
92
- // Timestamps
93
- createdAt: timestamp("created_at", { withTimezone: true })
94
- .defaultNow()
95
- .notNull(),
96
- updatedAt: timestamp("updated_at", { withTimezone: true })
97
- .defaultNow()
98
- .notNull(),
99
- },
100
- (table) => ({
101
- userIdIndex: index("idx_customers_user_id").on(table.userId),
102
- emailIndex: index("idx_customers_email").on(table.email),
103
- statusIndex: index("idx_customers_status").on(table.status),
104
- typeIndex: index("idx_customers_type").on(table.customerType),
105
- registrationSourceIndex: index("idx_customers_registration_source").on(
106
- table.registrationSource,
107
- ),
108
- storeLocationIndex: index("idx_customers_store_location").on(
109
- table.storeLocationId,
110
- ),
111
- lastActiveIndex: index("idx_customers_last_active").on(table.lastActiveAt),
112
- }),
113
- );
114
-
115
- // Customer Addresses Table
116
- export const customerAddresses = pgTable(
117
- "customer_addresses",
118
- {
119
- id: text("id")
120
- .primaryKey()
121
- .$defaultFn(() => createId()),
122
-
123
- customerId: text("customer_id")
124
- .notNull()
125
- .references(() => customers.id, { onDelete: "cascade" }),
126
-
127
- // Address Type
128
- type: varchar("type", { length: 20 })
129
- .notNull()
130
- .$type<"BILLING" | "SHIPPING" | "BOTH">(),
131
-
132
- // Address Details
133
- label: varchar("label", { length: 50 }), // e.g., "Home", "Work", "Office"
134
- firstName: varchar("first_name", { length: 100 }).notNull(),
135
- lastName: varchar("last_name", { length: 100 }).notNull(),
136
- company: varchar("company", { length: 100 }),
137
- addressLine1: text("address_line_1").notNull(),
138
- addressLine2: text("address_line_2"),
139
- city: varchar("city", { length: 100 }).notNull(),
140
- state: varchar("state", { length: 100 }).notNull(),
141
- postalCode: varchar("postal_code", { length: 20 }).notNull(),
142
- country: varchar("country", { length: 2 }).notNull(), // ISO country code
143
- phoneNumber: varchar("phone_number", { length: 20 }),
144
-
145
- // Status
146
- isDefault: boolean("is_default").notNull().default(false),
147
- isActive: boolean("is_active").notNull().default(true),
148
-
149
- // Verification
150
- isVerified: boolean("is_verified").notNull().default(false),
151
- verifiedAt: timestamp("verified_at", { withTimezone: true }),
152
-
153
- // Timestamps
154
- createdAt: timestamp("created_at", { withTimezone: true })
155
- .defaultNow()
156
- .notNull(),
157
- updatedAt: timestamp("updated_at", { withTimezone: true })
158
- .defaultNow()
159
- .notNull(),
160
- },
161
- (table) => ({
162
- customerIdIndex: index("idx_addresses_customer_id").on(table.customerId),
163
- typeIndex: index("idx_addresses_type").on(table.type),
164
- defaultIndex: index("idx_addresses_default").on(table.isDefault),
165
- countryIndex: index("idx_addresses_country").on(table.country),
166
- }),
167
- );
168
-
169
- // Customer Support Tickets Table
170
- export const customerSupportTickets = pgTable(
171
- "customer_support_tickets",
172
- {
173
- id: text("id")
174
- .primaryKey()
175
- .$defaultFn(() => createId()),
176
-
177
- customerId: text("customer_id")
178
- .notNull()
179
- .references(() => customers.id, { onDelete: "cascade" }),
180
-
181
- // Ticket Details
182
- ticketNumber: varchar("ticket_number", { length: 20 }).notNull().unique(),
183
- subject: varchar("subject", { length: 255 }).notNull(),
184
- description: text("description").notNull(),
185
- category: varchar("category", { length: 50 })
186
- .notNull()
187
- .$type<
188
- | "GENERAL_INQUIRY"
189
- | "ORDER_ISSUE"
190
- | "BILLING_ISSUE"
191
- | "TECHNICAL_SUPPORT"
192
- | "PRODUCT_QUESTION"
193
- | "RETURN_REFUND"
194
- | "ACCOUNT_ISSUE"
195
- | "COMPLAINT"
196
- | "FEATURE_REQUEST"
197
- >(),
198
-
199
- // Priority and Status
200
- priority: varchar("priority", { length: 20 })
201
- .notNull()
202
- .default("MEDIUM")
203
- .$type<"LOW" | "MEDIUM" | "HIGH" | "URGENT" | "CRITICAL">(),
204
-
205
- status: varchar("status", { length: 20 })
206
- .notNull()
207
- .default("OPEN")
208
- .$type<
209
- "OPEN" | "IN_PROGRESS" | "PENDING_CUSTOMER" | "RESOLVED" | "CLOSED"
210
- >(),
211
-
212
- // Assignment
213
- assignedTo: text("assigned_to"), // Admin/Support user ID
214
- assignedAt: timestamp("assigned_at", { withTimezone: true }),
215
-
216
- // Resolution
217
- resolution: text("resolution"),
218
- resolvedAt: timestamp("resolved_at", { withTimezone: true }),
219
- closedAt: timestamp("closed_at", { withTimezone: true }),
220
-
221
- // Customer Satisfaction
222
- satisfactionRating: integer("satisfaction_rating"), // 1-5
223
- satisfactionFeedback: text("satisfaction_feedback"),
224
-
225
- // Metadata
226
- tags: jsonb("tags").$type<string[]>().default([]),
227
- metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
228
-
229
- // Timestamps
230
- createdAt: timestamp("created_at", { withTimezone: true })
231
- .defaultNow()
232
- .notNull(),
233
- updatedAt: timestamp("updated_at", { withTimezone: true })
234
- .defaultNow()
235
- .notNull(),
236
- },
237
- (table) => ({
238
- customerIdIndex: index("idx_tickets_customer_id").on(table.customerId),
239
- ticketNumberIndex: index("idx_tickets_number").on(table.ticketNumber),
240
- statusIndex: index("idx_tickets_status").on(table.status),
241
- priorityIndex: index("idx_tickets_priority").on(table.priority),
242
- categoryIndex: index("idx_tickets_category").on(table.category),
243
- assignedIndex: index("idx_tickets_assigned").on(table.assignedTo),
244
- createdAtIndex: index("idx_tickets_created_at").on(table.createdAt),
245
- }),
246
- );
247
-
248
- // Customer Reviews Table
249
- export const customerReviews = pgTable(
250
- "customer_reviews",
251
- {
252
- id: text("id")
253
- .primaryKey()
254
- .$defaultFn(() => createId()),
255
-
256
- customerId: text("customer_id")
257
- .notNull()
258
- .references(() => customers.id, { onDelete: "cascade" }),
259
-
260
- // Review Target
261
- targetType: varchar("target_type", { length: 30 })
262
- .notNull()
263
- .$type<"PRODUCT" | "STORE" | "ORDER" | "SERVICE">(),
264
- targetId: text("target_id").notNull(),
265
-
266
- // Review Content
267
- rating: integer("rating").notNull(), // 1-5
268
- title: varchar("title", { length: 255 }),
269
- reviewText: text("review_text"),
270
- images: jsonb("images").$type<string[]>().default([]),
271
-
272
- // Review Status
273
- status: varchar("status", { length: 20 })
274
- .notNull()
275
- .default("PENDING")
276
- .$type<"PENDING" | "APPROVED" | "REJECTED" | "FLAGGED">(),
277
-
278
- isVerifiedPurchase: boolean("is_verified_purchase")
279
- .notNull()
280
- .default(false),
281
-
282
- // Moderation
283
- moderatedBy: text("moderated_by"),
284
- moderatedAt: timestamp("moderated_at", { withTimezone: true }),
285
- moderationNotes: text("moderation_notes"),
286
-
287
- // Helpfulness
288
- helpfulVotes: integer("helpful_votes").notNull().default(0),
289
- unhelpfulVotes: integer("unhelpful_votes").notNull().default(0),
290
-
291
- // Timestamps
292
- createdAt: timestamp("created_at", { withTimezone: true })
293
- .defaultNow()
294
- .notNull(),
295
- updatedAt: timestamp("updated_at", { withTimezone: true })
296
- .defaultNow()
297
- .notNull(),
298
- },
299
- (table) => ({
300
- customerIdIndex: index("idx_reviews_customer_id").on(table.customerId),
301
- targetIndex: index("idx_reviews_target").on(
302
- table.targetType,
303
- table.targetId,
304
- ),
305
- ratingIndex: index("idx_reviews_rating").on(table.rating),
306
- statusIndex: index("idx_reviews_status").on(table.status),
307
- verifiedIndex: index("idx_reviews_verified").on(table.isVerifiedPurchase),
308
- createdAtIndex: index("idx_reviews_created_at").on(table.createdAt),
309
- }),
310
- );
311
-
312
- // Customer Visit History Table
313
- export const customerVisitHistory = pgTable(
314
- "customer_visit_history",
315
- {
316
- id: text("id")
317
- .primaryKey()
318
- .$defaultFn(() => createId()),
319
-
320
- customerId: text("customer_id")
321
- .notNull()
322
- .references(() => customers.id, { onDelete: "cascade" }),
323
-
324
- // Visit Details
325
- visitType: varchar("visit_type", { length: 30 })
326
- .notNull()
327
- .$type<
328
- | "WEBSITE"
329
- | "STORE_VISIT"
330
- | "PHONE_CALL"
331
- | "EMAIL"
332
- | "CHAT"
333
- | "SOCIAL_MEDIA"
334
- >(),
335
-
336
- storeLocationId: text("store_location_id"), // For physical visits
337
- sessionId: text("session_id"), // For online visits
338
-
339
- // Visit Data
340
- duration: integer("duration"), // in minutes
341
- pagesViewed: integer("pages_viewed"), // for online visits
342
- productsViewed: jsonb("products_viewed").$type<string[]>().default([]),
343
-
344
- // Interaction Details
345
- interactionCount: integer("interaction_count").notNull().default(0),
346
- hasInteraction: boolean("has_interaction").notNull().default(false),
347
-
348
- // Visit Outcome
349
- outcome: varchar("outcome", { length: 30 }).$type<
350
- "BROWSING" | "INQUIRY" | "PURCHASE" | "SUPPORT" | "COMPLAINT" | "RETURN"
351
- >(),
352
-
353
- // Notes and Context
354
- notes: text("notes"),
355
- metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
356
-
357
- // Timestamps
358
- visitStartAt: timestamp("visit_start_at", { withTimezone: true }).notNull(),
359
- visitEndAt: timestamp("visit_end_at", { withTimezone: true }),
360
- createdAt: timestamp("created_at", { withTimezone: true })
361
- .defaultNow()
362
- .notNull(),
363
- },
364
- (table) => ({
365
- customerIdIndex: index("idx_visits_customer_id").on(table.customerId),
366
- visitTypeIndex: index("idx_visits_type").on(table.visitType),
367
- storeLocationIndex: index("idx_visits_store_location").on(
368
- table.storeLocationId,
369
- ),
370
- visitDateIndex: index("idx_visits_date").on(table.visitStartAt),
371
- outcomeIndex: index("idx_visits_outcome").on(table.outcome),
372
- }),
373
- );
374
-
375
- // Customer Interaction History Table
376
- export const customerInteractionHistory = pgTable(
377
- "customer_interaction_history",
378
- {
379
- id: text("id")
380
- .primaryKey()
381
- .$defaultFn(() => createId()),
382
-
383
- customerId: text("customer_id")
384
- .notNull()
385
- .references(() => customers.id, { onDelete: "cascade" }),
386
-
387
- visitId: text("visit_id").references(() => customerVisitHistory.id, {
388
- onDelete: "cascade",
389
- }),
390
-
391
- // Interaction Details
392
- interactionType: varchar("interaction_type", { length: 30 })
393
- .notNull()
394
- .$type<
395
- | "PRODUCT_VIEW"
396
- | "PRODUCT_INQUIRY"
397
- | "ADD_TO_CART"
398
- | "WISHLIST_ADD"
399
- | "SEARCH"
400
- | "FILTER_USE"
401
- | "SUPPORT_REQUEST"
402
- | "COMPLAINT"
403
- | "FEEDBACK"
404
- | "NEWSLETTER_SIGNUP"
405
- >(),
406
-
407
- // Target Information
408
- targetType: varchar("target_type", { length: 30 }).$type<
409
- "PRODUCT" | "CATEGORY" | "BRAND" | "PAGE" | "FEATURE" | "SUPPORT"
410
- >(),
411
- targetId: text("target_id"),
412
-
413
- // Interaction Data
414
- interactionData: jsonb("interaction_data")
415
- .$type<Record<string, unknown>>()
416
- .default({}),
417
-
418
- // Staff/Agent Information (for in-store or assisted interactions)
419
- staffId: text("staff_id"),
420
- staffName: varchar("staff_name", { length: 100 }),
421
-
422
- // Context
423
- description: text("description"),
424
- outcome: varchar("outcome", { length: 30 }).$type<
425
- "POSITIVE" | "NEUTRAL" | "NEGATIVE" | "UNRESOLVED" | "COMPLETED"
426
- >(),
427
-
428
- // Timestamps
429
- createdAt: timestamp("created_at", { withTimezone: true })
430
- .defaultNow()
431
- .notNull(),
432
- },
433
- (table) => ({
434
- customerIdIndex: index("idx_interactions_customer_id").on(table.customerId),
435
- visitIdIndex: index("idx_interactions_visit_id").on(table.visitId),
436
- interactionTypeIndex: index("idx_interactions_type").on(
437
- table.interactionType,
438
- ),
439
- targetIndex: index("idx_interactions_target").on(
440
- table.targetType,
441
- table.targetId,
442
- ),
443
- staffIndex: index("idx_interactions_staff").on(table.staffId),
444
- createdAtIndex: index("idx_interactions_created_at").on(table.createdAt),
445
- }),
446
- );
447
-
448
- // Customer Notes Table
449
- export const customerNotes = pgTable(
450
- "customer_notes",
451
- {
452
- id: text("id")
453
- .primaryKey()
454
- .$defaultFn(() => createId()),
455
-
456
- customerId: text("customer_id")
457
- .notNull()
458
- .references(() => customers.id, { onDelete: "cascade" }),
459
-
460
- // Note Details
461
- noteType: varchar("note_type", { length: 30 })
462
- .notNull()
463
- .$type<
464
- | "GENERAL"
465
- | "PREFERENCE"
466
- | "COMPLAINT"
467
- | "COMPLIMENT"
468
- | "SPECIAL_REQUEST"
469
- | "VIP_NOTE"
470
- | "ALERT"
471
- | "FOLLOW_UP"
472
- >(),
473
-
474
- title: varchar("title", { length: 255 }),
475
- content: text("content").notNull(),
476
-
477
- // Priority and Visibility
478
- priority: varchar("priority", { length: 20 })
479
- .notNull()
480
- .default("NORMAL")
481
- .$type<"LOW" | "NORMAL" | "HIGH" | "URGENT">(),
482
-
483
- isVisible: boolean("is_visible").notNull().default(true),
484
- isAlert: boolean("is_alert").notNull().default(false),
485
-
486
- // Staff Information
487
- createdBy: text("created_by").notNull(), // Staff/user ID
488
- createdByName: varchar("created_by_name", { length: 100 }).notNull(),
489
-
490
- // Context
491
- relatedOrderId: text("related_order_id"),
492
- relatedTicketId: text("related_ticket_id"),
493
-
494
- // Follow-up
495
- followUpRequired: boolean("follow_up_required").notNull().default(false),
496
- followUpDate: timestamp("follow_up_date", { withTimezone: true }),
497
- followUpCompleted: boolean("follow_up_completed").notNull().default(false),
498
-
499
- // Timestamps
500
- createdAt: timestamp("created_at", { withTimezone: true })
501
- .defaultNow()
502
- .notNull(),
503
- updatedAt: timestamp("updated_at", { withTimezone: true })
504
- .defaultNow()
505
- .notNull(),
506
- },
507
- (table) => ({
508
- customerIdIndex: index("idx_notes_customer_id").on(table.customerId),
509
- noteTypeIndex: index("idx_notes_type").on(table.noteType),
510
- priorityIndex: index("idx_notes_priority").on(table.priority),
511
- alertIndex: index("idx_notes_alert").on(table.isAlert),
512
- followUpIndex: index("idx_notes_follow_up").on(
513
- table.followUpRequired,
514
- table.followUpDate,
515
- ),
516
- createdByIndex: index("idx_notes_created_by").on(table.createdBy),
517
- }),
518
- );
519
-
520
- // Customer Preferences Table
521
- export const customerPreferences = pgTable(
522
- "customer_preferences",
523
- {
524
- id: text("id")
525
- .primaryKey()
526
- .$defaultFn(() => createId()),
527
-
528
- customerId: text("customer_id")
529
- .notNull()
530
- .references(() => customers.id, { onDelete: "cascade" })
531
- .unique(),
532
-
533
- // Shopping Preferences
534
- preferredCategories: jsonb("preferred_categories")
535
- .$type<string[]>()
536
- .default([]),
537
- preferredBrands: jsonb("preferred_brands").$type<string[]>().default([]),
538
- priceRange: jsonb("price_range")
539
- .$type<{ min?: number; max?: number }>()
540
- .default({}),
541
-
542
- // Notification Preferences
543
- emailMarketing: boolean("email_marketing").notNull().default(false),
544
- smsMarketing: boolean("sms_marketing").notNull().default(false),
545
- pushMarketing: boolean("push_marketing").notNull().default(false),
546
- orderUpdates: boolean("order_updates").notNull().default(true),
547
- productRecommendations: boolean("product_recommendations")
548
- .notNull()
549
- .default(true),
550
- priceDropAlerts: boolean("price_drop_alerts").notNull().default(false),
551
- backInStockAlerts: boolean("back_in_stock_alerts").notNull().default(false),
552
-
553
- // Privacy Preferences
554
- dataSharing: boolean("data_sharing").notNull().default(false),
555
- analyticsTracking: boolean("analytics_tracking").notNull().default(true),
556
- personalizedExperience: boolean("personalized_experience")
557
- .notNull()
558
- .default(true),
559
-
560
- // Custom Preferences
561
- customPreferences: jsonb("custom_preferences")
562
- .$type<Record<string, unknown>>()
563
- .default({}),
564
-
565
- // Timestamps
566
- createdAt: timestamp("created_at", { withTimezone: true })
567
- .defaultNow()
568
- .notNull(),
569
- updatedAt: timestamp("updated_at", { withTimezone: true })
570
- .defaultNow()
571
- .notNull(),
572
- },
573
- (table) => ({
574
- customerIdIndex: index("idx_preferences_customer_id").on(table.customerId),
575
- }),
576
- );