@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,282 @@
1
+ /*
2
+ * WHY: Scans and artifacts are private bytes that must never be watermarked,
3
+ * never be publicly addressable, and never traverse Vercel.
4
+ * WHAT: Paths, the signed upload/read pair, magic-byte finalize, and tree
5
+ * deletion.
6
+ *
7
+ * 🛑 THREE RULES, each closing a failure this project has already paid for.
8
+ *
9
+ * 1. NOT through /api/media/[...slug]. That route applies NO authentication —
10
+ * deliberately, because its job is public product photography — and it
11
+ * ALWAYS watermarks (`resolveWatermarkConfig` has no skip path), which would
12
+ * ruin a cut-out and make the printed mosaic unusable. See § "Digital
13
+ * Content Delivery" for the same decision made for purchased assets.
14
+ *
15
+ * 2. NO `mediaAssets` row. That row IS the public `/media/{shortId}` slug — the
16
+ * resolver looks up `mediaAssets/{slug}` to find `storagePath` — so no row
17
+ * means no public URL exists at all. Object names are RANDOM, against the
18
+ * SEO-slug convention used everywhere else, because media filenames here are
19
+ * content-derived and therefore guessable, and guessable is the one property
20
+ * a private asset must not have.
21
+ *
22
+ * 3. VERCEL RETURNS JSON, NEVER BYTES. Every binary is fetched by the browser
23
+ * straight from GCS with a 5-minute signed URL; the route returns
24
+ * `{ url, expiresAt }`. Vercel's Fast Origin Transfer allowance is 10 GB and
25
+ * exceeding it PAUSED this project once already — 52.52 GB against that 10 GB
26
+ * (Root Cause #104). Cloud Storage's own egress allowance is ten times
27
+ * larger and is not a cliff.
28
+ *
29
+ * Accepted residue, stated rather than hidden: a signed URL is a 5-minute
30
+ * bearer token. Acceptable here because an admin's own scan is not a paid
31
+ * good — which was the precise reason `digital-content` refused this and
32
+ * streams through its route instead.
33
+ *
34
+ * EXPORTS: SCAN_ROOT, runPrefix, signScanUpload, finalizeScanUpload,
35
+ * signedReadUrl, writeRunArtifact, deleteRunTree, deleteSheetTree,
36
+ * measureSheetBytes
37
+ *
38
+ * @tag domain:sticker-trace
39
+ * @tag layer:server
40
+ * @tag pattern:none
41
+ * @tag access:server
42
+ * @tag consumers:api/admin/sticker-sheets,functions/sticker-trace
43
+ * @tag sideEffects:storage
44
+ */
45
+ import { normalizeError } from "../../../../errors/normalize.js";
46
+ import { serverLogger } from "../../../../monitoring/index.js";
47
+ import { getAdminStorage } from "../../../../providers/db-firebase/index.js";
48
+ /** Nothing under here is reachable without a signed URL minted by this module. */
49
+ export const SCAN_ROOT = "private/sticker-sheets";
50
+ /** How long a read URL lives. Long enough to fetch, short enough to be useless if leaked. */
51
+ const READ_TTL_MS = 5 * 60 * 1000;
52
+ /** An upload may take a while on a 79 MB PNG over a domestic uplink. */
53
+ const UPLOAD_TTL_MS = 15 * 60 * 1000;
54
+ /** Magic-byte families a scan may legitimately be. */
55
+ const ALLOWED_SCAN_MIMES = new Set([
56
+ "image/jpeg",
57
+ "image/png",
58
+ "image/webp",
59
+ "image/tiff",
60
+ "image/bmp",
61
+ ]);
62
+ export function runPrefix(sheetId, runId) {
63
+ return `${SCAN_ROOT}/${sheetId}/${runId}`;
64
+ }
65
+ /**
66
+ * A random object name, with the caller's extension preserved for tooling only.
67
+ *
68
+ * `crypto.randomUUID` rather than a slug: see rule 2 in the header. The
69
+ * original name is NOT used — a human-supplied filename is both guessable and
70
+ * a path-traversal surface.
71
+ */
72
+ function randomObjectName(extension) {
73
+ const ext = /^[a-z0-9]{1,5}$/i.test(extension) ? extension.toLowerCase() : "bin";
74
+ return `${globalThis.crypto.randomUUID()}.${ext}`;
75
+ }
76
+ /**
77
+ * Mint a one-object write URL for a scan.
78
+ *
79
+ * 🛑 `contentType` is BOUND INTO THE SIGNATURE. A client that PUTs different
80
+ * bytes than it declared gets a 403 from GCS rather than a stored lie — which
81
+ * is the cheap half of the guarantee. The expensive half is `finalizeScanUpload`
82
+ * below, because a matching header still proves nothing about the content.
83
+ */
84
+ export async function signScanUpload(input) {
85
+ const assetPath = `${runPrefix(input.sheetId, input.runId)}/${randomObjectName(input.extension)}`;
86
+ const expiresAt = new Date(Date.now() + UPLOAD_TTL_MS);
87
+ const [uploadUrl] = await getAdminStorage()
88
+ .bucket()
89
+ .file(assetPath)
90
+ .getSignedUrl({
91
+ version: "v4",
92
+ action: "write",
93
+ expires: expiresAt,
94
+ contentType: input.contentType,
95
+ });
96
+ return { uploadUrl, assetPath, expiresAt };
97
+ }
98
+ /**
99
+ * Prove the uploaded object is the image it claims to be.
100
+ *
101
+ * 🛑 The DECLARED type is not evidence, and a mismatch DELETES the object.
102
+ * Leaving it would mean an arbitrary file sitting in our bucket at a path our
103
+ * own code will later hand a signed URL for. The same magic-byte discipline as
104
+ * `/api/media/finalize`, which emits a structured 422 for exactly this.
105
+ *
106
+ * Reads only the first 4 KB — magic bytes live in the first few, and
107
+ * downloading a 79 MB scan into a Vercel function to inspect its header would
108
+ * be the egress mistake this module exists to prevent.
109
+ */
110
+ export async function finalizeScanUpload(assetPath, maxBytes) {
111
+ const file = getAdminStorage().bucket().file(assetPath);
112
+ let bytes = 0;
113
+ try {
114
+ const [meta] = await file.getMetadata();
115
+ bytes = Number(meta.size ?? 0);
116
+ }
117
+ catch (err) {
118
+ const n = normalizeError(err);
119
+ serverLogger.warn("Sticker scan finalize: object missing", { assetPath, message: n.message });
120
+ return { ok: false, code: "MISSING", message: "The upload did not complete." };
121
+ }
122
+ if (bytes > maxBytes) {
123
+ await deleteObject(assetPath);
124
+ return {
125
+ ok: false,
126
+ code: "TOO_LARGE",
127
+ message: `That scan is ${(bytes / 1048576).toFixed(1)} MB; the limit is ${(maxBytes / 1048576).toFixed(0)} MB.`,
128
+ };
129
+ }
130
+ try {
131
+ const [head] = await file.download({ start: 0, end: 4095 });
132
+ const { fileTypeFromBuffer } = await import("file-type");
133
+ const detected = await fileTypeFromBuffer(head);
134
+ if (!detected || !ALLOWED_SCAN_MIMES.has(detected.mime)) {
135
+ await deleteObject(assetPath);
136
+ return {
137
+ ok: false,
138
+ code: "MIME_MISMATCH",
139
+ message: "That file is not a readable image. Upload a JPEG, PNG, WebP, TIFF or BMP scan.",
140
+ };
141
+ }
142
+ return { ok: true, contentType: detected.mime, bytes };
143
+ }
144
+ catch (err) {
145
+ const n = normalizeError(err);
146
+ serverLogger.error("Sticker scan finalize: magic-byte read failed", {
147
+ assetPath,
148
+ message: n.message,
149
+ });
150
+ // 🛑 Fails CLOSED and deletes. An object we could not verify must not stay
151
+ // at a path this module will later sign a URL for.
152
+ await deleteObject(assetPath);
153
+ return { ok: false, code: "MIME_MISMATCH", message: "The upload could not be verified." };
154
+ }
155
+ }
156
+ /**
157
+ * A short-lived read URL for one private object.
158
+ *
159
+ * 🛑 The CALLER is responsible for having authorised the request. This function
160
+ * mints capability and checks nothing — it cannot, because it does not see the
161
+ * session. Every call site must sit behind `createRouteHandler`'s admin gate.
162
+ *
163
+ * SVG is forced to `attachment`: it is deliberately excluded from
164
+ * `ALLOWED_IMAGE_MIMES` as an XSS surface, and a signed GCS URL is still our
165
+ * origin's data. The canvas draws cut lines from `geometry.json`, never by
166
+ * rendering the SVG.
167
+ */
168
+ export async function signedReadUrl(assetPath, opts) {
169
+ const expiresAt = new Date(Date.now() + READ_TTL_MS);
170
+ const forceDownload = opts?.downloadAs || /\.svg$/i.test(assetPath);
171
+ const [url] = await getAdminStorage()
172
+ .bucket()
173
+ .file(assetPath)
174
+ .getSignedUrl({
175
+ version: "v4",
176
+ action: "read",
177
+ expires: expiresAt,
178
+ ...(forceDownload
179
+ ? {
180
+ responseDisposition: `attachment; filename="${(opts?.downloadAs ?? "cut.svg").replace(/"/g, "")}"`,
181
+ }
182
+ : {}),
183
+ });
184
+ return { url, expiresAt };
185
+ }
186
+ /** Save an artifact the tracer produced. Server-side only — never a client upload. */
187
+ export async function writeRunArtifact(input) {
188
+ const assetPath = `${runPrefix(input.sheetId, input.runId)}/${input.name}`;
189
+ const buffer = Buffer.isBuffer(input.body) ? input.body : Buffer.from(input.body);
190
+ await getAdminStorage()
191
+ .bucket()
192
+ .file(assetPath)
193
+ .save(buffer, { contentType: input.contentType, resumable: false });
194
+ return { assetPath, bytes: buffer.byteLength };
195
+ }
196
+ async function deleteObject(assetPath) {
197
+ try {
198
+ await getAdminStorage().bucket().file(assetPath).delete({ ignoreNotFound: true });
199
+ }
200
+ catch (err) {
201
+ const n = normalizeError(err);
202
+ serverLogger.warn("Sticker trace object delete failed", { assetPath, message: n.message });
203
+ }
204
+ }
205
+ /**
206
+ * Delete everything one run wrote.
207
+ *
208
+ * `deleteFiles` pages internally, so this is safe on a `max`-tier run that
209
+ * produced sixty parts plus exports — unlike a hand-batched delete, which is
210
+ * the shape that died with `Transaction too big` on `categories`.
211
+ */
212
+ export async function deleteRunTree(sheetId, runId) {
213
+ await deletePrefix(`${runPrefix(sheetId, runId)}/`);
214
+ }
215
+ /** Delete a whole sheet: the scan, and every run under it. */
216
+ export async function deleteSheetTree(sheetId) {
217
+ await deletePrefix(`${SCAN_ROOT}/${sheetId}/`);
218
+ }
219
+ async function deletePrefix(prefix) {
220
+ /*
221
+ * 🛑 The trailing slash is load-bearing. `prefix: "…/sheet-1"` also matches
222
+ * `…/sheet-10`, so deleting one sheet would delete its numeric neighbours —
223
+ * silently, and reported as success.
224
+ */
225
+ if (!prefix.startsWith(`${SCAN_ROOT}/`) || !prefix.endsWith("/")) {
226
+ throw new Error(`Refusing to delete outside ${SCAN_ROOT}: ${prefix}`);
227
+ }
228
+ await getAdminStorage().bucket().deleteFiles({ prefix, force: true });
229
+ }
230
+ /**
231
+ * Total stored bytes and object count under one sheet, for the usage meter.
232
+ *
233
+ * 🛑 A quota only discoverable in the GCP console is a quota nobody watches —
234
+ * the same failure as Root Cause #104, where the first signal was a billing
235
+ * page. This is what makes "keep every run" a safe default rather than a
236
+ * deferred surprise.
237
+ */
238
+ export async function measureSheetBytes(sheetId) {
239
+ const [files] = await getAdminStorage()
240
+ .bucket()
241
+ .getFiles({ prefix: `${SCAN_ROOT}/${sheetId}/` });
242
+ let bytes = 0;
243
+ for (const f of files)
244
+ bytes += Number(f.metadata?.size ?? 0);
245
+ return { bytes, objects: files.length };
246
+ }
247
+ /**
248
+ * Download a stored object's bytes.
249
+ *
250
+ * 🛑 Takes a RAW assetPath, which is only ever a value this module produced —
251
+ * `sourceScan.assetPath` or a `runPrefix`-derived artifact path. It must never
252
+ * be fed a client-supplied string: the bucket's storage rule is
253
+ * `allow read: if true`, so path containment is enforced here or nowhere.
254
+ */
255
+ export async function readScanBytes(assetPath) {
256
+ if (!assetPath.startsWith(`${SCAN_ROOT}/`)) {
257
+ // Loud, not a silent empty read: an out-of-tree path means a caller
258
+ // assembled it rather than reading it off a document, and returning
259
+ // nothing would look like an empty scan.
260
+ throw new Error(`Refusing to read outside ${SCAN_ROOT}: ${assetPath}`);
261
+ }
262
+ const [buf] = await getAdminStorage().bucket().file(assetPath).download();
263
+ return new Uint8Array(buf);
264
+ }
265
+ /**
266
+ * Read and parse a run's `geometry.json`.
267
+ *
268
+ * 🛑 Parsed HERE rather than in the shard, so the one place that knows the
269
+ * artifact's name also owns its shape. A shard assembling the path itself
270
+ * would be a second definition of the layout — and the `parts` array is what
271
+ * every shard slices, so a disagreement renders the wrong crops rather than
272
+ * failing.
273
+ */
274
+ export async function readRunGeometry(runId, sheetId) {
275
+ const assetPath = `${runPrefix(sheetId, runId)}/geometry.json`;
276
+ const [buf] = await getAdminStorage().bucket().file(assetPath).download();
277
+ const parsed = JSON.parse(buf.toString("utf8"));
278
+ if (!parsed || !Array.isArray(parsed.parts) || !parsed.workingSize) {
279
+ throw new Error(`geometry.json is malformed for run ${runId}`);
280
+ }
281
+ return parsed;
282
+ }
@@ -7,6 +7,7 @@ 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
9
  export declare const revenueRollup: import("./types").ScheduledFunctionDefinition;
