@mohasinac/appkit 4.1.1 → 4.2.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 (225) hide show
  1. package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
  2. package/dist/_internal/client/features/maintenance/index.js +1 -0
  3. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
  5. package/dist/_internal/server/features/auctions/index.d.ts +1 -1
  6. package/dist/_internal/server/features/auctions/index.js +1 -1
  7. package/dist/_internal/server/features/auctions/service.js +2 -2
  8. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  9. package/dist/_internal/server/features/checkout/actions.js +245 -182
  10. package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
  11. package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
  12. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  13. package/dist/_internal/server/functions/scheduled.js +9 -1
  14. package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
  15. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  16. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  17. package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
  18. package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
  19. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  20. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  21. package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
  22. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  23. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  24. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  25. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  26. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  27. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  28. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  29. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  30. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  32. package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
  33. package/dist/_internal/shared/features/auctions/config.js +1 -1
  34. package/dist/_internal/shared/features/auctions/schema.js +1 -1
  35. package/dist/_internal/shared/features/cart/schema.js +2 -2
  36. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  37. package/dist/_internal/shared/features/checkout/config.js +7 -0
  38. package/dist/_internal/shared/features/events/schema.js +1 -1
  39. package/dist/_internal/shared/features/orders/schema.js +1 -1
  40. package/dist/_internal/shared/features/products/schema.js +3 -3
  41. package/dist/_internal/shared/features/promotions/schema.js +3 -3
  42. package/dist/_internal/shared/fees/calculator.d.ts +32 -7
  43. package/dist/_internal/shared/fees/calculator.js +25 -0
  44. package/dist/client.d.ts +4 -4
  45. package/dist/client.js +4 -2
  46. package/dist/constants/api-endpoints.d.ts +9 -6
  47. package/dist/constants/api-endpoints.js +3 -2
  48. package/dist/contracts/client-payment-gateway.d.ts +1 -2
  49. package/dist/features/about/components/AboutView.d.ts +6 -16
  50. package/dist/features/about/components/AboutView.js +3 -3
  51. package/dist/features/about/components/FeesView.js +18 -0
  52. package/dist/features/about/index.d.ts +2 -1
  53. package/dist/features/about/schemas/firestore.d.ts +47 -0
  54. package/dist/features/about/schemas/firestore.js +6 -0
  55. package/dist/features/about/schemas/index.d.ts +210 -0
  56. package/dist/features/about/schemas/index.js +45 -0
  57. package/dist/features/account/hooks/useProfile.js +3 -0
  58. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  59. package/dist/features/admin/actions/notification-actions.js +56 -10
  60. package/dist/features/admin/components/AdminAdsView.js +1 -1
  61. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
  62. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
  63. package/dist/features/admin/components/AdminCartsView.js +16 -3
  64. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  65. package/dist/features/admin/components/AdminCouponsView.js +14 -4
  66. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
  67. package/dist/features/admin/components/AdminMediaView.js +1 -1
  68. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  69. package/dist/features/admin/components/AdminNotificationsView.js +31 -19
  70. package/dist/features/admin/components/AdminOrdersView.js +7 -0
  71. package/dist/features/admin/components/AdminPayoutsView.js +18 -3
  72. package/dist/features/admin/components/AdminProductsView.js +17 -2
  73. package/dist/features/admin/components/AdminReviewsView.js +16 -2
  74. package/dist/features/admin/components/AdminScammersView.js +7 -0
  75. package/dist/features/admin/components/AdminSectionsView.js +4 -0
  76. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  77. package/dist/features/admin/components/AdminSiteSettingsView.js +127 -8
  78. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  79. package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
  80. package/dist/features/admin/components/AdminStoresView.js +1 -1
  81. package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
  82. package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
  83. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
  84. package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
  85. package/dist/features/admin/components/index.d.ts +2 -0
  86. package/dist/features/admin/components/index.js +1 -0
  87. package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
  88. package/dist/features/admin/constants/filter-tabs.js +55 -27
  89. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  90. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  91. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  92. package/dist/features/admin/schemas/firestore.d.ts +46 -0
  93. package/dist/features/admin/schemas/firestore.js +62 -24
  94. package/dist/features/auctions/actions/bid-actions.js +1 -1
  95. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  96. package/dist/features/auctions/components/AuctionsListView.js +8 -1
  97. package/dist/features/auth/components/LoginForm.js +2 -2
  98. package/dist/features/auth/components/RegisterForm.js +2 -2
  99. package/dist/features/auth/permissions/constants.d.ts +16 -0
  100. package/dist/features/auth/permissions/constants.js +53 -0
  101. package/dist/features/auth/schemas/firestore.js +1 -0
  102. package/dist/features/blog/components/BlogFilters.js +1 -1
  103. package/dist/features/cart/schemas/firestore.d.ts +1 -1
  104. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  105. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  106. package/dist/features/events/components/EventFilters.js +2 -0
  107. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  108. package/dist/features/layout/BackToTop.d.ts +9 -5
  109. package/dist/features/layout/BackToTop.js +17 -21
  110. package/dist/features/layout/TitleBarLayout.d.ts +2 -2
  111. package/dist/features/layout/TitleBarLayout.js +4 -4
  112. package/dist/features/media/AvatarUpload.js +7 -3
  113. package/dist/features/media/MediaVideo.js +1 -1
  114. package/dist/features/media/finalize.d.ts +9 -13
  115. package/dist/features/media/finalize.js +23 -74
  116. package/dist/features/media/server.d.ts +1 -1
  117. package/dist/features/media/server.js +1 -1
  118. package/dist/features/orders/actions/refund-actions.js +1 -0
  119. package/dist/features/orders/hooks/useOrders.js +16 -4
  120. package/dist/features/orders/schemas/firestore.d.ts +19 -5
  121. package/dist/features/payments/schemas/firestore.d.ts +2 -2
  122. package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
  123. package/dist/features/products/components/ArtStickersListView.js +5 -0
  124. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
  125. package/dist/features/products/components/ProductForm.js +14 -1
  126. package/dist/features/products/components/ProductsIndexPageView.js +7 -0
  127. package/dist/features/products/constants/action-defs.d.ts +29 -3
  128. package/dist/features/products/constants/action-defs.js +77 -1
  129. package/dist/features/products/schemas/firestore.d.ts +1 -1
  130. package/dist/features/products/schemas/product-templates.d.ts +1 -1
  131. package/dist/features/products/types/index.d.ts +4 -0
  132. package/dist/features/scams/schemas/index.js +2 -2
  133. package/dist/features/search/components/Search.js +3 -3
  134. package/dist/features/seller/components/SellerAuctionsView.js +27 -2
  135. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  136. package/dist/features/seller/components/SellerPayoutsView.js +24 -10
  137. package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
  138. package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
  139. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  140. package/dist/features/seller/components/SellerProductShell.js +1 -1
  141. package/dist/features/seller/components/SellerProductsView.js +8 -6
  142. package/dist/features/seller/components/SellerReviewsView.js +27 -6
  143. package/dist/features/seller/components/index.d.ts +0 -2
  144. package/dist/features/seller/components/index.js +0 -1
  145. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  146. package/dist/features/stores/components/StoreProductsView.js +1 -1
  147. package/dist/features/stores/schemas/firestore.d.ts +11 -0
  148. package/dist/features/stores/schemas/firestore.js +5 -0
  149. package/dist/features/stores/schemas/index.d.ts +2 -2
  150. package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
  151. package/dist/features/tester/actions/checklist-item-actions.js +2 -0
  152. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
  153. package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
  154. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
  155. package/dist/features/tester/components/TesterHubView.js +34 -21
  156. package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
  157. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
  158. package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
  159. package/dist/features/tester/schemas/firestore.d.ts +8 -3
  160. package/dist/features/tester/schemas/firestore.js +6 -0
  161. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
  162. package/dist/features/tester/utils/phases.d.ts +31 -0
  163. package/dist/features/tester/utils/phases.js +46 -0
  164. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
  165. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  166. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  167. package/dist/features/whatsapp-bot/types/index.d.ts +20 -1
  168. package/dist/features/wishlist/types/index.d.ts +3 -0
  169. package/dist/index.d.ts +15 -6
  170. package/dist/index.js +19 -9
  171. package/dist/next/components/NotFoundView.js +2 -1
  172. package/dist/next/routing/route-map.d.ts +6 -12
  173. package/dist/next/routing/route-map.js +3 -4
  174. package/dist/providers/db-firebase/admin.d.ts +1 -0
  175. package/dist/providers/db-firebase/admin.js +1 -1
  176. package/dist/repositories/index.d.ts +2 -0
  177. package/dist/repositories/index.js +1 -0
  178. package/dist/schemas/registry.d.ts +2 -2
  179. package/dist/seed/addresses-seed-data.js +18 -18
  180. package/dist/seed/bids-seed-data.js +4 -4
  181. package/dist/seed/blog-posts-seed-data.js +82 -82
  182. package/dist/seed/carousel-slides-seed-data.js +14 -10
  183. package/dist/seed/carousels-seed-data.js +3 -3
  184. package/dist/seed/cart-seed-data.js +92 -92
  185. package/dist/seed/claimed-coupons-seed-data.js +10 -10
  186. package/dist/seed/conversations-seed-data.js +75 -75
  187. package/dist/seed/coupon-usage-seed-data.js +12 -12
  188. package/dist/seed/coupons-seed-data.js +41 -41
  189. package/dist/seed/events-seed-data.js +105 -105
  190. package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
  191. package/dist/seed/grouped-listings-seed-data.js +79 -143
  192. package/dist/seed/history-seed-data.js +38 -38
  193. package/dist/seed/homepage-sections-seed-data.js +9 -9
  194. package/dist/seed/notifications-seed-data.js +56 -56
  195. package/dist/seed/offers-seed-data.js +91 -91
  196. package/dist/seed/orders-seed-data.js +121 -106
  197. package/dist/seed/payouts-seed-data.js +42 -42
  198. package/dist/seed/products-auctions-seed-data.js +10 -2
  199. package/dist/seed/products-standard-seed-data.js +40 -8
  200. package/dist/seed/reviews-seed-data.js +42 -23
  201. package/dist/seed/scammers-seed-data.js +22 -22
  202. package/dist/seed/sessions-seed-data.js +7 -7
  203. package/dist/seed/shipments-seed-data.js +10 -10
  204. package/dist/seed/site-settings-seed-data.js +89 -0
  205. package/dist/seed/store-addresses-seed-data.js +2 -2
  206. package/dist/seed/store-extensions-seed-data.js +93 -93
  207. package/dist/seed/stores-seed-data.js +2 -0
  208. package/dist/seed/support-tickets-seed-data.js +47 -47
  209. package/dist/seed/users-seed-data.js +55 -89
  210. package/dist/seed/wishlists-seed-data.js +22 -22
  211. package/dist/server.d.ts +4 -3
  212. package/dist/server.js +7 -7
  213. package/dist/styles.css +1 -1
  214. package/dist/tailwind-utilities.css +1 -1
  215. package/dist/ui/components/PageLoader.js +8 -3
  216. package/dist/ui/components/Select.d.ts +8 -1
  217. package/dist/ui/components/Select.js +2 -2
  218. package/dist/ui/components/SiteMark.d.ts +23 -0
  219. package/dist/ui/components/SiteMark.js +76 -0
  220. package/dist/ui/index.d.ts +2 -0
  221. package/dist/ui/index.js +1 -0
  222. package/dist/validation/schemas.d.ts +0 -2
  223. package/dist/validation/schemas.js +0 -2
  224. package/package.json +1 -1
  225. package/scripts/seed-cli.mjs +17 -15
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Google Cloud Logging integration for the `/admin/maintenance/cloud-logs`
3
+ * page. Reuses the same 3-tier credential resolution as
4
+ * `providers/db-firebase/admin.ts` (local `firebase-admin-key.json` → Vercel
5
+ * `FIREBASE_ADMIN_*` env vars → Application Default Credentials), so no new
6
+ * env vars are required.
7
+ *
8
+ * Bounded to ONE `Logging#getEntries()` call per invocation — never
9
+ * `getEntriesStream()` in an unbounded loop — to respect the Vercel Hobby
10
+ * 10s sync-function ceiling (CLAUDE.md Rule #6). Pagination is client-driven
11
+ * via `nextPageToken`, not exhausted server-side.
12
+ *
13
+ * `@google-cloud/logging` is a consumer-side (root `package.json`) dependency
14
+ * — it is never imported at module scope here. The `Logging` client is
15
+ * lazily `require()`d inside a function body via `(module as any).require`,
16
+ * exactly like `admin.ts` lazily loads `firebase-admin/*`, so Turbopack never
17
+ * sees a static import of a Node-only client reachable from a client bundle
18
+ * (CLAUDE.md Root Cause #6 / #24). A minimal local interface describes the
19
+ * only surface we call — this file never imports `@google-cloud/logging`'s
20
+ * own types, so it type-checks even though the package is deliberately not
21
+ * listed as an appkit dependency (it lives in the consumer's root
22
+ * `package.json` only, since this integration is Next.js-route-only).
23
+ */
24
+ import { cache } from "react";
25
+ import { parsePrivateKey } from "../../../../providers/db-firebase/admin";
26
+ function loadLogging() {
27
+ return module.require("@google-cloud/logging");
28
+ }
29
+ function nodePath() { return require("path"); }
30
+ function nodeFs() { return require("fs"); }
31
+ // process.cwd() is Node.js-only; access via module.require so the Edge
32
+ // static analyser does not flag this file (mirrors admin.ts).
33
+ function nodeCwd() { return module.require("process").cwd(); }
34
+ // Plain module-scope singleton (not `declare global` + `globalThis`) — this
35
+ // module is only ever reachable through one resolution path per process, so
36
+ // a module-local cache is sufficient and avoids global-augmentation clashes
37
+ // when the same declaration is picked up from both compiled `.d.ts` output
38
+ // and live source under a `file:./appkit` consumer tsconfig include.
39
+ let cachedLoggingClient = null;
40
+ function getCached() {
41
+ return cachedLoggingClient;
42
+ }
43
+ function setCached(client) {
44
+ cachedLoggingClient = client;
45
+ }
46
+ function getLoggingClient() {
47
+ const cached = getCached();
48
+ if (cached)
49
+ return cached;
50
+ const { Logging } = loadLogging();
51
+ const keyPath = nodePath().join(nodeCwd(), "firebase-admin-key.json");
52
+ let client;
53
+ if (nodeFs().existsSync(keyPath)) {
54
+ // Local dev — the JSON key file also carries project_id, so the Logging
55
+ // client resolves the project automatically from `keyFilename`.
56
+ client = new Logging({ keyFilename: keyPath });
57
+ }
58
+ else if (process.env.FIREBASE_ADMIN_PROJECT_ID &&
59
+ process.env.FIREBASE_ADMIN_CLIENT_EMAIL &&
60
+ process.env.FIREBASE_ADMIN_PRIVATE_KEY) {
61
+ // Vercel — same three env vars the Firebase Admin SDK uses.
62
+ client = new Logging({
63
+ projectId: process.env.FIREBASE_ADMIN_PROJECT_ID.trim(),
64
+ credentials: {
65
+ client_email: process.env.FIREBASE_ADMIN_CLIENT_EMAIL.trim(),
66
+ private_key: parsePrivateKey(process.env.FIREBASE_ADMIN_PRIVATE_KEY),
67
+ },
68
+ });
69
+ }
70
+ else if (
71
+ // Application Default Credentials — Firebase Functions / Cloud Run / GCE.
72
+ process.env.FUNCTION_TARGET ||
73
+ process.env.K_SERVICE ||
74
+ process.env.FIREBASE_CONFIG ||
75
+ process.env.GOOGLE_APPLICATION_CREDENTIALS) {
76
+ const firebaseConfig = process.env.FIREBASE_CONFIG
77
+ ? JSON.parse(process.env.FIREBASE_CONFIG)
78
+ : {};
79
+ const projectId = firebaseConfig.projectId ?? process.env.GCLOUD_PROJECT;
80
+ client = new Logging(projectId ? { projectId } : {});
81
+ }
82
+ else {
83
+ throw new Error("@mohasinac/appkit: Cloud Logging credentials not found.\n" +
84
+ "Add firebase-admin-key.json to project root, or set " +
85
+ "FIREBASE_ADMIN_PROJECT_ID, FIREBASE_ADMIN_CLIENT_EMAIL, " +
86
+ "FIREBASE_ADMIN_PRIVATE_KEY environment variables.");
87
+ }
88
+ setCached(client);
89
+ return client;
90
+ }
91
+ const DEFAULT_WINDOW_HOURS = 24;
92
+ const MAX_PAGE_SIZE = 100;
93
+ const DEFAULT_PAGE_SIZE = 50;
94
+ /**
95
+ * This project's Firebase Functions are all 2nd-gen
96
+ * (`firebase-functions/v2/*` — see
97
+ * `_internal/server/functions/bind-all.ts` + `jobs/runtime/adapters/firebase.ts`,
98
+ * which import exclusively from `firebase-functions/v2/{scheduler,firestore,https}`).
99
+ * 2nd-gen Functions run on Cloud Run under the hood, so their log entries
100
+ * carry `resource.type="cloud_run_revision"` — NOT the 1st-gen
101
+ * `"cloud_function"` resource type.
102
+ */
103
+ const DEFAULT_RESOURCE_TYPE = "cloud_run_revision";
104
+ function buildDefaultFilter() {
105
+ const since = new Date(Date.now() - DEFAULT_WINDOW_HOURS * 3600000).toISOString();
106
+ return `resource.type="${DEFAULT_RESOURCE_TYPE}" AND timestamp>="${since}"`;
107
+ }
108
+ function excerpt(value, max = 300) {
109
+ if (value === null || value === undefined || value === "")
110
+ return "";
111
+ const text = typeof value === "string" ? value : JSON.stringify(value);
112
+ return text.length > max ? text.slice(0, max) + "…" : text;
113
+ }
114
+ function toCloudLogEntry(raw, index) {
115
+ const metadata = raw.metadata ?? {};
116
+ const tsRaw = metadata.timestamp;
117
+ const timestamp = tsRaw instanceof Date
118
+ ? tsRaw.toISOString()
119
+ : typeof tsRaw === "string"
120
+ ? tsRaw
121
+ : new Date().toISOString();
122
+ const resource = metadata.resource ?? {};
123
+ return {
124
+ id: metadata.insertId ?? `${timestamp}-${index}`,
125
+ timestamp,
126
+ severity: String(metadata.severity ?? "DEFAULT"),
127
+ resourceType: resource.type ?? "unknown",
128
+ resourceLabels: resource.labels ?? {},
129
+ logName: metadata.logName ?? "",
130
+ message: excerpt(raw.data),
131
+ };
132
+ }
133
+ /**
134
+ * Bounded, single-page Cloud Logging read. One `getEntries()` call per
135
+ * invocation — the caller drives further pages via `opts.pageToken`
136
+ * (client-side "Load more"), never a server-side exhaust loop.
137
+ */
138
+ export const listCloudLogEntries = cache(async (opts = {}) => {
139
+ const pageSize = Math.min(Math.max(opts.pageSize ?? DEFAULT_PAGE_SIZE, 1), MAX_PAGE_SIZE);
140
+ const filter = opts.filter?.trim() ? opts.filter.trim() : buildDefaultFilter();
141
+ const logging = getLoggingClient();
142
+ const [entries, , apiResponse] = await logging.getEntries({
143
+ filter,
144
+ pageSize,
145
+ orderBy: "timestamp desc",
146
+ ...(opts.pageToken ? { pageToken: opts.pageToken } : {}),
147
+ });
148
+ return {
149
+ entries: entries.map((e, i) => toCloudLogEntry(e, i)),
150
+ nextPageToken: apiResponse?.nextPageToken || null,
151
+ };
152
+ });
@@ -6,6 +6,7 @@ export declare const paymentReviewAutoApprove: import("./types").ScheduledFuncti
6
6
  export declare const couponExpiry: import("./types").ScheduledFunctionDefinition;
7
7
  export declare const offerExpiry: import("./types").ScheduledFunctionDefinition;
8
8
  export declare const productStatsSync: import("./types").ScheduledFunctionDefinition;
9
+ export declare const revenueRollup: import("./types").ScheduledFunctionDefinition;
9
10
  export declare const dailyDataCleanup: import("./types").ScheduledFunctionDefinition;
10
11
  export declare const countersReconcile: import("./types").ScheduledFunctionDefinition;
11
12
  export declare const positionsReconcile: import("./types").ScheduledFunctionDefinition;
@@ -25,4 +26,4 @@ export declare const prizeRevealReminder: import("./types").ScheduledFunctionDef
25
26
  export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
26
27
  export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
27
28
  export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
28
- export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
29
+ export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
@@ -1,6 +1,6 @@
1
1
  // Scheduled function definitions (Cloud Scheduler triggers).
2
2
  // Cron syntax accepts both Firebase shorthand and standard cron expressions.
3
- import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealOpenHandler, prizeRevealReminderHandler, productStatsSyncHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, } from "../jobs/handlers";
3
+ import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealOpenHandler, prizeRevealReminderHandler, productStatsSyncHandler, revenueRollupHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, } from "../jobs/handlers";
4
4
  import { defineFunction } from "./define";
