@mohasinac/appkit 4.52.4 → 4.53.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 (633) hide show
  1. package/dist/.build-stamp.json +4 -4
  2. package/dist/_internal/client/features/sticker-trace/canvas-codec.d.ts +2 -0
  3. package/dist/_internal/client/features/sticker-trace/canvas-codec.js +148 -0
  4. package/dist/_internal/server/features/auctions/actions.js +1 -1
  5. package/dist/_internal/server/features/categories/data.js +23 -7
  6. package/dist/_internal/server/features/checkout/actions.js +10 -3
  7. package/dist/_internal/server/features/checkout/data.d.ts +15 -0
  8. package/dist/_internal/server/features/checkout/data.js +20 -3
  9. package/dist/_internal/server/features/lottery/actions.js +9 -1
  10. package/dist/_internal/server/features/lottery/og.d.ts +40 -0
  11. package/dist/_internal/server/features/lottery/og.js +79 -0
  12. package/dist/_internal/server/features/messages/actions.js +4 -0
  13. package/dist/_internal/server/features/offers/adapters.js +17 -3
  14. package/dist/_internal/server/features/orders/actions.js +7 -2
  15. package/dist/_internal/server/features/raffle/actions.d.ts +19 -0
  16. package/dist/_internal/server/features/raffle/actions.js +25 -0
  17. package/dist/_internal/server/features/reviews/actions.js +1 -1
  18. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  19. package/dist/_internal/server/features/search/index-build/artifact-store.d.ts +89 -0
  20. package/dist/_internal/server/features/search/index-build/artifact-store.js +208 -0
  21. package/dist/_internal/server/features/search/index-build/build.d.ts +57 -0
  22. package/dist/_internal/server/features/search/index-build/build.js +285 -0
  23. package/dist/_internal/server/features/search/index-build/sources.d.ts +53 -0
  24. package/dist/_internal/server/features/search/index-build/sources.js +385 -0
  25. package/dist/_internal/server/features/search/search-sources.d.ts +0 -15
  26. package/dist/_internal/server/features/search/search-sources.js +142 -7
  27. package/dist/_internal/server/features/seo/robots.js +21 -1
  28. package/dist/_internal/server/features/seo/sitemap.js +1 -1
  29. package/dist/_internal/server/features/shared/listing-data-factory.js +16 -4
  30. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  31. package/dist/_internal/server/features/site-settings/adapters.js +26 -0
  32. package/dist/_internal/server/features/sticker-trace/actions.d.ts +109 -0
  33. package/dist/_internal/server/features/sticker-trace/actions.js +172 -0
  34. package/dist/_internal/server/features/sticker-trace/adapters.d.ts +80 -0
  35. package/dist/_internal/server/features/sticker-trace/adapters.js +174 -0
  36. package/dist/_internal/server/features/sticker-trace/build-cut-file.d.ts +26 -0
  37. package/dist/_internal/server/features/sticker-trace/build-cut-file.js +127 -0
  38. package/dist/_internal/server/features/sticker-trace/coordinator.d.ts +27 -0
  39. package/dist/_internal/server/features/sticker-trace/coordinator.js +169 -0
  40. package/dist/_internal/server/features/sticker-trace/fanout.d.ts +43 -0
  41. package/dist/_internal/server/features/sticker-trace/fanout.js +180 -0
  42. package/dist/_internal/server/features/sticker-trace/finalizer.d.ts +25 -0
  43. package/dist/_internal/server/features/sticker-trace/finalizer.js +123 -0
  44. package/dist/_internal/server/features/sticker-trace/mosaic-inputs.d.ts +30 -0
  45. package/dist/_internal/server/features/sticker-trace/mosaic-inputs.js +112 -0
  46. package/dist/_internal/server/features/sticker-trace/progress.d.ts +81 -0
  47. package/dist/_internal/server/features/sticker-trace/progress.js +198 -0
  48. package/dist/_internal/server/features/sticker-trace/promote.d.ts +23 -0
  49. package/dist/_internal/server/features/sticker-trace/promote.js +193 -0
  50. package/dist/_internal/server/features/sticker-trace/shard.d.ts +50 -0
  51. package/dist/_internal/server/features/sticker-trace/shard.js +174 -0
  52. package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +141 -0
  53. package/dist/_internal/server/features/sticker-trace/sheet-actions.js +279 -0
  54. package/dist/_internal/server/features/sticker-trace/storage.d.ts +135 -0
  55. package/dist/_internal/server/features/sticker-trace/storage.js +282 -0
  56. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  57. package/dist/_internal/server/functions/scheduled.js +25 -1
  58. package/dist/_internal/server/jobs/core/_event-ref.d.ts +51 -0
  59. package/dist/_internal/server/jobs/core/_event-ref.js +63 -0
  60. package/dist/_internal/server/jobs/core/assignSpinPrize.d.ts +18 -0
  61. package/dist/_internal/server/jobs/core/assignSpinPrize.js +77 -16
  62. package/dist/_internal/server/jobs/core/auctionSettlement.js +13 -4
  63. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +44 -15
  64. package/dist/_internal/server/jobs/core/bundleStockSync.js +3 -4
  65. package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +44 -0
  66. package/dist/_internal/server/jobs/core/event-draw-reasons.d.ts +78 -0
  67. package/dist/_internal/server/jobs/core/event-draw-reasons.js +133 -0
  68. package/dist/_internal/server/jobs/core/listingProcessor.js +28 -8
  69. package/dist/_internal/server/jobs/core/onOrderCreate.js +12 -1
  70. package/dist/_internal/server/jobs/core/onScamReportCreate.js +13 -2
  71. package/dist/_internal/server/jobs/core/onScamReportRejected.js +8 -2
  72. package/dist/_internal/server/jobs/core/onScamReportVerified.js +8 -2
  73. package/dist/_internal/server/jobs/core/onStoreWrite.js +11 -4
  74. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +5 -4
  75. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +1 -4
  76. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +1 -2
  77. package/dist/_internal/server/jobs/core/promotions.js +7 -4
  78. package/dist/_internal/server/jobs/core/searchIndexBuild.d.ts +7 -0
  79. package/dist/_internal/server/jobs/core/searchIndexBuild.js +211 -0
  80. package/dist/_internal/server/jobs/core/triggerEventRaffle.d.ts +35 -0
  81. package/dist/_internal/server/jobs/core/triggerEventRaffle.js +213 -31
  82. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +20 -14
  83. package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +8 -0
  84. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +15 -15
  85. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  86. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  87. package/dist/_internal/server/jobs/handlers/onCategoryWrite.js +18 -0
  88. package/dist/_internal/server/jobs/handlers/onProductWrite.js +18 -0
  89. package/dist/_internal/server/jobs/handlers/onReviewWrite.js +18 -0
  90. package/dist/_internal/server/jobs/handlers/onScamReportUpdate.js +18 -0
  91. package/dist/_internal/server/jobs/handlers/onStoreWrite.js +18 -0
  92. package/dist/_internal/server/jobs/handlers/searchIndexBuild.d.ts +2 -0
  93. package/dist/_internal/server/jobs/handlers/searchIndexBuild.js +2 -0
  94. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +8 -0
  95. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -6
  96. package/dist/_internal/shared/actions/action-registry.js +29 -0
  97. package/dist/_internal/shared/cache/revalidate-targets.d.ts +0 -1
  98. package/dist/_internal/shared/cache/revalidate-targets.js +10 -8
  99. package/dist/_internal/shared/features/events/config.d.ts +14 -0
  100. package/dist/_internal/shared/features/events/config.js +14 -0
  101. package/dist/_internal/shared/features/notifications/action-url.js +8 -0
  102. package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
  103. package/dist/_internal/shared/features/sticker-trace/codec.d.ts +81 -0
  104. package/dist/_internal/shared/features/sticker-trace/codec.js +30 -0
  105. package/dist/_internal/shared/features/sticker-trace/config.d.ts +81 -0
  106. package/dist/_internal/shared/features/sticker-trace/config.js +177 -0
  107. package/dist/_internal/shared/features/sticker-trace/cut-contour.d.ts +20 -0
  108. package/dist/_internal/shared/features/sticker-trace/cut-contour.js +60 -0
  109. package/dist/_internal/shared/features/sticker-trace/export/build-export-set.d.ts +65 -0
  110. package/dist/_internal/shared/features/sticker-trace/export/build-export-set.js +176 -0
  111. package/dist/_internal/shared/features/sticker-trace/export/cut-svg.d.ts +56 -0
  112. package/dist/_internal/shared/features/sticker-trace/export/cut-svg.js +120 -0
  113. package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.d.ts +92 -0
  114. package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.js +153 -0
  115. package/dist/_internal/shared/features/sticker-trace/export/nesting.d.ts +51 -0
  116. package/dist/_internal/shared/features/sticker-trace/export/nesting.js +110 -0
  117. package/dist/_internal/shared/features/sticker-trace/export/page-sizes.d.ts +68 -0
  118. package/dist/_internal/shared/features/sticker-trace/export/page-sizes.js +223 -0
  119. package/dist/_internal/shared/features/sticker-trace/export/part-tiling.d.ts +51 -0
  120. package/dist/_internal/shared/features/sticker-trace/export/part-tiling.js +178 -0
  121. package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.d.ts +26 -0
  122. package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.js +134 -0
  123. package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.d.ts +40 -0
  124. package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.js +195 -0
  125. package/dist/_internal/shared/features/sticker-trace/index.d.ts +2 -0
  126. package/dist/_internal/shared/features/sticker-trace/index.js +59 -0
  127. package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.d.ts +11 -0
  128. package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.js +66 -0
  129. package/dist/_internal/shared/features/sticker-trace/math/edt.d.ts +27 -0
  130. package/dist/_internal/shared/features/sticker-trace/math/edt.js +165 -0
  131. package/dist/_internal/shared/features/sticker-trace/math/label.d.ts +15 -0
  132. package/dist/_internal/shared/features/sticker-trace/math/label.js +156 -0
  133. package/dist/_internal/shared/features/sticker-trace/math/marching-squares.d.ts +16 -0
  134. package/dist/_internal/shared/features/sticker-trace/math/marching-squares.js +242 -0
  135. package/dist/_internal/shared/features/sticker-trace/math/morphology.d.ts +46 -0
  136. package/dist/_internal/shared/features/sticker-trace/math/morphology.js +168 -0
  137. package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.d.ts +88 -0
  138. package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.js +286 -0
  139. package/dist/_internal/shared/features/sticker-trace/math/rdp.d.ts +10 -0
  140. package/dist/_internal/shared/features/sticker-trace/math/rdp.js +74 -0
  141. package/dist/_internal/shared/features/sticker-trace/resume.d.ts +50 -0
  142. package/dist/_internal/shared/features/sticker-trace/resume.js +160 -0
  143. package/dist/_internal/shared/features/sticker-trace/trace/cluster.d.ts +20 -0
  144. package/dist/_internal/shared/features/sticker-trace/trace/cluster.js +132 -0
  145. package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.d.ts +28 -0
  146. package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.js +120 -0
  147. package/dist/_internal/shared/features/sticker-trace/trace/flood.d.ts +21 -0
  148. package/dist/_internal/shared/features/sticker-trace/trace/flood.js +104 -0
  149. package/dist/_internal/shared/features/sticker-trace/trace/geometry.d.ts +3 -0
  150. package/dist/_internal/shared/features/sticker-trace/trace/geometry.js +140 -0
  151. package/dist/_internal/shared/features/sticker-trace/trace/line-filter.d.ts +15 -0
  152. package/dist/_internal/shared/features/sticker-trace/trace/line-filter.js +138 -0
  153. package/dist/_internal/shared/features/sticker-trace/trace/merge.d.ts +27 -0
  154. package/dist/_internal/shared/features/sticker-trace/trace/merge.js +187 -0
  155. package/dist/_internal/shared/features/sticker-trace/trace/paper.d.ts +32 -0
  156. package/dist/_internal/shared/features/sticker-trace/trace/paper.js +147 -0
  157. package/dist/_internal/shared/features/sticker-trace/trace/prefilter.d.ts +30 -0
  158. package/dist/_internal/shared/features/sticker-trace/trace/prefilter.js +125 -0
  159. package/dist/_internal/shared/features/sticker-trace/trace/quantise.d.ts +10 -0
  160. package/dist/_internal/shared/features/sticker-trace/trace/quantise.js +153 -0
  161. package/dist/_internal/shared/features/sticker-trace/trace/ridge.d.ts +88 -0
  162. package/dist/_internal/shared/features/sticker-trace/trace/ridge.js +344 -0
  163. package/dist/_internal/shared/features/sticker-trace/trace/segment.d.ts +10 -0
  164. package/dist/_internal/shared/features/sticker-trace/trace/segment.js +251 -0
  165. package/dist/_internal/shared/features/sticker-trace/types.d.ts +197 -0
  166. package/dist/_internal/shared/features/sticker-trace/types.js +49 -0
  167. package/dist/_internal/shared/listing-types/_registry.d.ts +11 -0
  168. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  169. package/dist/_internal/shared/listing-types/art/config.js +1 -0
  170. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  171. package/dist/_internal/shared/listing-types/auction/config.js +1 -0
  172. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  173. package/dist/_internal/shared/listing-types/classified/config.js +1 -0
  174. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  175. package/dist/_internal/shared/listing-types/digital-code/config.js +1 -0
  176. package/dist/_internal/shared/listing-types/feature-flags.d.ts +7 -0
  177. package/dist/_internal/shared/listing-types/feature-flags.js +9 -0
  178. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  179. package/dist/_internal/shared/listing-types/live/config.js +1 -0
  180. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  181. package/dist/_internal/shared/listing-types/pre-order/config.js +1 -0
  182. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  183. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -0
  184. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  185. package/dist/_internal/shared/listing-types/standard/config.js +1 -0
  186. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  187. package/dist/_internal/shared/listing-types/stickers/config.js +1 -0
  188. package/dist/_internal/shared/media/limits.d.ts +12 -2
  189. package/dist/_internal/shared/media/limits.js +12 -2
  190. package/dist/_internal/shared/search/artifact-url.d.ts +35 -0
  191. package/dist/_internal/shared/search/artifact-url.js +119 -0
  192. package/dist/_internal/shared/search/index-record.d.ts +68 -0
  193. package/dist/_internal/shared/search/index-record.js +96 -0
  194. package/dist/_internal/shared/search/match.d.ts +65 -0
  195. package/dist/_internal/shared/search/match.js +263 -0
  196. package/dist/client.d.ts +8 -7
  197. package/dist/client.js +12 -5
  198. package/dist/configs/eslint.js +22 -2
  199. package/dist/constants/api-endpoints.d.ts +68 -0
  200. package/dist/constants/api-endpoints.js +32 -0
  201. package/dist/constants/field-names.d.ts +7 -1
  202. package/dist/constants/field-names.js +36 -1
  203. package/dist/contracts/repository.d.ts +8 -7
  204. package/dist/contracts/search.d.ts +2 -2
  205. package/dist/contracts/table.d.ts +3 -3
  206. package/dist/contracts/table.js +3 -3
  207. package/dist/core/contact-submissions.repository.js +2 -2
  208. package/dist/core/integration-keys.js +7 -1
  209. package/dist/core/newsletter.repository.js +2 -2
  210. package/dist/core/site-config.js +1 -1
  211. package/dist/errors/error-codes.d.ts +30 -0
  212. package/dist/errors/error-codes.js +30 -0
  213. package/dist/errors/error-display-map.js +40 -1
  214. package/dist/features/account/api/[userId]/addresses/route.js +1 -1
  215. package/dist/features/account/components/UserBidsView.js +1 -1
  216. package/dist/features/account/components/index.d.ts +0 -8
  217. package/dist/features/account/components/index.js +0 -4
  218. package/dist/features/addresses/repository/addresses.repository.d.ts +17 -6
  219. package/dist/features/addresses/repository/addresses.repository.js +65 -9
  220. package/dist/features/addresses/schemas/firestore.d.ts +17 -6
  221. package/dist/features/admin/actions/notification-actions.js +15 -6
  222. package/dist/features/admin/api/bids/route.js +1 -1
  223. package/dist/features/admin/api/coupons/route.js +1 -1
  224. package/dist/features/admin/api/products/route.js +1 -1
  225. package/dist/features/admin/api/reviews/route.js +1 -1
  226. package/dist/features/admin/components/AdminAdEditorView.js +19 -5
  227. package/dist/features/admin/components/AdminAuditLogView.js +1 -1
  228. package/dist/features/admin/components/AdminBlogEditorView.js +0 -1
  229. package/dist/features/admin/components/AdminBlogView.js +1 -1
  230. package/dist/features/admin/components/AdminBrandsView.js +17 -1
  231. package/dist/features/admin/components/AdminCategoryEditorView.js +0 -1
  232. package/dist/features/admin/components/AdminContactView.js +1 -1
  233. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -3
  234. package/dist/features/admin/components/AdminNewsletterView.js +1 -1
  235. package/dist/features/admin/components/AdminOrdersView.js +9 -2
  236. package/dist/features/admin/components/AdminPayoutsView.js +1 -1
  237. package/dist/features/admin/components/AdminPrizeDrawsView.js +11 -0
  238. package/dist/features/admin/components/AdminProductEditorView.d.ts +10 -1
  239. package/dist/features/admin/components/AdminProductEditorView.js +24 -37
  240. package/dist/features/admin/components/AdminProductsView.js +10 -3
  241. package/dist/features/admin/components/AdminScammersView.js +1 -1
  242. package/dist/features/admin/components/AdminSectionsView.js +21 -5
  243. package/dist/features/admin/components/AdminSessionsView.js +1 -1
  244. package/dist/features/admin/components/AdminSublistingCategoriesView.js +22 -1
  245. package/dist/features/admin/components/AdminTeamView.js +1 -1
  246. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +0 -1
  247. package/dist/features/admin/components/AdminUsersView.js +1 -1
  248. package/dist/features/admin/components/DataListingView.d.ts +2 -1
  249. package/dist/features/admin/components/DataListingView.js +9 -4
  250. package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +0 -2
  251. package/dist/features/admin/hooks/useAdminListing.d.ts +1 -1
  252. package/dist/features/admin/repository/site-settings.repository.js +8 -3
  253. package/dist/features/admin/schemas/firestore.d.ts +62 -1
  254. package/dist/features/admin/schemas/firestore.js +32 -0
  255. package/dist/features/admin/types/index.d.ts +1 -1
  256. package/dist/features/auctions/actions/bid-actions.d.ts +2 -2
  257. package/dist/features/auctions/actions/bid-actions.js +16 -4
  258. package/dist/features/auctions/api/route.js +1 -1
  259. package/dist/features/auctions/components/AuctionBottomActions.js +4 -0
  260. package/dist/features/auctions/repository/bid.repository.d.ts +46 -4
  261. package/dist/features/auctions/repository/bid.repository.js +114 -13
  262. package/dist/features/auctions/schemas/firestore.d.ts +24 -4
  263. package/dist/features/auctions/schemas/firestore.js +1 -0
  264. package/dist/features/auth/actions/profile-actions.js +30 -4
  265. package/dist/features/auth/components/index.d.ts +0 -1
  266. package/dist/features/auth/components/index.js +0 -1
  267. package/dist/features/auth/repository/session.repository.d.ts +17 -3
  268. package/dist/features/auth/repository/session.repository.js +46 -5
  269. package/dist/features/auth/repository/token.repository.d.ts +27 -4
  270. package/dist/features/auth/repository/token.repository.js +65 -11
  271. package/dist/features/auth/repository/user.repository.d.ts +33 -7
  272. package/dist/features/auth/repository/user.repository.js +100 -15
  273. package/dist/features/auth/schemas/firestore.d.ts +63 -12
  274. package/dist/features/auth/schemas/firestore.js +11 -0
  275. package/dist/features/before-after/api/route.js +1 -1
  276. package/dist/features/blog/components/BlogListView.js +16 -3
  277. package/dist/features/blog/components/BlogPostView.js +1 -1
  278. package/dist/features/blog/schemas/index.d.ts +2 -2
  279. package/dist/features/cart/api/route.js +1 -1
  280. package/dist/features/catalogue/repository/catalogue.repository.d.ts +7 -7
  281. package/dist/features/catalogue/repository/catalogue.repository.js +28 -10
  282. package/dist/features/catalogue/schemas/firestore.d.ts +11 -0
  283. package/dist/features/categories/api/route.js +10 -10
  284. package/dist/features/categories/components/CategoryDetailPageView.js +35 -5
  285. package/dist/features/categories/components/CategorySelectorCreate.js +0 -1
  286. package/dist/features/categories/components/index.d.ts +0 -2
  287. package/dist/features/categories/components/index.js +0 -1
  288. package/dist/features/categories/repository/categories.repository.d.ts +1 -0
  289. package/dist/features/categories/repository/categories.repository.js +31 -1
  290. package/dist/features/categories/schemas/index.d.ts +2 -2
  291. package/dist/features/categories/utils/taxonomy-visual.d.ts +21 -0
  292. package/dist/features/categories/utils/taxonomy-visual.js +58 -0
  293. package/dist/features/checkout/actions/checkout-value-otp-actions.js +8 -1
  294. package/dist/features/collections/api/[slug]/route.js +3 -3
  295. package/dist/features/collections/api/route.js +1 -1
  296. package/dist/features/consultation/api/route.js +1 -1
  297. package/dist/features/consultation/repository/consultations.repository.js +1 -1
  298. package/dist/features/consultation/types/index.d.ts +1 -1
  299. package/dist/features/contact/components/ContactForm.js +1 -3
  300. package/dist/features/corporate/api/route.js +1 -1
  301. package/dist/features/corporate/repository/corporate.repository.js +1 -1
  302. package/dist/features/corporate/types/index.d.ts +1 -1
  303. package/dist/features/cron/jobs/auction-expiry.job.js +1 -1
  304. package/dist/features/cron/jobs/preorder-reminder.job.js +1 -1
  305. package/dist/features/events/actions/event-actions.d.ts +14 -0
  306. package/dist/features/events/actions/event-actions.js +82 -3
  307. package/dist/features/events/api/[id]/route.js +2 -2
  308. package/dist/features/events/components/AdminEventEditorView.js +22 -3
  309. package/dist/features/events/components/AdminEventsView.js +32 -8
  310. package/dist/features/events/components/EventRafflesSection.d.ts +34 -1
  311. package/dist/features/events/components/EventRafflesSection.js +52 -3
  312. package/dist/features/events/components/SpinWheelView.d.ts +2 -2
  313. package/dist/features/events/components/SpinWheelView.js +41 -4
  314. package/dist/features/events/repository/event-entry.repository.d.ts +14 -2
  315. package/dist/features/events/repository/event-entry.repository.js +41 -7
  316. package/dist/features/events/schemas/firestore.d.ts +17 -4
  317. package/dist/features/events/types/index.d.ts +37 -0
  318. package/dist/features/faq/repository/faqs.repository.js +1 -1
  319. package/dist/features/faq/schemas/index.d.ts +5 -2
  320. package/dist/features/faq/schemas/index.js +6 -0
  321. package/dist/features/faq/types/index.d.ts +0 -1
  322. package/dist/features/grouped/components/GroupedListingEditorView.js +17 -2
  323. package/dist/features/homepage/api/carousel/[id]/route.js +1 -1
  324. package/dist/features/homepage/api/carousel/route.js +4 -4
  325. package/dist/features/homepage/api/route.js +2 -2
  326. package/dist/features/homepage/api/sections/route.js +1 -1
  327. package/dist/features/homepage/components/AnnouncementBar.d.ts +1 -9
  328. package/dist/features/homepage/components/AnnouncementBar.js +13 -19
  329. package/dist/features/homepage/components/MarketplaceHomepageView.js +6 -1
  330. package/dist/features/homepage/components/index.d.ts +0 -2
  331. package/dist/features/homepage/components/index.js +0 -1
  332. package/dist/features/homepage/repository/homepage.repository.js +1 -1
  333. package/dist/features/homepage/schemas/firestore.d.ts +6 -0
  334. package/dist/features/homepage/schemas/index.d.ts +10 -10
  335. package/dist/features/layout/AppLayoutShell.d.ts +12 -3
  336. package/dist/features/layout/AppLayoutShell.js +65 -13
  337. package/dist/features/layout/BottomActionsContext.d.ts +16 -0
  338. package/dist/features/layout/BottomActionsContext.js +5 -0
  339. package/dist/features/layout/BottomChrome.js +48 -9
  340. package/dist/features/layout/BottomNavbar.d.ts +14 -0
  341. package/dist/features/layout/BottomNavbar.js +43 -3
  342. package/dist/features/layout/MainNavbar.d.ts +11 -0
  343. package/dist/features/layout/NavbarLayout.d.ts +3 -0
  344. package/dist/features/layout/NavbarLayout.js +88 -11
  345. package/dist/features/layout/SideActionDock.d.ts +1 -0
  346. package/dist/features/layout/SideActionDock.js +219 -0
  347. package/dist/features/layout/TitleBarLayout.d.ts +1 -3
  348. package/dist/features/layout/TitleBarLayout.js +3 -4
  349. package/dist/features/layout/cta-anchor.d.ts +31 -0
  350. package/dist/features/layout/cta-anchor.js +31 -0
  351. package/dist/features/layout/hooks/useBottomActions.d.ts +7 -0
  352. package/dist/features/layout/hooks/useBottomActions.js +6 -1
  353. package/dist/features/layout/index.d.ts +9 -1
  354. package/dist/features/layout/index.js +9 -1
  355. package/dist/features/layout/useCtaReveal.d.ts +24 -0
  356. package/dist/features/layout/useCtaReveal.js +82 -0
  357. package/dist/features/layout/useHoldToOpen.d.ts +41 -0
  358. package/dist/features/layout/useHoldToOpen.js +84 -0
  359. package/dist/features/lottery/repository/lottery-entry.repository.d.ts +18 -3
  360. package/dist/features/lottery/repository/lottery-entry.repository.js +48 -6
  361. package/dist/features/lottery/schemas/firestore.d.ts +33 -5
  362. package/dist/features/loyalty/api/balance/route.js +1 -1
  363. package/dist/features/loyalty/api/history/route.js +1 -1
  364. package/dist/features/media/MediaImage.js +0 -1
  365. package/dist/features/media/MediaPickerModal.js +7 -1
  366. package/dist/features/media/modals/ImageEditor.js +5 -1
  367. package/dist/features/media/modals/VideoThumbnailSelector.js +17 -1
  368. package/dist/features/media/repository/media-assets.repository.d.ts +19 -0
  369. package/dist/features/media/upload/CameraCapture.js +16 -1
  370. package/dist/features/media/upload/ImageUpload.js +42 -4
  371. package/dist/features/media/upload/MediaUploadField.js +69 -13
  372. package/dist/features/media/upload/downscale-image.d.ts +56 -0
  373. package/dist/features/media/upload/downscale-image.js +150 -0
  374. package/dist/features/messages/components/ConversationThreadView.js +45 -2
  375. package/dist/features/messages/components/MessageSellerButton.d.ts +15 -1
  376. package/dist/features/messages/components/MessageSellerButton.js +10 -2
  377. package/dist/features/messages/schemas/firestore.d.ts +15 -0
  378. package/dist/features/orders/hooks/useOrders.js +4 -2
  379. package/dist/features/orders/repository/orders.repository.d.ts +15 -4
  380. package/dist/features/orders/repository/orders.repository.js +52 -20
  381. package/dist/features/orders/schemas/firestore.d.ts +15 -4
  382. package/dist/features/orders/schemas/index.d.ts +8 -8
  383. package/dist/features/orders/types/index.d.ts +2 -2
  384. package/dist/features/payments/repository/payments.repository.js +2 -2
  385. package/dist/features/payments/repository/payout.repository.d.ts +23 -6
  386. package/dist/features/payments/repository/payout.repository.js +63 -23
  387. package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +17 -6
  388. package/dist/features/payments/repository/saved-payment-methods.repository.js +59 -11
  389. package/dist/features/payments/schemas/firestore.d.ts +14 -3
  390. package/dist/features/payments/schemas/saved-methods-firestore.d.ts +12 -1
  391. package/dist/features/pre-orders/api/[slug]/route.js +1 -1
  392. package/dist/features/pre-orders/api/admin/route.js +1 -1
  393. package/dist/features/pre-orders/api/route.js +1 -1
  394. package/dist/features/pre-orders/repository/pre-orders.repository.d.ts +3 -3
  395. package/dist/features/pre-orders/repository/pre-orders.repository.js +6 -6
  396. package/dist/features/products/api/[id]/route.js +1 -1
  397. package/dist/features/products/components/ListingBottomActions.js +4 -0
  398. package/dist/features/products/components/ProductDetailPageView.js +2 -1
  399. package/dist/features/products/components/ProductGalleryClient.js +2 -2
  400. package/dist/features/products/components/ProductGrid.js +12 -1
  401. package/dist/features/products/components/index.d.ts +0 -2
  402. package/dist/features/products/components/index.js +0 -1
  403. package/dist/features/products/config/listing-type-listing-config.d.ts +0 -3
  404. package/dist/features/products/config/listing-type-listing-config.js +27 -2
  405. package/dist/features/products/constants/listing-tabs.d.ts +9 -0
  406. package/dist/features/products/constants/listing-tabs.js +3 -2
  407. package/dist/features/products/hooks/useProducts.js +2 -2
  408. package/dist/features/products/repository/products.repository.js +13 -1
  409. package/dist/features/products/schemas/index.d.ts +2 -2
  410. package/dist/features/products/types/index.d.ts +1 -1
  411. package/dist/features/promotions/api/route.js +1 -1
  412. package/dist/features/reviews/actions/review-actions.d.ts +6 -6
  413. package/dist/features/reviews/actions/review-actions.js +22 -8
  414. package/dist/features/reviews/components/ReviewDetailPageView.js +13 -1
  415. package/dist/features/reviews/components/ReviewsIndexPageView.js +21 -4
  416. package/dist/features/reviews/repository/reviews.repository.d.ts +19 -7
  417. package/dist/features/reviews/repository/reviews.repository.js +63 -12
  418. package/dist/features/reviews/schemas/firestore.d.ts +13 -2
  419. package/dist/features/reviews/schemas/index.d.ts +2 -2
  420. package/dist/features/scams/actions/scam-actions.js +16 -4
  421. package/dist/features/search/components/SearchResultsPageView.js +2 -1
  422. package/dist/features/search/hooks/useSearchIndex.d.ts +34 -0
  423. package/dist/features/search/hooks/useSearchIndex.js +172 -0
  424. package/dist/features/search/repository/search.repository.js +2 -2
  425. package/dist/features/seller/actions/offer-actions.d.ts +5 -5
  426. package/dist/features/seller/actions/offer-actions.js +38 -8
  427. package/dist/features/seller/actions/seller-actions.js +34 -7
  428. package/dist/features/seller/api/coupons/route.js +1 -1
  429. package/dist/features/seller/api/products/route.js +1 -1
  430. package/dist/features/seller/api/store/route.js +1 -1
  431. package/dist/features/seller/components/SellerBidsView.js +1 -1
  432. package/dist/features/seller/components/SellerCouponsView.js +1 -1
  433. package/dist/features/seller/components/SellerListingTypeView.js +9 -1
  434. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  435. package/dist/features/seller/components/SellerPayoutSettingsView.js +0 -1
  436. package/dist/features/seller/components/SellerPayoutsView.js +1 -1
  437. package/dist/features/seller/components/SellerProductShell.js +0 -1
  438. package/dist/features/seller/components/SellerProductsView.js +1 -1
  439. package/dist/features/seller/components/SellerShippingView.js +0 -1
  440. package/dist/features/seller/components/SellerStorefrontView.js +0 -1
  441. package/dist/features/seller/components/index.d.ts +0 -4
  442. package/dist/features/seller/components/index.js +0 -2
  443. package/dist/features/seller/repository/offer.repository.d.ts +15 -3
  444. package/dist/features/seller/repository/offer.repository.js +43 -7
  445. package/dist/features/seller/schemas/firestore.d.ts +14 -3
  446. package/dist/features/seller/schemas/index.d.ts +4 -4
  447. package/dist/features/seller/schemas/order-forms.d.ts +39 -0
  448. package/dist/features/seller/schemas/order-forms.js +37 -0
  449. package/dist/features/shell/FormShell.js +6 -2
  450. package/dist/features/shell/QuickFormDrawer.js +15 -1
  451. package/dist/features/shipments/components/AdminShipmentEditorView.js +0 -1
  452. package/dist/features/shipments/components/AdminShipmentsView.js +1 -1
  453. package/dist/features/sticker-trace/components/StickerCommitPanel.d.ts +30 -0
  454. package/dist/features/sticker-trace/components/StickerCommitPanel.js +93 -0
  455. package/dist/features/sticker-trace/components/StickerPrintPanel.d.ts +33 -0
  456. package/dist/features/sticker-trace/components/StickerPrintPanel.js +104 -0
  457. package/dist/features/sticker-trace/components/StickerRetryPanel.d.ts +20 -0
  458. package/dist/features/sticker-trace/components/StickerRetryPanel.js +128 -0
  459. package/dist/features/sticker-trace/components/StickerRunHistoryPanel.d.ts +45 -0
  460. package/dist/features/sticker-trace/components/StickerRunHistoryPanel.js +84 -0
  461. package/dist/features/sticker-trace/components/StickerTraceStudio.d.ts +27 -0
  462. package/dist/features/sticker-trace/components/StickerTraceStudio.js +171 -0
  463. package/dist/features/sticker-trace/constants/help-content.d.ts +23 -0
  464. package/dist/features/sticker-trace/constants/help-content.js +172 -0
  465. package/dist/features/sticker-trace/hooks/useLiveTrace.d.ts +27 -0
  466. package/dist/features/sticker-trace/hooks/useLiveTrace.js +160 -0
  467. package/dist/features/sticker-trace/hooks/useStickerTraceProgress.d.ts +22 -0
  468. package/dist/features/sticker-trace/hooks/useStickerTraceProgress.js +106 -0
  469. package/dist/features/sticker-trace/repository/sticker-sheets.repository.d.ts +113 -0
  470. package/dist/features/sticker-trace/repository/sticker-sheets.repository.js +146 -0
  471. package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.d.ts +132 -0
  472. package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.js +174 -0
  473. package/dist/features/sticker-trace/schemas/firestore.d.ts +248 -0
  474. package/dist/features/sticker-trace/schemas/firestore.js +79 -0
  475. package/dist/features/sticker-trace/server.d.ts +8 -0
  476. package/dist/features/sticker-trace/server.js +51 -0
  477. package/dist/features/stores/actions/store-query-actions.d.ts +2 -2
  478. package/dist/features/stores/actions/store-query-actions.js +7 -2
  479. package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -3
  480. package/dist/features/stores/api/[storeSlug]/products/route.js +3 -3
  481. package/dist/features/stores/api/[storeSlug]/reviews/route.js +1 -1
  482. package/dist/features/stores/api/[storeSlug]/route.js +1 -1
  483. package/dist/features/stores/components/StoreNavTabs.js +17 -2
  484. package/dist/features/stores/manifest.js +8 -4
  485. package/dist/features/support/repository/support.repository.d.ts +14 -3
  486. package/dist/features/support/repository/support.repository.js +93 -13
  487. package/dist/features/support/schemas/firestore.d.ts +16 -5
  488. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +61 -19
  489. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +19 -3
  490. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
  491. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +243 -0
  492. package/dist/features/tester/seed-data/authored/content-discovery__events.js +36 -10
  493. package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.d.ts +2 -0
  494. package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.js +209 -0
  495. package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.d.ts +2 -0
  496. package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.js +90 -0
  497. package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.d.ts +2 -0
  498. package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.js +85 -0
  499. package/dist/features/tester/seed-data/authored/index.js +10 -0
  500. package/dist/features/tester/seed-data/authored/selling__media-limits.js +19 -17
  501. package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +10 -5
  502. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.d.ts +2 -0
  503. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +283 -0
  504. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -22
  505. package/dist/index.d.ts +10 -23
  506. package/dist/index.js +27 -19
  507. package/dist/next/api/routeHandler.js +9 -0
  508. package/dist/next/routing/require-record.d.ts +13 -0
  509. package/dist/next/routing/require-record.js +62 -0
  510. package/dist/next/routing/route-map.d.ts +4 -4
  511. package/dist/next/routing/route-map.js +25 -3
  512. package/dist/providers/db-firebase/base.d.ts +4 -2
  513. package/dist/providers/db-firebase/base.js +21 -5
  514. package/dist/providers/db-firebase/base.repository.d.ts +11 -9
  515. package/dist/providers/db-firebase/base.repository.js +19 -0
  516. package/dist/providers/db-firebase/rtdb-paths.d.ts +12 -0
  517. package/dist/providers/db-firebase/rtdb-paths.js +12 -0
  518. package/dist/react/ErrorBoundary.js +6 -1
  519. package/dist/react/hooks/realtime-event-constants.d.ts +1 -0
  520. package/dist/react/hooks/realtime-event-constants.js +8 -0
  521. package/dist/react/hooks/useEntityDelete.js +5 -1
  522. package/dist/react/hooks/useMessage.js +5 -1
  523. package/dist/repositories/index.d.ts +2 -2
  524. package/dist/repositories/index.js +6 -1
  525. package/dist/schemas/registry.d.ts +13 -20
  526. package/dist/schemas/registry.js +31 -14
  527. package/dist/schemas/webhooks/google-oauth.d.ts +2 -2
  528. package/dist/security/encrypted.d.ts +117 -0
  529. package/dist/security/encrypted.js +100 -0
  530. package/dist/security/index.d.ts +5 -2
  531. package/dist/security/index.js +16 -2
  532. package/dist/security/pii-accessors.d.ts +71 -0
  533. package/dist/security/pii-accessors.js +150 -0
  534. package/dist/security/pii-encrypt.d.ts +0 -8
  535. package/dist/security/pii-encrypt.js +22 -15
  536. package/dist/security/pii-mask.d.ts +18 -0
  537. package/dist/security/pii-mask.js +27 -0
  538. package/dist/seed/_helpers/search-txt-wrappers.d.ts +6 -6
  539. package/dist/seed/_helpers/search-txt-wrappers.js +22 -0
  540. package/dist/seed/addresses-seed-data.d.ts +2 -2
  541. package/dist/seed/authenticity-knowledge-seed-data.d.ts +34 -0
  542. package/dist/seed/authenticity-knowledge-seed-data.js +1277 -0
  543. package/dist/seed/bids-seed-data.d.ts +2 -2
  544. package/dist/seed/bids-seed-data.js +23 -2
  545. package/dist/seed/events-seed-data.d.ts +2 -2
  546. package/dist/seed/events-seed-data.js +5 -0
  547. package/dist/seed/faq-seed-data.js +175 -5
  548. package/dist/seed/homepage-sections-seed-data.js +39 -23
  549. package/dist/seed/index.d.ts +1 -0
  550. package/dist/seed/index.js +1 -0
  551. package/dist/seed/lottery-entries-seed-data.d.ts +2 -2
  552. package/dist/seed/offers-seed-data.d.ts +2 -2
  553. package/dist/seed/offers-seed-data.js +1 -1
  554. package/dist/seed/orders-seed-data.d.ts +2 -2
  555. package/dist/seed/payouts-seed-data.d.ts +2 -2
  556. package/dist/seed/payouts-seed-data.js +1 -1
  557. package/dist/seed/reviews-seed-data.d.ts +1 -2
  558. package/dist/seed/seed-purpose.js +7 -0
  559. package/dist/seed/sessions-seed-data.d.ts +2 -2
  560. package/dist/seed/sessions-seed-data.js +5 -0
  561. package/dist/seed/site-settings-seed-data.js +1 -1
  562. package/dist/seed/support-tickets-seed-data.d.ts +2 -2
  563. package/dist/seed/support-tickets-seed-data.js +1 -1
  564. package/dist/seed/users-seed-data.d.ts +2 -2
  565. package/dist/seed/users-seed-data.js +1 -1
  566. package/dist/seo/json-ld.js +5 -1
  567. package/dist/server.d.ts +18 -3
  568. package/dist/server.js +59 -3
  569. package/dist/styles.css +237 -1
  570. package/dist/tailwind-utilities.css +1 -1
  571. package/dist/ui/components/Button.js +7 -1
  572. package/dist/ui/components/FilterChipGroup.d.ts +11 -0
  573. package/dist/ui/components/FilterChipGroup.js +1 -1
  574. package/dist/ui/components/ImageGallery.js +6 -1
  575. package/dist/ui/components/ListingLayout.js +14 -8
  576. package/dist/ui/components/OverflowRow.d.ts +92 -0
  577. package/dist/ui/components/OverflowRow.js +203 -0
  578. package/dist/ui/components/OverflowRow.style.css +235 -0
  579. package/dist/ui/components/RecordDetailModal.js +14 -1
  580. package/dist/ui/components/RowActionMenu.js +5 -1
  581. package/dist/ui/components/SideDrawer.js +5 -1
  582. package/dist/ui/components/SideModal.js +9 -1
  583. package/dist/ui/components/TabBarShell.d.ts +12 -1
  584. package/dist/ui/components/TabBarShell.js +3 -2
  585. package/dist/ui/components/Tabs.js +36 -24
  586. package/dist/ui/components/Toast.d.ts +3 -24
  587. package/dist/ui/components/Toast.js +2 -19
  588. package/dist/ui/components/TopBannerPortal.d.ts +15 -0
  589. package/dist/ui/components/TopBannerPortal.js +19 -0
  590. package/dist/ui/components/UnsavedChangesModal.js +5 -1
  591. package/dist/ui/components/index.style.css +1 -0
  592. package/dist/ui/components/row-collapse.d.ts +15 -0
  593. package/dist/ui/components/row-collapse.js +127 -0
  594. package/dist/ui/components/toast-context.d.ts +63 -0
  595. package/dist/ui/components/toast-context.js +48 -0
  596. package/dist/ui/components/top-banner-slot.d.ts +37 -0
  597. package/dist/ui/components/top-banner-slot.js +45 -0
  598. package/dist/ui/icons/icon-registry.d.ts +6 -0
  599. package/dist/ui/icons/icon-registry.js +17 -1
  600. package/dist/ui/index.d.ts +2 -0
  601. package/dist/ui/index.js +1 -0
  602. package/dist/utils/array.helper.d.ts +2 -2
  603. package/dist/utils/array.helper.js +5 -5
  604. package/dist/utils/id-generators.d.ts +6 -0
  605. package/dist/utils/id-generators.js +10 -0
  606. package/dist/utils/media-url.js +0 -2
  607. package/dist/utils/search-txt-builders.d.ts +28 -4
  608. package/dist/utils/search-txt-builders.js +41 -1
  609. package/dist/validation/schemas.d.ts +8 -8
  610. package/package.json +6 -2
  611. package/scripts/seed-cli.mjs +3 -0
  612. package/dist/features/account/components/UserAddressesView.d.ts +0 -13
  613. package/dist/features/account/components/UserAddressesView.js +0 -5
  614. package/dist/features/account/components/UserNotificationsView.d.ts +0 -15
  615. package/dist/features/account/components/UserNotificationsView.js +0 -12
  616. package/dist/features/account/components/UserSettingsView.d.ts +0 -16
  617. package/dist/features/account/components/UserSettingsView.js +0 -13
  618. package/dist/features/account/components/UserSupportView.d.ts +0 -3
  619. package/dist/features/account/components/UserSupportView.js +0 -166
  620. package/dist/features/admin/components/DemoSeedView.d.ts +0 -11
  621. package/dist/features/admin/components/DemoSeedView.js +0 -5
  622. package/dist/features/auth/components/AuthStatusPanel.d.ts +0 -10
  623. package/dist/features/auth/components/AuthStatusPanel.js +0 -22
  624. package/dist/features/categories/components/CategoryProductsView.d.ts +0 -45
  625. package/dist/features/categories/components/CategoryProductsView.js +0 -21
  626. package/dist/features/homepage/components/HowItWorksInfoView.d.ts +0 -36
  627. package/dist/features/homepage/components/HowItWorksInfoView.js +0 -10
  628. package/dist/features/layout/BottomActions.d.ts +0 -1
  629. package/dist/features/layout/BottomActions.js +0 -311
  630. package/dist/features/products/components/ProductsView.d.ts +0 -6
  631. package/dist/features/products/components/ProductsView.js +0 -5
  632. package/dist/features/seller/components/SellerStoreView.d.ts +0 -8
  633. package/dist/features/seller/components/SellerStoreView.js +0 -8