10
+ export declare const searchIndexBuild: import("./types").ScheduledFunctionDefinition;
10
11
  export declare const priceIndexRollup: import("./types").ScheduledFunctionDefinition;
11
12
  export declare const analyticsAlertEvaluate: import("./types").ScheduledFunctionDefinition;
12
13
  export declare const pageViewRollup: import("./types").ScheduledFunctionDefinition;
@@ -27,4 +28,4 @@ export declare const prizeDrawExpiryReveal: import("./types").ScheduledFunctionD
27
28
  export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
28
29
  export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
29
30
  export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
30
- 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, import("./types").ScheduledFunctionDefinition];
31
+ 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, 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, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, priceIndexRollupHandler, analyticsAlertEvaluateHandler, pageViewRollupHandler, pageViewPruneHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testDataCleanupHandler, } from "../jobs/handlers/index.js";
3
+ import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, priceIndexRollupHandler, searchIndexBuildHandler, analyticsAlertEvaluateHandler, pageViewRollupHandler, pageViewPruneHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testDataCleanupHandler, } from "../jobs/handlers/index.js";
4
4
  import { defineFunction } from "./define.js";
5
5
  const REGION = "asia-south1";
6
6
  const EVERY_5_MIN = "every 5 minutes";
@@ -83,6 +83,29 @@ export const revenueRollup = defineFunction({
83
83
  * additionally drop each category page's ISR entry nightly on a project that
84
84
  * was paused for exceeding ISR writes by 15.5×.
85
85
  */
86
+ /*
87
+ * Build the public search index artifact — every 15 minutes, but it EXITS on
88
+ * one read unless something changed.
89
+ *
90
+ * 🛑 The cadence is not the cost. Firestore bills per document read
91
+ * regardless of `select()`, so a full rebuild at this cadence would be
92
+ * ~358,000 reads/day against a 50,000/day tier. The job is idle-exit /
93
+ * incremental / daily-full instead, landing at ~4,000/day. See the core's
94
+ * header; it logs its mode and read count on every run so a regression to
95
+ * always-full is visible before it is a quota wall.
96
+ *
97
+ * Its own job rather than folded into an existing cadence: `countersReconcile`
98
+ * and `productStatsSync` already run near their 540s ceiling, and a rebuild
99
+ * failing inside one would take category metrics down with it — the argument
100
+ * `priceIndexRollup` makes directly below.
101
+ */
102
+ export const searchIndexBuild = defineFunction({
103
+ name: "searchIndexBuild",
104
+ description: "Build the public search index artifact into Storage (idle-exit / incremental / daily full).",
105
+ trigger: { kind: "schedule", cron: "*/15 * * * *", timeZone: "UTC" },
106
+ handler: searchIndexBuildHandler,
107
+ options: { region: REGION, timeoutSeconds: 540, memory: "512MiB", maxInstances: 1 },
108
+ });
86
109
  export const priceIndexRollup = defineFunction({
87
110
  name: "priceIndexRollup",
88
111
  description: "Roll up realised sale prices per leaf category into priceIndex/{leafId} (daily 04:30 UTC = 10:00 IST).",
@@ -277,6 +300,7 @@ export const SCHEDULED_FUNCTIONS = [
277
300
  productStatsSync,
278
301
  revenueRollup,
279
302
  priceIndexRollup,
303
+ searchIndexBuild,
280
304
  analyticsAlertEvaluate,
281
305
  pageViewRollup,
282
306
  pageViewPrune,
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Resolve an event reference from a value that may be either a document id or
3
+ * a slug — the ONE implementation for the `ctx.db` (job-core) layer.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * Public URLs link by SLUG: `EventCard` builds its href from
8
+ * `event.slug ?? event.id`, and `/events/[id]/participate` reuses that same
9
+ * route param. So every job core reached from a public surface receives a slug
10
+ * far more often than an id.
11
+ *
12
+ * `assignSpinPrize` and `triggerEventRaffle` both did a bare
13
+ * `.collection("events").doc(eventId).get()` with no slug fallback, so a spin
14
+ * launched from any card link returned `event_not_found` — 404 on every
15
+ * attempt, forever. `enterEvent` had always resolved it correctly
16
+ * (`eventRepository.findByIdOrSlug`, under a comment saying "eventId may have
17
+ * arrived as a slug (public URLs link by slug)"); the two job cores were the
18
+ * copies that never learned the rule.
19
+ *
20
+ * ## Why not `eventRepository.findByIdOrSlug`
21
+ *
22
+ * That is the right call at the repository layer and `enterEvent` keeps using
23
+ * it. A job core holds a raw `Firestore` from `JobContext` and no repository —
24
+ * and no repository in this codebase imports another feature's, because of the
25
+ * Turbopack import-chain trap (Root Cause #6/#18). A raw collection read has
26
+ * neither problem. This function also returns the `DocumentReference`, which a
27
+ * repository method cannot, and the cores need it to write.
28
+ *
29
+ * ## Slug first, deliberately
30
+ *
31
+ * Mirrors `findByIdOrSlug`'s own order. The common caller is a public URL, so
32
+ * the slug query hits on the first try; an id lookup then costs one extra
33
+ * query only on the admin path, where it is already behind a role gate.
34
+ */
35
+ import type { DocumentReference, Firestore } from "firebase-admin/firestore";
36
+ export interface ResolvedEventRef<T = unknown> {
37
+ /** The canonical DOCUMENT ID — never the slug, whichever was passed in. */
38
+ id: string;
39
+ ref: DocumentReference;
40
+ data: T;
41
+ }
42
+ /**
43
+ * @returns the resolved event, or `null` when neither a slug nor an id matches.
44
+ *
45
+ * 🛑 Callers must use the returned `id` for every subsequent query and write.
46
+ * Carrying the caller's original `idOrSlug` forward is how a guest identity
47
+ * ends up hashed on the slug on one path and on the doc id on another — two
48
+ * identities for one person on one event, which silently resets any per-user
49
+ * cap (see `hashGuestIdentity` at the spin route).
50
+ */
51
+ export declare function resolveEventRef<T = unknown>(db: Firestore, idOrSlug: string): Promise<ResolvedEventRef<T> | null>;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Resolve an event reference from a value that may be either a document id or
3
+ * a slug — the ONE implementation for the `ctx.db` (job-core) layer.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * Public URLs link by SLUG: `EventCard` builds its href from
8
+ * `event.slug ?? event.id`, and `/events/[id]/participate` reuses that same
9
+ * route param. So every job core reached from a public surface receives a slug
10
+ * far more often than an id.
11
+ *
12
+ * `assignSpinPrize` and `triggerEventRaffle` both did a bare
13
+ * `.collection("events").doc(eventId).get()` with no slug fallback, so a spin
14
+ * launched from any card link returned `event_not_found` — 404 on every
15
+ * attempt, forever. `enterEvent` had always resolved it correctly
16
+ * (`eventRepository.findByIdOrSlug`, under a comment saying "eventId may have
17
+ * arrived as a slug (public URLs link by slug)"); the two job cores were the
18
+ * copies that never learned the rule.
19
+ *
20
+ * ## Why not `eventRepository.findByIdOrSlug`
21
+ *
22
+ * That is the right call at the repository layer and `enterEvent` keeps using
23
+ * it. A job core holds a raw `Firestore` from `JobContext` and no repository —
24
+ * and no repository in this codebase imports another feature's, because of the
25
+ * Turbopack import-chain trap (Root Cause #6/#18). A raw collection read has
26
+ * neither problem. This function also returns the `DocumentReference`, which a
27
+ * repository method cannot, and the cores need it to write.
28
+ *
29
+ * ## Slug first, deliberately
30
+ *
31
+ * Mirrors `findByIdOrSlug`'s own order. The common caller is a public URL, so
32
+ * the slug query hits on the first try; an id lookup then costs one extra
33
+ * query only on the admin path, where it is already behind a role gate.
34
+ */
35
+ import { EVENT_FIELDS } from "../../../../constants/field-names.js";
36
+ const EVENTS_COLLECTION = "events";
37
+ /**
38
+ * @returns the resolved event, or `null` when neither a slug nor an id matches.
39
+ *
40
+ * 🛑 Callers must use the returned `id` for every subsequent query and write.
41
+ * Carrying the caller's original `idOrSlug` forward is how a guest identity
42
+ * ends up hashed on the slug on one path and on the doc id on another — two
43
+ * identities for one person on one event, which silently resets any per-user
44
+ * cap (see `hashGuestIdentity` at the spin route).
45
+ */
46
+ export async function resolveEventRef(db, idOrSlug) {
47
+ if (!idOrSlug)
48
+ return null;
49
+ const bySlug = await db
50
+ .collection(EVENTS_COLLECTION)
51
+ .where(EVENT_FIELDS.SLUG, "==", idOrSlug)
52
+ .limit(1)
53
+ .get();
54
+ if (!bySlug.empty) {
55
+ const doc = bySlug.docs[0];
56
+ return { id: doc.id, ref: doc.ref, data: doc.data() };
57
+ }
58
+ const byId = await db.collection(EVENTS_COLLECTION).doc(idOrSlug).get();
59
+ if (byId.exists) {
60
+ return { id: byId.id, ref: byId.ref, data: byId.data() };
61
+ }
62
+ return null;
63
+ }
@@ -9,6 +9,23 @@
9
9
  * find/create/assign/mark-used sequence runs inside a single Firestore
10
10
  * transaction so two rapid spins from the same identity can't both win a
11
11
  * prize (the old read-then-`ref.update()` version had this race).
12
+ *
13
+ * ## Every non-success branch returns a `reason`, and that is load-bearing
14
+ *
15
+ * Both entry points — `POST /api/events/[id]/spin` and the Firebase callable —
16
+ * decide success by `if (data.reason && !data.spinPrizeId)`. A branch that
17
+ * returns early WITHOUT a reason is therefore reported to the caller as a
18
+ * successful spin that awarded nothing. Reasons come from
19
+ * `EVENT_DRAW_REASON`; `reasonToHttp` turns each into one status and one
20
+ * stable code for both entry points (Root Cause #85).
21
+ *
22
+ * The `FEATURE_PRIZE_DRAWS` gate that used to open this function was DELETED
23
+ * on 2026-10-19. The whole `FEATURE_*` mechanism had already been retired —
24
+ * `audit-feature-flags` bans it strict-zero and `.env.example` declares no
25
+ * such key — but that audit only matched `process.env.FEATURE_*`, and this
26
+ * read it as `ctx.env("FEATURE_PRIZE_DRAWS")`, which the regex could not see.
27
+ * So an env var nothing documented was never set in production and every spin
28
+ * returned 503 `feature_disabled` while the Spin button rendered enabled.
12
29
  */
13
30
  import type { JobContext } from "../runtime/types";
14
31
  export interface AssignSpinPrizeInput {
@@ -19,6 +36,7 @@ export interface AssignSpinPrizeInput {
19
36
  guestIpHash?: string;
20
37
  }
21
38
  export interface AssignSpinPrizeResult {
39
+ /** The canonical DOCUMENT ID, even when the caller passed a slug. */
22
40
  eventId: string;
23
41
  userId?: string;
24
42
  spinPrizeId?: string;
@@ -9,39 +9,88 @@
9
9
  * find/create/assign/mark-used sequence runs inside a single Firestore
10
10
  * transaction so two rapid spins from the same identity can't both win a
11
11
  * prize (the old read-then-`ref.update()` version had this race).
12
+ *
13
+ * ## Every non-success branch returns a `reason`, and that is load-bearing
14
+ *
15
+ * Both entry points — `POST /api/events/[id]/spin` and the Firebase callable —
16
+ * decide success by `if (data.reason && !data.spinPrizeId)`. A branch that
17
+ * returns early WITHOUT a reason is therefore reported to the caller as a
18
+ * successful spin that awarded nothing. Reasons come from
19
+ * `EVENT_DRAW_REASON`; `reasonToHttp` turns each into one status and one
20
+ * stable code for both entry points (Root Cause #85).
21
+ *
22
+ * The `FEATURE_PRIZE_DRAWS` gate that used to open this function was DELETED
23
+ * on 2026-10-19. The whole `FEATURE_*` mechanism had already been retired —
24
+ * `audit-feature-flags` bans it strict-zero and `.env.example` declares no
25
+ * such key — but that audit only matched `process.env.FEATURE_*`, and this
26
+ * read it as `ctx.env("FEATURE_PRIZE_DRAWS")`, which the regex could not see.
27
+ * So an env var nothing documented was never set in production and every spin
28
+ * returned 503 `feature_disabled` while the Spin button rendered enabled.
12
29
  */
13
30
  import crypto from "node:crypto";
14
- import { EVENT_ENTRY_FIELDS, COMMON_FIELDS } from "../../../../constants/field-names.js";
31
+ import { EVENT_ENTRY_FIELDS, EVENT_FIELDS, COMMON_FIELDS, } from "../../../../constants/field-names.js";
15
32
  import { resolveDate } from "../../../../utils/index.js";
16
- const EVENTS_COLLECTION = "events";
33
+ import { resolveEventRef } from "./_event-ref.js";
34
+ import { EVENT_DRAW_REASON } from "./event-draw-reasons.js";
17
35
  const EVENT_ENTRIES_COLLECTION = "eventEntries";
18
36
  const COUPONS_COLLECTION = "coupons";
19
37
  export async function runAssignSpinPrize(input, ctx) {
20
- if (ctx.env("FEATURE_PRIZE_DRAWS") !== "true") {
21
- ctx.logger.info("FEATURE_PRIZE_DRAWS disabled — skipping spin prize assignment");
22
- return { eventId: input.eventId, userId: input.userId, reason: "feature_disabled" };
23
- }
24
- const { eventId, userId, guestIpHash } = input;
38
+ const { userId, guestIpHash } = input;
25
39
  if (!userId && !guestIpHash) {
26
- return { eventId, userId, reason: "identity_required" };
40
+ return {
41
+ eventId: input.eventId,
42
+ userId,
43
+ reason: EVENT_DRAW_REASON.IDENTITY_REQUIRED,
44
+ };
45
+ }
46
+ /*
47
+ * 🛑 Slug-or-id, not `.doc(eventId)`. Public URLs link by SLUG (`EventCard`
48
+ * builds its href from `event.slug ?? event.id`, and the participate tab
49
+ * reuses that param), so a spin launched from any card link used to resolve
50
+ * to nothing and return `event_not_found` — a 404 on every attempt, forever.
51
+ *
52
+ * Every query and write below uses the RESOLVED `eventId`, never the
53
+ * caller's input: carrying the slug forward is what let one guest count as
54
+ * two identities and silently reset `spinMaxPerUser`.
55
+ */
56
+ const resolved = await resolveEventRef(ctx.db, input.eventId);
57
+ if (!resolved) {
58
+ return {
59
+ eventId: input.eventId,
60
+ userId,
61
+ reason: EVENT_DRAW_REASON.EVENT_NOT_FOUND,
62
+ };
63
+ }
64
+ const eventId = resolved.id;
65
+ const event = resolved.data;
66
+ /*
67
+ * The page redirects a non-running event to the overview, so these were
68
+ * never reachable through the UI — but `POST /api/events/<ended>/spin`
69
+ * answered 200 and awarded a prize. A redirect is navigation, not a
70
+ * precondition.
71
+ */
72
+ if (event.type && event.type !== EVENT_FIELDS.TYPE_VALUES.SPIN_WHEEL) {
73
+ return { eventId, userId, reason: EVENT_DRAW_REASON.NOT_A_SPIN_WHEEL };
27
74
  }
28
- const eventSnap = await ctx.db.collection(EVENTS_COLLECTION).doc(eventId).get();
29
- if (!eventSnap.exists) {
30
- return { eventId, userId, reason: "event_not_found" };
75
+ const endsAt = resolveDate(event.endsAt);
76
+ if (event.status !== EVENT_FIELDS.STATUS_VALUES.ACTIVE ||
77
+ (endsAt && ctx.now > endsAt)) {
78
+ return { eventId, userId, reason: EVENT_DRAW_REASON.NOT_ACTIVE };
31
79
  }
32
- const event = eventSnap.data();
33
80
  if (!userId && !event.allowGuestParticipation) {
34
- return { eventId, userId, reason: "login_required" };
81
+ return { eventId, userId, reason: EVENT_DRAW_REASON.LOGIN_REQUIRED };
35
82
  }
36
- const prizes = Array.isArray(event.spinPrizes) ? event.spinPrizes : [];
83
+ // Active prizes only — see the `isActive` note on SpinPrize. The weighted
84
+ // pick below iterates this same array, so the gate and the draw agree.
85
+ const prizes = (Array.isArray(event.spinPrizes) ? event.spinPrizes : []).filter((p) => p.isActive !== false);
37
86
  const totalWeight = prizes.reduce((s, p) => s + (p.weight ?? 0), 0);
38
87
  if (totalWeight <= 0) {
39
- return { eventId, userId, reason: "no_prizes_configured" };
88
+ return { eventId, userId, reason: EVENT_DRAW_REASON.NO_PRIZES_CONFIGURED };
40
89
  }
41
90
  const windowStart = resolveDate(event.spinWindowStart);
42
91
  const windowEnd = resolveDate(event.spinWindowEnd);
43
92
  if ((windowStart && ctx.now < windowStart) || (windowEnd && ctx.now > windowEnd)) {
44
- return { eventId, userId, reason: "outside_spin_window" };
93
+ return { eventId, userId, reason: EVENT_DRAW_REASON.OUTSIDE_SPIN_WINDOW };
45
94
  }
46
95
  const maxPerUser = typeof event.spinMaxPerUser === "number" && event.spinMaxPerUser > 0
47
96
  ? event.spinMaxPerUser
@@ -59,11 +108,23 @@ export async function runAssignSpinPrize(input, ctx) {
59
108
  const existingData = existingDoc?.data();
60
109
  const priorSpinCount = existingData?.spinCount ?? (existingData?.spinUsed ? 1 : 0);
61
110
  if (priorSpinCount >= maxPerUser) {
111
+ /*
112
+ * 🛑 `reason` is REQUIRED here, and its absence was a bug.
113
+ *
114
+ * Both entry points gate on `if (data.reason && !data.spinPrizeId)`, so
115
+ * this branch used to fall through to `successResponse(…, "Spin
116
+ * recorded")` — HTTP 200 for a refusal. When the stored entry happened
117
+ * to carry no `spinPrizeId`, the client then saw a 200 with no prize and
118
+ * printed "Spin failed. Please try again." for what is really "you have
119
+ * already spun".
120
+ */
62
121
  return {
63
122
  eventId,
64
123
  userId,
65
124
  spinPrizeId: existingData?.spinPrizeId,
125
+ spinPrizeCouponCode: existingData?.spinPrizeCouponCode ?? undefined,
66
126
  alreadyUsed: true,
127
+ reason: EVENT_DRAW_REASON.ALREADY_USED,
67
128
  };
68
129
  }
69
130
  const roll = crypto.randomInt(0, totalWeight);
@@ -211,10 +211,19 @@ async function settleAuction(ctx, product) {
211
211
  });
212
212
  }
213
213
  export async function runAuctionSettlement(ctx) {
214
- if (ctx.env("FEATURE_AUCTIONS") !== "true") {
215
- ctx.logger.info("FEATURE_AUCTIONS disabled — skipping auction settlement");
216
- return;
217
- }
214
+ /*
215
+ * 🛑 `FEATURE_AUCTIONS` gate DELETED 2026-10-19, and this one was live.
216
+ *
217
+ * The flag mechanism was retired on 2026-08-29, but `audit-feature-flags`
218
+ * only matched `process.env.FEATURE_*` and this read it through
219
+ * `ctx.env(...)` — so it survived unseen. This is a SCHEDULED Firebase
220
+ * Function, which reads `functions/.env.<projectId>`, and measured on
221
+ * 2026-10-19 that file contains exactly one flag: `FEATURE_PAYOUTS=true`.
222
+ * `FEATURE_AUCTIONS` was never set there, so this sweep returned on its
223
+ * first line on every run — no expired auction was ever settled in
224
+ * production, no winner ever got a cart line, and the only trace was an
225
+ * `info` log saying it had been skipped.
226
+ */
218
227
  ctx.logger.info("Starting auction settlement sweep");
219
228
  const expired = await productRepository.getExpiredAuctions(ctx.now);
220
229
  if (expired.length === 0) {