@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,661 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import { relations } from "drizzle-orm";
3
+ import {
4
+ boolean,
5
+ index,
6
+ integer,
7
+ jsonb,
8
+ pgTable,
9
+ text,
10
+ timestamp,
11
+ unique,
12
+ varchar,
13
+ numeric,
14
+ } from "drizzle-orm/pg-core";
15
+
16
+ // Core Store Table - Enhanced with performance optimizations
17
+ export const stores = pgTable(
18
+ "stores",
19
+ {
20
+ id: text("id")
21
+ .primaryKey()
22
+ .$defaultFn(() => createId()),
23
+ userId: text("user_id").notNull(),
24
+ organizationId: text("organization_id"), // Organization ID from Fastify Better Auth
25
+ storeName: varchar("store_name", { length: 255 }).notNull(),
26
+ subdomain: varchar("subdomain", { length: 63 }).notNull(),
27
+ storeUrl: varchar("store_url", { length: 255 }),
28
+
29
+ // Core Settings
30
+ timezone: varchar("timezone", { length: 50 }).notNull().default("UTC"),
31
+ currency: varchar("currency", { length: 3 }).notNull().default("USD"),
32
+ languages: jsonb("languages").$type<string[]>().notNull().default(["en"]),
33
+
34
+ // Store Configuration
35
+ storeType: varchar("store_type", { length: 50 })
36
+ .notNull()
37
+ .default("STANDARD")
38
+ .$type<"STANDARD" | "PREMIUM" | "ENTERPRISE">(),
39
+
40
+ plan: varchar("plan", { length: 50 })
41
+ .notNull()
42
+ .default("FREE")
43
+ .$type<"FREE" | "BASIC" | "PRO" | "ENTERPRISE">(),
44
+
45
+ // Ethiopian Business Registration
46
+ tinNumber: varchar("tin_number", { length: 50 }),
47
+ businessType: varchar("business_type", { length: 20 })
48
+ .$type<"starter" | "business_owner">()
49
+ .default("starter"),
50
+ region: varchar("region", { length: 100 }),
51
+ subcity: varchar("subcity", { length: 100 }),
52
+ woreda: varchar("woreda", { length: 100 }),
53
+ houseNumber: varchar("house_number", { length: 50 }),
54
+ tradeNames: jsonb("trade_names").$type<string[]>().default([]),
55
+
56
+ // Store Additional Details
57
+ attributes: jsonb("attributes")
58
+ .$type<Record<string, unknown>>()
59
+ .default({}),
60
+ settings: jsonb("settings")
61
+ .$type<{
62
+ enablePublicListing?: boolean;
63
+ enableReviews?: boolean;
64
+ enableInventoryTracking?: boolean;
65
+ enableMultiCurrency?: boolean;
66
+ maintenanceMode?: boolean;
67
+ customCss?: string;
68
+ logoUrl?: string;
69
+ faviconUrl?: string;
70
+ themeColor?: string;
71
+ }>()
72
+ .default({}),
73
+
74
+ // Performance metrics
75
+ monthlyVisitors: integer("monthly_visitors").default(0),
76
+ totalOrders: integer("total_orders").default(0),
77
+ totalRevenue: integer("total_revenue").default(0), // in cents
78
+
79
+ // Status and lifecycle
80
+ isActive: boolean("is_active").notNull().default(true),
81
+ isPublished: boolean("is_published").notNull().default(false),
82
+ launchedAt: timestamp("launched_at", { withTimezone: true }),
83
+
84
+ // Audit fields
85
+ createdAt: timestamp("created_at", { withTimezone: true })
86
+ .defaultNow()
87
+ .notNull(),
88
+ updatedAt: timestamp("updated_at", { withTimezone: true })
89
+ .defaultNow()
90
+ .notNull(),
91
+ deletedAt: timestamp("deleted_at", { withTimezone: true }),
92
+ },
93
+ (table) => ({
94
+ // Primary indexes for performance
95
+ subdomainUnique: unique("idx_stores_subdomain_unique").on(table.subdomain),
96
+ storeNameIndex: index("idx_stores_name").on(table.storeName),
97
+ userIdIndex: index("idx_stores_user_id").on(table.userId),
98
+ organizationIdIndex: index("idx_stores_organization_id").on(
99
+ table.organizationId,
100
+ ),
101
+
102
+ // Composite indexes for common queries
103
+ userActiveStoresIndex: index("idx_stores_user_active").on(
104
+ table.userId,
105
+ table.isActive,
106
+ ),
107
+ activePublishedIndex: index("idx_stores_active_published").on(
108
+ table.isActive,
109
+ table.isPublished,
110
+ ),
111
+ planTypeIndex: index("idx_stores_plan_type").on(
112
+ table.plan,
113
+ table.storeType,
114
+ ),
115
+
116
+ // Performance tracking indexes
117
+ visitorsIndex: index("idx_stores_monthly_visitors").on(
118
+ table.monthlyVisitors,
119
+ ),
120
+ revenueIndex: index("idx_stores_total_revenue").on(table.totalRevenue),
121
+
122
+ // Temporal indexes
123
+ createdAtIndex: index("idx_stores_created_at").on(table.createdAt),
124
+ launchedAtIndex: index("idx_stores_launched_at").on(table.launchedAt),
125
+
126
+ // Ethiopian business indexes
127
+ tinNumberIndex: index("idx_stores_tin_number").on(table.tinNumber),
128
+ businessTypeIndex: index("idx_stores_business_type").on(table.businessType),
129
+ locationIndex: index("idx_stores_location").on(
130
+ table.region,
131
+ table.subcity,
132
+ table.woreda,
133
+ ),
134
+ }),
135
+ );
136
+
137
+ // Store Business Details Table - Enhanced
138
+ export const storeBusinessDetails = pgTable(
139
+ "store_business_details",
140
+ {
141
+ id: text("id")
142
+ .primaryKey()
143
+ .$defaultFn(() => createId()),
144
+ storeId: text("store_id")
145
+ .notNull()
146
+ .references(() => stores.id, { onDelete: "cascade" }),
147
+
148
+ // Business Type and Legal Structure
149
+ businessType: varchar("business_type", { length: 20 })
150
+ .notNull()
151
+ .$type<"INDIVIDUAL" | "BUSINESS" | "NON_PROFIT" | "GOVERNMENT">()
152
+ .default("INDIVIDUAL"),
153
+
154
+ legalStructure: varchar("legal_structure", { length: 30 }).$type<
155
+ | "SOLE_PROPRIETORSHIP"
156
+ | "PARTNERSHIP"
157
+ | "LLC"
158
+ | "CORPORATION"
159
+ | "S_CORP"
160
+ | "NON_PROFIT"
161
+ >(),
162
+
163
+ // Ethiopian Registration Details
164
+ legalBusinessNameAmh: varchar("legal_business_name_amh", { length: 255 }),
165
+ registrationDate: timestamp("registration_date", { withTimezone: true }),
166
+ paidUpCapital: numeric("paid_up_capital", { precision: 15, scale: 2 }),
167
+ licenseDetails: jsonb("license_details").$type<any[]>().default([]),
168
+ associateInfo: jsonb("associate_info").$type<any[]>().default([]),
169
+ tradeNames: jsonb("trade_names").$type<string[]>().default([]),
170
+
171
+ // Business Details
172
+ businessName: varchar("business_name", { length: 255 }),
173
+ dbaName: varchar("dba_name", { length: 255 }), // "Doing Business As"
174
+ businessRegistrationNumber: varchar("business_registration_number", {
175
+ length: 100,
176
+ }),
177
+ taxIdentificationNumber: varchar("tax_identification_number", {
178
+ length: 100,
179
+ }),
180
+ vatNumber: varchar("vat_number", { length: 50 }),
181
+
182
+ // Address Information
183
+ businessAddress: jsonb("business_address").$type<{
184
+ street: string;
185
+ street2?: string;
186
+ city: string;
187
+ state: string;
188
+ postalCode: string;
189
+ country: string;
190
+ }>(),
191
+
192
+ mailingAddress: jsonb("mailing_address").$type<{
193
+ street: string;
194
+ street2?: string;
195
+ city: string;
196
+ state: string;
197
+ postalCode: string;
198
+ country: string;
199
+ sameAsBusiness?: boolean;
200
+ }>(),
201
+
202
+ // Industry and Size
203
+ industry: varchar("industry", { length: 100 }),
204
+ subIndustry: varchar("sub_industry", { length: 100 }),
205
+ employeeCount: varchar("employee_count", { length: 20 }).$type<
206
+ "1" | "2-10" | "11-50" | "51-200" | "201-500" | "500+"
207
+ >(),
208
+
209
+ // Financial Information
210
+ annualRevenue: varchar("annual_revenue", { length: 30 }).$type<
211
+ | "<10K"
212
+ | "10K-50K"
213
+ | "50K-100K"
214
+ | "100K-500K"
215
+ | "500K-1M"
216
+ | "1M-10M"
217
+ | "10M+"
218
+ >(),
219
+
220
+ // Verification Status
221
+ isVerified: boolean("is_verified").notNull().default(false),
222
+ verificationStatus: varchar("verification_status", { length: 30 })
223
+ .notNull()
224
+ .default("PENDING")
225
+ .$type<
226
+ "PENDING" | "IN_PROGRESS" | "VERIFIED" | "REJECTED" | "REQUIRES_UPDATE"
227
+ >(),
228
+
229
+ verificationDocuments: jsonb("verification_documents").$type<{
230
+ businessLicense?: string;
231
+ taxCertificate?: string;
232
+ bankStatement?: string;
233
+ proofOfAddress?: string;
234
+ other?: string[];
235
+ }>(),
236
+
237
+ verifiedAt: timestamp("verified_at", { withTimezone: true }),
238
+ verifiedBy: text("verified_by"),
239
+
240
+ createdAt: timestamp("created_at", { withTimezone: true })
241
+ .defaultNow()
242
+ .notNull(),
243
+ updatedAt: timestamp("updated_at", { withTimezone: true })
244
+ .defaultNow()
245
+ .notNull(),
246
+ },
247
+ (table) => ({
248
+ storeIdIndex: index("idx_business_details_store_id").on(table.storeId),
249
+ businessTypeIndex: index("idx_business_details_type").on(
250
+ table.businessType,
251
+ ),
252
+ verificationStatusIndex: index("idx_business_details_verification").on(
253
+ table.verificationStatus,
254
+ ),
255
+ industryIndex: index("idx_business_details_industry").on(table.industry),
256
+
257
+ // Composite indexes for verification workflows
258
+ verificationWorkflowIndex: index("idx_business_verification_workflow").on(
259
+ table.verificationStatus,
260
+ table.businessType,
261
+ ),
262
+
263
+ // Ethiopian indexes
264
+ tinIndex: index("idx_business_details_tin").on(table.taxIdentificationNumber),
265
+ registrationDateIndex: index("idx_business_details_registration_date").on(table.registrationDate),
266
+ }),
267
+ );
268
+
269
+ // Store Contacts Table - Enhanced
270
+ export const storeContacts = pgTable(
271
+ "store_contacts",
272
+ {
273
+ id: text("id")
274
+ .primaryKey()
275
+ .$defaultFn(() => createId()),
276
+ storeId: text("store_id")
277
+ .notNull()
278
+ .references(() => stores.id, { onDelete: "cascade" }),
279
+
280
+ // Primary Contact Information
281
+ contactEmail: varchar("contact_email", { length: 255 }).notNull(),
282
+ contactName: varchar("contact_name", { length: 255 }),
283
+ contactTitle: varchar("contact_title", { length: 100 }),
284
+
285
+ // Departmental Emails
286
+ supportEmail: varchar("support_email", { length: 255 }),
287
+ salesEmail: varchar("sales_email", { length: 255 }),
288
+ technicalEmail: varchar("technical_email", { length: 255 }),
289
+ billingEmail: varchar("billing_email", { length: 255 }),
290
+ marketingEmail: varchar("marketing_email", { length: 255 }),
291
+ legalEmail: varchar("legal_email", { length: 255 }),
292
+
293
+ // Phone Numbers with international support
294
+ primaryPhone: varchar("primary_phone", { length: 20 }),
295
+ supportPhone: varchar("support_phone", { length: 20 }),
296
+ whatsappNumber: varchar("whatsapp_number", { length: 20 }),
297
+ faxNumber: varchar("fax_number", { length: 20 }),
298
+
299
+ // Business Hours
300
+ businessHours: jsonb("business_hours").$type<{
301
+ timezone: string;
302
+ monday?: { open: string; close: string; closed?: boolean };
303
+ tuesday?: { open: string; close: string; closed?: boolean };
304
+ wednesday?: { open: string; close: string; closed?: boolean };
305
+ thursday?: { open: string; close: string; closed?: boolean };
306
+ friday?: { open: string; close: string; closed?: boolean };
307
+ saturday?: { open: string; close: string; closed?: boolean };
308
+ sunday?: { open: string; close: string; closed?: boolean };
309
+ holidays?: Array<{ date: string; name: string }>;
310
+ }>(),
311
+
312
+ // Emergency and After-hours Contact
313
+ emergencyContact: jsonb("emergency_contact").$type<{
314
+ name: string;
315
+ position: string;
316
+ phone: string;
317
+ email: string;
318
+ available24x7: boolean;
319
+ escalationProcess?: string;
320
+ }>(),
321
+
322
+ // Communication Preferences
323
+ preferredContactMethod: varchar("preferred_contact_method", { length: 20 })
324
+ .$type<"EMAIL" | "PHONE" | "SMS" | "WHATSAPP">()
325
+ .default("EMAIL"),
326
+
327
+ // Social Media and Other Channels
328
+ communicationChannels: jsonb("communication_channels").$type<{
329
+ slack?: string;
330
+ discord?: string;
331
+ telegram?: string;
332
+ skype?: string;
333
+ zoom?: string;
334
+ teams?: string;
335
+ }>(),
336
+
337
+ createdAt: timestamp("created_at", { withTimezone: true })
338
+ .defaultNow()
339
+ .notNull(),
340
+ updatedAt: timestamp("updated_at", { withTimezone: true })
341
+ .defaultNow()
342
+ .notNull(),
343
+ },
344
+ (table) => ({
345
+ storeIdIndex: index("idx_contacts_store_id").on(table.storeId),
346
+ contactEmailIndex: index("idx_contacts_email").on(table.contactEmail),
347
+ preferredMethodIndex: index("idx_contacts_preferred_method").on(
348
+ table.preferredContactMethod,
349
+ ),
350
+ }),
351
+ );
352
+
353
+ // Store Social Profiles Table - Enhanced
354
+ export const storeSocialProfiles = pgTable(
355
+ "store_social_profiles",
356
+ {
357
+ id: text("id")
358
+ .primaryKey()
359
+ .$defaultFn(() => createId()),
360
+ storeId: text("store_id")
361
+ .notNull()
362
+ .references(() => stores.id, { onDelete: "cascade" }),
363
+
364
+ // Major Social Platforms
365
+ facebookUrl: varchar("facebook_url", { length: 500 }),
366
+ facebookEnabled: boolean("facebook_enabled").notNull().default(false),
367
+ facebookPageId: varchar("facebook_page_id", { length: 100 }),
368
+
369
+ instagramUrl: varchar("instagram_url", { length: 500 }),
370
+ instagramEnabled: boolean("instagram_enabled").notNull().default(false),
371
+ instagramHandle: varchar("instagram_handle", { length: 100 }),
372
+
373
+ twitterUrl: varchar("twitter_url", { length: 500 }),
374
+ twitterEnabled: boolean("twitter_enabled").notNull().default(false),
375
+ twitterHandle: varchar("twitter_handle", { length: 100 }),
376
+
377
+ linkedinUrl: varchar("linkedin_url", { length: 500 }),
378
+ linkedinEnabled: boolean("linkedin_enabled").notNull().default(false),
379
+ linkedinPageId: varchar("linkedin_page_id", { length: 100 }),
380
+
381
+ youtubeUrl: varchar("youtube_url", { length: 500 }),
382
+ youtubeEnabled: boolean("youtube_enabled").notNull().default(false),
383
+ youtubeChannelId: varchar("youtube_channel_id", { length: 100 }),
384
+
385
+ tiktokUrl: varchar("tiktok_url", { length: 500 }),
386
+ tiktokEnabled: boolean("tiktok_enabled").notNull().default(false),
387
+ tiktokHandle: varchar("tiktok_handle", { length: 100 }),
388
+
389
+ pinterestUrl: varchar("pinterest_url", { length: 500 }),
390
+ pinterestEnabled: boolean("pinterest_enabled").notNull().default(false),
391
+ pinterestHandle: varchar("pinterest_handle", { length: 100 }),
392
+
393
+ // Professional and Niche Platforms
394
+ githubUrl: varchar("github_url", { length: 500 }),
395
+ githubEnabled: boolean("github_enabled").notNull().default(false),
396
+
397
+ snapchatUrl: varchar("snapchat_url", { length: 500 }),
398
+ snapchatEnabled: boolean("snapchat_enabled").notNull().default(false),
399
+
400
+ redditUrl: varchar("reddit_url", { length: 500 }),
401
+ redditEnabled: boolean("reddit_enabled").notNull().default(false),
402
+
403
+ // Messaging Platforms
404
+ discordUrl: varchar("discord_url", { length: 500 }),
405
+ discordEnabled: boolean("discord_enabled").notNull().default(false),
406
+
407
+ telegramUrl: varchar("telegram_url", { length: 500 }),
408
+ telegramEnabled: boolean("telegram_enabled").notNull().default(false),
409
+
410
+ whatsappUrl: varchar("whatsapp_url", { length: 500 }),
411
+ whatsappEnabled: boolean("whatsapp_enabled").notNull().default(false),
412
+
413
+ // Custom and Regional Platforms
414
+ customPlatforms: jsonb("custom_platforms")
415
+ .$type<
416
+ Array<{
417
+ name: string;
418
+ url: string;
419
+ enabled: boolean;
420
+ icon?: string;
421
+ displayOrder?: number;
422
+ }>
423
+ >()
424
+ .default([]),
425
+
426
+ // Social Media Strategy
427
+ socialMediaStrategy: jsonb("social_media_strategy").$type<{
428
+ primaryPlatforms: string[];
429
+ postingSchedule?: Record<string, string[]>; // platform -> days/times
430
+ contentThemes?: string[];
431
+ hashtagStrategy?: Record<string, string[]>; // platform -> hashtags
432
+ engagementGoals?: Record<string, number>;
433
+ }>(),
434
+
435
+ // Analytics and Tracking
436
+ socialAnalytics: jsonb("social_analytics")
437
+ .$type<{
438
+ trackingEnabled: boolean;
439
+ utmSource?: string;
440
+ utmMedium?: string;
441
+ utmCampaign?: string;
442
+ conversionTracking?: boolean;
443
+ }>()
444
+ .default({ trackingEnabled: false }),
445
+
446
+ createdAt: timestamp("created_at", { withTimezone: true })
447
+ .defaultNow()
448
+ .notNull(),
449
+ updatedAt: timestamp("updated_at", { withTimezone: true })
450
+ .defaultNow()
451
+ .notNull(),
452
+ },
453
+ (table) => ({
454
+ storeIdIndex: index("idx_social_store_id").on(table.storeId),
455
+ // Index for enabled platforms for quick filtering
456
+ enabledPlatformsIndex: index("idx_social_enabled_facebook").on(
457
+ table.storeId,
458
+ table.facebookEnabled,
459
+ ),
460
+ enabledInstagramIndex: index("idx_social_enabled_instagram").on(
461
+ table.storeId,
462
+ table.instagramEnabled,
463
+ ),
464
+ enabledTwitterIndex: index("idx_social_enabled_twitter").on(
465
+ table.storeId,
466
+ table.twitterEnabled,
467
+ ),
468
+ }),
469
+ );
470
+
471
+ // Store Blogs Table - Enhanced
472
+ export const storeBlogs = pgTable(
473
+ "store_blogs",
474
+ {
475
+ id: text("id")
476
+ .primaryKey()
477
+ .$defaultFn(() => createId()),
478
+ storeId: text("store_id")
479
+ .notNull()
480
+ .references(() => stores.id, { onDelete: "cascade" }),
481
+
482
+ // Content
483
+ title: varchar("title", { length: 255 }).notNull(),
484
+ slug: varchar("slug", { length: 255 }).notNull(),
485
+ excerpt: text("excerpt"),
486
+ content: text("content").notNull(),
487
+
488
+ // Media
489
+ featuredImage: varchar("featured_image", { length: 500 }),
490
+ gallery: jsonb("gallery").$type<string[]>().default([]),
491
+ media: jsonb("media")
492
+ .$type<
493
+ Array<{
494
+ type:
495
+ | "video"
496
+ | "audio"
497
+ | "podcast"
498
+ | "youtube"
499
+ | "vimeo"
500
+ | "spotify"
501
+ | "soundcloud"
502
+ | "other";
503
+ url: string;
504
+ title?: string;
505
+ description?: string;
506
+ thumbnail?: string;
507
+ duration?: number; // Duration in seconds
508
+ size?: number; // File size in bytes
509
+ }>
510
+ >()
511
+ .default([]),
512
+
513
+ // SEO and Metadata
514
+ metaTitle: varchar("meta_title", { length: 255 }),
515
+ metaDescription: text("meta_description"),
516
+ metaKeywords: jsonb("meta_keywords").$type<string[]>().default([]),
517
+ canonicalUrl: varchar("canonical_url", { length: 500 }),
518
+
519
+ // Content Organization
520
+ categoryId: text("category_id"),
521
+ tags: jsonb("tags").$type<string[]>().notNull().default([]),
522
+ topics: jsonb("topics").$type<string[]>().default([]),
523
+
524
+ // Publishing
525
+ status: varchar("status", { length: 20 })
526
+ .notNull()
527
+ .default("draft")
528
+ .$type<"draft" | "pending" | "published" | "archived" | "deleted">(),
529
+
530
+ publishedAt: timestamp("published_at", { withTimezone: true }),
531
+ scheduledFor: timestamp("scheduled_for", { withTimezone: true }),
532
+
533
+ // Authoring
534
+ authors: jsonb("authors")
535
+ .$type<
536
+ Array<{
537
+ id: string;
538
+ name: string;
539
+ role?: string;
540
+ bio?: string;
541
+ avatar?: string;
542
+ }>
543
+ >()
544
+ .notNull()
545
+ .default([]),
546
+
547
+ // Engagement and Analytics
548
+ viewCount: integer("view_count").default(0),
549
+ likeCount: integer("like_count").default(0),
550
+ shareCount: integer("share_count").default(0),
551
+ commentCount: integer("comment_count").default(0),
552
+
553
+ // Content Settings
554
+ allowComments: boolean("allow_comments").notNull().default(true),
555
+ allowSharing: boolean("allow_sharing").notNull().default(true),
556
+ allowRating: boolean("allow_rating").notNull().default(false),
557
+
558
+ // Features
559
+ isFeatured: boolean("is_featured").notNull().default(false),
560
+ isSticky: boolean("is_sticky").notNull().default(false),
561
+ isPremium: boolean("is_premium").notNull().default(false),
562
+
563
+ // Reading Experience
564
+ estimatedReadTime: integer("estimated_read_time"), // in minutes
565
+ difficulty: varchar("difficulty", { length: 20 }).$type<
566
+ "beginner" | "intermediate" | "advanced"
567
+ >(),
568
+
569
+ // Language and Localization
570
+ language: varchar("language", { length: 10 }).default("en"),
571
+ translations: jsonb("translations")
572
+ .$type<Record<string, string>>()
573
+ .default({}),
574
+
575
+ createdAt: timestamp("created_at", { withTimezone: true })
576
+ .defaultNow()
577
+ .notNull(),
578
+ updatedAt: timestamp("updated_at", { withTimezone: true })
579
+ .defaultNow()
580
+ .notNull(),
581
+ },
582
+ (table) => ({
583
+ storeIdIndex: index("idx_blog_store_id").on(table.storeId),
584
+ slugUniqueIndex: unique("idx_blog_slug_unique").on(
585
+ table.storeId,
586
+ table.slug,
587
+ ),
588
+ statusIndex: index("idx_blog_status").on(table.status),
589
+ publishedAtIndex: index("idx_blog_published_at").on(table.publishedAt),
590
+
591
+ // Content discovery indexes
592
+ featuredIndex: index("idx_blog_featured").on(
593
+ table.isFeatured,
594
+ table.status,
595
+ ),
596
+ categoryIndex: index("idx_blog_category").on(
597
+ table.categoryId,
598
+ table.status,
599
+ ),
600
+
601
+ // Performance indexes
602
+ viewCountIndex: index("idx_blog_view_count").on(table.viewCount),
603
+
604
+ // Publishing workflow indexes
605
+ publishingWorkflowIndex: index("idx_blog_publishing_workflow").on(
606
+ table.status,
607
+ table.scheduledFor,
608
+ ),
609
+ }),
610
+ );
611
+
612
+ // Relations
613
+ export const storesRelations = relations(stores, ({ one, many }) => ({
614
+ businessDetails: one(storeBusinessDetails, {
615
+ fields: [stores.id],
616
+ references: [storeBusinessDetails.storeId],
617
+ }),
618
+ contacts: one(storeContacts, {
619
+ fields: [stores.id],
620
+ references: [storeContacts.storeId],
621
+ }),
622
+ socialProfiles: one(storeSocialProfiles, {
623
+ fields: [stores.id],
624
+ references: [storeSocialProfiles.storeId],
625
+ }),
626
+ blogs: many(storeBlogs),
627
+ }));
628
+
629
+ export const storeBusinessDetailsRelations = relations(
630
+ storeBusinessDetails,
631
+ ({ one }) => ({
632
+ store: one(stores, {
633
+ fields: [storeBusinessDetails.storeId],
634
+ references: [stores.id],
635
+ }),
636
+ }),
637
+ );
638
+
639
+ export const storeContactsRelations = relations(storeContacts, ({ one }) => ({
640
+ store: one(stores, {
641
+ fields: [storeContacts.storeId],
642
+ references: [stores.id],
643
+ }),
644
+ }));
645
+
646
+ export const storeSocialProfilesRelations = relations(
647
+ storeSocialProfiles,
648
+ ({ one }) => ({
649
+ store: one(stores, {
650
+ fields: [storeSocialProfiles.storeId],
651
+ references: [stores.id],
652
+ }),
653
+ }),
654
+ );
655
+
656
+ export const storeBlogsRelations = relations(storeBlogs, ({ one }) => ({
657
+ store: one(stores, {
658
+ fields: [storeBlogs.storeId],
659
+ references: [stores.id],
660
+ }),
661
+ }));