5
5
  const REGION = "asia-south1";
6
6
  const EVERY_5_MIN = "every 5 minutes";
@@ -60,6 +60,13 @@ export const productStatsSync = defineFunction({
60
60
  handler: productStatsSyncHandler,
61
61
  options: { region: REGION, timeoutSeconds: 540, memory: "256MiB", maxInstances: 1 },
62
62
  });
63
+ export const revenueRollup = defineFunction({
64
+ name: "revenueRollup",
65
+ description: "Pre-compute total delivered-order revenue into analytics/dashboardRollup, replacing an unbounded per-request scan (daily 01:30 UTC).",
66
+ trigger: { kind: "schedule", cron: "30 1 * * *", timeZone: "UTC" },
67
+ handler: revenueRollupHandler,
68
+ options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
69
+ });
63
70
  export const dailyDataCleanup = defineFunction({
64
71
  name: "dailyDataCleanup",
65
72
  description: "Daily data cleanup (drafts, transient records) at 02:00 UTC.",
@@ -202,6 +209,7 @@ export const SCHEDULED_FUNCTIONS = [
202
209
  couponExpiry,
203
210
  offerExpiry,
204
211
  productStatsSync,
212
+ revenueRollup,
205
213
  dailyDataCleanup,
206
214
  countersReconcile,
207
215
  positionsReconcile,
@@ -11,6 +11,8 @@
11
11
  import { runWeeklyPayoutEligibility } from "./weeklyPayoutEligibility";
12
12
  import { runHardBanCascade } from "./hardBanCascade";
13
13
  import { runResetOtpVerification } from "./resetOtpVerification";
14
+ import { runWhatsAppNotify } from "./whatsappNotify";
15
+ import { runNewsletterExport } from "./newsletterExport";
14
16
  async function runPayoutsWeeklyJob(_payload, ctx) {
15
17
  const { payoutsCreated, ordersProcessed, payoutIds } = await runWeeklyPayoutEligibility(ctx);
16
18
  return {
@@ -37,8 +39,16 @@ async function runHardBanCascadeJob(payload, ctx) {
37
39
  async function runResetOtpVerificationJob(_payload, ctx) {
38
40
  return runResetOtpVerification(ctx);
39
41
  }
42
+ async function runWhatsAppNotifyJob(payload, ctx) {
43
+ return runWhatsAppNotify(payload, ctx);
44
+ }
45
+ async function runNewsletterExportJob(_payload, ctx) {
46
+ return runNewsletterExport(ctx);
47
+ }
40
48
  export const JOB_RUNNERS = {
41
49
  payoutsWeekly: runPayoutsWeeklyJob,
42
50
  hardBanCascade: runHardBanCascadeJob,
43
51
  resetOtpVerification: runResetOtpVerificationJob,
52
+ whatsappNotify: runWhatsAppNotifyJob,
53
+ newsletterExport: runNewsletterExportJob,
44
54
  };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Core: async newsletter-subscriber CSV export (`newsletterExport` job).
3
+ *
4
+ * `GET /api/admin/newsletter/export` used to build the full CSV in-process,
5
+ * synchronously, for up to 10,000 subscribers — a real 10s-timeout risk on
6
+ * Vercel Hobby as the list grows (CLAUDE.md Rule #6). This runner does the
7
+ * same work inside a Firebase Function instead and writes the CSV text onto
8
+ * the job's `result.data.csv` field — small enough for a Firestore doc at
9
+ * realistic subscriber counts, and avoids routing through Storage (which
10
+ * would need a `/media/...` proxy URL per the Media Architecture rules
11
+ * rather than a raw signed URL).
12
+ */
13
+ import type { JobContext } from "../runtime/types";
14
+ import type { JobRunResult } from "./jobRunners";
15
+ export declare function runNewsletterExport(ctx: JobContext): Promise<JobRunResult>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Core: async newsletter-subscriber CSV export (`newsletterExport` job).
3
+ *
4
+ * `GET /api/admin/newsletter/export` used to build the full CSV in-process,
5
+ * synchronously, for up to 10,000 subscribers — a real 10s-timeout risk on
6
+ * Vercel Hobby as the list grows (CLAUDE.md Rule #6). This runner does the
7
+ * same work inside a Firebase Function instead and writes the CSV text onto
8
+ * the job's `result.data.csv` field — small enough for a Firestore doc at
9
+ * realistic subscriber counts, and avoids routing through Storage (which
10
+ * would need a `/media/...` proxy URL per the Media Architecture rules
11
+ * rather than a raw signed URL).
12
+ */
13
+ import { newsletterRepository } from "../../../../repositories";
14
+ import { sortBy } from "../../../../constants/sort";
15
+ import { COMMON_FIELDS } from "../../../../constants/field-names";
16
+ function escape(value) {
17
+ const s = String(value ?? "");
18
+ return s.includes(",") || s.includes('"') || s.includes("\n")
19
+ ? `"${s.replace(/"/g, '""')}"`
20
+ : s;
21
+ }
22
+ function csvRow(cols) {
23
+ return cols.map(escape).join(",");
24
+ }
25
+ export async function runNewsletterExport(ctx) {
26
+ const result = await newsletterRepository.list({
27
+ sorts: sortBy(COMMON_FIELDS.CREATED_AT),
28
+ page: "1",
29
+ pageSize: "10000",
30
+ });
31
+ const rows = result.data;
32
+ const header = csvRow(["id", "email", "status", "source", "subscribedAt", "createdAt"]);
33
+ const dataRows = rows.map((s) => csvRow([s["id"], s["email"] ?? "", s["status"] ?? "", s["source"] ?? "", s["subscribedAt"] ?? "", s["createdAt"] ?? ""]));
34
+ const csv = [header, ...dataRows].join("\r\n");
35
+ ctx.logger.info("newsletterExport: complete", { subscriberCount: rows.length });
36
+ return {
37
+ summary: { total: rows.length, succeeded: rows.length, skipped: 0, failed: 0 },
38
+ succeeded: [],
39
+ skipped: [],
40
+ failed: [],
41
+ data: { csv, subscriberCount: rows.length },
42
+ };
43
+ }
@@ -1,18 +1,10 @@
1
1
  import type { JobContext } from "../runtime/types";
2
- interface OrderItem {
3
- title?: string;
4
- name?: string;
5
- }
6
- export interface NewOrder {
7
- buyerDisplayName?: string;
8
- buyerId?: string;
9
- items?: OrderItem[];
10
- totalAmount?: number;
11
- storeId?: string;
12
- }
2
+ import type { OrderDocument, OrderDocumentItem } from "../../../../features/orders/schemas/firestore";
3
+ export type NewOrder = Pick<OrderDocument, "userName" | "userId" | "totalPrice" | "storeId"> & {
4
+ items?: Pick<OrderDocumentItem, "productTitle">[];
5
+ };
13
6
  export interface HandleOrderCreateInput {
14
7
  orderId: string;
15
8
  order: NewOrder;
16
9
  }
17
10
  export declare function handleOrderCreate(input: HandleOrderCreateInput, ctx: JobContext): Promise<void>;
18
- export {};
@@ -54,14 +54,14 @@ export async function handleOrderCreate(input, ctx) {
54
54
  }
55
55
  const items = order.items ?? [];
56
56
  const firstItem = items[0];
57
- const firstItemName = firstItem?.title ?? firstItem?.name ?? "an item";
57
+ const firstItemName = firstItem?.productTitle ?? "an item";
58
58
  const additionalItemCount = Math.max(0, items.length - 1);
59
- const buyerName = order.buyerDisplayName ?? "A customer";
59
+ const buyerName = order.userName ?? "A customer";
60
60
  const message = buildPurchaseAnnouncementMessage({
61
61
  buyerName,
62
62
  firstItemName,
63
63
  additionalItemCount,
64
- totalAmount: order.totalAmount ?? 0,
64
+ totalAmount: order.totalPrice ?? 0,
65
65
  orderId,
66
66
  });
67
67
  const adminNumbersRaw = ctx.env("WHATSAPP_ADMIN_NOTIFY_NUMBERS") ?? "";
@@ -6,6 +6,8 @@ export type OrderAfter = {
6
6
  userEmail: string;
7
7
  productTitle: string;
8
8
  trackingNumber?: string;
9
+ /** Buyer opted into the ₹10 WhatsApp order-updates addon at checkout. */
10
+ whatsappNotifyAddon?: boolean;
9
11
  };
10
12
  export type OrderBefore = {
11
13
  status: OrderStatus;
@@ -56,6 +56,7 @@ export async function handleOrderStatusChange(input, ctx) {
56
56
  relatedId: orderId,
57
57
  relatedType: "order",
58
58
  userEmail,
59
+ orderWhatsappAddonPaid: after.whatsappNotifyAddon === true,
59
60
  });
60
61
  try {
61
62
  await getAdminRealtimeDb().ref(`notifications/${after.userId}`).push({
@@ -10,6 +10,14 @@ const UNAVAILABLE_PRODUCT_STATUSES = new Set([
10
10
  ProductStatusValues.ARCHIVED,
11
11
  ]);
12
12
  void UNAVAILABLE_PRODUCT_STATUSES;
13
+ function getEffectiveCategory(doc) {
14
+ if (!doc)
15
+ return null;
16
+ const categorySlugs = doc.categorySlugs;
17
+ if (Array.isArray(categorySlugs) && categorySlugs.length > 0)
18
+ return categorySlugs[0];
19
+ return doc.category ?? null;
20
+ }
13
21
  async function getParentIds(categoryId) {
14
22
  if (!categoryId)
15
23
  return [];
@@ -63,8 +71,12 @@ export async function handleProductWrite(input, ctx) {
63
71
  const { productId, before, after } = input;
64
72
  const beforeStatus = before?.status ?? null;
65
73
  const afterStatus = after?.status ?? null;
66
- const beforeCategory = before?.category ?? null;
67
- const afterCategory = after?.category ?? null;
74
+ // `category` is @deprecated in favor of `categorySlugs[]` — the repository's
75
+ // mapDoc() normalizes this on read, but a raw trigger snapshot bypasses that,
76
+ // so a product written via categorySlugs-only paths would otherwise silently
77
+ // no-op category metrics here. Prefer categorySlugs[0], fall back to legacy category.
78
+ const beforeCategory = getEffectiveCategory(before);
79
+ const afterCategory = getEffectiveCategory(after);
68
80
  const beforeStoreId = (before?.storeId || before?.sellerId) ?? null;
69
81
  const afterStoreId = (after?.storeId || after?.sellerId) ?? null;
70
82
  const isAuction = after?.listingType === PRODUCT_FIELDS.LISTING_TYPE_VALUES.AUCTION;
@@ -51,6 +51,7 @@ export async function runPaymentWindowTimeout(ctx) {
51
51
  message: ORDER_MESSAGES.CANCELLED_PAYMENT_WINDOW_MESSAGE(entry.data.productTitle),
52
52
  relatedId: entry.id,
53
53
  relatedType: "order",
54
+ orderWhatsappAddonPaid: entry.data.whatsappNotifyAddon === true,
54
55
  })));
55
56
  ctx.logger.info("Payment window timeout sweep complete", {
56
57
  scanned: expired.length,
@@ -49,6 +49,7 @@ export async function runPendingOrderTimeout(ctx) {
49
49
  message: ORDER_MESSAGES.CANCELLED_TIMEOUT_MESSAGE(entry.data.productTitle, timeoutHours),
50
50
  relatedId: entry.id,
51
51
  relatedType: "order",
52
+ orderWhatsappAddonPaid: entry.data.whatsappNotifyAddon === true,
52
53
  })));
53
54
  ctx.logger.info("Pending order timeout complete", { cancelled: timedOut.length, restored });
54
55
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Core: daily revenue rollup.
3
+ *
4
+ * `GET /api/admin/dashboard` used to sum `totalPrice` over the FULL
5
+ * `delivered`-status orders list on every request (`orderRepository.findByStatus`
6
+ * has no bound) — a real, growing Firestore-read cost and a 10s-timeout risk
7
+ * on Vercel Hobby (CLAUDE.md Rule #6) as order volume increases. This job
8
+ * pre-computes the same aggregate once a day and writes it to the
9
+ * `analytics/dashboardRollup` singleton doc; the API route now reads that
10
+ * single doc instead of scanning the collection.
11
+ */
12
+ import type { JobContext } from "../runtime/types";
13
+ export declare function runRevenueRollup(ctx: JobContext): Promise<void>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Core: daily revenue rollup.
3
+ *
4
+ * `GET /api/admin/dashboard` used to sum `totalPrice` over the FULL
5
+ * `delivered`-status orders list on every request (`orderRepository.findByStatus`
6
+ * has no bound) — a real, growing Firestore-read cost and a 10s-timeout risk
7
+ * on Vercel Hobby (CLAUDE.md Rule #6) as order volume increases. This job
8
+ * pre-computes the same aggregate once a day and writes it to the
9
+ * `analytics/dashboardRollup` singleton doc; the API route now reads that
10
+ * single doc instead of scanning the collection.
11
+ */
12
+ import { orderRepository } from "../../../../repositories";
13
+ import { analyticsRollupRepository } from "../../../../repositories";
14
+ export async function runRevenueRollup(ctx) {
15
+ const deliveredOrders = await orderRepository.findByStatus("delivered").catch(() => []);
16
+ const totalRevenue = deliveredOrders.reduce((sum, order) => sum + (Number(order.totalPrice ?? 0) || 0), 0);
17
+ await analyticsRollupRepository.setDashboardRollup({
18
+ totalRevenue,
19
+ deliveredOrderCount: deliveredOrders.length,
20
+ });
21
+ ctx.logger.info("revenueRollup: complete", { totalRevenue, deliveredOrderCount: deliveredOrders.length });
22
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Core: async WhatsApp notification dispatch (`whatsappNotify` job).
3
+ *
4
+ * `sendNotification()` (appkit/src/features/admin/actions/notification-actions.ts)
5
+ * no longer calls the Meta Cloud API synchronously in-request — it enqueues
6
+ * this job instead (CLAUDE.md Rule #6 async job primitive) and returns
7
+ * immediately. This runner does the actual send, with a bounded retry, and
8
+ * writes the outcome back onto `notifications/{notificationId}` so admins
9
+ * have visibility beyond the `jobs/{jobId}` doc itself.
10
+ *
11
+ * Retry note: `sendWhatsAppTemplateMessage`/`sendWhatsAppBusinessMessage`
12
+ * return a plain boolean (not a status code), so this runner cannot cleanly
13
+ * distinguish a transient 5xx from a terminal 4xx (bad token, unknown
14
+ * template) the way a status-code-aware client could. It retries any
15
+ * failure up to MAX_ATTEMPTS with a short backoff — an acceptable
16
+ * simplification since this runs off the critical path, well inside the
17
+ * Firebase Function's 300s budget, and a terminal error just burns two
18
+ * extra fast failures before giving up.
19
+ */
20
+ import type { JobContext } from "../runtime/types";
21
+ import type { JobRunResult } from "./jobRunners";
22
+ export interface WhatsAppNotifyPayload {
23
+ toPhone: string;
24
+ title: string;
25
+ message: string;
26
+ type: string;
27
+ /** Approved Meta template name for this notification type, if configured. */
28
+ templateName?: string;
29
+ /** BCP-47 language code the template was approved in. Defaults to "en". */
30
+ templateLanguage?: string;
31
+ notificationId: string;
32
+ phoneNumberId: string;
33
+ accessToken: string;
34
+ }
35
+ export declare function runWhatsAppNotify(payload: WhatsAppNotifyPayload, ctx: JobContext): Promise<JobRunResult>;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Core: async WhatsApp notification dispatch (`whatsappNotify` job).
3
+ *
4
+ * `sendNotification()` (appkit/src/features/admin/actions/notification-actions.ts)
5
+ * no longer calls the Meta Cloud API synchronously in-request — it enqueues
6
+ * this job instead (CLAUDE.md Rule #6 async job primitive) and returns
7
+ * immediately. This runner does the actual send, with a bounded retry, and
8
+ * writes the outcome back onto `notifications/{notificationId}` so admins
9
+ * have visibility beyond the `jobs/{jobId}` doc itself.
10
+ *
11
+ * Retry note: `sendWhatsAppTemplateMessage`/`sendWhatsAppBusinessMessage`
12
+ * return a plain boolean (not a status code), so this runner cannot cleanly
13
+ * distinguish a transient 5xx from a terminal 4xx (bad token, unknown
14
+ * template) the way a status-code-aware client could. It retries any
15
+ * failure up to MAX_ATTEMPTS with a short backoff — an acceptable
16
+ * simplification since this runs off the critical path, well inside the
17
+ * Firebase Function's 300s budget, and a terminal error just burns two
18
+ * extra fast failures before giving up.
19
+ */
20
+ import { normalizeError } from "../../../../errors/normalize";
21
+ import { notificationRepository } from "../../../../features/admin/repository/notification.repository";
22
+ import { sendWhatsAppTemplateMessage, sendWhatsAppBusinessMessage, } from "../../../../features/whatsapp-bot/helpers/whatsapp";
23
+ const MAX_ATTEMPTS = 3;
24
+ const RETRY_DELAY_MS = 1500;
25
+ function delay(ms) {
26
+ return new Promise((resolve) => setTimeout(resolve, ms));
27
+ }
28
+ export async function runWhatsAppNotify(payload, ctx) {
29
+ const { toPhone, title, message, templateName, templateLanguage, notificationId, phoneNumberId, accessToken } = payload;
30
+ let sent = false;
31
+ let lastError;
32
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS && !sent; attempt++) {
33
+ try {
34
+ sent = templateName
35
+ ? await sendWhatsAppTemplateMessage({
36
+ toPhone,
37
+ phoneNumberId,
38
+ accessToken,
39
+ templateName,
40
+ languageCode: templateLanguage ?? "en",
41
+ bodyParams: [title, message],
42
+ })
43
+ : await sendWhatsAppBusinessMessage({
44
+ toPhone,
45
+ phoneNumberId,
46
+ accessToken,
47
+ message: `*${title}*\n${message}`,
48
+ });
49
+ if (!sent)
50
+ lastError = "Meta WhatsApp API returned a non-OK response";
51
+ }
52
+ catch (err) {
53
+ void normalizeError(err);
54
+ lastError = err instanceof Error ? err.message : String(err);
55
+ }
56
+ if (!sent && attempt < MAX_ATTEMPTS) {
57
+ ctx.logger.warn("whatsappNotify: send attempt failed, retrying", { notificationId, attempt, error: lastError ?? "" });
58
+ await delay(RETRY_DELAY_MS * attempt);
59
+ }
60
+ }
61
+ if (!templateName) {
62
+ ctx.logger.warn("whatsappNotify: no approved template configured for this notification type — sent as free-form text, which Meta rejects outside the 24h customer-service window", { notificationId });
63
+ }
64
+ await notificationRepository
65
+ .update(notificationId, { whatsappStatus: sent ? "sent" : "failed" })
66
+ .catch((err) => {
67
+ void normalizeError(err);
68
+ ctx.logger.warn("whatsappNotify: failed to write back whatsappStatus onto notification doc", {
69
+ notificationId,
70
+ error: err instanceof Error ? err.message : String(err),
71
+ });
72
+ });
73
+ if (!sent) {
74
+ throw new Error(lastError ?? "WhatsApp send failed after retries");
75
+ }
76
+ return {
77
+ summary: { total: 1, succeeded: 1, skipped: 0, failed: 0 },
78
+ succeeded: [notificationId],
79
+ skipped: [],
80
+ failed: [],
81
+ };
82
+ }
@@ -28,6 +28,7 @@ export { paymentWindowTimeoutHandler } from "./paymentWindowTimeout";
28
28
  export { hardBanReinstatementHandler } from "./hardBanReinstatement";
29
29
  export { paymentReviewAutoApproveHandler } from "./paymentReviewAutoApprove";
30
30
  export { productStatsSyncHandler } from "./productStatsSync";
31
+ export { revenueRollupHandler } from "./revenueRollup";
31
32
  export { positionsReconcileHandler } from "./positionsReconcile";
32
33
  export { payoutBatchHandler } from "./payoutBatch";
33
34
  export { weeklyPayoutEligibilityHandler } from "./weeklyPayoutEligibility";
@@ -28,6 +28,7 @@ export { paymentWindowTimeoutHandler } from "./paymentWindowTimeout";
28
28
  export { hardBanReinstatementHandler } from "./hardBanReinstatement";
29
29
  export { paymentReviewAutoApproveHandler } from "./paymentReviewAutoApprove";
30
30
  export { productStatsSyncHandler } from "./productStatsSync";
31
+ export { revenueRollupHandler } from "./revenueRollup";
31
32
  export { positionsReconcileHandler } from "./positionsReconcile";
32
33
  export { payoutBatchHandler } from "./payoutBatch";
33
34
  export { weeklyPayoutEligibilityHandler } from "./weeklyPayoutEligibility";
@@ -0,0 +1,2 @@
1
+ import type { ScheduleHandler } from "../runtime/types";
2
+ export declare const revenueRollupHandler: ScheduleHandler;
@@ -0,0 +1,2 @@
1
+ import { runRevenueRollup } from "../core/revenueRollup";
2
+ export const revenueRollupHandler = runRevenueRollup;
@@ -2,5 +2,5 @@ export declare const AUCTIONS_PAGE_SIZE = 20;
2
2
  export declare const AUCTIONS_ACTIVE_LIMIT = 50;
3
3
  export declare const AUCTIONS_SITEMAP_LIMIT = 5000;
4
4
  export declare const AUCTION_DEFAULT_EXTENSION_MINUTES = 5;
5
- export declare const AUCTION_MIN_BID_INCREMENT_PAISE = 100;
5
+ export declare const AUCTION_MIN_BID_INCREMENT = 1;
6
6
  export declare const AUCTION_SNIPING_WINDOW_SECONDS = 300;
@@ -2,5 +2,5 @@ export const AUCTIONS_PAGE_SIZE = 20;
2
2
  export const AUCTIONS_ACTIVE_LIMIT = 50;
3
3
  export const AUCTIONS_SITEMAP_LIMIT = 5000;
4
4
  export const AUCTION_DEFAULT_EXTENSION_MINUTES = 5;
5
- export const AUCTION_MIN_BID_INCREMENT_PAISE = 100; // ₹1
5
+ export const AUCTION_MIN_BID_INCREMENT = 1; // ₹1
6
6
  export const AUCTION_SNIPING_WINDOW_SECONDS = 300; // 5 minutes — triggers auto-extend
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  export const placeBidSchema = z.object({
3
3
  auctionId: z.string().min(1),
4
- amount: z.number().int().positive("Bid amount must be a positive integer (paise)"),
4
+ amount: z.number().positive("Bid amount must be a positive number"),
5
5
  });
6
6
  export const cancelBidSchema = z.object({
7
7
  bidId: z.string().min(1),
@@ -3,7 +3,7 @@ const cartItemBaseSchema = z.object({
3
3
  productId: z.string().min(1),
4
4
  productTitle: z.string(),
5
5
  productImage: z.string().default(""),
6
- price: z.number().int().min(0),
6
+ price: z.number().min(0),
7
7
  currency: z.string().default("INR"),
8
8
  storeId: z.string(),
9
9
  storeName: z.string().default(""),
@@ -21,7 +21,7 @@ const cartItemBaseSchema = z.object({
21
21
  ]),
22
22
  isOffer: z.boolean().optional(),
23
23
  offerId: z.string().optional(),
24
- lockedPrice: z.number().int().optional(),
24
+ lockedPrice: z.number().optional(),
25
25
  });
26
26
  export const addToCartSchema = cartItemBaseSchema;
27
27
  export const updateCartItemSchema = z.object({
@@ -2,6 +2,13 @@ export declare const CHECKOUT_DEFAULT_COMMISSIONS: {
2
2
  readonly codDepositPercent: 10;
3
3
  readonly codHandlingFeeMin: 200;
4
4
  readonly codHandlingFeePercent: 10;
5
+ readonly whatsappNotifyFeeEnabled: false;
6
+ readonly whatsappNotifyFee: 10;
7
+ readonly giftWrapFeeEnabled: false;
8
+ readonly giftWrapFee: 49;
9
+ readonly shipmentProtectionFeeEnabled: false;
10
+ readonly shipmentProtectionFeePercent: 2;
11
+ readonly shipmentProtectionFeeMin: 30;
5
12
  readonly sellerShippingFixed: 50;
6
13
  readonly platformShippingPercent: 10;
7
14
  readonly platformShippingFixedMin: 50;