@@ -0,0 +1,211 @@
1
+ /*
2
+ * Core: build the public search index artifact.
3
+ *
4
+ * ## 🛑 THE READ BUDGET IS WHY THIS JOB HAS THREE MODES
5
+ *
6
+ * Firestore bills per document read regardless of `select()`. A full rebuild
7
+ * every 15 minutes is `96 × 3,730 ≈ 358,000 reads/day` against a 50,000/day
8
+ * free tier — seven times over. Hourly is still ~89,000. So:
9
+ *
10
+ * idle manifest not dirty, full not due 1 read ~90/day
11
+ * incremental dirty: `updatedAt >` per source 13 + changed ~200/day
12
+ * full >24h since the last one 3,730 3,730/day
13
+ *
14
+ * ≈ 4,000 reads/day, about 8% of the tier. Measured on production: a full
15
+ * build is 3,750 reads, 3,589 records, 384 KB raw / 48 KB brotli, in 4.1s.
16
+ *
17
+ * 🛑 EVERY RUN LOGS ITS MODE AND READ COUNT. If the incremental path ever
18
+ * breaks and silently falls back to FULL each cadence, that is 358,000
19
+ * reads/day and a quota wall — and the only way anyone finds out before the
20
+ * wall is this line. A job that does the wrong thing cheaply is a bug; one
21
+ * that does it seven times over the tier is an outage.
22
+ *
23
+ * ## Why its own scheduled job rather than folding into an existing one
24
+ *
25
+ * `countersReconcile` and `productStatsSync` already run near their 540s
26
+ * ceiling, and a full rebuild failing inside one of them would take category
27
+ * metrics down with it. `priceIndexRollup`'s header makes the same argument
28
+ * for staying separate. CLAUDE.md explicitly accepts ~$0.10/month for a new
29
+ * distinct-cadence Scheduler job rather than routing around it.
30
+ *
31
+ * @tag domain:search
32
+ * @tag layer:server
33
+ * @tag pattern:job-core
34
+ * @tag access:server
35
+ * @tag consumers:searchIndexBuildHandler
36
+ * @tag sideEffects:firestore,storage
37
+ */
38
+ import { getAdminStorage } from "../../../../providers/db-firebase/index.js";
39
+ import { normalizeError } from "../../../../errors/normalize.js";
40
+ import { serverLogger } from "../../../../monitoring/server-logger.js";
41
+ import { buildFull, buildIncremental, } from "../../features/search/index-build/build.js";
42
+ import { manifestRef, publishArtifact, publishManifestMirror, readManifest, storagePathFor, } from "../../features/search/index-build/artifact-store.js";
43
+ /** How stale the index may get before a reconciling full pass is forced. */
44
+ const FULL_REBUILD_INTERVAL_MS = 24 * 60 * 60 * 1000;
45
+ function dueForFull(manifest, now) {
46
+ if (!manifest || !manifest.lastFullAt)
47
+ return true;
48
+ const last = Date.parse(manifest.lastFullAt);
49
+ if (!Number.isFinite(last))
50
+ return true;
51
+ return now.getTime() - last >= FULL_REBUILD_INTERVAL_MS;
52
+ }
53
+ /**
54
+ * Fetch the previous artifact so an incremental pass can patch it.
55
+ *
56
+ * Returns null on any failure, which makes the caller fall back to a FULL
57
+ * build. That is the right degradation: a full build is expensive but
58
+ * correct, whereas patching an artifact we could not read is not possible at
59
+ * all.
60
+ */
61
+ async function readPreviousArtifact(storagePath) {
62
+ try {
63
+ const bucket = getAdminStorage().bucket();
64
+ const [buf] = await bucket.file(storagePath).download();
65
+ return JSON.parse(buf.toString("utf8"));
66
+ }
67
+ catch (err) {
68
+ const e = normalizeError(err);
69
+ /*
70
+ * WARN, not silence. Returning null here is a correct degradation — the
71
+ * caller falls back to a FULL build, which is expensive but right — and
72
+ * that is exactly why it needs a log line: a permanently unreadable
73
+ * previous artifact turns every run into a 3,730-read full build and the
74
+ * only outward sign is the read count creeping up. The fallback hides the
75
+ * fault, so the log is the one thing that does not.
76
+ */
77
+ serverLogger.warn("search index: previous artifact unreadable, falling back to FULL", {
78
+ storagePath,
79
+ message: e.message,
80
+ });
81
+ return null;
82
+ }
83
+ }
84
+ export async function runSearchIndexBuild(ctx) {
85
+ const { db, logger, now } = ctx;
86
+ const manifest = await readManifest(db);
87
+ const wantFull = dueForFull(manifest, now);
88
+ /*
89
+ * The idle exit. One read, ~90 times a day. Without it the job would do a
90
+ * 13-query incremental pass every 15 minutes whether or not anything
91
+ * changed, which is 1,250 reads/day spent proving nothing happened.
92
+ */
93
+ if (!wantFull && manifest && manifest.dirty !== true) {
94
+ logger.info("search index: idle (not dirty, full not due)", { reads: 1 });
95
+ return;
96
+ }
97
+ const version = (manifest?.version ?? 0) + 1;
98
+ let result;
99
+ let mode = "full";
100
+ if (!wantFull && manifest) {
101
+ const previous = await readPreviousArtifact(manifest.storagePath);
102
+ if (previous) {
103
+ const since = new Date(manifest.builtAt);
104
+ result = await buildIncremental(db, previous, since, version, now);
105
+ mode = "incremental";
106
+ }
107
+ }
108
+ if (!result) {
109
+ result = await buildFull(db, version, now);
110
+ mode = "full";
111
+ }
112
+ const { artifact, stats } = result;
113
+ const published = await publishArtifact(getAdminStorage().bucket(), artifact);
114
+ const next = {
115
+ schema: artifact.schema,
116
+ version,
117
+ builtAt: artifact.builtAt,
118
+ lastFullAt: mode === "full" ? artifact.builtAt : (manifest?.lastFullAt ?? artifact.builtAt),
119
+ storagePath: published.storagePath,
120
+ recordCount: artifact.r.length,
121
+ bytes: published.bytes,
122
+ perType: stats.perType,
123
+ /*
124
+ * Cleared LAST, after the artifact is live — clearing it first would lose
125
+ * every write that arrived while this build was running.
126
+ *
127
+ * 🛑 And NOT cleared at all if any source threw. An incremental pass skips
128
+ * a failing source by design (the previous values stay, which is correct
129
+ * and slightly stale), but clearing `dirty` on top of that would mean the
130
+ * next run exits idle and those types never recover until the daily full
131
+ * pass — a partial index reported as a clean one. Staying dirty costs one
132
+ * retry per cadence and is self-healing.
133
+ *
134
+ * This mattered immediately: the first incremental run against production
135
+ * failed on every source with a `where` clause, for a missing composite
136
+ * index, and without this it would have published a mostly-empty patch
137
+ * and called it done.
138
+ */
139
+ dirty: stats.failed > 0,
140
+ };
141
+ await manifestRef(db).set(next, { merge: true });
142
+ /*
143
+ * Firestore first (the next build reads it), the public mirror second (the
144
+ * browser reads it). See `publishManifestMirror` for why a failure here is
145
+ * logged rather than thrown.
146
+ */
147
+ await publishManifestMirror(getAdminStorage().bucket(), next);
148
+ /*
149
+ * 🛑 The line that makes a runaway visible. `reads` is the number that has
150
+ * to stay inside the budget, and `mode` is what explains it.
151
+ */
152
+ const line = stats.failed > 0 ? logger.warn : logger.info;
153
+ line("search index built", {
154
+ mode,
155
+ version,
156
+ reads: stats.reads,
157
+ // 🛑 Surfaced, not swallowed. A non-zero count means the artifact is
158
+ // incomplete for those types and the manifest stays dirty for a retry.
159
+ failedSources: stats.failed,
160
+ records: artifact.r.length,
161
+ dropped: stats.dropped,
162
+ bytes: published.bytes,
163
+ prunedVersions: published.pruned,
164
+ durationMs: stats.durationMs,
165
+ });
166
+ }
167
+ /**
168
+ * Force a full rebuild, ignoring the manifest's cadence. For a one-off repair
169
+ * after a schema bump or a reseed.
170
+ */
171
+ export async function runSearchIndexFullRebuild(ctx) {
172
+ const { db, logger, now } = ctx;
173
+ const manifest = await readManifest(db);
174
+ const version = (manifest?.version ?? 0) + 1;
175
+ try {
176
+ const { artifact, stats } = await buildFull(db, version, now);
177
+ const published = await publishArtifact(getAdminStorage().bucket(), artifact);
178
+ const next = {
179
+ schema: artifact.schema,
180
+ version,
181
+ builtAt: artifact.builtAt,
182
+ lastFullAt: artifact.builtAt,
183
+ storagePath: published.storagePath,
184
+ recordCount: artifact.r.length,
185
+ bytes: published.bytes,
186
+ perType: stats.perType,
187
+ dirty: false,
188
+ };
189
+ await manifestRef(db).set(next, { merge: true });
190
+ /*
191
+ * 🛑 The mirror belongs on BOTH paths. This one is the manual repair
192
+ * entry point — the one somebody reaches for precisely when the index is
193
+ * wrong — so a rebuild that fixed the artifact and left every browser
194
+ * pointed at the broken version would be the worst possible outcome of
195
+ * running it.
196
+ */
197
+ await publishManifestMirror(getAdminStorage().bucket(), next);
198
+ logger.info("search index FULL rebuild complete", {
199
+ version,
200
+ reads: stats.reads,
201
+ records: artifact.r.length,
202
+ bytes: published.bytes,
203
+ path: storagePathFor(version),
204
+ });
205
+ }
206
+ catch (err) {
207
+ const e = normalizeError(err);
208
+ logger.error("search index FULL rebuild failed", { message: e.message });
209
+ throw err;
210
+ }
211
+ }
@@ -1,6 +1,38 @@
1
1
  /**
2
2
  * Core: select a raffle winner for an event using `crypto.randomInt()`. Pool
3
3
  * logic keys off the event's `raffleType`.
4
+ *
5
+ * ## Three things in here had never worked
6
+ *
7
+ * 1. **The pool query could not match any entry created by this app.** It
8
+ * asked for `status == "CONFIRMED"` — a real field with a real admin
9
+ * workflow (`AdminAllEventEntriesView`'s Confirm / Waitlist / Cancel row
10
+ * actions) but with NO writer at creation time. `enterEvent` never set it
11
+ * and neither did the seed, so the pool was empty unless an admin had
12
+ * hand-confirmed every entry one row at a time — on a raffle seeded with
13
+ * 1,892 entries. Every draw returned `no_eligible_entries`; the raffle had
14
+ * never once been drawable.
15
+ *
16
+ * The predicate is now `reviewStatus == "approved"`: written on every entry
17
+ * path, already indexed `(eventId, reviewStatus, submittedAt DESC)`, and
18
+ * auto-approved for raffles by `enterEvent`. `status` keeps its RSVP
19
+ * meaning and is deliberately NOT the gate — see the note on both fields in
20
+ * `constants/field-names.ts`.
21
+ *
22
+ * 2. **`drawsPermitted: 1` was not enforced.** The already-drawn check and the
23
+ * winner write sat outside any transaction, so two concurrent admin clicks
24
+ * both saw no winner and both wrote — two winners for one prize. The
25
+ * check-and-write is now inside `runTransaction`, following the sibling
26
+ * core `prizeDrawAssignWinner`.
27
+ *
28
+ * 3. **The winner was never told.** A winner was recorded and the only way to
29
+ * find out was to revisit the event page.
30
+ *
31
+ * The `FEATURE_PRIZE_DRAWS` gate that used to open this function was deleted
32
+ * 2026-10-19 — see the header of `assignSpinPrize.ts`. Its branch here was the
33
+ * second-worst shaped of the nine: it returned early with **no `reason`**, and
34
+ * the route decides success by `if (data.reason && !data.raffleWinnerUserId)`,
35
+ * so a disabled feature answered `200 "Raffle triggered"` with no winner.
4
36
  */
