@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.
- package/dist/.build-stamp.json +4 -4
- package/dist/_internal/client/features/sticker-trace/canvas-codec.d.ts +2 -0
- package/dist/_internal/client/features/sticker-trace/canvas-codec.js +148 -0
- package/dist/_internal/server/features/auctions/actions.js +1 -1
- package/dist/_internal/server/features/categories/data.js +23 -7
- package/dist/_internal/server/features/checkout/actions.js +10 -3
- package/dist/_internal/server/features/checkout/data.d.ts +15 -0
- package/dist/_internal/server/features/checkout/data.js +20 -3
- package/dist/_internal/server/features/lottery/actions.js +9 -1
- package/dist/_internal/server/features/lottery/og.d.ts +40 -0
- package/dist/_internal/server/features/lottery/og.js +79 -0
- package/dist/_internal/server/features/messages/actions.js +4 -0
- package/dist/_internal/server/features/offers/adapters.js +17 -3
- package/dist/_internal/server/features/orders/actions.js +7 -2
- package/dist/_internal/server/features/raffle/actions.d.ts +19 -0
- package/dist/_internal/server/features/raffle/actions.js +25 -0
- package/dist/_internal/server/features/reviews/actions.js +1 -1
- package/dist/_internal/server/features/reviews/data.d.ts +1 -1
- package/dist/_internal/server/features/search/index-build/artifact-store.d.ts +89 -0
- package/dist/_internal/server/features/search/index-build/artifact-store.js +208 -0
- package/dist/_internal/server/features/search/index-build/build.d.ts +57 -0
- package/dist/_internal/server/features/search/index-build/build.js +285 -0
- package/dist/_internal/server/features/search/index-build/sources.d.ts +53 -0
- package/dist/_internal/server/features/search/index-build/sources.js +385 -0
- package/dist/_internal/server/features/search/search-sources.d.ts +0 -15
- package/dist/_internal/server/features/search/search-sources.js +142 -7
- package/dist/_internal/server/features/seo/robots.js +21 -1
- package/dist/_internal/server/features/seo/sitemap.js +1 -1
- package/dist/_internal/server/features/shared/listing-data-factory.js +16 -4
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +26 -0
- package/dist/_internal/server/features/sticker-trace/actions.d.ts +109 -0
- package/dist/_internal/server/features/sticker-trace/actions.js +172 -0
- package/dist/_internal/server/features/sticker-trace/adapters.d.ts +80 -0
- package/dist/_internal/server/features/sticker-trace/adapters.js +174 -0
- package/dist/_internal/server/features/sticker-trace/build-cut-file.d.ts +26 -0
- package/dist/_internal/server/features/sticker-trace/build-cut-file.js +127 -0
- package/dist/_internal/server/features/sticker-trace/coordinator.d.ts +27 -0
- package/dist/_internal/server/features/sticker-trace/coordinator.js +169 -0
- package/dist/_internal/server/features/sticker-trace/fanout.d.ts +43 -0
- package/dist/_internal/server/features/sticker-trace/fanout.js +180 -0
- package/dist/_internal/server/features/sticker-trace/finalizer.d.ts +25 -0
- package/dist/_internal/server/features/sticker-trace/finalizer.js +123 -0
- package/dist/_internal/server/features/sticker-trace/mosaic-inputs.d.ts +30 -0
- package/dist/_internal/server/features/sticker-trace/mosaic-inputs.js +112 -0
- package/dist/_internal/server/features/sticker-trace/progress.d.ts +81 -0
- package/dist/_internal/server/features/sticker-trace/progress.js +198 -0
- package/dist/_internal/server/features/sticker-trace/promote.d.ts +23 -0
- package/dist/_internal/server/features/sticker-trace/promote.js +193 -0
- package/dist/_internal/server/features/sticker-trace/shard.d.ts +50 -0
- package/dist/_internal/server/features/sticker-trace/shard.js +174 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +141 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.js +279 -0
- package/dist/_internal/server/features/sticker-trace/storage.d.ts +135 -0
- package/dist/_internal/server/features/sticker-trace/storage.js +282 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +25 -1
- package/dist/_internal/server/jobs/core/_event-ref.d.ts +51 -0
- package/dist/_internal/server/jobs/core/_event-ref.js +63 -0
- package/dist/_internal/server/jobs/core/assignSpinPrize.d.ts +18 -0
- package/dist/_internal/server/jobs/core/assignSpinPrize.js +77 -16
- package/dist/_internal/server/jobs/core/auctionSettlement.js +13 -4
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +44 -15
- package/dist/_internal/server/jobs/core/bundleStockSync.js +3 -4
- package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +44 -0
- package/dist/_internal/server/jobs/core/event-draw-reasons.d.ts +78 -0
- package/dist/_internal/server/jobs/core/event-draw-reasons.js +133 -0
- package/dist/_internal/server/jobs/core/listingProcessor.js +28 -8
- package/dist/_internal/server/jobs/core/onOrderCreate.js +12 -1
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +13 -2
- package/dist/_internal/server/jobs/core/onScamReportRejected.js +8 -2
- package/dist/_internal/server/jobs/core/onScamReportVerified.js +8 -2
- package/dist/_internal/server/jobs/core/onStoreWrite.js +11 -4
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +5 -4
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +1 -4
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +1 -2
- package/dist/_internal/server/jobs/core/promotions.js +7 -4
- package/dist/_internal/server/jobs/core/searchIndexBuild.d.ts +7 -0
- package/dist/_internal/server/jobs/core/searchIndexBuild.js +211 -0
- package/dist/_internal/server/jobs/core/triggerEventRaffle.d.ts +35 -0
- package/dist/_internal/server/jobs/core/triggerEventRaffle.js +213 -31
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +20 -14
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +8 -0
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +15 -15
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/onCategoryWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onProductWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onReviewWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onScamReportUpdate.js +18 -0
- package/dist/_internal/server/jobs/handlers/onStoreWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/searchIndexBuild.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/searchIndexBuild.js +2 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +8 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -6
- package/dist/_internal/shared/actions/action-registry.js +29 -0
- package/dist/_internal/shared/cache/revalidate-targets.d.ts +0 -1
- package/dist/_internal/shared/cache/revalidate-targets.js +10 -8
- package/dist/_internal/shared/features/events/config.d.ts +14 -0
- package/dist/_internal/shared/features/events/config.js +14 -0
- package/dist/_internal/shared/features/notifications/action-url.js +8 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
- package/dist/_internal/shared/features/sticker-trace/codec.d.ts +81 -0
- package/dist/_internal/shared/features/sticker-trace/codec.js +30 -0
- package/dist/_internal/shared/features/sticker-trace/config.d.ts +81 -0
- package/dist/_internal/shared/features/sticker-trace/config.js +177 -0
- package/dist/_internal/shared/features/sticker-trace/cut-contour.d.ts +20 -0
- package/dist/_internal/shared/features/sticker-trace/cut-contour.js +60 -0
- package/dist/_internal/shared/features/sticker-trace/export/build-export-set.d.ts +65 -0
- package/dist/_internal/shared/features/sticker-trace/export/build-export-set.js +176 -0
- package/dist/_internal/shared/features/sticker-trace/export/cut-svg.d.ts +56 -0
- package/dist/_internal/shared/features/sticker-trace/export/cut-svg.js +120 -0
- package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.d.ts +92 -0
- package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.js +153 -0
- package/dist/_internal/shared/features/sticker-trace/export/nesting.d.ts +51 -0
- package/dist/_internal/shared/features/sticker-trace/export/nesting.js +110 -0
- package/dist/_internal/shared/features/sticker-trace/export/page-sizes.d.ts +68 -0
- package/dist/_internal/shared/features/sticker-trace/export/page-sizes.js +223 -0
- package/dist/_internal/shared/features/sticker-trace/export/part-tiling.d.ts +51 -0
- package/dist/_internal/shared/features/sticker-trace/export/part-tiling.js +178 -0
- package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.d.ts +26 -0
- package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.js +134 -0
- package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.d.ts +40 -0
- package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.js +195 -0
- package/dist/_internal/shared/features/sticker-trace/index.d.ts +2 -0
- package/dist/_internal/shared/features/sticker-trace/index.js +59 -0
- package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.d.ts +11 -0
- package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.js +66 -0
- package/dist/_internal/shared/features/sticker-trace/math/edt.d.ts +27 -0
- package/dist/_internal/shared/features/sticker-trace/math/edt.js +165 -0
- package/dist/_internal/shared/features/sticker-trace/math/label.d.ts +15 -0
- package/dist/_internal/shared/features/sticker-trace/math/label.js +156 -0
- package/dist/_internal/shared/features/sticker-trace/math/marching-squares.d.ts +16 -0
- package/dist/_internal/shared/features/sticker-trace/math/marching-squares.js +242 -0
- package/dist/_internal/shared/features/sticker-trace/math/morphology.d.ts +46 -0
- package/dist/_internal/shared/features/sticker-trace/math/morphology.js +168 -0
- package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.d.ts +88 -0
- package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.js +286 -0
- package/dist/_internal/shared/features/sticker-trace/math/rdp.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/math/rdp.js +74 -0
- package/dist/_internal/shared/features/sticker-trace/resume.d.ts +50 -0
- package/dist/_internal/shared/features/sticker-trace/resume.js +160 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cluster.d.ts +20 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cluster.js +132 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.d.ts +28 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.js +120 -0
- package/dist/_internal/shared/features/sticker-trace/trace/flood.d.ts +21 -0
- package/dist/_internal/shared/features/sticker-trace/trace/flood.js +104 -0
- package/dist/_internal/shared/features/sticker-trace/trace/geometry.d.ts +3 -0
- package/dist/_internal/shared/features/sticker-trace/trace/geometry.js +140 -0
- package/dist/_internal/shared/features/sticker-trace/trace/line-filter.d.ts +15 -0
- package/dist/_internal/shared/features/sticker-trace/trace/line-filter.js +138 -0
- package/dist/_internal/shared/features/sticker-trace/trace/merge.d.ts +27 -0
- package/dist/_internal/shared/features/sticker-trace/trace/merge.js +187 -0
- package/dist/_internal/shared/features/sticker-trace/trace/paper.d.ts +32 -0
- package/dist/_internal/shared/features/sticker-trace/trace/paper.js +147 -0
- package/dist/_internal/shared/features/sticker-trace/trace/prefilter.d.ts +30 -0
- package/dist/_internal/shared/features/sticker-trace/trace/prefilter.js +125 -0
- package/dist/_internal/shared/features/sticker-trace/trace/quantise.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/trace/quantise.js +153 -0
- package/dist/_internal/shared/features/sticker-trace/trace/ridge.d.ts +88 -0
- package/dist/_internal/shared/features/sticker-trace/trace/ridge.js +344 -0
- package/dist/_internal/shared/features/sticker-trace/trace/segment.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/trace/segment.js +251 -0
- package/dist/_internal/shared/features/sticker-trace/types.d.ts +197 -0
- package/dist/_internal/shared/features/sticker-trace/types.js +49 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +11 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +1 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +1 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -0
- package/dist/_internal/shared/listing-types/feature-flags.d.ts +7 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +9 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +1 -0
- package/dist/_internal/shared/media/limits.d.ts +12 -2
- package/dist/_internal/shared/media/limits.js +12 -2
- package/dist/_internal/shared/search/artifact-url.d.ts +35 -0
- package/dist/_internal/shared/search/artifact-url.js +119 -0
- package/dist/_internal/shared/search/index-record.d.ts +68 -0
- package/dist/_internal/shared/search/index-record.js +96 -0
- package/dist/_internal/shared/search/match.d.ts +65 -0
- package/dist/_internal/shared/search/match.js +263 -0
- package/dist/client.d.ts +8 -7
- package/dist/client.js +12 -5
- package/dist/configs/eslint.js +22 -2
- package/dist/constants/api-endpoints.d.ts +68 -0
- package/dist/constants/api-endpoints.js +32 -0
- package/dist/constants/field-names.d.ts +7 -1
- package/dist/constants/field-names.js +36 -1
- package/dist/contracts/repository.d.ts +8 -7
- package/dist/contracts/search.d.ts +2 -2
- package/dist/contracts/table.d.ts +3 -3
- package/dist/contracts/table.js +3 -3
- package/dist/core/contact-submissions.repository.js +2 -2
- package/dist/core/integration-keys.js +7 -1
- package/dist/core/newsletter.repository.js +2 -2
- package/dist/core/site-config.js +1 -1
- package/dist/errors/error-codes.d.ts +30 -0
- package/dist/errors/error-codes.js +30 -0
- package/dist/errors/error-display-map.js +40 -1
- package/dist/features/account/api/[userId]/addresses/route.js +1 -1
- package/dist/features/account/components/UserBidsView.js +1 -1
- package/dist/features/account/components/index.d.ts +0 -8
- package/dist/features/account/components/index.js +0 -4
- package/dist/features/addresses/repository/addresses.repository.d.ts +17 -6
- package/dist/features/addresses/repository/addresses.repository.js +65 -9
- package/dist/features/addresses/schemas/firestore.d.ts +17 -6
- package/dist/features/admin/actions/notification-actions.js +15 -6
- package/dist/features/admin/api/bids/route.js +1 -1
- package/dist/features/admin/api/coupons/route.js +1 -1
- package/dist/features/admin/api/products/route.js +1 -1
- package/dist/features/admin/api/reviews/route.js +1 -1
- package/dist/features/admin/components/AdminAdEditorView.js +19 -5
- package/dist/features/admin/components/AdminAuditLogView.js +1 -1
- package/dist/features/admin/components/AdminBlogEditorView.js +0 -1
- package/dist/features/admin/components/AdminBlogView.js +1 -1
- package/dist/features/admin/components/AdminBrandsView.js +17 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +0 -1
- package/dist/features/admin/components/AdminContactView.js +1 -1
- package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -3
- package/dist/features/admin/components/AdminNewsletterView.js +1 -1
- package/dist/features/admin/components/AdminOrdersView.js +9 -2
- package/dist/features/admin/components/AdminPayoutsView.js +1 -1
- package/dist/features/admin/components/AdminPrizeDrawsView.js +11 -0
- package/dist/features/admin/components/AdminProductEditorView.d.ts +10 -1
- package/dist/features/admin/components/AdminProductEditorView.js +24 -37
- package/dist/features/admin/components/AdminProductsView.js +10 -3
- package/dist/features/admin/components/AdminScammersView.js +1 -1
- package/dist/features/admin/components/AdminSectionsView.js +21 -5
- package/dist/features/admin/components/AdminSessionsView.js +1 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +22 -1
- package/dist/features/admin/components/AdminTeamView.js +1 -1
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +0 -1
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/DataListingView.d.ts +2 -1
- package/dist/features/admin/components/DataListingView.js +9 -4
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +0 -2
- package/dist/features/admin/hooks/useAdminListing.d.ts +1 -1
- package/dist/features/admin/repository/site-settings.repository.js +8 -3
- package/dist/features/admin/schemas/firestore.d.ts +62 -1
- package/dist/features/admin/schemas/firestore.js +32 -0
- package/dist/features/admin/types/index.d.ts +1 -1
- package/dist/features/auctions/actions/bid-actions.d.ts +2 -2
- package/dist/features/auctions/actions/bid-actions.js +16 -4
- package/dist/features/auctions/api/route.js +1 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +4 -0
- package/dist/features/auctions/repository/bid.repository.d.ts +46 -4
- package/dist/features/auctions/repository/bid.repository.js +114 -13
- package/dist/features/auctions/schemas/firestore.d.ts +24 -4
- package/dist/features/auctions/schemas/firestore.js +1 -0
- package/dist/features/auth/actions/profile-actions.js +30 -4
- package/dist/features/auth/components/index.d.ts +0 -1
- package/dist/features/auth/components/index.js +0 -1
- package/dist/features/auth/repository/session.repository.d.ts +17 -3
- package/dist/features/auth/repository/session.repository.js +46 -5
- package/dist/features/auth/repository/token.repository.d.ts +27 -4
- package/dist/features/auth/repository/token.repository.js +65 -11
- package/dist/features/auth/repository/user.repository.d.ts +33 -7
- package/dist/features/auth/repository/user.repository.js +100 -15
- package/dist/features/auth/schemas/firestore.d.ts +63 -12
- package/dist/features/auth/schemas/firestore.js +11 -0
- package/dist/features/before-after/api/route.js +1 -1
- package/dist/features/blog/components/BlogListView.js +16 -3
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/blog/schemas/index.d.ts +2 -2
- package/dist/features/cart/api/route.js +1 -1
- package/dist/features/catalogue/repository/catalogue.repository.d.ts +7 -7
- package/dist/features/catalogue/repository/catalogue.repository.js +28 -10
- package/dist/features/catalogue/schemas/firestore.d.ts +11 -0
- package/dist/features/categories/api/route.js +10 -10
- package/dist/features/categories/components/CategoryDetailPageView.js +35 -5
- package/dist/features/categories/components/CategorySelectorCreate.js +0 -1
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/categories/repository/categories.repository.d.ts +1 -0
- package/dist/features/categories/repository/categories.repository.js +31 -1
- package/dist/features/categories/schemas/index.d.ts +2 -2
- package/dist/features/categories/utils/taxonomy-visual.d.ts +21 -0
- package/dist/features/categories/utils/taxonomy-visual.js +58 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +8 -1
- package/dist/features/collections/api/[slug]/route.js +3 -3
- package/dist/features/collections/api/route.js +1 -1
- package/dist/features/consultation/api/route.js +1 -1
- package/dist/features/consultation/repository/consultations.repository.js +1 -1
- package/dist/features/consultation/types/index.d.ts +1 -1
- package/dist/features/contact/components/ContactForm.js +1 -3
- package/dist/features/corporate/api/route.js +1 -1
- package/dist/features/corporate/repository/corporate.repository.js +1 -1
- package/dist/features/corporate/types/index.d.ts +1 -1
- package/dist/features/cron/jobs/auction-expiry.job.js +1 -1
- package/dist/features/cron/jobs/preorder-reminder.job.js +1 -1
- package/dist/features/events/actions/event-actions.d.ts +14 -0
- package/dist/features/events/actions/event-actions.js +82 -3
- package/dist/features/events/api/[id]/route.js +2 -2
- package/dist/features/events/components/AdminEventEditorView.js +22 -3
- package/dist/features/events/components/AdminEventsView.js +32 -8
- package/dist/features/events/components/EventRafflesSection.d.ts +34 -1
- package/dist/features/events/components/EventRafflesSection.js +52 -3
- package/dist/features/events/components/SpinWheelView.d.ts +2 -2
- package/dist/features/events/components/SpinWheelView.js +41 -4
- package/dist/features/events/repository/event-entry.repository.d.ts +14 -2
- package/dist/features/events/repository/event-entry.repository.js +41 -7
- package/dist/features/events/schemas/firestore.d.ts +17 -4
- package/dist/features/events/types/index.d.ts +37 -0
- package/dist/features/faq/repository/faqs.repository.js +1 -1
- package/dist/features/faq/schemas/index.d.ts +5 -2
- package/dist/features/faq/schemas/index.js +6 -0
- package/dist/features/faq/types/index.d.ts +0 -1
- package/dist/features/grouped/components/GroupedListingEditorView.js +17 -2
- package/dist/features/homepage/api/carousel/[id]/route.js +1 -1
- package/dist/features/homepage/api/carousel/route.js +4 -4
- package/dist/features/homepage/api/route.js +2 -2
- package/dist/features/homepage/api/sections/route.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.d.ts +1 -9
- package/dist/features/homepage/components/AnnouncementBar.js +13 -19
- package/dist/features/homepage/components/MarketplaceHomepageView.js +6 -1
- package/dist/features/homepage/components/index.d.ts +0 -2
- package/dist/features/homepage/components/index.js +0 -1
- package/dist/features/homepage/repository/homepage.repository.js +1 -1
- package/dist/features/homepage/schemas/firestore.d.ts +6 -0
- package/dist/features/homepage/schemas/index.d.ts +10 -10
- package/dist/features/layout/AppLayoutShell.d.ts +12 -3
- package/dist/features/layout/AppLayoutShell.js +65 -13
- package/dist/features/layout/BottomActionsContext.d.ts +16 -0
- package/dist/features/layout/BottomActionsContext.js +5 -0
- package/dist/features/layout/BottomChrome.js +48 -9
- package/dist/features/layout/BottomNavbar.d.ts +14 -0
- package/dist/features/layout/BottomNavbar.js +43 -3
- package/dist/features/layout/MainNavbar.d.ts +11 -0
- package/dist/features/layout/NavbarLayout.d.ts +3 -0
- package/dist/features/layout/NavbarLayout.js +88 -11
- package/dist/features/layout/SideActionDock.d.ts +1 -0
- package/dist/features/layout/SideActionDock.js +219 -0
- package/dist/features/layout/TitleBarLayout.d.ts +1 -3
- package/dist/features/layout/TitleBarLayout.js +3 -4
- package/dist/features/layout/cta-anchor.d.ts +31 -0
- package/dist/features/layout/cta-anchor.js +31 -0
- package/dist/features/layout/hooks/useBottomActions.d.ts +7 -0
- package/dist/features/layout/hooks/useBottomActions.js +6 -1
- package/dist/features/layout/index.d.ts +9 -1
- package/dist/features/layout/index.js +9 -1
- package/dist/features/layout/useCtaReveal.d.ts +24 -0
- package/dist/features/layout/useCtaReveal.js +82 -0
- package/dist/features/layout/useHoldToOpen.d.ts +41 -0
- package/dist/features/layout/useHoldToOpen.js +84 -0
- package/dist/features/lottery/repository/lottery-entry.repository.d.ts +18 -3
- package/dist/features/lottery/repository/lottery-entry.repository.js +48 -6
- package/dist/features/lottery/schemas/firestore.d.ts +33 -5
- package/dist/features/loyalty/api/balance/route.js +1 -1
- package/dist/features/loyalty/api/history/route.js +1 -1
- package/dist/features/media/MediaImage.js +0 -1
- package/dist/features/media/MediaPickerModal.js +7 -1
- package/dist/features/media/modals/ImageEditor.js +5 -1
- package/dist/features/media/modals/VideoThumbnailSelector.js +17 -1
- package/dist/features/media/repository/media-assets.repository.d.ts +19 -0
- package/dist/features/media/upload/CameraCapture.js +16 -1
- package/dist/features/media/upload/ImageUpload.js +42 -4
- package/dist/features/media/upload/MediaUploadField.js +69 -13
- package/dist/features/media/upload/downscale-image.d.ts +56 -0
- package/dist/features/media/upload/downscale-image.js +150 -0
- package/dist/features/messages/components/ConversationThreadView.js +45 -2
- package/dist/features/messages/components/MessageSellerButton.d.ts +15 -1
- package/dist/features/messages/components/MessageSellerButton.js +10 -2
- package/dist/features/messages/schemas/firestore.d.ts +15 -0
- package/dist/features/orders/hooks/useOrders.js +4 -2
- package/dist/features/orders/repository/orders.repository.d.ts +15 -4
- package/dist/features/orders/repository/orders.repository.js +52 -20
- package/dist/features/orders/schemas/firestore.d.ts +15 -4
- package/dist/features/orders/schemas/index.d.ts +8 -8
- package/dist/features/orders/types/index.d.ts +2 -2
- package/dist/features/payments/repository/payments.repository.js +2 -2
- package/dist/features/payments/repository/payout.repository.d.ts +23 -6
- package/dist/features/payments/repository/payout.repository.js +63 -23
- package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +17 -6
- package/dist/features/payments/repository/saved-payment-methods.repository.js +59 -11
- package/dist/features/payments/schemas/firestore.d.ts +14 -3
- package/dist/features/payments/schemas/saved-methods-firestore.d.ts +12 -1
- package/dist/features/pre-orders/api/[slug]/route.js +1 -1
- package/dist/features/pre-orders/api/admin/route.js +1 -1
- package/dist/features/pre-orders/api/route.js +1 -1
- package/dist/features/pre-orders/repository/pre-orders.repository.d.ts +3 -3
- package/dist/features/pre-orders/repository/pre-orders.repository.js +6 -6
- package/dist/features/products/api/[id]/route.js +1 -1
- package/dist/features/products/components/ListingBottomActions.js +4 -0
- package/dist/features/products/components/ProductDetailPageView.js +2 -1
- package/dist/features/products/components/ProductGalleryClient.js +2 -2
- package/dist/features/products/components/ProductGrid.js +12 -1
- package/dist/features/products/components/index.d.ts +0 -2
- package/dist/features/products/components/index.js +0 -1
- package/dist/features/products/config/listing-type-listing-config.d.ts +0 -3
- package/dist/features/products/config/listing-type-listing-config.js +27 -2
- package/dist/features/products/constants/listing-tabs.d.ts +9 -0
- package/dist/features/products/constants/listing-tabs.js +3 -2
- package/dist/features/products/hooks/useProducts.js +2 -2
- package/dist/features/products/repository/products.repository.js +13 -1
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/products/types/index.d.ts +1 -1
- package/dist/features/promotions/api/route.js +1 -1
- package/dist/features/reviews/actions/review-actions.d.ts +6 -6
- package/dist/features/reviews/actions/review-actions.js +22 -8
- package/dist/features/reviews/components/ReviewDetailPageView.js +13 -1
- package/dist/features/reviews/components/ReviewsIndexPageView.js +21 -4
- package/dist/features/reviews/repository/reviews.repository.d.ts +19 -7
- package/dist/features/reviews/repository/reviews.repository.js +63 -12
- package/dist/features/reviews/schemas/firestore.d.ts +13 -2
- package/dist/features/reviews/schemas/index.d.ts +2 -2
- package/dist/features/scams/actions/scam-actions.js +16 -4
- package/dist/features/search/components/SearchResultsPageView.js +2 -1
- package/dist/features/search/hooks/useSearchIndex.d.ts +34 -0
- package/dist/features/search/hooks/useSearchIndex.js +172 -0
- package/dist/features/search/repository/search.repository.js +2 -2
- package/dist/features/seller/actions/offer-actions.d.ts +5 -5
- package/dist/features/seller/actions/offer-actions.js +38 -8
- package/dist/features/seller/actions/seller-actions.js +34 -7
- package/dist/features/seller/api/coupons/route.js +1 -1
- package/dist/features/seller/api/products/route.js +1 -1
- package/dist/features/seller/api/store/route.js +1 -1
- package/dist/features/seller/components/SellerBidsView.js +1 -1
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerListingTypeView.js +9 -1
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +0 -1
- package/dist/features/seller/components/SellerPayoutsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.js +0 -1
- package/dist/features/seller/components/SellerProductsView.js +1 -1
- package/dist/features/seller/components/SellerShippingView.js +0 -1
- package/dist/features/seller/components/SellerStorefrontView.js +0 -1
- package/dist/features/seller/components/index.d.ts +0 -4
- package/dist/features/seller/components/index.js +0 -2
- package/dist/features/seller/repository/offer.repository.d.ts +15 -3
- package/dist/features/seller/repository/offer.repository.js +43 -7
- package/dist/features/seller/schemas/firestore.d.ts +14 -3
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/seller/schemas/order-forms.d.ts +39 -0
- package/dist/features/seller/schemas/order-forms.js +37 -0
- package/dist/features/shell/FormShell.js +6 -2
- package/dist/features/shell/QuickFormDrawer.js +15 -1
- package/dist/features/shipments/components/AdminShipmentEditorView.js +0 -1
- package/dist/features/shipments/components/AdminShipmentsView.js +1 -1
- package/dist/features/sticker-trace/components/StickerCommitPanel.d.ts +30 -0
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +93 -0
- package/dist/features/sticker-trace/components/StickerPrintPanel.d.ts +33 -0
- package/dist/features/sticker-trace/components/StickerPrintPanel.js +104 -0
- package/dist/features/sticker-trace/components/StickerRetryPanel.d.ts +20 -0
- package/dist/features/sticker-trace/components/StickerRetryPanel.js +128 -0
- package/dist/features/sticker-trace/components/StickerRunHistoryPanel.d.ts +45 -0
- package/dist/features/sticker-trace/components/StickerRunHistoryPanel.js +84 -0
- package/dist/features/sticker-trace/components/StickerTraceStudio.d.ts +27 -0
- package/dist/features/sticker-trace/components/StickerTraceStudio.js +171 -0
- package/dist/features/sticker-trace/constants/help-content.d.ts +23 -0
- package/dist/features/sticker-trace/constants/help-content.js +172 -0
- package/dist/features/sticker-trace/hooks/useLiveTrace.d.ts +27 -0
- package/dist/features/sticker-trace/hooks/useLiveTrace.js +160 -0
- package/dist/features/sticker-trace/hooks/useStickerTraceProgress.d.ts +22 -0
- package/dist/features/sticker-trace/hooks/useStickerTraceProgress.js +106 -0
- package/dist/features/sticker-trace/repository/sticker-sheets.repository.d.ts +113 -0
- package/dist/features/sticker-trace/repository/sticker-sheets.repository.js +146 -0
- package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.d.ts +132 -0
- package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.js +174 -0
- package/dist/features/sticker-trace/schemas/firestore.d.ts +248 -0
- package/dist/features/sticker-trace/schemas/firestore.js +79 -0
- package/dist/features/sticker-trace/server.d.ts +8 -0
- package/dist/features/sticker-trace/server.js +51 -0
- package/dist/features/stores/actions/store-query-actions.d.ts +2 -2
- package/dist/features/stores/actions/store-query-actions.js +7 -2
- package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -3
- package/dist/features/stores/api/[storeSlug]/products/route.js +3 -3
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +1 -1
- package/dist/features/stores/api/[storeSlug]/route.js +1 -1
- package/dist/features/stores/components/StoreNavTabs.js +17 -2
- package/dist/features/stores/manifest.js +8 -4
- package/dist/features/support/repository/support.repository.d.ts +14 -3
- package/dist/features/support/repository/support.repository.js +93 -13
- package/dist/features/support/schemas/firestore.d.ts +16 -5
- package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +61 -19
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +19 -3
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +243 -0
- package/dist/features/tester/seed-data/authored/content-discovery__events.js +36 -10
- package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.js +209 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.js +90 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.js +85 -0
- package/dist/features/tester/seed-data/authored/index.js +10 -0
- package/dist/features/tester/seed-data/authored/selling__media-limits.js +19 -17
- package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +10 -5
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +283 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -22
- package/dist/index.d.ts +10 -23
- package/dist/index.js +27 -19
- package/dist/next/api/routeHandler.js +9 -0
- package/dist/next/routing/require-record.d.ts +13 -0
- package/dist/next/routing/require-record.js +62 -0
- package/dist/next/routing/route-map.d.ts +4 -4
- package/dist/next/routing/route-map.js +25 -3
- package/dist/providers/db-firebase/base.d.ts +4 -2
- package/dist/providers/db-firebase/base.js +21 -5
- package/dist/providers/db-firebase/base.repository.d.ts +11 -9
- package/dist/providers/db-firebase/base.repository.js +19 -0
- package/dist/providers/db-firebase/rtdb-paths.d.ts +12 -0
- package/dist/providers/db-firebase/rtdb-paths.js +12 -0
- package/dist/react/ErrorBoundary.js +6 -1
- package/dist/react/hooks/realtime-event-constants.d.ts +1 -0
- package/dist/react/hooks/realtime-event-constants.js +8 -0
- package/dist/react/hooks/useEntityDelete.js +5 -1
- package/dist/react/hooks/useMessage.js +5 -1
- package/dist/repositories/index.d.ts +2 -2
- package/dist/repositories/index.js +6 -1
- package/dist/schemas/registry.d.ts +13 -20
- package/dist/schemas/registry.js +31 -14
- package/dist/schemas/webhooks/google-oauth.d.ts +2 -2
- package/dist/security/encrypted.d.ts +117 -0
- package/dist/security/encrypted.js +100 -0
- package/dist/security/index.d.ts +5 -2
- package/dist/security/index.js +16 -2
- package/dist/security/pii-accessors.d.ts +71 -0
- package/dist/security/pii-accessors.js +150 -0
- package/dist/security/pii-encrypt.d.ts +0 -8
- package/dist/security/pii-encrypt.js +22 -15
- package/dist/security/pii-mask.d.ts +18 -0
- package/dist/security/pii-mask.js +27 -0
- package/dist/seed/_helpers/search-txt-wrappers.d.ts +6 -6
- package/dist/seed/_helpers/search-txt-wrappers.js +22 -0
- package/dist/seed/addresses-seed-data.d.ts +2 -2
- package/dist/seed/authenticity-knowledge-seed-data.d.ts +34 -0
- package/dist/seed/authenticity-knowledge-seed-data.js +1277 -0
- package/dist/seed/bids-seed-data.d.ts +2 -2
- package/dist/seed/bids-seed-data.js +23 -2
- package/dist/seed/events-seed-data.d.ts +2 -2
- package/dist/seed/events-seed-data.js +5 -0
- package/dist/seed/faq-seed-data.js +175 -5
- package/dist/seed/homepage-sections-seed-data.js +39 -23
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/lottery-entries-seed-data.d.ts +2 -2
- package/dist/seed/offers-seed-data.d.ts +2 -2
- package/dist/seed/offers-seed-data.js +1 -1
- package/dist/seed/orders-seed-data.d.ts +2 -2
- package/dist/seed/payouts-seed-data.d.ts +2 -2
- package/dist/seed/payouts-seed-data.js +1 -1
- package/dist/seed/reviews-seed-data.d.ts +1 -2
- package/dist/seed/seed-purpose.js +7 -0
- package/dist/seed/sessions-seed-data.d.ts +2 -2
- package/dist/seed/sessions-seed-data.js +5 -0
- package/dist/seed/site-settings-seed-data.js +1 -1
- package/dist/seed/support-tickets-seed-data.d.ts +2 -2
- package/dist/seed/support-tickets-seed-data.js +1 -1
- package/dist/seed/users-seed-data.d.ts +2 -2
- package/dist/seed/users-seed-data.js +1 -1
- package/dist/seo/json-ld.js +5 -1
- package/dist/server.d.ts +18 -3
- package/dist/server.js +59 -3
- package/dist/styles.css +237 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.js +7 -1
- package/dist/ui/components/FilterChipGroup.d.ts +11 -0
- package/dist/ui/components/FilterChipGroup.js +1 -1
- package/dist/ui/components/ImageGallery.js +6 -1
- package/dist/ui/components/ListingLayout.js +14 -8
- package/dist/ui/components/OverflowRow.d.ts +92 -0
- package/dist/ui/components/OverflowRow.js +203 -0
- package/dist/ui/components/OverflowRow.style.css +235 -0
- package/dist/ui/components/RecordDetailModal.js +14 -1
- package/dist/ui/components/RowActionMenu.js +5 -1
- package/dist/ui/components/SideDrawer.js +5 -1
- package/dist/ui/components/SideModal.js +9 -1
- package/dist/ui/components/TabBarShell.d.ts +12 -1
- package/dist/ui/components/TabBarShell.js +3 -2
- package/dist/ui/components/Tabs.js +36 -24
- package/dist/ui/components/Toast.d.ts +3 -24
- package/dist/ui/components/Toast.js +2 -19
- package/dist/ui/components/TopBannerPortal.d.ts +15 -0
- package/dist/ui/components/TopBannerPortal.js +19 -0
- package/dist/ui/components/UnsavedChangesModal.js +5 -1
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/components/row-collapse.d.ts +15 -0
- package/dist/ui/components/row-collapse.js +127 -0
- package/dist/ui/components/toast-context.d.ts +63 -0
- package/dist/ui/components/toast-context.js +48 -0
- package/dist/ui/components/top-banner-slot.d.ts +37 -0
- package/dist/ui/components/top-banner-slot.js +45 -0
- package/dist/ui/icons/icon-registry.d.ts +6 -0
- package/dist/ui/icons/icon-registry.js +17 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/utils/array.helper.d.ts +2 -2
- package/dist/utils/array.helper.js +5 -5
- package/dist/utils/id-generators.d.ts +6 -0
- package/dist/utils/id-generators.js +10 -0
- package/dist/utils/media-url.js +0 -2
- package/dist/utils/search-txt-builders.d.ts +28 -4
- package/dist/utils/search-txt-builders.js +41 -1
- package/dist/validation/schemas.d.ts +8 -8
- package/package.json +6 -2
- package/scripts/seed-cli.mjs +3 -0
- package/dist/features/account/components/UserAddressesView.d.ts +0 -13
- package/dist/features/account/components/UserAddressesView.js +0 -5
- package/dist/features/account/components/UserNotificationsView.d.ts +0 -15
- package/dist/features/account/components/UserNotificationsView.js +0 -12
- package/dist/features/account/components/UserSettingsView.d.ts +0 -16
- package/dist/features/account/components/UserSettingsView.js +0 -13
- package/dist/features/account/components/UserSupportView.d.ts +0 -3
- package/dist/features/account/components/UserSupportView.js +0 -166
- package/dist/features/admin/components/DemoSeedView.d.ts +0 -11
- package/dist/features/admin/components/DemoSeedView.js +0 -5
- package/dist/features/auth/components/AuthStatusPanel.d.ts +0 -10
- package/dist/features/auth/components/AuthStatusPanel.js +0 -22
- package/dist/features/categories/components/CategoryProductsView.d.ts +0 -45
- package/dist/features/categories/components/CategoryProductsView.js +0 -21
- package/dist/features/homepage/components/HowItWorksInfoView.d.ts +0 -36
- package/dist/features/homepage/components/HowItWorksInfoView.js +0 -10
- package/dist/features/layout/BottomActions.d.ts +0 -1
- package/dist/features/layout/BottomActions.js +0 -311
- package/dist/features/products/components/ProductsView.d.ts +0 -6
- package/dist/features/products/components/ProductsView.js +0 -5
- package/dist/features/seller/components/SellerStoreView.d.ts +0 -8
- 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 {
|
|
7
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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:
|
|
62
|
+
reason: EVENT_DRAW_REASON.EVENT_NOT_FOUND,
|
|
21
63
|
};
|
|
22
64
|
}
|
|
23
|
-
const
|
|
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
|
|
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, "==",
|
|
37
|
-
.where(EVENT_ENTRY_FIELDS.
|
|
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
|
|
144
|
+
eventId,
|
|
64
145
|
raffleEntryCount: 0,
|
|
65
|
-
reason:
|
|
146
|
+
reason: EVENT_DRAW_REASON.NO_ELIGIBLE_ENTRIES,
|
|
66
147
|
};
|
|
67
148
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
232
|
+
eventId,
|
|
81
233
|
raffleType,
|
|
82
234
|
poolSize: pool.length,
|
|
83
|
-
winnerId:
|
|
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
|
|
87
|
-
raffleWinnerUserId:
|
|
88
|
-
raffleWinnerDisplayName:
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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:
|
|
67
|
-
upiId:
|
|
68
|
-
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:
|
|
71
|
-
accountNumberMasked:
|
|
72
|
-
ifscCode:
|
|
73
|
-
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.
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
};
|