@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,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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
29
|
-
if (
|
|
30
|
-
|
|
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:
|
|
81
|
+
return { eventId, userId, reason: EVENT_DRAW_REASON.LOGIN_REQUIRED };
|
|
35
82
|
}
|
|
36
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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) {
|