5
37
  import type { JobContext } from "../runtime/types";
6
38
  export interface TriggerEventRaffleInput {
@@ -9,9 +41,12 @@ export interface TriggerEventRaffleInput {
9
41
  topN?: number;
10
42
  }
11
43
  export interface TriggerEventRaffleResult {
44
+ /** The canonical DOCUMENT ID, even when the caller passed a slug. */
12
45
  eventId: string;
13
46
  raffleWinnerUserId?: string;
14
47
  raffleWinnerDisplayName?: string;
48
+ /** The prize coupon's CODE, resolved at draw time and recorded on the event. */
49
+ rafflePrizeCouponCode?: string;
15
50
  raffleEntryCount: number;
16
51
  alreadyTriggered?: boolean;
17
52
  reason?: string;
@@ -1,40 +1,121 @@
1
1
  /**
2
2
  * Core: select a raffle winner for an event using `crypto.randomInt()`. Pool
3
3
  * logic keys off the event's `raffleType`.
4
+ *
5
+ * ## Three things in here had never worked
6
+ *
7
+ * 1. **The pool query could not match any entry created by this app.** It
8
+ * asked for `status == "CONFIRMED"` — a real field with a real admin
9
+ * workflow (`AdminAllEventEntriesView`'s Confirm / Waitlist / Cancel row
10
+ * actions) but with NO writer at creation time. `enterEvent` never set it
11
+ * and neither did the seed, so the pool was empty unless an admin had
12
+ * hand-confirmed every entry one row at a time — on a raffle seeded with
13
+ * 1,892 entries. Every draw returned `no_eligible_entries`; the raffle had
14
+ * never once been drawable.
15
+ *
16
+ * The predicate is now `reviewStatus == "approved"`: written on every entry
17
+ * path, already indexed `(eventId, reviewStatus, submittedAt DESC)`, and
18
+ * auto-approved for raffles by `enterEvent`. `status` keeps its RSVP
19
+ * meaning and is deliberately NOT the gate — see the note on both fields in
20
+ * `constants/field-names.ts`.
21
+ *
22
+ * 2. **`drawsPermitted: 1` was not enforced.** The already-drawn check and the
23
+ * winner write sat outside any transaction, so two concurrent admin clicks
24
+ * both saw no winner and both wrote — two winners for one prize. The
25
+ * check-and-write is now inside `runTransaction`, following the sibling
26
+ * core `prizeDrawAssignWinner`.
27
+ *
28
+ * 3. **The winner was never told.** A winner was recorded and the only way to
29
+ * find out was to revisit the event page.
30
+ *
31
+ * The `FEATURE_PRIZE_DRAWS` gate that used to open this function was deleted
32
+ * 2026-10-19 — see the header of `assignSpinPrize.ts`. Its branch here was the
33
+ * second-worst shaped of the nine: it returned early with **no `reason`**, and
34
+ * the route decides success by `if (data.reason && !data.raffleWinnerUserId)`,
35
+ * so a disabled feature answered `200 "Raffle triggered"` with no winner.
4
36
  */
5
37
  import crypto from "node:crypto";
6
- import { EVENT_ENTRY_FIELDS } from "../../../../constants/field-names.js";
7
- const EVENTS_COLLECTION = "events";
38
+ import { normalizeError } from "../../../../errors/normalize.js";
39
+ import { sendNotification } from "../../../../features/admin/actions/notification-actions.js";
40
+ import { EVENT_ENTRY_FIELDS, EVENT_FIELDS } from "../../../../constants/field-names.js";
41
+ import { EVENT_ENTRY_PII_FIELDS, maskPii } from "../../../../security/index.js";
42
+ import { resolveEventRef } from "./_event-ref.js";
43
+ import { EVENT_DRAW_REASON } from "./event-draw-reasons.js";
44
+ /**
45
+ * The winner's display name, masked for a PUBLIC announcement.
46
+ *
47
+ * `maskPii` rather than `eventEntryRepository.maskedEntry` because the pool
48
+ * here is typed as a local `EntryDoc`, not a full `EventEntryDocument` — and
49
+ * `maskPii` takes a plain object, so no cast is needed to use the shared rule.
50
+ */
51
+ const maskedWinnerName = (data) => maskPii(data, EVENT_ENTRY_PII_FIELDS).userDisplayName;
8
52
  const EVENT_ENTRIES_COLLECTION = "eventEntries";
53
+ const COUPONS_COLLECTION = "coupons";
9
54
  export async function runTriggerEventRaffle(input, ctx) {
10
- if (ctx.env("FEATURE_PRIZE_DRAWS") !== "true") {
11
- ctx.logger.info("FEATURE_PRIZE_DRAWS disabled — skipping event raffle");
12
- return { eventId: input.eventId, raffleEntryCount: 0 };
13
- }
14
- const eventRef = ctx.db.collection(EVENTS_COLLECTION).doc(input.eventId);
15
- const eventSnap = await eventRef.get();
16
- if (!eventSnap.exists) {
55
+ // Slug-or-id — the admin UI passes a doc id, but this shares its resolver
56
+ // with the spin core, which is reached from public slug URLs.
57
+ const resolved = await resolveEventRef(ctx.db, input.eventId);
58
+ if (!resolved) {
17
59
  return {
18
60
  eventId: input.eventId,
19
61
  raffleEntryCount: 0,
20
- reason: "event_not_found",
62
+ reason: EVENT_DRAW_REASON.EVENT_NOT_FOUND,
21
63
  };
22
64
  }
23
- const event = eventSnap.data();
65
+ const eventId = resolved.id;
66
+ const eventRef = resolved.ref;
67
+ const event = resolved.data;
68
+ /*
69
+ * Nothing checked that this event HAS a raffle, so the runner would happily
70
+ * draw a winner on a poll or a sale and write `raffleWinnerUserId` onto it.
71
+ * `hasRaffle` is the bolt-on flag; `type === "raffle"` is the dedicated
72
+ * event type. Either qualifies — a raffle-typed event with the flag unset is
73
+ * a seeding omission, not a reason to refuse a legitimate draw.
74
+ */
75
+ if (event.hasRaffle !== true && event.type !== EVENT_FIELDS.TYPE_VALUES.RAFFLE) {
76
+ return { eventId, raffleEntryCount: 0, reason: EVENT_DRAW_REASON.NOT_A_RAFFLE };
77
+ }
78
+ /*
79
+ * A draw is permitted on an ACTIVE or ENDED event, and deliberately not on
80
+ * draft / paused / cancelled. "Ended" is the normal case — a raffle is drawn
81
+ * after entries close — so gating on `active` alone would refuse every real
82
+ * draw. Cancelled is the one that matters: drawing a winner for a cancelled
83
+ * raffle creates an obligation nobody intended.
84
+ */
85
+ if (event.status !== EVENT_FIELDS.STATUS_VALUES.ACTIVE &&
86
+ event.status !== EVENT_FIELDS.STATUS_VALUES.ENDED) {
87
+ return { eventId, raffleEntryCount: 0, reason: EVENT_DRAW_REASON.NOT_ACTIVE };
88
+ }
89
+ // Cheap pre-check so the common "already drawn" case costs no transaction.
90
+ // The authoritative check is inside the transaction below — this one races.
24
91
  if (event.raffleWinnerUserId) {
25
92
  return {
26
- eventId: input.eventId,
93
+ eventId,
27
94
  raffleEntryCount: 0,
28
95
  alreadyTriggered: true,
29
96
  raffleWinnerUserId: event.raffleWinnerUserId,
97
+ reason: EVENT_DRAW_REASON.ALREADY_TRIGGERED,
30
98
  };
31
99
  }
32
100
  const raffleType = input.raffleType ?? event.raffleType ?? "open_raffle";
33
101
  const topN = input.topN ?? event.raffleTopN ?? 0;
102
+ /*
103
+ * Built OUTSIDE the transaction, on purpose. The pool can be large (a seeded
104
+ * raffle claims 1,892 entries) and a Firestore transaction holds locks for
105
+ * everything it reads — so reading the pool inside would make a draw scale
106
+ * its lock footprint with entrant count. The same split as
107
+ * `prizeDrawAssignWinner`: read the candidates normally, then do the
108
+ * check-and-write transactionally.
109
+ *
110
+ * 🛑 `reviewStatus == "approved"`, NOT `status == "CONFIRMED"`. See this
111
+ * file's header. `enterEvent` now auto-approves raffle entries, so a live
112
+ * entrant lands in this pool without an admin touching anything; a FLAGGED
113
+ * entry correctly drops out of it.
114
+ */
34
115
  const entriesSnap = await ctx.db
35
116
  .collection(EVENT_ENTRIES_COLLECTION)
36
- .where(EVENT_ENTRY_FIELDS.EVENT_ID, "==", input.eventId)
37
- .where(EVENT_ENTRY_FIELDS.STATUS, "==", EVENT_ENTRY_FIELDS.STATUS_VALUES.CONFIRMED)
117
+ .where(EVENT_ENTRY_FIELDS.EVENT_ID, "==", eventId)
118
+ .where(EVENT_ENTRY_FIELDS.REVIEW_STATUS, "==", EVENT_ENTRY_FIELDS.REVIEW_STATUS_VALUES.APPROVED)
38
119
  .get();
39
120
  let pool = entriesSnap.docs.map((d) => ({
40
121
  id: d.id,
@@ -60,32 +141,133 @@ export async function runTriggerEventRaffle(input, ctx) {
60
141
  }
61
142
  if (pool.length === 0) {
62
143
  return {
63
- eventId: input.eventId,
144
+ eventId,
64
145
  raffleEntryCount: 0,
65
- reason: "no_eligible_entries",
146
+ reason: EVENT_DRAW_REASON.NO_ELIGIBLE_ENTRIES,
66
147
  };
67
148
  }
68
- const winnerIdx = crypto.randomInt(0, pool.length);
69
- const winner = pool[winnerIdx];
70
- await eventRef.update({
71
- raffleWinnerUserId: winner.data.userId,
72
- raffleWinnerDisplayName: winner.data.userDisplayName ?? "",
73
- raffleWinnerEntryId: winner.id,
74
- raffleTriggeredAt: ctx.now,
75
- raffleEntryCount: entriesSnap.size,
76
- raffleGithubFunctionUrl: `${ctx.env("PROOF_OF_FAIRNESS_BASE_URL") ?? ""}/raffles/${input.eventId}.json`,
77
- updatedAt: ctx.now,
149
+ /*
150
+ * Resolve the prize coupon's CODE before the transaction and record it on the
151
+ * event alongside the winner.
152
+ *
153
+ * `rafflePrizeCouponId` was write-only: accepted by the admin create/update
154
+ * routes, declared in `EVENT_FIELDS`, rendered by the winner page — and never
155
+ * read by this runner. Recording the code here makes the prize durable
156
+ * against a later edit of the event's coupon, the same reason
157
+ * `assignSpinPrize` stores `spinPrizeCouponCode` on the entry rather than
158
+ * re-deriving it. Resolution is best-effort: a missing coupon must not cost
159
+ * somebody their win.
160
+ */
161
+ let rafflePrizeCouponCode;
162
+ if (event.rafflePrizeCouponId) {
163
+ const coupSnap = await ctx.db
164
+ .collection(COUPONS_COLLECTION)
165
+ .doc(event.rafflePrizeCouponId)
166
+ .get();
167
+ if (coupSnap.exists) {
168
+ rafflePrizeCouponCode = coupSnap.data().code;
169
+ }
170
+ else {
171
+ ctx.logger.warn("Raffle prize couponId does not resolve to a coupon", {
172
+ eventId,
173
+ couponId: event.rafflePrizeCouponId,
174
+ });
175
+ }
176
+ }
177
+ /*
178
+ * 🛑 The one-draw invariant. Re-read the event INSIDE the transaction.
179
+ *
180
+ * The pre-check above reads outside it, so two admins clicking at the same
181
+ * moment both pass it. Firestore aborts and retries a transaction whose read
182
+ * set changed, so whichever commits second sees the first winner here and
183
+ * bails. Two winners for one prize is the failure the tester's own case
184
+ * calls the most serious on this page, and it was fully reachable.
185
+ */
186
+ const drawn = await ctx.db.runTransaction(async (tx) => {
187
+ const fresh = await tx.get(eventRef);
188
+ const freshData = (fresh.data() ?? {});
189
+ if (freshData.raffleWinnerUserId) {
190
+ return {
191
+ alreadyTriggered: true,
192
+ raffleWinnerUserId: freshData.raffleWinnerUserId,
193
+ };
194
+ }
195
+ const winner = pool[crypto.randomInt(0, pool.length)];
196
+ const maskedName = maskedWinnerName(winner.data) ?? "";
197
+ tx.update(eventRef, {
198
+ [EVENT_FIELDS.RAFFLE_WINNER_USER_ID]: winner.data.userId,
199
+ /*
200
+ * 🛑 MASKED, not raw. `userDisplayName` is an `EVENT_ENTRY_PII_FIELDS`
201
+ * member, so an entry read returns ciphertext — and this writes onto the
202
+ * EVENT document, where the field is neither encrypted nor decrypted, and
203
+ * is then shown PUBLICLY as the raffle winner. Masked rather than revealed
204
+ * because a public winner announcement is exactly the surface that should
205
+ * read "R*** K***".
206
+ */
207
+ [EVENT_FIELDS.RAFFLE_WINNER_DISPLAY_NAME]: maskedName,
208
+ [EVENT_FIELDS.RAFFLE_WINNER_ENTRY_ID]: winner.id,
209
+ [EVENT_FIELDS.RAFFLE_TRIGGERED_AT]: ctx.now,
210
+ [EVENT_FIELDS.RAFFLE_ENTRY_COUNT]: entriesSnap.size,
211
+ ...(rafflePrizeCouponCode ? { rafflePrizeCouponCode } : {}),
212
+ [EVENT_FIELDS.RAFFLE_GITHUB_FUNCTION_URL]: `${ctx.env("PROOF_OF_FAIRNESS_BASE_URL") ?? ""}/raffles/${eventId}.json`,
213
+ [EVENT_FIELDS.UPDATED_AT]: ctx.now,
214
+ });
215
+ return {
216
+ alreadyTriggered: false,
217
+ winnerId: winner.id,
218
+ userId: winner.data.userId,
219
+ maskedName,
220
+ };
78
221
  });
222
+ if (drawn.alreadyTriggered) {
223
+ return {
224
+ eventId,
225
+ raffleEntryCount: 0,
226
+ alreadyTriggered: true,
227
+ raffleWinnerUserId: drawn.raffleWinnerUserId,
228
+ reason: EVENT_DRAW_REASON.ALREADY_TRIGGERED,
229
+ };
230
+ }
79
231
  ctx.logger.info("Event raffle triggered", {
80
- eventId: input.eventId,
232
+ eventId,
81
233
  raffleType,
82
234
  poolSize: pool.length,
83
- winnerId: winner.id,
235
+ winnerId: drawn.winnerId,
84
236
  });
237
+ /*
238
+ * Tell the winner. Best-effort and AFTER the commit: a notification that
239
+ * fails must not roll back a recorded draw (the draw is irreversible and
240
+ * re-running it would pick a different winner), and a send inside the
241
+ * transaction would be retried on every abort. Same shape as
242
+ * `prizeDrawAssignWinner`.
243
+ */
244
+ if (drawn.userId) {
245
+ try {
246
+ await sendNotification({
247
+ userId: drawn.userId,
248
+ type: "prize_won",
249
+ priority: "high",
250
+ title: "You won the raffle!",
251
+ message: rafflePrizeCouponCode
252
+ ? `You've been drawn as the winner. Your prize coupon code is ${rafflePrizeCouponCode}.`
253
+ : "You've been drawn as the winner — open the event to see your prize.",
254
+ relatedId: eventId,
255
+ relatedType: "event",
256
+ });
257
+ }
258
+ catch (err) {
259
+ void normalizeError(err);
260
+ ctx.logger.warn("Raffle winner notification failed", {
261
+ eventId,
262
+ err: err instanceof Error ? err.message : String(err),
263
+ });
264
+ }
265
+ }
85
266
  return {
86
- eventId: input.eventId,
87
- raffleWinnerUserId: winner.data.userId,
88
- raffleWinnerDisplayName: winner.data.userDisplayName,
267
+ eventId,
268
+ raffleWinnerUserId: drawn.userId,
269
+ raffleWinnerDisplayName: drawn.maskedName,
270
+ rafflePrizeCouponCode,
89
271
  raffleEntryCount: entriesSnap.size,
90
272
  };
91
273
  }
@@ -8,10 +8,7 @@ import { getDefaultCurrency } from "../../../../core/index.js";
8
8
  import { computePayoutDeduction } from "../../../shared/fees/calculator.js";
9
9
  import { ordersTaxableValue } from "../../../shared/checkout/order-math.js";
10
10
  export async function runWeeklyPayoutEligibility(ctx) {
11
- if (ctx.env("FEATURE_PAYOUTS") !== "true") {
12
- ctx.logger.info("FEATURE_PAYOUTS disabled — skipping weekly payout eligibility");
13
- return { payoutsCreated: 0, ordersProcessed: 0, payoutIds: [] };
14
- }
11
+ // `FEATURE_PAYOUTS` gate deleted 2026-10-19 — see `autoPayoutEligibility.ts`.
15
12
  ctx.logger.info("Starting weekly payout eligibility sweep");
16
13
  const siteSettings = await siteSettingsRepository.getSingleton();
17
14
  const commissions = siteSettings.commissions;
@@ -48,10 +45,19 @@ export async function runWeeklyPayoutEligibility(ctx) {
48
45
  * one pay different amounts for the same orders. */
49
46
  const grossAmount = ordersTaxableValue(orders.map((o) => o.data));
50
47
  const { platformFee, gatewayFee, gstOnFee, netAmount } = computePayoutDeduction(grossAmount, commissions);
48
+ /*
49
+ * 🛑 REVEALED before building a payout — see the identical note in
50
+ * `autoPayoutEligibility.ts`. `findById` is ciphertext-by-default and
51
+ * `createPayout` encrypts `sellerEmail`/`upiId` again, so the stored
52
+ * values went in double-encrypted and permanently unreadable, and the
53
+ * HMAC'd `sellerEmailIndex` could never match a real lookup.
54
+ */
55
+ const sellerPlain = userRepository.revealUser(seller);
51
56
  const payoutInput = {
52
57
  storeId,
53
- sellerName: (seller.displayName ?? seller.email ?? store.ownerId),
54
- sellerEmail: (seller.email ?? ""),
58
+ // `displayName` is plaintext by D1; the email fallback must be revealed.
59
+ sellerName: (seller.displayName ?? sellerPlain.email ?? store.ownerId),
60
+ sellerEmail: sellerPlain.email ?? "",
55
61
  orderIds: orders.map((o) => o.id),
56
62
  amount: netAmount,
57
63
  grossAmount,
@@ -63,20 +69,20 @@ export async function runWeeklyPayoutEligibility(ctx) {
63
69
  gstRate: commissions.gstPercent / 100,
64
70
  currency: defaultCurrency,
65
71
  status: "pending",
66
- paymentMethod: seller.payoutDetails?.method === "upi" ? "upi" : "bank_transfer",
67
- upiId: seller.payoutDetails?.method === "upi" ? seller.payoutDetails.upiId : undefined,
68
- bankAccount: seller.payoutDetails?.method === "bank_transfer" && seller.payoutDetails.bankAccount
72
+ paymentMethod: sellerPlain.payoutDetails?.method === "upi" ? "upi" : "bank_transfer",
73
+ upiId: sellerPlain.payoutDetails?.method === "upi" ? sellerPlain.payoutDetails.upiId : undefined,
74
+ bankAccount: sellerPlain.payoutDetails?.method === "bank_transfer" && sellerPlain.payoutDetails.bankAccount
69
75
  ? {
70
- accountHolderName: seller.payoutDetails.bankAccount.accountHolderName,
71
- accountNumberMasked: seller.payoutDetails.bankAccount.accountNumberMasked,
72
- ifscCode: seller.payoutDetails.bankAccount.ifscCode,
73
- bankName: seller.payoutDetails.bankAccount.bankName,
76
+ accountHolderName: sellerPlain.payoutDetails.bankAccount.accountHolderName,
77
+ accountNumberMasked: sellerPlain.payoutDetails.bankAccount.accountNumberMasked,
78
+ ifscCode: sellerPlain.payoutDetails.bankAccount.ifscCode,
79
+ bankName: sellerPlain.payoutDetails.bankAccount.bankName,
74
80
  }
75
81
  : undefined,
76
82
  notes: `Automated weekly payout — ${orders.length} delivered order(s)`,
77
83
  requestedAt: new Date(),
78
84
  };
79
- const { id: payoutId } = await payoutRepository.create(payoutInput);
85
+ const { id: payoutId } = await payoutRepository.createPayout(payoutInput);
80
86
  for (let i = 0; i < orders.length; i += BATCH_LIMIT) {
81
87
  const slice = orders.slice(i, i + BATCH_LIMIT);
82
88
  const batch = ctx.db.batch();
@@ -1,3 +1,11 @@
1
1
  import type { CallableHandler } from "../runtime/types";
2
2
  import { type AssignSpinPrizeInput, type AssignSpinPrizeResult } from "../core/assignSpinPrize";
3
+ /**
4
+ * Statuses come from `reasonToHttp`, shared with `POST /api/events/[id]/spin`.
5
+ *
6
+ * This used to be a hand-written if-chain, and it disagreed with the route it
7
+ * mirrors: `identity_required` was 400 here and 401 there, and there was no
8
+ * `feature_disabled` branch at all, so that reason fell through as a SUCCESS.
9
+ * Two entry points, one handler, two behaviours — Root Cause #85.
10
+ */
3
11
  export declare const assignSpinPrizeHandler: CallableHandler<AssignSpinPrizeInput, AssignSpinPrizeResult>;
@@ -1,25 +1,25 @@
1
1
  import { runAssignSpinPrize, } from "../core/assignSpinPrize.js";
2
- function httpError(message, status) {
2
+ import { reasonToHttp } from "../core/event-draw-reasons.js";
3
+ function httpError(message, status, code) {
3
4
  const err = new Error(message);
4
5
  err.httpStatus = status;
6
+ err.code = code;
5
7
  return err;
6
8
  }
9
+ /**
10
+ * Statuses come from `reasonToHttp`, shared with `POST /api/events/[id]/spin`.
11
+ *
12
+ * This used to be a hand-written if-chain, and it disagreed with the route it
13
+ * mirrors: `identity_required` was 400 here and 401 there, and there was no
14
+ * `feature_disabled` branch at all, so that reason fell through as a SUCCESS.
15
+ * Two entry points, one handler, two behaviours — Root Cause #85.
16
+ */
7
17
  export const assignSpinPrizeHandler = async (input, ctx) => {
8
18
  const result = await runAssignSpinPrize(input, ctx);
9
- if (result.reason === "event_not_found") {
10
- throw httpError("Event not found", 404);
11
- }
12
- if (result.reason === "identity_required") {
13
- throw httpError("No user or guest identity provided", 400);
14
- }
15
- if (result.reason === "login_required") {
16
- throw httpError("You must be logged in to spin this wheel", 401);
17
- }
18
- if (result.reason === "outside_spin_window") {
19
- throw httpError("This spin wheel is not open right now", 403);
20
- }
21
- if (result.reason === "no_prizes_configured") {
22
- throw httpError("No spin prizes configured for this event", 422);
19
+ // Mirrors the route's own success test: a reason with no prize is a refusal.
20
+ if (result.reason && !result.spinPrizeId) {
21
+ const { status, code, message } = reasonToHttp(result.reason);
22
+ throw httpError(message, status, code);
23
23
  }
24
24
  return result;
25
25
  };