@mohasinac/appkit 4.2.0 → 4.3.1
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/_internal/client/features/layout/navActive.d.ts +17 -0
- package/dist/_internal/client/features/layout/navActive.js +33 -0
- package/dist/_internal/server/features/auth/actions.d.ts +0 -1
- package/dist/_internal/server/features/auth/actions.js +0 -1
- package/dist/_internal/server/features/bundles/data.d.ts +22 -4
- package/dist/_internal/server/features/bundles/data.js +41 -4
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/checkout/actions.js +7 -78
- package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
- package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
- package/dist/_internal/server/features/orders/adapters.js +1 -14
- package/dist/_internal/server/features/products/actions.js +8 -3
- package/dist/_internal/server/features/products/service.d.ts +15 -0
- package/dist/_internal/server/features/products/service.js +41 -0
- package/dist/_internal/server/functions/firestore.d.ts +3 -1
- package/dist/_internal/server/functions/firestore.js +17 -1
- package/dist/_internal/server/functions/scheduled.d.ts +2 -5
- package/dist/_internal/server/functions/scheduled.js +6 -30
- package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
- package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
- package/dist/_internal/server/jobs/core/index.d.ts +4 -4
- package/dist/_internal/server/jobs/core/index.js +4 -4
- package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
- package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +2 -2
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +3 -4
- package/dist/_internal/server/jobs/handlers/index.js +8 -6
- package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
- package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
- package/dist/_internal/shared/actions/action-registry.js +40 -10
- package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
- package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
- package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
- package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
- package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
- package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
- package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
- package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
- package/dist/constants/api-endpoints.d.ts +6 -9
- package/dist/constants/api-endpoints.js +5 -5
- package/dist/constants/field-names.d.ts +17 -1
- package/dist/constants/field-names.js +17 -1
- package/dist/features/account/components/UserSidebar.js +21 -9
- package/dist/features/account/hooks/useNotifications.js +5 -0
- package/dist/features/admin/actions/admin-actions.js +6 -0
- package/dist/features/admin/actions/notification-actions.js +1 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.js +15 -1
- package/dist/features/admin/components/AdminPaymentsGuideView.d.ts +2 -0
- package/dist/features/admin/components/AdminPaymentsGuideView.js +36 -0
- package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
- package/dist/features/admin/components/AdminSidebar.js +18 -9
- package/dist/features/admin/components/AdminSiteSettingsView.js +33 -6
- package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
- package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +2 -0
- package/dist/features/admin/components/AdminWhatsAppGuideView.js +35 -0
- package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
- package/dist/features/admin/hooks/useAdminListing.js +4 -2
- package/dist/features/admin/schemas/firestore.d.ts +13 -5
- package/dist/features/admin/schemas/firestore.js +1 -2
- package/dist/features/auctions/actions/bid-actions.js +3 -1
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
- package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
- package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
- package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
- package/dist/features/auctions/components/CollapsibleBidHistory.js +22 -3
- package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
- package/dist/features/auctions/components/LiveBidPrice.js +17 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
- package/dist/features/auctions/hooks/useBids.d.ts +33 -0
- package/dist/features/auctions/hooks/useBids.js +32 -0
- package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
- package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
- package/dist/features/auth/checkout-value-otp.d.ts +7 -10
- package/dist/features/auth/checkout-value-otp.js +14 -10
- package/dist/features/auth/server.d.ts +0 -1
- package/dist/features/auth/server.js +0 -1
- package/dist/features/blog/api/[slug]/route.d.ts +5 -0
- package/dist/features/blog/api/[slug]/route.js +12 -3
- package/dist/features/blog/components/BlogPostView.d.ts +2 -0
- package/dist/features/blog/components/BlogPostView.js +5 -4
- package/dist/features/blog/hooks/useBlog.d.ts +2 -0
- package/dist/features/blog/hooks/useBlog.js +2 -0
- package/dist/features/blog/repository/blog.repository.d.ts +7 -0
- package/dist/features/blog/repository/blog.repository.js +44 -0
- package/dist/features/blog/schemas/firestore.d.ts +3 -0
- package/dist/features/blog/schemas/firestore.js +1 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/index.d.ts +0 -2
- package/dist/features/cart/components/index.js +0 -1
- package/dist/features/cart/hooks/useCartCount.js +22 -5
- package/dist/features/cart/hooks/useGuestCart.js +11 -1
- package/dist/features/cart/utils/guest-cart.d.ts +4 -0
- package/dist/features/cart/utils/guest-cart.js +10 -0
- package/dist/features/cart/utils/pending-ops.d.ts +17 -0
- package/dist/features/cart/utils/pending-ops.js +46 -5
- package/dist/features/categories/components/BrandDetailPageView.js +6 -2
- package/dist/features/categories/components/BundleDetailView.js +4 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
- package/dist/features/categories/schemas/firestore.d.ts +7 -0
- package/dist/features/checkout/actions/index.d.ts +0 -1
- package/dist/features/checkout/actions/index.js +0 -1
- package/dist/features/checkout/schemas/firestore.d.ts +2 -2
- package/dist/features/events/actions/event-actions.d.ts +2 -0
- package/dist/features/events/actions/event-actions.js +5 -0
- package/dist/features/events/components/EventCard.js +2 -3
- package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
- package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
- package/dist/features/events/components/index.d.ts +2 -0
- package/dist/features/events/components/index.js +1 -0
- package/dist/features/events/repository/events.repository.d.ts +6 -0
- package/dist/features/events/repository/events.repository.js +21 -0
- package/dist/features/history/repository/user-history.repository.d.ts +1 -1
- package/dist/features/history/utils/guest-history.d.ts +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
- package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/homepage/components/WelcomeSection.js +1 -1
- package/dist/features/layout/AppLayoutShell.js +44 -10
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +2 -2
- package/dist/features/media/MediaVideo.d.ts +7 -0
- package/dist/features/media/MediaVideo.js +36 -5
- package/dist/features/orders/components/OrdersList.js +1 -5
- package/dist/features/orders/schemas/firestore.d.ts +2 -8
- package/dist/features/orders/schemas/index.d.ts +3 -32
- package/dist/features/orders/schemas/index.js +1 -4
- package/dist/features/orders/types/index.d.ts +6 -9
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
- package/dist/features/products/actions/product-actions.js +13 -10
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
- package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
- package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
- package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
- package/dist/features/products/components/PrizeRevealModal.js +18 -147
- package/dist/features/products/components/ProductDetailPageView.js +72 -30
- package/dist/features/products/components/ProductForm.js +7 -14
- package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
- package/dist/features/products/components/ProductGalleryClient.js +29 -10
- package/dist/features/products/components/ProductsIndexListing.js +2 -1
- package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
- package/dist/features/products/components/ShowGroupSection.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +1 -1
- package/dist/features/products/components/index.d.ts +4 -2
- package/dist/features/products/components/index.js +1 -0
- package/dist/features/products/constants/action-defs.d.ts +1 -3
- package/dist/features/products/constants/action-defs.js +0 -5
- package/dist/features/products/repository/products.repository.d.ts +6 -0
- package/dist/features/products/repository/products.repository.js +20 -1
- package/dist/features/products/schemas/firestore.d.ts +28 -3
- package/dist/features/products/schemas/index.d.ts +11 -3
- package/dist/features/products/schemas/index.js +10 -2
- package/dist/features/products/types/index.d.ts +2 -1
- package/dist/features/reviews/actions/review-actions.js +1 -0
- package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
- package/dist/features/reviews/components/ReviewsList.js +2 -1
- package/dist/features/reviews/schemas/firestore.d.ts +3 -0
- package/dist/features/reviews/schemas/index.d.ts +6 -0
- package/dist/features/reviews/schemas/index.js +2 -0
- package/dist/features/reviews/types/index.d.ts +3 -0
- package/dist/features/scams/actions/scam-actions.d.ts +3 -0
- package/dist/features/scams/actions/scam-actions.js +3 -2
- package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
- package/dist/features/scams/repository/scammer.repository.js +24 -0
- package/dist/features/seller/actions/seller-actions.js +8 -0
- package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
- package/dist/features/seller/components/SellerSidebar.js +20 -9
- package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
- package/dist/features/stores/components/StoreGuideHubView.js +7 -1
- package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
- package/dist/features/stores/components/StoreNavTabs.js +22 -9
- package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreWhatsAppGuideView.js +12 -0
- package/dist/features/stores/components/index.d.ts +2 -0
- package/dist/features/stores/components/index.js +1 -0
- package/dist/features/tester/actions/index.d.ts +1 -0
- package/dist/features/tester/actions/index.js +1 -0
- package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
- package/dist/features/tester/actions/leaderboard-actions.js +5 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
- package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
- package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
- package/dist/features/tester/components/index.d.ts +2 -0
- package/dist/features/tester/components/index.js +1 -0
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
- package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
- package/dist/features/tester/schemas/firestore.d.ts +22 -1
- package/dist/features/tester/schemas/firestore.js +8 -0
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
- package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
- package/dist/features/tester/seed-data/products-tester-seed-data.js +154 -9
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +252 -15
- package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
- package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
- package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
- package/dist/index.d.ts +23 -22
- package/dist/index.js +31 -58
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +2 -2
- package/dist/next/routing/route-map.d.ts +9 -2
- package/dist/next/routing/route-map.js +4 -1
- package/dist/schemas/registry.d.ts +6 -26
- package/dist/seed/bids-seed-data.js +14 -2
- package/dist/seed/blog-posts-seed-data.js +2 -1
- package/dist/seed/categories-seed-data.js +11 -2
- package/dist/seed/events-seed-data.js +11 -11
- package/dist/seed/faq-seed-data.js +5 -1
- package/dist/seed/products-auctions-seed-data.js +2 -2
- package/dist/seed/products-preorders-seed-data.d.ts +2 -1
- package/dist/seed/products-prize-draws-seed-data.js +56 -10
- package/dist/seed/products-standard-seed-data.js +80 -3
- package/dist/seed/scammers-seed-data.js +75 -2
- package/dist/seed/site-settings-seed-data.js +5 -2
- package/dist/server-entry.d.ts +0 -1
- package/dist/server-entry.js +0 -1
- package/dist/server.d.ts +4 -20
- package/dist/server.js +10 -57
- package/dist/styles.css +1 -277
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/BaseListingCard.js +1 -1
- package/dist/ui/components/ImageLightbox.d.ts +9 -0
- package/dist/ui/components/ImageLightbox.js +6 -3
- package/dist/ui/components/ListingToolbar.d.ts +18 -1
- package/dist/ui/components/ListingToolbar.js +5 -2
- package/dist/ui/components/StickyToolbar.js +13 -8
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* The path patterns mirror the original `functions/src/index.ts` declarations
|
|
7
7
|
* to preserve identical deploy behavior.
|
|
8
8
|
*/
|
|
9
|
-
import { onBidPlacedHandler, onCategoryWriteHandler, onOrderCreateHandler, onOrderStatusChangeHandler, onProductStockChangeHandler, onProductWriteHandler, onReviewWriteHandler, onScamReportCreateHandler, onScamReportUpdateHandler, onStoreWriteHandler, onSupportTicketCreateHandler, onSupportTicketUpdateHandler, onUserBanChangeHandler, onShipmentItemWriteHandler, onShipmentLotWriteHandler, onShipmentHeaderWriteHandler, onShipmentDeletedHandler, onCatalogueSubmittedForApprovalHandler, onJobCreatedHandler, } from "../jobs/handlers";
|
|
9
|
+
import { onBidPlacedHandler, onCategoryWriteHandler, onOrderCreateHandler, onOrderStatusChangeHandler, onProductStockChangeHandler, onProductWriteHandler, onReviewWriteHandler, onScamReportCreateHandler, onScamReportUpdateHandler, onStoreWriteHandler, onSupportTicketCreateHandler, onSupportTicketUpdateHandler, onUserBanChangeHandler, onShipmentItemWriteHandler, onShipmentLotWriteHandler, onShipmentHeaderWriteHandler, onShipmentDeletedHandler, onCatalogueSubmittedForApprovalHandler, onJobCreatedHandler, onPrizeDrawPaymentConfirmedHandler, prizeDrawSoldOutRevealHandler, } from "../jobs/handlers";
|
|
10
10
|
import { defineFunction } from "./define";
|
|
11
11
|
const REGION = "asia-south1";
|
|
12
12
|
export const onBidPlaced = defineFunction({
|
|
@@ -44,6 +44,20 @@ export const onProductStockChange = defineFunction({
|
|
|
44
44
|
handler: onProductStockChangeHandler,
|
|
45
45
|
options: { region: REGION },
|
|
46
46
|
});
|
|
47
|
+
export const onPrizeDrawPaymentConfirmed = defineFunction({
|
|
48
|
+
name: "onPrizeDrawPaymentConfirmed",
|
|
49
|
+
description: "Instant-mode prize-draw winner assignment the moment an order's payment is confirmed.",
|
|
50
|
+
trigger: { kind: "documentUpdated", pathPattern: "orders/{orderId}" },
|
|
51
|
+
handler: onPrizeDrawPaymentConfirmedHandler,
|
|
52
|
+
options: { region: REGION },
|
|
53
|
+
});
|
|
54
|
+
export const prizeDrawSoldOutReveal = defineFunction({
|
|
55
|
+
name: "prizeDrawSoldOutReveal",
|
|
56
|
+
description: "Assign winners for all outstanding prize-draw orders the moment a scheduled-mode draw sells out.",
|
|
57
|
+
trigger: { kind: "documentWritten", pathPattern: "products/{productId}" },
|
|
58
|
+
handler: prizeDrawSoldOutRevealHandler,
|
|
59
|
+
options: { region: REGION },
|
|
60
|
+
});
|
|
47
61
|
export const onReviewWrite = defineFunction({
|
|
48
62
|
name: "onReviewWrite",
|
|
49
63
|
description: "Recompute product + store rating aggregates on review writes.",
|
|
@@ -155,6 +169,8 @@ export const FIRESTORE_TRIGGER_FUNCTIONS = [
|
|
|
155
169
|
onOrderStatusChange,
|
|
156
170
|
onProductWrite,
|
|
157
171
|
onProductStockChange,
|
|
172
|
+
onPrizeDrawPaymentConfirmed,
|
|
173
|
+
prizeDrawSoldOutReveal,
|
|
158
174
|
onReviewWrite,
|
|
159
175
|
onCategoryWrite,
|
|
160
176
|
onStoreWrite,
|
|
@@ -19,11 +19,8 @@ export declare const cleanupRtdbEvents: import("./types").ScheduledFunctionDefin
|
|
|
19
19
|
export declare const mediaTmpCleanup: import("./types").ScheduledFunctionDefinition;
|
|
20
20
|
export declare const draftPrune: import("./types").ScheduledFunctionDefinition;
|
|
21
21
|
export declare const testerSandboxCleanup: import("./types").ScheduledFunctionDefinition;
|
|
22
|
-
export declare const
|
|
23
|
-
export declare const prizeRevealClose: import("./types").ScheduledFunctionDefinition;
|
|
24
|
-
export declare const prizeRevealExpiry: import("./types").ScheduledFunctionDefinition;
|
|
25
|
-
export declare const prizeRevealReminder: import("./types").ScheduledFunctionDefinition;
|
|
22
|
+
export declare const prizeDrawExpiryReveal: import("./types").ScheduledFunctionDefinition;
|
|
26
23
|
export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
|
|
27
24
|
export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
|
|
28
25
|
export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
|
|
29
|
-
export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition
|
|
26
|
+
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];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Scheduled function definitions (Cloud Scheduler triggers).
|
|
2
2
|
// Cron syntax accepts both Firebase shorthand and standard cron expressions.
|
|
3
|
-
import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler,
|
|
3
|
+
import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, } from "../jobs/handlers";
|
|
4
4
|
import { defineFunction } from "./define";
|
|
5
5
|
const REGION = "asia-south1";
|
|
6
6
|
const EVERY_5_MIN = "every 5 minutes";
|
|
@@ -151,32 +151,11 @@ export const testerSandboxCleanup = defineFunction({
|
|
|
151
151
|
handler: testerSandboxCleanupHandler,
|
|
152
152
|
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
153
153
|
});
|
|
154
|
-
export const
|
|
155
|
-
name: "
|
|
156
|
-
description: "
|
|
154
|
+
export const prizeDrawExpiryReveal = defineFunction({
|
|
155
|
+
name: "prizeDrawExpiryReveal",
|
|
156
|
+
description: "Assign winners for outstanding paid orders on scheduled-mode prize draws past their expiry, then close them (every 5 minutes).",
|
|
157
157
|
trigger: { kind: "schedule", cron: EVERY_5_MIN },
|
|
158
|
-
handler:
|
|
159
|
-
options: { region: REGION, timeoutSeconds: 120, memory: "256MiB", maxInstances: 1 },
|
|
160
|
-
});
|
|
161
|
-
export const prizeRevealClose = defineFunction({
|
|
162
|
-
name: "prizeRevealClose",
|
|
163
|
-
description: "Flip prize-draw reveals open→closed.",
|
|
164
|
-
trigger: { kind: "schedule", cron: EVERY_5_MIN },
|
|
165
|
-
handler: prizeRevealCloseHandler,
|
|
166
|
-
options: { region: REGION, timeoutSeconds: 60, memory: "256MiB", maxInstances: 1 },
|
|
167
|
-
});
|
|
168
|
-
export const prizeRevealExpiry = defineFunction({
|
|
169
|
-
name: "prizeRevealExpiry",
|
|
170
|
-
description: "Auto-refund unrevealed prize-draw entries past deadline (every 6 hours UTC).",
|
|
171
|
-
trigger: { kind: "schedule", cron: "0 */6 * * *", timeZone: "UTC" },
|
|
172
|
-
handler: prizeRevealExpiryHandler,
|
|
173
|
-
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
174
|
-
});
|
|
175
|
-
export const prizeRevealReminder = defineFunction({
|
|
176
|
-
name: "prizeRevealReminder",
|
|
177
|
-
description: "Nudge prize-draw buyers <24h to deadline (daily 10:00 IST).",
|
|
178
|
-
trigger: { kind: "schedule", cron: "0 10 * * *", timeZone: "Asia/Kolkata" },
|
|
179
|
-
handler: prizeRevealReminderHandler,
|
|
158
|
+
handler: prizeDrawExpiryRevealHandler,
|
|
180
159
|
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
181
160
|
});
|
|
182
161
|
export const bundleStockSync = defineFunction({
|
|
@@ -222,10 +201,7 @@ export const SCHEDULED_FUNCTIONS = [
|
|
|
222
201
|
mediaTmpCleanup,
|
|
223
202
|
draftPrune,
|
|
224
203
|
testerSandboxCleanup,
|
|
225
|
-
|
|
226
|
-
prizeRevealClose,
|
|
227
|
-
prizeRevealExpiry,
|
|
228
|
-
prizeRevealReminder,
|
|
204
|
+
prizeDrawExpiryReveal,
|
|
229
205
|
bundleStockSync,
|
|
230
206
|
emiInstallmentReminder,
|
|
231
207
|
catalogueImageStalenessReminder,
|
|
@@ -16,9 +16,12 @@ function isMemberAvailable(data) {
|
|
|
16
16
|
return false;
|
|
17
17
|
return data.status === PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED;
|
|
18
18
|
}
|
|
19
|
-
async function
|
|
19
|
+
async function sweepBundleMembers(productIds, ctx) {
|
|
20
20
|
if (productIds.length === 0)
|
|
21
|
-
return "out_of_stock";
|
|
21
|
+
return { stockStatus: "out_of_stock", originalTotal: undefined };
|
|
22
|
+
let stockStatus = "in_stock";
|
|
23
|
+
let originalTotal = 0;
|
|
24
|
+
let resolvedCount = 0;
|
|
22
25
|
for (let i = 0; i < productIds.length; i += 30) {
|
|
23
26
|
const chunk = productIds.slice(i, i + 30);
|
|
24
27
|
const snap = await ctx.db
|
|
@@ -26,14 +29,26 @@ async function computeBundleStockStatus(productIds, ctx) {
|
|
|
26
29
|
.where("__name__", "in", chunk)
|
|
27
30
|
.get();
|
|
28
31
|
if (snap.size < chunk.length)
|
|
29
|
-
|
|
32
|
+
stockStatus = "out_of_stock";
|
|
30
33
|
for (const doc of snap.docs) {
|
|
31
34
|
const data = doc.data();
|
|
32
35
|
if (!isMemberAvailable(data))
|
|
33
|
-
|
|
36
|
+
stockStatus = "out_of_stock";
|
|
37
|
+
if (typeof data.price === "number" && originalTotal !== undefined) {
|
|
38
|
+
originalTotal += data.price;
|
|
39
|
+
resolvedCount++;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
originalTotal = undefined;
|
|
43
|
+
}
|
|
34
44
|
}
|
|
35
45
|
}
|
|
36
|
-
|
|
46
|
+
if (resolvedCount !== productIds.length)
|
|
47
|
+
originalTotal = undefined;
|
|
48
|
+
return {
|
|
49
|
+
stockStatus,
|
|
50
|
+
originalTotal: originalTotal !== undefined ? Math.round(originalTotal * 100) / 100 : undefined,
|
|
51
|
+
};
|
|
37
52
|
}
|
|
38
53
|
export async function runBundleStockSync(ctx) {
|
|
39
54
|
if (ctx.env("FEATURE_BUNDLES") !== "true") {
|
|
@@ -57,10 +72,13 @@ export async function runBundleStockSync(ctx) {
|
|
|
57
72
|
const productIds = data.bundleProductIds ?? [];
|
|
58
73
|
if (productIds.length === 0)
|
|
59
74
|
continue;
|
|
60
|
-
const nextStatus = await
|
|
61
|
-
|
|
75
|
+
const { stockStatus: nextStatus, originalTotal: nextOriginalTotal } = await sweepBundleMembers(productIds, ctx);
|
|
76
|
+
const statusChanged = nextStatus !== data.bundleStockStatus;
|
|
77
|
+
const totalChanged = nextOriginalTotal !== data.bundleOriginalTotal;
|
|
78
|
+
if (statusChanged || totalChanged) {
|
|
62
79
|
await bundleDoc.ref.update({
|
|
63
80
|
[CATEGORY_FIELDS.BUNDLE_STOCK_STATUS]: nextStatus,
|
|
81
|
+
[CATEGORY_FIELDS.BUNDLE_ORIGINAL_TOTAL]: nextOriginalTotal ?? null,
|
|
64
82
|
[CATEGORY_FIELDS.BUNDLE_QUERY_RESOLVED_AT]: ctx.now,
|
|
65
83
|
[COMMON_FIELDS.UPDATED_AT]: ctx.now,
|
|
66
84
|
});
|
|
@@ -15,7 +15,13 @@ async function reconcileCategories(ctx) {
|
|
|
15
15
|
const leafCounts = {};
|
|
16
16
|
for (const doc of snap.docs) {
|
|
17
17
|
const data = doc.data();
|
|
18
|
-
|
|
18
|
+
// `category` is @deprecated in favor of `categorySlugs[]` — mirrors the
|
|
19
|
+
// same effective-category selection as onProductWrite.ts's live trigger
|
|
20
|
+
// so the nightly reconcile agrees with real-time updates instead of
|
|
21
|
+
// silently no-op'ing on products written via categorySlugs-only paths.
|
|
22
|
+
const catId = Array.isArray(data.categorySlugs) && data.categorySlugs.length > 0
|
|
23
|
+
? data.categorySlugs[0]
|
|
24
|
+
: data.category;
|
|
19
25
|
if (!catId)
|
|
20
26
|
continue;
|
|
21
27
|
if (!leafCounts[catId])
|
|
@@ -26,10 +26,10 @@ export { runWeeklyPayoutEligibility, type WeeklyPayoutEligibilityResult, } from
|
|
|
26
26
|
export { runHardBanCascade } from "./hardBanCascade";
|
|
27
27
|
export { JOB_RUNNERS, type JobRunner, type JobRunResult, } from "./jobRunners";
|
|
28
28
|
export { runAuctionSettlement } from "./auctionSettlement";
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
29
|
+
export { assignPrizeDrawWinner, type AssignPrizeDrawWinnerResult } from "./prizeDrawAssignWinner";
|
|
30
|
+
export { handlePrizeDrawPaymentConfirmed } from "./onPrizeDrawPaymentConfirmed";
|
|
31
|
+
export { handlePrizeDrawSoldOut } from "./prizeDrawSoldOutReveal";
|
|
32
|
+
export { runPrizeDrawExpiryReveal } from "./prizeDrawExpiryReveal";
|
|
33
33
|
export { runPromotions, type PromotionsCallableResult } from "./promotions";
|
|
34
34
|
export { runListingProcessor, supportedListingCollections, type ListingRequestBody, type ListingResponseBody, } from "./listingProcessor";
|
|
35
35
|
export { runStoreAnalytics, type StoreAnalyticsInput, type StoreAnalyticsResult, } from "./storeAnalytics";
|
|
@@ -27,10 +27,10 @@ export { runWeeklyPayoutEligibility, } from "./weeklyPayoutEligibility";
|
|
|
27
27
|
export { runHardBanCascade } from "./hardBanCascade";
|
|
28
28
|
export { JOB_RUNNERS, } from "./jobRunners";
|
|
29
29
|
export { runAuctionSettlement } from "./auctionSettlement";
|
|
30
|
-
export {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
30
|
+
export { assignPrizeDrawWinner } from "./prizeDrawAssignWinner";
|
|
31
|
+
export { handlePrizeDrawPaymentConfirmed } from "./onPrizeDrawPaymentConfirmed";
|
|
32
|
+
export { handlePrizeDrawSoldOut } from "./prizeDrawSoldOutReveal";
|
|
33
|
+
export { runPrizeDrawExpiryReveal } from "./prizeDrawExpiryReveal";
|
|
34
34
|
// Callables
|
|
35
35
|
export { runPromotions } from "./promotions";
|
|
36
36
|
export { runListingProcessor, supportedListingCollections, } from "./listingProcessor";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { JobContext } from "../runtime/types";
|
|
2
|
+
export interface PrizeDrawOrderSnapshot {
|
|
3
|
+
paymentStatus?: string;
|
|
4
|
+
prizeDrawProductId?: string;
|
|
5
|
+
items?: Array<{
|
|
6
|
+
listingType?: string;
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Instant-mode prize-draw winner assignment. Fires when an order's
|
|
11
|
+
* paymentStatus transitions into "paid" — not at order creation, since a
|
|
12
|
+
* prize-draw order isn't guaranteed to be paid yet at that point (manual
|
|
13
|
+
* bank/UPI payments are confirmed later by the seller/admin; Razorpay
|
|
14
|
+
* confirms near-instantly but still goes through this same status flip).
|
|
15
|
+
*/
|
|
16
|
+
export declare function handlePrizeDrawPaymentConfirmed(input: {
|
|
17
|
+
orderId: string;
|
|
18
|
+
order: PrizeDrawOrderSnapshot;
|
|
19
|
+
}, ctx: JobContext): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
2
|
+
import { assignPrizeDrawWinner } from "./prizeDrawAssignWinner";
|
|
3
|
+
const PRODUCT_COLLECTION = "products";
|
|
4
|
+
/**
|
|
5
|
+
* Instant-mode prize-draw winner assignment. Fires when an order's
|
|
6
|
+
* paymentStatus transitions into "paid" — not at order creation, since a
|
|
7
|
+
* prize-draw order isn't guaranteed to be paid yet at that point (manual
|
|
8
|
+
* bank/UPI payments are confirmed later by the seller/admin; Razorpay
|
|
9
|
+
* confirms near-instantly but still goes through this same status flip).
|
|
10
|
+
*/
|
|
11
|
+
export async function handlePrizeDrawPaymentConfirmed(input, ctx) {
|
|
12
|
+
const { orderId, order } = input;
|
|
13
|
+
if (!order.prizeDrawProductId)
|
|
14
|
+
return;
|
|
15
|
+
if (!order.items?.some((it) => it.listingType === "prize-draw"))
|
|
16
|
+
return;
|
|
17
|
+
let product;
|
|
18
|
+
try {
|
|
19
|
+
const snap = await ctx.db.collection(PRODUCT_COLLECTION).doc(order.prizeDrawProductId).get();
|
|
20
|
+
product = snap.exists ? snap.data() : undefined;
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
void normalizeError(err);
|
|
24
|
+
ctx.logger.error("Failed to load prize-draw product for instant reveal", err, { orderId });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!product || product.prizeRevealMode !== "instant")
|
|
28
|
+
return;
|
|
29
|
+
await assignPrizeDrawWinner(ctx, { orderId, productId: order.prizeDrawProductId });
|
|
30
|
+
}
|
|
@@ -5,8 +5,8 @@ import type { JobContext } from "../runtime/types";
|
|
|
5
5
|
* its `paymentDeadline`. Runs every 5 minutes (`EVERY_5_MIN`), so an order
|
|
6
6
|
* can overshoot its 15-minute deadline by up to ~5 minutes before this
|
|
7
7
|
* catches it — acceptable, matches the existing tight-interval precedent
|
|
8
|
-
* (`
|
|
9
|
-
*
|
|
8
|
+
* (`prizeDrawExpiryReveal`) rather than inventing a 1-minute cron that would
|
|
9
|
+
* multiply invocation cost for marginal UX gain.
|
|
10
10
|
*
|
|
11
11
|
* Orders that already have a `paymentProofUrl` are excluded by the
|
|
12
12
|
* repository query — those are the 2-hour auto-approve sweep's domain
|
|
@@ -11,8 +11,8 @@ import { ORDER_MESSAGES } from "../handlers/messages";
|
|
|
11
11
|
* its `paymentDeadline`. Runs every 5 minutes (`EVERY_5_MIN`), so an order
|
|
12
12
|
* can overshoot its 15-minute deadline by up to ~5 minutes before this
|
|
13
13
|
* catches it — acceptable, matches the existing tight-interval precedent
|
|
14
|
-
* (`
|
|
15
|
-
*
|
|
14
|
+
* (`prizeDrawExpiryReveal`) rather than inventing a 1-minute cron that would
|
|
15
|
+
* multiply invocation cost for marginal UX gain.
|
|
16
16
|
*
|
|
17
17
|
* Orders that already have a `paymentProofUrl` are excluded by the
|
|
18
18
|
* repository query — those are the 2-hour auto-approve sweep's domain
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { JobContext } from "../runtime/types";
|
|
2
|
+
import type { OrderDocument } from "../../../../features/orders/schemas/firestore";
|
|
3
|
+
export interface AssignPrizeDrawWinnerResult {
|
|
4
|
+
kind: "revealed" | "already_revealed" | "pool_exhausted" | "not_eligible";
|
|
5
|
+
prizeWon?: OrderDocument["prizeWon"];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The single place a prize-draw winner is ever picked. Called by every
|
|
9
|
+
* trigger point (payment-confirmed for "instant" mode, sellout-detection,
|
|
10
|
+
* and the expiry sweep for "scheduled" mode) — never call the underlying
|
|
11
|
+
* transaction logic anywhere else.
|
|
12
|
+
*
|
|
13
|
+
* Preserves the exact `crypto.randomInt` rejection-sampled fairness RNG that
|
|
14
|
+
* buyers are shown a public GitHub source link for (product.prizeGithubFileUrl)
|
|
15
|
+
* — do not change the selection algorithm.
|
|
16
|
+
*/
|
|
17
|
+
export declare function assignPrizeDrawWinner(ctx: JobContext, args: {
|
|
18
|
+
orderId: string;
|
|
19
|
+
productId: string;
|
|
20
|
+
}): Promise<AssignPrizeDrawWinnerResult>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
3
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
4
|
+
const PRODUCT_COLLECTION = "products";
|
|
5
|
+
const ORDER_COLLECTION = "orders";
|
|
6
|
+
/**
|
|
7
|
+
* The single place a prize-draw winner is ever picked. Called by every
|
|
8
|
+
* trigger point (payment-confirmed for "instant" mode, sellout-detection,
|
|
9
|
+
* and the expiry sweep for "scheduled" mode) — never call the underlying
|
|
10
|
+
* transaction logic anywhere else.
|
|
11
|
+
*
|
|
12
|
+
* Preserves the exact `crypto.randomInt` rejection-sampled fairness RNG that
|
|
13
|
+
* buyers are shown a public GitHub source link for (product.prizeGithubFileUrl)
|
|
14
|
+
* — do not change the selection algorithm.
|
|
15
|
+
*/
|
|
16
|
+
export async function assignPrizeDrawWinner(ctx, args) {
|
|
17
|
+
if (ctx.env("FEATURE_PRIZE_DRAWS") !== "true") {
|
|
18
|
+
ctx.logger.info("FEATURE_PRIZE_DRAWS disabled — skipping winner assignment");
|
|
19
|
+
return { kind: "not_eligible" };
|
|
20
|
+
}
|
|
21
|
+
const { orderId, productId } = args;
|
|
22
|
+
const orderRef = ctx.db.collection(ORDER_COLLECTION).doc(orderId);
|
|
23
|
+
const productRef = ctx.db.collection(PRODUCT_COLLECTION).doc(productId);
|
|
24
|
+
const result = await ctx.db.runTransaction(async (tx) => {
|
|
25
|
+
const [productSnap, orderSnap] = await Promise.all([
|
|
26
|
+
tx.get(productRef),
|
|
27
|
+
tx.get(orderRef),
|
|
28
|
+
]);
|
|
29
|
+
if (!productSnap.exists || !orderSnap.exists) {
|
|
30
|
+
return { kind: "not_eligible" };
|
|
31
|
+
}
|
|
32
|
+
const product = productSnap.data();
|
|
33
|
+
const order = orderSnap.data();
|
|
34
|
+
// Idempotent — a winner was already assigned to this order.
|
|
35
|
+
if (order.prizeWon) {
|
|
36
|
+
return { kind: "already_revealed", prizeWon: order.prizeWon };
|
|
37
|
+
}
|
|
38
|
+
if (order.paymentStatus !== "paid" ||
|
|
39
|
+
order.status === "cancelled" ||
|
|
40
|
+
order.status === "refunded") {
|
|
41
|
+
return { kind: "not_eligible" };
|
|
42
|
+
}
|
|
43
|
+
const items = Array.isArray(product.prizeDrawItems)
|
|
44
|
+
? [...product.prizeDrawItems]
|
|
45
|
+
: [];
|
|
46
|
+
const unwonIndices = items
|
|
47
|
+
.map((it, idx) => (it.isWon ? -1 : idx))
|
|
48
|
+
.filter((idx) => idx >= 0);
|
|
49
|
+
if (unwonIndices.length === 0) {
|
|
50
|
+
// Defensive only — preflightChecks already blocks overselling past
|
|
51
|
+
// prizeMaxEntries, so this should be unreachable outside a genuine
|
|
52
|
+
// race between two concurrent checkouts. No auto-refund here (that
|
|
53
|
+
// was the old buyer-claim-deadline behavior, now retired) — just
|
|
54
|
+
// report it so the caller can log/notify.
|
|
55
|
+
return { kind: "pool_exhausted" };
|
|
56
|
+
}
|
|
57
|
+
// crypto.randomInt is rejection-sampled and uniform — safe for fairness.
|
|
58
|
+
const pickPosition = crypto.randomInt(0, unwonIndices.length);
|
|
59
|
+
const pickIndex = unwonIndices[pickPosition];
|
|
60
|
+
const winningItem = items[pickIndex];
|
|
61
|
+
items[pickIndex] = { ...winningItem, isWon: true, wonByOrderId: orderId };
|
|
62
|
+
tx.update(productRef, {
|
|
63
|
+
prizeDrawItems: items,
|
|
64
|
+
updatedAt: ctx.now,
|
|
65
|
+
});
|
|
66
|
+
const prizeWon = {
|
|
67
|
+
itemNumber: winningItem.itemNumber,
|
|
68
|
+
title: winningItem.title,
|
|
69
|
+
images: winningItem.images,
|
|
70
|
+
wonAt: ctx.now,
|
|
71
|
+
};
|
|
72
|
+
const orderItems = Array.isArray(order.items) ? [...order.items] : [];
|
|
73
|
+
const revealedItems = orderItems.map((it) => it.listingType === "prize-draw" ? { ...it, prizeRevealStatus: "revealed" } : it);
|
|
74
|
+
tx.update(orderRef, {
|
|
75
|
+
prizeWon,
|
|
76
|
+
...(revealedItems.length > 0 ? { items: revealedItems } : {}),
|
|
77
|
+
updatedAt: ctx.now,
|
|
78
|
+
});
|
|
79
|
+
return { kind: "revealed", prizeWon, userId: order.userId, productTitle: product.title };
|
|
80
|
+
});
|
|
81
|
+
if (result.kind === "revealed") {
|
|
82
|
+
ctx.logger.info("Prize-draw winner assigned", {
|
|
83
|
+
orderId,
|
|
84
|
+
productId,
|
|
85
|
+
itemNumber: result.prizeWon.itemNumber,
|
|
86
|
+
});
|
|
87
|
+
if (result.userId) {
|
|
88
|
+
try {
|
|
89
|
+
await sendNotification({
|
|
90
|
+
userId: result.userId,
|
|
91
|
+
type: "prize_won",
|
|
92
|
+
priority: "high",
|
|
93
|
+
title: "Your prize is ready!",
|
|
94
|
+
message: `You won "${result.prizeWon.title}" in "${result.productTitle ?? "the draw"}" — check your order for details.`,
|
|
95
|
+
relatedId: orderId,
|
|
96
|
+
relatedType: "order",
|
|
97
|
+
actionUrl: `/user/orders/view/${orderId}`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
void normalizeError(err);
|
|
102
|
+
ctx.logger.warn("Prize-won notification failed", {
|
|
103
|
+
err: err instanceof Error ? err.message : String(err),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { kind: "revealed", prizeWon: result.prizeWon };
|
|
108
|
+
}
|
|
109
|
+
if (result.kind === "pool_exhausted") {
|
|
110
|
+
ctx.logger.warn("Prize-draw pool exhausted at assignment time", { orderId, productId });
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JobContext } from "../runtime/types";
|
|
2
|
+
/**
|
|
3
|
+
* "Scheduled" reveal mode, expiry half. Fires once a draw's
|
|
4
|
+
* prizeRevealWindowEnd passes: assigns a winner to every paid, unrevealed
|
|
5
|
+
* order for that draw, then closes it. Any prize items with no corresponding
|
|
6
|
+
* order simply stay unrevealed / with the seller — that's expected on a
|
|
7
|
+
* partial sellout, not an error.
|
|
8
|
+
*
|
|
9
|
+
* The sellout half of "scheduled" mode lives in prizeDrawSoldOutReveal.ts —
|
|
10
|
+
* whichever fires first (expiry here, or a full sellout there) closes the
|
|
11
|
+
* draw; this sweep naturally no-ops afterward since the product is already
|
|
12
|
+
* "closed".
|
|
13
|
+
*/
|
|
14
|
+
export declare function runPrizeDrawExpiryReveal(ctx: JobContext): Promise<void>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
2
|
+
import { PRODUCT_FIELDS, ORDER_FIELDS, COMMON_FIELDS } from "../../../../constants/field-names";
|
|
3
|
+
import { assignPrizeDrawWinner } from "./prizeDrawAssignWinner";
|
|
4
|
+
const PRODUCT_COLLECTION = "products";
|
|
5
|
+
const ORDER_COLLECTION = "orders";
|
|
6
|
+
const PRIZE_DRAW_LISTING_TYPE = "prize-draw";
|
|
7
|
+
/**
|
|
8
|
+
* "Scheduled" reveal mode, expiry half. Fires once a draw's
|
|
9
|
+
* prizeRevealWindowEnd passes: assigns a winner to every paid, unrevealed
|
|
10
|
+
* order for that draw, then closes it. Any prize items with no corresponding
|
|
11
|
+
* order simply stay unrevealed / with the seller — that's expected on a
|
|
12
|
+
* partial sellout, not an error.
|
|
13
|
+
*
|
|
14
|
+
* The sellout half of "scheduled" mode lives in prizeDrawSoldOutReveal.ts —
|
|
15
|
+
* whichever fires first (expiry here, or a full sellout there) closes the
|
|
16
|
+
* draw; this sweep naturally no-ops afterward since the product is already
|
|
17
|
+
* "closed".
|
|
18
|
+
*/
|
|
19
|
+
export async function runPrizeDrawExpiryReveal(ctx) {
|
|
20
|
+
if (ctx.env("FEATURE_PRIZE_DRAWS") !== "true") {
|
|
21
|
+
ctx.logger.info("FEATURE_PRIZE_DRAWS disabled — skipping prize-draw expiry reveal");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
ctx.logger.info("Prize-draw expiry reveal sweep starting");
|
|
25
|
+
const snap = await ctx.db
|
|
26
|
+
.collection(PRODUCT_COLLECTION)
|
|
27
|
+
.where(PRODUCT_FIELDS.LISTING_TYPE, "==", PRIZE_DRAW_LISTING_TYPE)
|
|
28
|
+
.where(PRODUCT_FIELDS.PRIZE_REVEAL_STATUS, "==", PRODUCT_FIELDS.PRIZE_REVEAL_STATUS_VALUES.OPEN)
|
|
29
|
+
.where(PRODUCT_FIELDS.PRIZE_REVEAL_WINDOW_END, "<=", ctx.now)
|
|
30
|
+
.limit(100)
|
|
31
|
+
.get();
|
|
32
|
+
if (snap.empty) {
|
|
33
|
+
ctx.logger.info("No prize draws past expiry");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const doc of snap.docs) {
|
|
37
|
+
const product = doc.data();
|
|
38
|
+
if (product.prizeDrawMode === "lottery")
|
|
39
|
+
continue; // out of scope — separate mechanic
|
|
40
|
+
const orders = await ctx.db
|
|
41
|
+
.collection(ORDER_COLLECTION)
|
|
42
|
+
.where(ORDER_FIELDS.PRIZE_DRAW_PRODUCT_ID, "==", doc.id)
|
|
43
|
+
.where(ORDER_FIELDS.PAYMENT_STATUS, "==", ORDER_FIELDS.PAYMENT_STATUS_VALUES.PAID)
|
|
44
|
+
.where(ORDER_FIELDS.STATUS, "in", [
|
|
45
|
+
ORDER_FIELDS.STATUS_VALUES.PENDING,
|
|
46
|
+
ORDER_FIELDS.STATUS_VALUES.CONFIRMED,
|
|
47
|
+
ORDER_FIELDS.STATUS_VALUES.PROCESSING,
|
|
48
|
+
])
|
|
49
|
+
.get();
|
|
50
|
+
let revealed = 0;
|
|
51
|
+
for (const orderDoc of orders.docs) {
|
|
52
|
+
const order = orderDoc.data();
|
|
53
|
+
if (order.prizeWon)
|
|
54
|
+
continue;
|
|
55
|
+
try {
|
|
56
|
+
const result = await assignPrizeDrawWinner(ctx, {
|
|
57
|
+
orderId: orderDoc.id,
|
|
58
|
+
productId: doc.id,
|
|
59
|
+
});
|
|
60
|
+
if (result.kind === "revealed")
|
|
61
|
+
revealed++;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
void normalizeError(err);
|
|
65
|
+
ctx.logger.error("Expiry reveal failed for order", err, { orderId: orderDoc.id, productId: doc.id });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
await doc.ref.update({
|
|
69
|
+
[PRODUCT_FIELDS.PRIZE_REVEAL_STATUS]: PRODUCT_FIELDS.PRIZE_REVEAL_STATUS_VALUES.CLOSED,
|
|
70
|
+
[COMMON_FIELDS.UPDATED_AT]: ctx.now,
|
|
71
|
+
});
|
|
72
|
+
ctx.logger.info("Closed prize draw at expiry", { productId: doc.id, revealed });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { JobContext } from "../runtime/types";
|
|
2
|
+
export interface PrizeDrawStockSnapshot {
|
|
3
|
+
listingType?: string;
|
|
4
|
+
prizeDrawMode?: string;
|
|
5
|
+
prizeRevealMode?: string;
|
|
6
|
+
prizeRevealStatus?: string;
|
|
7
|
+
prizeMaxEntries?: number;
|
|
8
|
+
prizeCurrentEntries?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface HandlePrizeDrawSoldOutInput {
|
|
11
|
+
productId: string;
|
|
12
|
+
before: PrizeDrawStockSnapshot | null;
|
|
13
|
+
after: PrizeDrawStockSnapshot | null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* "Scheduled" reveal mode, sellout half — the other trigger condition besides
|
|
17
|
+
* expiry (prizeDrawExpiryReveal.ts). Fires the instant a scheduled-mode draw's
|
|
18
|
+
* last entry sells, so buyers don't have to wait for the expiry sweep once
|
|
19
|
+
* every ticket is gone. Assigns a winner to every paid, unrevealed order for
|
|
20
|
+
* the draw, then closes it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function handlePrizeDrawSoldOut(input: HandlePrizeDrawSoldOutInput, ctx: JobContext): Promise<void>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
2
|
+
import { PRODUCT_FIELDS, ORDER_FIELDS, COMMON_FIELDS } from "../../../../constants/field-names";
|
|
3
|
+
import { assignPrizeDrawWinner } from "./prizeDrawAssignWinner";
|
|
4
|
+
const ORDER_COLLECTION = "orders";
|
|
5
|
+
function isSoldOut(doc) {
|
|
6
|
+
if (!doc)
|
|
7
|
+
return false;
|
|
8
|
+
const max = doc.prizeMaxEntries ?? 0;
|
|
9
|
+
if (max <= 0)
|
|
10
|
+
return false;
|
|
11
|
+
return (doc.prizeCurrentEntries ?? 0) >= max;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* "Scheduled" reveal mode, sellout half — the other trigger condition besides
|
|
15
|
+
* expiry (prizeDrawExpiryReveal.ts). Fires the instant a scheduled-mode draw's
|
|
16
|
+
* last entry sells, so buyers don't have to wait for the expiry sweep once
|
|
17
|
+
* every ticket is gone. Assigns a winner to every paid, unrevealed order for
|
|
18
|
+
* the draw, then closes it.
|
|
19
|
+
*/
|
|
20
|
+
export async function handlePrizeDrawSoldOut(input, ctx) {
|
|
21
|
+
if (ctx.env("FEATURE_PRIZE_DRAWS") !== "true")
|
|
22
|
+
return;
|
|
23
|
+
const { productId, before, after } = input;
|
|
24
|
+
if (!after)
|
|
25
|
+
return;
|
|
26
|
+
if (after.listingType !== "prize-draw")
|
|
27
|
+
return;
|
|
28
|
+
if (after.prizeDrawMode === "lottery")
|
|
29
|
+
return; // out of scope — separate mechanic
|
|
30
|
+
if (after.prizeRevealMode !== "scheduled")
|
|
31
|
+
return;
|
|
32
|
+
if (after.prizeRevealStatus !== PRODUCT_FIELDS.PRIZE_REVEAL_STATUS_VALUES.OPEN)
|
|
33
|
+
return;
|
|
34
|
+
// Only act on the crossing transition into sold-out — avoids re-firing on
|
|
35
|
+
// every later write to an already-closed draw.
|
|
36
|
+
if (isSoldOut(before) || !isSoldOut(after))
|
|
37
|
+
return;
|
|
38
|
+
const orders = await ctx.db
|
|
39
|
+
.collection(ORDER_COLLECTION)
|
|
40
|
+
.where(ORDER_FIELDS.PRIZE_DRAW_PRODUCT_ID, "==", productId)
|
|
41
|
+
.where(ORDER_FIELDS.PAYMENT_STATUS, "==", ORDER_FIELDS.PAYMENT_STATUS_VALUES.PAID)
|
|
42
|
+
.where(ORDER_FIELDS.STATUS, "in", [
|
|
43
|
+
ORDER_FIELDS.STATUS_VALUES.PENDING,
|
|
44
|
+
ORDER_FIELDS.STATUS_VALUES.CONFIRMED,
|
|
45
|
+
ORDER_FIELDS.STATUS_VALUES.PROCESSING,
|
|
46
|
+
])
|
|
47
|
+
.get();
|
|
48
|
+
let revealed = 0;
|
|
49
|
+
for (const orderDoc of orders.docs) {
|
|
50
|
+
const order = orderDoc.data();
|
|
51
|
+
if (order.prizeWon)
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
const result = await assignPrizeDrawWinner(ctx, { orderId: orderDoc.id, productId });
|
|
55
|
+
if (result.kind === "revealed")
|
|
56
|
+
revealed++;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
void normalizeError(err);
|
|
60
|
+
ctx.logger.error("Sellout reveal failed for order", err, { orderId: orderDoc.id, productId });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
await ctx.db.collection("products").doc(productId).update({
|
|
64
|
+
[PRODUCT_FIELDS.PRIZE_REVEAL_STATUS]: PRODUCT_FIELDS.PRIZE_REVEAL_STATUS_VALUES.CLOSED,
|
|
65
|
+
[COMMON_FIELDS.UPDATED_AT]: ctx.now,
|
|
66
|
+
});
|
|
67
|
+
ctx.logger.info("Closed prize draw on sellout", { productId, revealed });
|
|
68
|
+
}
|
|
@@ -38,11 +38,10 @@ export { onStoreWriteHandler } from "./onStoreWrite";
|
|
|
38
38
|
export { adminAnalyticsHandler, type AdminAnalyticsResult } from "./adminAnalytics";
|
|
39
39
|
export { storeAnalyticsHandler, type StoreAnalyticsInput, type StoreAnalyticsResult, } from "./storeAnalytics";
|
|
40
40
|
export { listingProcessorHandler, supportedListingCollections, type ListingRequestBody, type ListingResponseBody, } from "./listingProcessor";
|
|
41
|
-
export { prizeRevealOpenHandler } from "./prizeRevealOpen";
|
|
42
|
-
export { prizeRevealCloseHandler } from "./prizeRevealClose";
|
|
43
|
-
export { prizeRevealExpiryHandler } from "./prizeRevealExpiry";
|
|
44
|
-
export { prizeRevealReminderHandler } from "./prizeRevealReminder";
|
|
45
41
|
export { bundleStockSyncHandler } from "./bundleStockSync";
|
|
42
|
+
export { onPrizeDrawPaymentConfirmedHandler } from "./onPrizeDrawPaymentConfirmed";
|
|
43
|
+
export { prizeDrawSoldOutRevealHandler } from "./prizeDrawSoldOutReveal";
|
|
44
|
+
export { prizeDrawExpiryRevealHandler } from "./prizeDrawExpiryReveal";
|
|
46
45
|
export { onProductStockChangeHandler } from "./onProductStockChange";
|
|
47
46
|
export { triggerEventRaffleHandler } from "./triggerEventRaffle";
|
|
48
47
|
export { assignSpinPrizeHandler } from "./assignSpinPrize";
|