@mohasinac/appkit 4.9.5 → 4.11.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/_internal/client/features/layout/DashboardLayoutClient.d.ts +6 -1
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +4 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +1 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +2 -2
- package/dist/_internal/server/features/admin/daily-digest-actions.d.ts +18 -0
- package/dist/_internal/server/features/admin/daily-digest-actions.js +37 -0
- package/dist/_internal/server/features/auctions/actions.js +3 -2
- package/dist/_internal/server/features/auctions/service.d.ts +4 -3
- package/dist/_internal/server/features/auctions/service.js +6 -6
- package/dist/_internal/server/features/audit-log/actions.d.ts +25 -0
- package/dist/_internal/server/features/audit-log/actions.js +38 -0
- package/dist/_internal/server/features/brands/actions.js +0 -2
- package/dist/_internal/server/features/brands/og.d.ts +4 -1
- package/dist/_internal/server/features/brands/og.js +1 -1
- package/dist/_internal/server/features/bundles/data.d.ts +9 -0
- package/dist/_internal/server/features/bundles/data.js +14 -0
- 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/catalogue/og.d.ts +20 -0
- package/dist/_internal/server/features/catalogue/og.js +27 -0
- package/dist/_internal/server/features/grouped/data.d.ts +16 -0
- package/dist/_internal/server/features/grouped/data.js +50 -1
- package/dist/_internal/server/features/grouped/index.d.ts +1 -1
- package/dist/_internal/server/features/grouped/index.js +1 -1
- package/dist/_internal/server/features/orders/actions.js +9 -0
- package/dist/_internal/server/features/orders/adapters.js +25 -0
- package/dist/_internal/server/features/products/data.d.ts +23 -0
- package/dist/_internal/server/features/products/data.js +159 -0
- package/dist/_internal/server/features/products/index.d.ts +1 -1
- package/dist/_internal/server/features/products/index.js +1 -1
- package/dist/_internal/server/functions/scheduled.d.ts +3 -1
- package/dist/_internal/server/functions/scheduled.js +17 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +40 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +164 -0
- package/dist/_internal/server/jobs/core/hardBanCascade.js +11 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +12 -0
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +85 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +16 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.js +4 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/index.js +3 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +2 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +27 -0
- package/dist/_internal/server/notifications/channel-health.js +101 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +20 -0
- package/dist/_internal/shared/features/auctions/config.js +31 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +0 -6
- package/dist/_internal/shared/features/brands/schema.js +0 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +7 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.js +3 -0
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/art/config.js +1 -1
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/classified/config.js +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/live/config.js +1 -1
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/stickers/config.js +1 -1
- package/dist/client.d.ts +21 -12
- package/dist/client.js +14 -7
- package/dist/constants/api-endpoints.d.ts +9 -9
- package/dist/constants/api-endpoints.js +3 -3
- package/dist/contracts/client-auth.d.ts +3 -7
- package/dist/contracts/email.d.ts +1 -0
- package/dist/features/account/components/AddressForm.js +24 -2
- package/dist/features/account/components/BecomeSellerView.d.ts +1 -2
- package/dist/features/account/components/BecomeSellerView.js +1 -1
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/components/UserBidsView.d.ts +1 -0
- package/dist/features/account/components/UserBidsView.js +47 -0
- package/dist/features/account/components/UserOrdersView.d.ts +4 -11
- package/dist/features/account/components/UserOrdersView.js +54 -4
- package/dist/features/account/components/UserReturnsView.d.ts +4 -9
- package/dist/features/account/components/UserReturnsView.js +37 -3
- package/dist/features/account/components/index.d.ts +3 -2
- package/dist/features/account/components/index.js +1 -0
- package/dist/features/admin/actions/admin-actions.js +22 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +10 -0
- package/dist/features/admin/actions/notification-actions.js +4 -3
- package/dist/features/admin/components/AdminAdsView.d.ts +2 -3
- package/dist/features/admin/components/AdminAdsView.js +86 -89
- package/dist/features/admin/components/AdminAllEventEntriesView.js +18 -3
- package/dist/features/admin/components/AdminAuditLogView.d.ts +11 -0
- package/dist/features/admin/components/AdminAuditLogView.js +95 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +37 -3
- package/dist/features/admin/components/AdminBrandEditorView.js +10 -6
- package/dist/features/admin/components/AdminBundleEditorView.js +18 -2
- package/dist/features/admin/components/AdminCarouselEditorView.js +2 -2
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +2 -2
- package/dist/features/admin/components/AdminCartsView.js +34 -4
- package/dist/features/admin/components/AdminCategoryEditorView.js +5 -4
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
- package/dist/features/admin/components/AdminHistoryView.js +11 -1
- package/dist/features/admin/components/AdminNotificationsView.js +27 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +10 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
- package/dist/features/admin/components/AdminOrdersView.js +92 -24
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.d.ts +7 -0
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +47 -0
- package/dist/features/admin/components/AdminPayoutsView.js +15 -31
- package/dist/features/admin/components/AdminProductEditorView.js +26 -3
- package/dist/features/admin/components/AdminSiteSettingsView.js +31 -4
- package/dist/features/admin/components/AdminStoreAddressesView.js +22 -2
- package/dist/features/admin/components/AdminStoresView.js +7 -0
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -2
- package/dist/features/admin/components/AdminUserEditorView.d.ts +2 -2
- package/dist/features/admin/components/AdminUserEditorView.js +4 -2
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/AdminWishlistsView.js +14 -1
- package/dist/features/admin/components/DataListingView.d.ts +2 -2
- package/dist/features/admin/components/ViewAuditLogEntryModal.d.ts +19 -0
- package/dist/features/admin/components/ViewAuditLogEntryModal.js +22 -0
- package/dist/features/admin/components/ViewNotificationModal.d.ts +21 -0
- package/dist/features/admin/components/ViewNotificationModal.js +15 -0
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +4 -7
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +56 -36
- package/dist/features/admin/components/index.d.ts +8 -1
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +49 -0
- package/dist/features/admin/constants/filter-tabs.js +27 -0
- package/dist/features/admin/hooks/useAdminListing.d.ts +6 -0
- package/dist/features/admin/hooks/useAdminListing.js +3 -1
- package/dist/features/admin/hooks/useAdminListingData.d.ts +9 -1
- package/dist/features/admin/hooks/useAdminListingData.js +9 -2
- package/dist/features/admin/repository/site-settings.repository.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.d.ts +21 -0
- package/dist/features/admin/schemas/firestore.js +6 -0
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +8 -0
- package/dist/features/auctions/actions/bid-actions.js +4 -1
- package/dist/features/auctions/components/AuctionBottomActions.d.ts +2 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +14 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +21 -9
- package/dist/features/auctions/components/LiveMinIncrement.d.ts +18 -0
- package/dist/features/auctions/components/LiveMinIncrement.js +17 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +5 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +7 -3
- package/dist/features/auctions/schemas/index.d.ts +5 -0
- package/dist/features/audit-log/repository/audit-log.repository.d.ts +45 -0
- package/dist/features/audit-log/repository/audit-log.repository.js +30 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +59 -0
- package/dist/features/audit-log/schemas/firestore.js +35 -0
- package/dist/features/auth/actions/index.d.ts +0 -1
- package/dist/features/auth/actions/index.js +0 -1
- package/dist/features/auth/hooks/useAuth.d.ts +8 -35
- package/dist/features/auth/hooks/useAuth.js +21 -37
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +2 -0
- package/dist/features/blog/schemas/index.d.ts +32 -0
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +31 -3
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.d.ts +11 -0
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +33 -0
- package/dist/features/catalogue/components/PublicCatalogueView.js +2 -1
- package/dist/features/catalogue/index.d.ts +2 -0
- package/dist/features/catalogue/index.js +1 -0
- package/dist/features/categories/components/BrandDetailPageView.js +14 -12
- package/dist/features/categories/components/BundleDetailView.d.ts +3 -1
- package/dist/features/categories/components/BundleDetailView.js +3 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +7 -3
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.d.ts +14 -0
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.js +22 -0
- package/dist/features/categories/components/CategoryProductsView.d.ts +1 -5
- package/dist/features/categories/components/CategoryProductsView.js +1 -1
- package/dist/features/categories/components/CategorySelectorCreate.js +16 -2
- package/dist/features/categories/schemas/firestore.d.ts +19 -2
- package/dist/features/categories/schemas/index.d.ts +24 -3
- package/dist/features/categories/schemas/index.js +3 -1
- package/dist/features/categories/types/index.d.ts +7 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +17 -3
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +101 -22
- package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +12 -0
- package/dist/features/classified/components/ClassifiedContactSellerPanel.js +49 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +7 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.js +69 -0
- package/dist/features/contact/components/ContactForm.js +17 -4
- package/dist/features/contact/components/ContactPageView.js +8 -2
- package/dist/features/contact/email.d.ts +1 -0
- package/dist/features/contact/email.js +3 -0
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.d.ts +19 -0
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +79 -0
- package/dist/features/events/components/AdminEventEditorView.js +54 -2
- package/dist/features/events/components/AdminEventEntriesView.d.ts +2 -16
- package/dist/features/events/components/AdminEventEntriesView.js +56 -89
- package/dist/features/events/components/EventFormDrawer.js +5 -1
- package/dist/features/events/components/EventRaffleEntryForm.d.ts +1 -3
- package/dist/features/events/components/EventRaffleEntryForm.js +1 -1
- package/dist/features/events/schemas/index.d.ts +22 -0
- package/dist/features/grouped/components/GroupedListingsCarousel.d.ts +13 -0
- package/dist/features/grouped/components/GroupedListingsCarousel.js +31 -0
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +12 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +34 -0
- package/dist/features/homepage/components/CharacterHotspotForm.js +202 -187
- package/dist/features/homepage/components/SectionCarousel.d.ts +1 -7
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/layout/BottomActions.js +4 -3
- package/dist/features/layout/BottomActionsContext.d.ts +10 -0
- package/dist/features/layout/BottomActionsContext.js +5 -1
- package/dist/features/layout/TitleBarLayout.d.ts +1 -1
- package/dist/features/layout/TitleBarLayout.js +3 -3
- package/dist/features/layout/hooks/useBottomActions.d.ts +2 -0
- package/dist/features/layout/hooks/useBottomActions.js +4 -1
- package/dist/features/live/components/LiveItemDetailPageView.d.ts +9 -0
- package/dist/features/live/components/LiveItemDetailPageView.js +74 -0
- package/dist/features/media/MediaImage.js +39 -3
- package/dist/features/media/MediaVideo.js +17 -1
- package/dist/features/media/types/index.d.ts +10 -0
- package/dist/features/media/types/index.js +1 -0
- package/dist/features/media/upload/MediaUploadField.d.ts +7 -1
- package/dist/features/media/upload/MediaUploadField.js +11 -7
- package/dist/features/media/upload/video-poster.d.ts +2 -0
- package/dist/features/media/upload/video-poster.js +16 -1
- package/dist/features/media/upload/video-poster.test.js +1 -1
- package/dist/features/orders/components/OrderStatusTimeline.d.ts +12 -0
- package/dist/features/orders/components/OrderStatusTimeline.js +55 -0
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/constants/payment-window.d.ts +25 -0
- package/dist/features/orders/constants/payment-window.js +25 -0
- package/dist/features/orders/repository/orders.repository.d.ts +32 -0
- package/dist/features/orders/repository/orders.repository.js +56 -0
- package/dist/features/orders/schemas/index.d.ts +24 -24
- package/dist/features/orders/types/index.d.ts +33 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +9 -1
- package/dist/features/pre-orders/schemas/index.d.ts +5 -0
- package/dist/features/products/components/BidHistory.js +9 -1
- package/dist/features/products/components/GroupSettingsPanel.js +25 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +11 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -4
- package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
- package/dist/features/products/components/ProductDetailActions.d.ts +1 -2
- package/dist/features/products/components/ProductDetailActions.js +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +11 -116
- package/dist/features/products/components/ProductGrid.js +15 -1
- package/dist/features/products/components/RelatedItemsSection.d.ts +16 -0
- package/dist/features/products/components/RelatedItemsSection.js +17 -0
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/schemas/index.d.ts +5 -0
- package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -3
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/search/components/SearchView.d.ts +1 -3
- package/dist/features/search/components/SearchView.js +1 -1
- package/dist/features/seller/components/QuickProductForm.js +2 -1
- package/dist/features/seller/components/SellerBidsView.js +66 -107
- package/dist/features/seller/components/SellerGoogleReviewsView.js +17 -1
- package/dist/features/seller/components/SellerOrdersView.d.ts +15 -3
- package/dist/features/seller/components/SellerOrdersView.js +134 -163
- package/dist/features/seller/components/SellerPayoutMethodsView.js +60 -48
- package/dist/features/seller/components/SellerPayoutSettingsView.js +69 -4
- package/dist/features/seller/components/SellerPayoutsView.d.ts +12 -0
- package/dist/features/seller/components/SellerPayoutsView.js +25 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +10 -3
- package/dist/features/seller/components/SellerProductShell.js +90 -37
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +2 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +4 -4
- package/dist/features/seller/components/SellerProductsView.d.ts +2 -4
- package/dist/features/seller/components/SellerProductsView.js +131 -189
- package/dist/features/seller/components/SellerReviewsView.js +95 -133
- package/dist/features/seller/components/SellerShippingConfigsView.js +47 -67
- package/dist/features/seller/components/SellerShippingView.js +41 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +51 -59
- package/dist/features/seller/components/SellerStorefrontView.d.ts +6 -1
- package/dist/features/seller/components/SellerStorefrontView.js +42 -5
- package/dist/features/seller/components/index.d.ts +2 -2
- package/dist/features/seller/components/index.js +2 -2
- package/dist/features/shell/FormShell.d.ts +24 -4
- package/dist/features/shell/FormShell.js +12 -6
- package/dist/features/shell/QuickFormDrawer.js +67 -12
- package/dist/features/shell/StepForm.d.ts +30 -1
- package/dist/features/shell/StepForm.js +47 -4
- package/dist/features/shell/index.d.ts +1 -1
- package/dist/features/shell/index.js +1 -1
- package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -2
- package/dist/features/shipments/schemas/validation.d.ts +14 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +23 -2
- package/dist/features/tester/components/TesterChecklistStepRow.js +1 -1
- package/dist/features/tester/components/TesterHubView.js +3 -2
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +9 -1
- package/dist/features/tester/seed-data/products-tester-seed-data.js +43 -32
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +487 -18
- package/dist/index.d.ts +17 -3
- package/dist/index.js +23 -5
- package/dist/next/routing/route-map.d.ts +13 -0
- package/dist/next/routing/route-map.js +12 -0
- package/dist/providers/email-resend/provider.js +1 -0
- package/dist/providers/firebase-client/auth.d.ts +1 -0
- package/dist/providers/firebase-client/auth.js +7 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +61 -25
- package/dist/security/pii-mask.js +1 -1
- package/dist/seed/bids-seed-data.js +48 -3
- package/dist/seed/categories-seed-data.js +69 -0
- package/dist/seed/grouped-listings-seed-data.js +167 -0
- package/dist/seed/payouts-seed-data.js +4 -4
- package/dist/seed/products-art-seed-data.js +188 -5
- package/dist/seed/products-auctions-seed-data.js +226 -6
- package/dist/seed/products-classifieds-seed-data.js +255 -8
- package/dist/seed/products-digital-codes-seed-data.js +258 -19
- package/dist/seed/products-live-items-seed-data.js +143 -10
- package/dist/seed/products-preorders-seed-data.js +213 -8
- package/dist/seed/products-prize-draws-seed-data.js +198 -0
- package/dist/seed/products-standard-seed-data.js +234 -4
- package/dist/seed/products-stickers-seed-data.js +178 -5
- package/dist/seed/site-settings-seed-data.js +23 -30
- package/dist/seed/store-extensions-seed-data.js +2 -2
- package/dist/server-entry.d.ts +6 -2
- package/dist/server-entry.js +24 -3
- package/dist/server.d.ts +10 -3
- package/dist/server.js +11 -4
- package/dist/styles.css +29 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Form.d.ts +11 -2
- package/dist/ui/components/Form.js +4 -3
- package/dist/ui/components/FormField.d.ts +3 -1
- package/dist/ui/components/FormField.js +22 -4
- package/dist/ui/components/ImageLightbox.js +37 -3
- package/dist/ui/components/ImageLightbox.style.css +28 -0
- package/dist/ui/components/ListingLayout.d.ts +1 -2
- package/dist/ui/components/ListingLayout.js +1 -1
- package/dist/ui/components/RecordDetailModal.d.ts +68 -0
- package/dist/ui/components/RecordDetailModal.js +8 -0
- package/dist/ui/components/SlottedListingView.js +9 -1
- package/dist/ui/forms/FormErrorSummary.d.ts +25 -0
- package/dist/ui/forms/FormErrorSummary.js +37 -0
- package/dist/ui/forms/FormShell.d.ts +37 -2
- package/dist/ui/forms/FormShell.js +37 -7
- package/dist/ui/forms/FormShell.style.css +17 -0
- package/dist/ui/forms/index.d.ts +2 -0
- package/dist/ui/forms/index.js +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +5 -0
- package/dist/utils/media-url.d.ts +12 -1
- package/dist/utils/media-url.js +59 -1
- package/dist/validation/schemas.d.ts +4 -4
- package/package.json +1 -1
- package/dist/_internal/client/features/classified/ClassifiedDetailView.d.ts +0 -9
- package/dist/_internal/client/features/classified/ClassifiedDetailView.js +0 -55
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.d.ts +0 -18
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +0 -21
- package/dist/_internal/client/features/live/LiveItemDetailView.d.ts +0 -9
- package/dist/_internal/client/features/live/LiveItemDetailView.js +0 -25
- package/dist/_internal/server/jobs/core/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealClose.js +0 -28
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +0 -61
- package/dist/_internal/server/jobs/core/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealOpen.js +0 -68
- package/dist/_internal/server/jobs/core/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealReminder.js +0 -49
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +0 -2
- package/dist/features/auth/consent-otp.d.ts +0 -57
- package/dist/features/auth/consent-otp.js +0 -127
- package/dist/features/cart/components/CheckoutOtpModal.d.ts +0 -15
- package/dist/features/cart/components/CheckoutOtpModal.js +0 -12
- package/dist/features/checkout/actions/checkout-actions.d.ts +0 -31
- package/dist/features/checkout/actions/checkout-actions.js +0 -124
- package/dist/features/classified/components/ClassifiedIndexListing.d.ts +0 -5
- package/dist/features/classified/components/ClassifiedIndexListing.js +0 -123
- package/dist/features/classified/components/ClassifiedListView.d.ts +0 -6
- package/dist/features/classified/components/ClassifiedListView.js +0 -44
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +0 -5
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +0 -114
- package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +0 -6
- package/dist/features/digital-codes/components/DigitalCodesListView.js +0 -38
- package/dist/features/live/components/LiveItemsIndexListing.d.ts +0 -5
- package/dist/features/live/components/LiveItemsIndexListing.js +0 -115
- package/dist/features/live/components/LiveItemsListView.d.ts +0 -6
- package/dist/features/live/components/LiveItemsListView.js +0 -44
- package/dist/features/seller/components/SellerTemplatesView.d.ts +0 -12
- package/dist/features/seller/components/SellerTemplatesView.js +0 -276
- package/dist/react/hooks/useVisibleItems.d.ts +0 -37
- package/dist/react/hooks/useVisibleItems.js +0 -59
- package/dist/ui/components/TabStrip.d.ts +0 -34
- package/dist/ui/components/TabStrip.js +0 -56
- package/dist/ui/components/TabStrip.style.css +0 -97
|
@@ -7,6 +7,7 @@ import { serverLogger } from "../../../../monitoring/server-logger";
|
|
|
7
7
|
import { PRODUCT_COLLECTION } from "../../../../features/products/schemas/firestore";
|
|
8
8
|
import { PRODUCTS_SITEMAP_LIMIT } from "../../../shared/features/products/config";
|
|
9
9
|
import { PRODUCT_FIELDS } from "../../../../constants/field-names";
|
|
10
|
+
import { SIEVE_OP, sieveAnd, sieveFilter, sortBy } from "../../../../utils/sieve-builder";
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Request-scoped cache — each function is deduplicated per RSC render tree.
|
|
12
13
|
// Both generateMetadata() and the page component get the same promise.
|
|
@@ -23,6 +24,38 @@ export const getProductForDetail = cache(async (slugOrId) => {
|
|
|
23
24
|
export const getReviewsForProduct = cache(async (productId) => {
|
|
24
25
|
return reviewRepository.findApprovedByProduct(productId).catch(() => []);
|
|
25
26
|
});
|
|
27
|
+
/** Firestore review doc → client Review shape. Shared by every listing-type detail page's reviews tab. */
|
|
28
|
+
export function toReview(doc) {
|
|
29
|
+
const images = Array.isArray(doc.images) ? doc.images : undefined;
|
|
30
|
+
const createdAt = doc.createdAt instanceof Date
|
|
31
|
+
? doc.createdAt.toISOString()
|
|
32
|
+
: typeof doc.createdAt === "string"
|
|
33
|
+
? doc.createdAt
|
|
34
|
+
: undefined;
|
|
35
|
+
const rawRating = typeof doc.rating === "number" ? doc.rating : 3;
|
|
36
|
+
const rating = Math.min(5, Math.max(1, Math.round(rawRating)));
|
|
37
|
+
return {
|
|
38
|
+
id: String(doc.id ?? ""),
|
|
39
|
+
productId: String(doc.productId ?? ""),
|
|
40
|
+
userId: String(doc.userId ?? ""),
|
|
41
|
+
userName: String(doc.userName ?? "Anonymous"),
|
|
42
|
+
userAvatar: typeof doc.userAvatar === "string" ? doc.userAvatar : undefined,
|
|
43
|
+
rating,
|
|
44
|
+
title: typeof doc.title === "string" ? doc.title : undefined,
|
|
45
|
+
comment: typeof doc.comment === "string" ? doc.comment : undefined,
|
|
46
|
+
images,
|
|
47
|
+
status: doc.status ?? "approved",
|
|
48
|
+
helpfulCount: typeof doc.helpfulCount === "number" ? doc.helpfulCount : undefined,
|
|
49
|
+
verified: doc.verified === true,
|
|
50
|
+
featured: doc.featured === true,
|
|
51
|
+
createdAt,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Fetch + map approved reviews for a product straight to the client Review shape, deduped per request. */
|
|
55
|
+
export const getReviewItemsForProduct = cache(async (productId) => {
|
|
56
|
+
const docs = await reviewRepository.findApprovedByProduct(productId).catch(() => []);
|
|
57
|
+
return docs.map(toReview);
|
|
58
|
+
});
|
|
26
59
|
/** List all published products for sitemap generation. */
|
|
27
60
|
export async function listSitemapProducts() {
|
|
28
61
|
try {
|
|
@@ -54,3 +87,129 @@ export async function listSitemapProducts() {
|
|
|
54
87
|
return [];
|
|
55
88
|
}
|
|
56
89
|
}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Related items — same 4-signal computation shared by every listing-type
|
|
92
|
+
// detail page (category / brand / tag-overlap / same-store).
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
const RELATED_QUERY_PAGE_SIZE = 9;
|
|
95
|
+
const RELATED_DISPLAY_LIMIT = 8;
|
|
96
|
+
/** Firestore doc → card-grid shape. Shared by every related/similar-items carousel. */
|
|
97
|
+
export function toProductItem(doc) {
|
|
98
|
+
return {
|
|
99
|
+
id: String(doc.id ?? ""),
|
|
100
|
+
title: String(doc.title ?? doc.name ?? ""),
|
|
101
|
+
price: typeof doc.price === "number" ? doc.price : 0,
|
|
102
|
+
originalPrice: typeof doc.originalPrice === "number" ? doc.originalPrice : undefined,
|
|
103
|
+
mainImage: Array.isArray(doc.images)
|
|
104
|
+
? doc.images[0]
|
|
105
|
+
: typeof doc.mainImage === "string"
|
|
106
|
+
? doc.mainImage
|
|
107
|
+
: undefined,
|
|
108
|
+
status: doc.status ?? "published",
|
|
109
|
+
slug: typeof doc.slug === "string" ? doc.slug : undefined,
|
|
110
|
+
storeName: typeof doc.storeName === "string" ? doc.storeName : undefined,
|
|
111
|
+
rating: typeof doc.rating === "number" ? doc.rating : undefined,
|
|
112
|
+
reviewCount: typeof doc.reviewCount === "number" ? doc.reviewCount : undefined,
|
|
113
|
+
// Without this, every related-item card silently linked to the standard
|
|
114
|
+
// PDP regardless of its real type (pluginFor() defaults to "standard"
|
|
115
|
+
// when listingType is missing) — same bug class as the Phase 1 routing
|
|
116
|
+
// fix, one hop further downstream in the related-items card link path.
|
|
117
|
+
listingType: doc.listingType ?? undefined,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Excludes the current product plus any listing-type-specific "invalid to
|
|
122
|
+
* surface as related" state: sold/archived/out-of-stock/draft, ended
|
|
123
|
+
* auctions, closed prize-draws, sold-out pre-orders, depleted digital-code
|
|
124
|
+
* pools.
|
|
125
|
+
*/
|
|
126
|
+
function isValidRelatedItem(r, currentProductId, now) {
|
|
127
|
+
if (r.id === currentProductId)
|
|
128
|
+
return false;
|
|
129
|
+
const s = r.status;
|
|
130
|
+
if (s && ["sold", "out_of_stock", "archived", "discontinued", "draft"].includes(s))
|
|
131
|
+
return false;
|
|
132
|
+
if (r.isSold === true)
|
|
133
|
+
return false;
|
|
134
|
+
if (r.availableQuantity === 0)
|
|
135
|
+
return false;
|
|
136
|
+
if (r.listingType === "auction" && r.auctionEndDate) {
|
|
137
|
+
const end = r.auctionEndDate;
|
|
138
|
+
const endDate = typeof end.toDate === "function"
|
|
139
|
+
? end.toDate()
|
|
140
|
+
: end instanceof Date
|
|
141
|
+
? end
|
|
142
|
+
: new Date(String(end));
|
|
143
|
+
if (endDate <= now)
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
if (r.listingType === "prize-draw" && r.prizeRevealStatus === "closed")
|
|
147
|
+
return false;
|
|
148
|
+
if (r.listingType === "pre-order" &&
|
|
149
|
+
typeof r.preOrderMaxQuantity === "number" &&
|
|
150
|
+
typeof r.preOrderCurrentCount === "number" &&
|
|
151
|
+
r.preOrderCurrentCount >= r.preOrderMaxQuantity) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (r.listingType === "digital-code" && r.codesAvailable === 0)
|
|
155
|
+
return false;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
function toRelatedItems(docs, currentProductId) {
|
|
159
|
+
const now = new Date();
|
|
160
|
+
return docs
|
|
161
|
+
.filter((r) => isValidRelatedItem(r, currentProductId, now))
|
|
162
|
+
.slice(0, RELATED_DISPLAY_LIMIT)
|
|
163
|
+
.map(toProductItem);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Same 4-signal related-items computation used across every listing-type
|
|
167
|
+
* detail page (category / brand / tag-overlap / same-store), each excluding
|
|
168
|
+
* invalid states for that item's own listing type. Extracted from
|
|
169
|
+
* ProductDetailPageView so pre-order/prize-draw/classified/digital-code/live
|
|
170
|
+
* pages can reuse it instead of having no related section at all.
|
|
171
|
+
*/
|
|
172
|
+
export const computeRelatedItems = cache(async (product) => {
|
|
173
|
+
const p = product;
|
|
174
|
+
const primaryCategorySlug = Array.isArray(p.categorySlugs) ? p.categorySlugs[0] : undefined;
|
|
175
|
+
const brand = typeof p.brand === "string" ? p.brand : undefined;
|
|
176
|
+
const tags = Array.isArray(p.tags) ? p.tags : [];
|
|
177
|
+
const storeId = typeof p.storeId === "string" ? p.storeId : undefined;
|
|
178
|
+
const currentProductId = product.id;
|
|
179
|
+
const [sameCategoryDocs, sameBrandDocs, tagOverlapDocs, sameStoreDocs] = await Promise.all([
|
|
180
|
+
primaryCategorySlug
|
|
181
|
+
? productRepository.findByCategory(primaryCategorySlug).catch(() => [])
|
|
182
|
+
: Promise.resolve([]),
|
|
183
|
+
brand
|
|
184
|
+
? productRepository
|
|
185
|
+
.list({
|
|
186
|
+
filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("brand", SIEVE_OP.EQ, brand)),
|
|
187
|
+
sorts: sortBy("createdAt", "DESC"),
|
|
188
|
+
page: 1,
|
|
189
|
+
pageSize: RELATED_QUERY_PAGE_SIZE,
|
|
190
|
+
})
|
|
191
|
+
.then((r) => r.items)
|
|
192
|
+
.catch(() => [])
|
|
193
|
+
: Promise.resolve([]),
|
|
194
|
+
tags.length > 0
|
|
195
|
+
? productRepository.findByTagsOverlap(tags, RELATED_QUERY_PAGE_SIZE).catch(() => [])
|
|
196
|
+
: Promise.resolve([]),
|
|
197
|
+
storeId
|
|
198
|
+
? productRepository
|
|
199
|
+
.list({
|
|
200
|
+
filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("storeId", SIEVE_OP.EQ, storeId)),
|
|
201
|
+
sorts: sortBy("createdAt", "DESC"),
|
|
202
|
+
page: 1,
|
|
203
|
+
pageSize: RELATED_QUERY_PAGE_SIZE,
|
|
204
|
+
})
|
|
205
|
+
.then((r) => r.items)
|
|
206
|
+
.catch(() => [])
|
|
207
|
+
: Promise.resolve([]),
|
|
208
|
+
]);
|
|
209
|
+
return {
|
|
210
|
+
relatedItems: toRelatedItems(sameCategoryDocs, currentProductId),
|
|
211
|
+
relatedByBrand: toRelatedItems(sameBrandDocs, currentProductId),
|
|
212
|
+
relatedByTags: toRelatedItems(tagOverlapDocs, currentProductId),
|
|
213
|
+
relatedByStore: toRelatedItems(sameStoreDocs, currentProductId),
|
|
214
|
+
};
|
|
215
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { getProductForDetail, getReviewsForProduct, listSitemapProducts, type SitemapProduct, } from "./data";
|
|
1
|
+
export { getProductForDetail, getReviewsForProduct, listSitemapProducts, type SitemapProduct, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, type RelatedItemsResult, } from "./data";
|
|
2
2
|
export { assertProductOwnership, assertStatusTransition, assertInStock, effectivePrice, isAvailableForPurchase, } from "./service";
|
|
3
3
|
export { createProductAction, createAuctionAction, createPreOrderAction, updateProductAction, deleteProductAction, setProductStatusAction, setProductFeaturedAction, } from "./actions";
|
|
4
4
|
export { PRODUCTS_PAGE_SIZE, PRODUCTS_FEATURED_LIMIT, PRODUCTS_RELATED_LIMIT, PRODUCTS_SITEMAP_LIMIT } from "../../../shared/features/products/config";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { getProductForDetail, getReviewsForProduct, listSitemapProducts, } from "./data";
|
|
1
|
+
export { getProductForDetail, getReviewsForProduct, listSitemapProducts, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, } from "./data";
|
|
2
2
|
export { assertProductOwnership, assertStatusTransition, assertInStock, effectivePrice, isAvailableForPurchase, } from "./service";
|
|
3
3
|
export { createProductAction, createAuctionAction, createPreOrderAction, updateProductAction, deleteProductAction, setProductStatusAction, setProductFeaturedAction, } from "./actions";
|
|
4
4
|
export { PRODUCTS_PAGE_SIZE, PRODUCTS_FEATURED_LIMIT, PRODUCTS_RELATED_LIMIT, PRODUCTS_SITEMAP_LIMIT } from "../../../shared/features/products/config";
|
|
@@ -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 dailyStatusDigest: import("./types").ScheduledFunctionDefinition;
|
|
10
11
|
export declare const dailyDataCleanup: import("./types").ScheduledFunctionDefinition;
|
|
11
12
|
export declare const countersReconcile: import("./types").ScheduledFunctionDefinition;
|
|
12
13
|
export declare const positionsReconcile: import("./types").ScheduledFunctionDefinition;
|
|
@@ -19,8 +20,9 @@ export declare const cleanupRtdbEvents: import("./types").ScheduledFunctionDefin
|
|
|
19
20
|
export declare const mediaTmpCleanup: import("./types").ScheduledFunctionDefinition;
|
|
20
21
|
export declare const draftPrune: import("./types").ScheduledFunctionDefinition;
|
|
21
22
|
export declare const testerSandboxCleanup: import("./types").ScheduledFunctionDefinition;
|
|
23
|
+
export declare const testerSandboxRefresh: import("./types").ScheduledFunctionDefinition;
|
|
22
24
|
export declare const prizeDrawExpiryReveal: import("./types").ScheduledFunctionDefinition;
|
|
23
25
|
export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
|
|
24
26
|
export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
|
|
25
27
|
export declare const catalogueImageStalenessReminder: 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];
|
|
28
|
+
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];
|
|
@@ -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, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, } from "../jobs/handlers";
|
|
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, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, testerSandboxRefreshHandler, } from "../jobs/handlers";
|
|
4
4
|
import { defineFunction } from "./define";
|
|
5
5
|
const REGION = "asia-south1";
|
|
6
6
|
const EVERY_5_MIN = "every 5 minutes";
|
|
@@ -67,6 +67,13 @@ export const revenueRollup = defineFunction({
|
|
|
67
67
|
handler: revenueRollupHandler,
|
|
68
68
|
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
69
69
|
});
|
|
70
|
+
export const dailyStatusDigest = defineFunction({
|
|
71
|
+
name: "dailyStatusDigest",
|
|
72
|
+
description: "Email the previous 24h order summary + platform status to the team every morning at 10:00 IST.",
|
|
73
|
+
trigger: { kind: "schedule", cron: "0 10 * * *", timeZone: "Asia/Kolkata" },
|
|
74
|
+
handler: dailyStatusDigestHandler,
|
|
75
|
+
options: { region: REGION, timeoutSeconds: 120, memory: "256MiB", maxInstances: 1 },
|
|
76
|
+
});
|
|
70
77
|
export const dailyDataCleanup = defineFunction({
|
|
71
78
|
name: "dailyDataCleanup",
|
|
72
79
|
description: "Daily data cleanup (drafts, transient records) at 02:00 UTC.",
|
|
@@ -151,6 +158,13 @@ export const testerSandboxCleanup = defineFunction({
|
|
|
151
158
|
handler: testerSandboxCleanupHandler,
|
|
152
159
|
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
153
160
|
});
|
|
161
|
+
export const testerSandboxRefresh = defineFunction({
|
|
162
|
+
name: "testerSandboxRefresh",
|
|
163
|
+
description: "Revert live tester QA sandbox fixtures to their canonical seed shape and prune tester-created extras (every 4 hours).",
|
|
164
|
+
trigger: { kind: "schedule", cron: "0 */4 * * *", timeZone: "UTC" },
|
|
165
|
+
handler: testerSandboxRefreshHandler,
|
|
166
|
+
options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
|
|
167
|
+
});
|
|
154
168
|
export const prizeDrawExpiryReveal = defineFunction({
|
|
155
169
|
name: "prizeDrawExpiryReveal",
|
|
156
170
|
description: "Assign winners for outstanding paid orders on scheduled-mode prize draws past their expiry, then close them (every 5 minutes).",
|
|
@@ -189,6 +203,7 @@ export const SCHEDULED_FUNCTIONS = [
|
|
|
189
203
|
offerExpiry,
|
|
190
204
|
productStatsSync,
|
|
191
205
|
revenueRollup,
|
|
206
|
+
dailyStatusDigest,
|
|
192
207
|
dailyDataCleanup,
|
|
193
208
|
countersReconcile,
|
|
194
209
|
positionsReconcile,
|
|
@@ -201,6 +216,7 @@ export const SCHEDULED_FUNCTIONS = [
|
|
|
201
216
|
mediaTmpCleanup,
|
|
202
217
|
draftPrune,
|
|
203
218
|
testerSandboxCleanup,
|
|
219
|
+
testerSandboxRefresh,
|
|
204
220
|
prizeDrawExpiryReveal,
|
|
205
221
|
bundleStockSync,
|
|
206
222
|
emiInstallmentReminder,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core: daily status digest email.
|
|
3
|
+
*
|
|
4
|
+
* Emails the previous 24h of order activity to the team every morning. The
|
|
5
|
+
* digest's arrival is itself the platform health signal — if it stops
|
|
6
|
+
* landing, something in the Firestore → Functions → Resend chain needs a
|
|
7
|
+
* look. Deliberately no separate error/exception tracking: there is no
|
|
8
|
+
* structured error-log collection to query, and building one just to fill
|
|
9
|
+
* this email would be scope no one asked for.
|
|
10
|
+
*
|
|
11
|
+
* Runs from a scheduled Firebase Function (not a Vercel route), so the
|
|
12
|
+
* unbounded-ish order scan below is safe against CLAUDE.md Rule #6's 10s
|
|
13
|
+
* sync ceiling — it has the Function's much larger budget.
|
|
14
|
+
*/
|
|
15
|
+
import type { JobContext } from "../runtime/types";
|
|
16
|
+
export interface DailyStatusDigestSummary {
|
|
17
|
+
orderCount: number;
|
|
18
|
+
revenue: number;
|
|
19
|
+
statusBreakdown: Record<string, number>;
|
|
20
|
+
stuckPendingCount: number;
|
|
21
|
+
activeListingCount: number;
|
|
22
|
+
}
|
|
23
|
+
/** Gathers the last-24h numbers. Exported so the admin manual-trigger route can reuse it. */
|
|
24
|
+
export declare function collectDailyStatusSummary(): Promise<DailyStatusDigestSummary>;
|
|
25
|
+
export interface DailyStatusDigestResult {
|
|
26
|
+
sent: boolean;
|
|
27
|
+
reason?: string;
|
|
28
|
+
summary?: DailyStatusDigestSummary;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Sends the digest once per deployment version.
|
|
32
|
+
*
|
|
33
|
+
* Called from server startup. On Vercel that means EVERY lambda cold start,
|
|
34
|
+
* not once per deploy — so the version marker below is what makes this a
|
|
35
|
+
* per-deployment event rather than a per-cold-start email storm. The claim
|
|
36
|
+
* runs in a transaction because several cold starts can race on the first
|
|
37
|
+
* request after a deploy; exactly one wins and sends.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runDeploymentDigest(ctx: JobContext, version: string): Promise<DailyStatusDigestResult>;
|
|
40
|
+
export declare function runDailyStatusDigest(ctx: JobContext, version?: string): Promise<DailyStatusDigestResult>;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Core: daily status digest email.
|
|
4
|
+
*
|
|
5
|
+
* Emails the previous 24h of order activity to the team every morning. The
|
|
6
|
+
* digest's arrival is itself the platform health signal — if it stops
|
|
7
|
+
* landing, something in the Firestore → Functions → Resend chain needs a
|
|
8
|
+
* look. Deliberately no separate error/exception tracking: there is no
|
|
9
|
+
* structured error-log collection to query, and building one just to fill
|
|
10
|
+
* this email would be scope no one asked for.
|
|
11
|
+
*
|
|
12
|
+
* Runs from a scheduled Firebase Function (not a Vercel route), so the
|
|
13
|
+
* unbounded-ish order scan below is safe against CLAUDE.md Rule #6's 10s
|
|
14
|
+
* sync ceiling — it has the Function's much larger budget.
|
|
15
|
+
*/
|
|
16
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
17
|
+
import { getAdminDb } from "../../../../providers/db-firebase";
|
|
18
|
+
import { sendEmail } from "../../../../features/contact/email";
|
|
19
|
+
import { siteSettingsRepository } from "../../../../features/admin/repository/site-settings.repository";
|
|
20
|
+
import { ORDER_COLLECTION, OrderStatusValues } from "../../../../features/orders/schemas/firestore";
|
|
21
|
+
import { PRODUCT_COLLECTION, ProductStatusValues } from "../../../../features/products/schemas/firestore";
|
|
22
|
+
import { ORDER_FIELDS, PRODUCT_FIELDS } from "../../../../constants/field-names";
|
|
23
|
+
import { EmailBold, EmailContainer, EmailDivider, EmailDoc, EmailFooter, EmailHeader, EmailRow, } from "../../../../features/email/primitives";
|
|
24
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
25
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
/** Statuses that shouldn't count toward revenue for the day. */
|
|
27
|
+
const NON_REVENUE_STATUSES = new Set([
|
|
28
|
+
OrderStatusValues.CANCELLED,
|
|
29
|
+
OrderStatusValues.REFUNDED,
|
|
30
|
+
]);
|
|
31
|
+
function currency(amount) {
|
|
32
|
+
return `₹${amount.toLocaleString("en-IN", { maximumFractionDigits: 2 })}`;
|
|
33
|
+
}
|
|
34
|
+
/** One "Label: value" line. Uses only email primitives — email clients need
|
|
35
|
+
* inline-styled, table-based markup, which the Email* components provide. */
|
|
36
|
+
function StatLine({ label, value }) {
|
|
37
|
+
return (_jsxs(_Fragment, { children: [label, ": ", _jsx(EmailBold, { children: String(value) }), _jsx("br", {})] }));
|
|
38
|
+
}
|
|
39
|
+
/** Gathers the last-24h numbers. Exported so the admin manual-trigger route can reuse it. */
|
|
40
|
+
export async function collectDailyStatusSummary() {
|
|
41
|
+
const db = getAdminDb();
|
|
42
|
+
const since = new Date(Date.now() - DAY_MS);
|
|
43
|
+
const recentSnap = await db
|
|
44
|
+
.collection(ORDER_COLLECTION)
|
|
45
|
+
.where(ORDER_FIELDS.CREATED_AT, ">=", since)
|
|
46
|
+
.get();
|
|
47
|
+
let revenue = 0;
|
|
48
|
+
const statusBreakdown = {};
|
|
49
|
+
for (const doc of recentSnap.docs) {
|
|
50
|
+
const data = doc.data();
|
|
51
|
+
const status = data.status ?? "unknown";
|
|
52
|
+
statusBreakdown[status] = (statusBreakdown[status] ?? 0) + 1;
|
|
53
|
+
if (!NON_REVENUE_STATUSES.has(status)) {
|
|
54
|
+
revenue += Number(data.totalPrice ?? 0) || 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Orders still sitting in PENDING beyond the 24h window — the one number
|
|
58
|
+
// here that signals something needs attention rather than just reporting
|
|
59
|
+
// volume.
|
|
60
|
+
const stuckSnap = await db
|
|
61
|
+
.collection(ORDER_COLLECTION)
|
|
62
|
+
.where(ORDER_FIELDS.STATUS, "==", OrderStatusValues.PENDING)
|
|
63
|
+
.where(ORDER_FIELDS.CREATED_AT, "<", since)
|
|
64
|
+
.count()
|
|
65
|
+
.get();
|
|
66
|
+
const activeSnap = await db
|
|
67
|
+
.collection(PRODUCT_COLLECTION)
|
|
68
|
+
.where(PRODUCT_FIELDS.STATUS, "==", ProductStatusValues.PUBLISHED)
|
|
69
|
+
.count()
|
|
70
|
+
.get();
|
|
71
|
+
return {
|
|
72
|
+
orderCount: recentSnap.size,
|
|
73
|
+
revenue,
|
|
74
|
+
statusBreakdown,
|
|
75
|
+
stuckPendingCount: stuckSnap.data().count,
|
|
76
|
+
activeListingCount: activeSnap.data().count,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function renderDigestHtml(summary, siteName, version) {
|
|
80
|
+
const statusRows = Object.entries(summary.statusBreakdown).sort(([a], [b]) => a.localeCompare(b));
|
|
81
|
+
const title = siteName ? `${siteName} — Daily Status` : "Daily Status";
|
|
82
|
+
return `<!DOCTYPE html>${renderToStaticMarkup(_jsx(EmailDoc, { title: title, children: _jsxs(EmailContainer, { children: [_jsx(EmailHeader, { brandName: siteName }), _jsxs(EmailRow, { children: [version
|
|
83
|
+
? `Deployment ${version} is live. Activity for the last 24 hours.`
|
|
84
|
+
: "Activity for the last 24 hours.", _jsx("br", {}), _jsx("br", {}), _jsx(StatLine, { label: "Orders placed", value: summary.orderCount }), _jsx(StatLine, { label: "Revenue (excl. cancelled/refunded)", value: currency(summary.revenue) }), _jsx(StatLine, { label: "Active listings", value: summary.activeListingCount }), _jsx(StatLine, { label: "Pending > 24h (needs attention)", value: summary.stuckPendingCount })] }), _jsx(EmailDivider, {}), _jsxs(EmailRow, { children: [_jsx(EmailBold, { children: "Orders by status" }), _jsx("br", {}), _jsx("br", {}), statusRows.length === 0 ? ("No orders in this window.") : (statusRows.map(([status, count]) => (_jsx(StatLine, { label: status, value: count }, status))))] }), _jsx(EmailFooter, { copyright: `© ${new Date().getFullYear()}${siteName ? ` ${siteName}` : ""}. All rights reserved.` })] }) }))}`;
|
|
85
|
+
}
|
|
86
|
+
const DEPLOY_MARKER_COLLECTION = "system";
|
|
87
|
+
const DEPLOY_MARKER_DOC = "deployDigest";
|
|
88
|
+
/**
|
|
89
|
+
* Sends the digest once per deployment version.
|
|
90
|
+
*
|
|
91
|
+
* Called from server startup. On Vercel that means EVERY lambda cold start,
|
|
92
|
+
* not once per deploy — so the version marker below is what makes this a
|
|
93
|
+
* per-deployment event rather than a per-cold-start email storm. The claim
|
|
94
|
+
* runs in a transaction because several cold starts can race on the first
|
|
95
|
+
* request after a deploy; exactly one wins and sends.
|
|
96
|
+
*/
|
|
97
|
+
export async function runDeploymentDigest(ctx, version) {
|
|
98
|
+
if (!version)
|
|
99
|
+
return { sent: false, reason: "no_version" };
|
|
100
|
+
const markerRef = getAdminDb().collection(DEPLOY_MARKER_COLLECTION).doc(DEPLOY_MARKER_DOC);
|
|
101
|
+
let claimed = false;
|
|
102
|
+
try {
|
|
103
|
+
await getAdminDb().runTransaction(async (tx) => {
|
|
104
|
+
const snap = await tx.get(markerRef);
|
|
105
|
+
const lastVersion = snap.data()?.lastVersion;
|
|
106
|
+
if (lastVersion === version)
|
|
107
|
+
return;
|
|
108
|
+
tx.set(markerRef, { lastVersion: version, sentAt: new Date() }, { merge: true });
|
|
109
|
+
claimed = true;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
void normalizeError(err);
|
|
114
|
+
ctx.logger.warn("deploymentDigest: could not claim version marker — skipping", { version });
|
|
115
|
+
return { sent: false, reason: "claim_failed" };
|
|
116
|
+
}
|
|
117
|
+
if (!claimed)
|
|
118
|
+
return { sent: false, reason: "already_sent_for_version" };
|
|
119
|
+
ctx.logger.info("deploymentDigest: claimed version, sending", { version });
|
|
120
|
+
return runDailyStatusDigest(ctx, version);
|
|
121
|
+
}
|
|
122
|
+
export async function runDailyStatusDigest(ctx, version) {
|
|
123
|
+
const settings = await siteSettingsRepository.getSingleton().catch((err) => {
|
|
124
|
+
void normalizeError(err);
|
|
125
|
+
return null;
|
|
126
|
+
});
|
|
127
|
+
const digestConfig = settings?.emailSettings?.dailyDigest;
|
|
128
|
+
if (digestConfig?.enabled !== true) {
|
|
129
|
+
ctx.logger.info("dailyStatusDigest: disabled in site settings — skipping");
|
|
130
|
+
return { sent: false, reason: "disabled" };
|
|
131
|
+
}
|
|
132
|
+
const recipients = (digestConfig.recipients ?? []).filter((r) => r.trim().length > 0);
|
|
133
|
+
if (recipients.length === 0) {
|
|
134
|
+
ctx.logger.warn("dailyStatusDigest: enabled but no recipients configured — skipping");
|
|
135
|
+
return { sent: false, reason: "no_recipients" };
|
|
136
|
+
}
|
|
137
|
+
const ccRecipients = (digestConfig.ccRecipients ?? []).filter((r) => r.trim().length > 0);
|
|
138
|
+
const summary = await collectDailyStatusSummary();
|
|
139
|
+
// No hardcoded brand fallback — appkit is consumer-agnostic; the site name
|
|
140
|
+
// comes from settings or the consumer's own env var.
|
|
141
|
+
const siteName = settings?.siteName?.trim() || process.env.NEXT_PUBLIC_SITE_NAME?.trim() || "";
|
|
142
|
+
const { error } = await sendEmail({
|
|
143
|
+
to: recipients,
|
|
144
|
+
// Spread rather than `cc: undefined` — SendEmailOptions has a
|
|
145
|
+
// Record<string, JsonValue> index signature that rejects undefined.
|
|
146
|
+
...(ccRecipients.length > 0 ? { cc: ccRecipients } : {}),
|
|
147
|
+
subject: "Daily Status",
|
|
148
|
+
html: renderDigestHtml(summary, siteName, version),
|
|
149
|
+
text: (version ? `Deployment ${version} is live.\n\n` : "") +
|
|
150
|
+
`Orders placed: ${summary.orderCount}\n` +
|
|
151
|
+
`Revenue: ${currency(summary.revenue)}\n` +
|
|
152
|
+
`Active listings: ${summary.activeListingCount}\n` +
|
|
153
|
+
`Pending > 24h: ${summary.stuckPendingCount}`,
|
|
154
|
+
});
|
|
155
|
+
if (error) {
|
|
156
|
+
ctx.logger.error("dailyStatusDigest: send failed", { error: String(error) });
|
|
157
|
+
throw new Error(`Daily status digest send failed: ${String(error)}`);
|
|
158
|
+
}
|
|
159
|
+
ctx.logger.info("dailyStatusDigest: sent", {
|
|
160
|
+
recipients: recipients.length,
|
|
161
|
+
orderCount: summary.orderCount,
|
|
162
|
+
});
|
|
163
|
+
return { sent: true, summary };
|
|
164
|
+
}
|
|
@@ -21,6 +21,8 @@ import { userRepository, sessionRepository, storeRepository, productRepository,
|
|
|
21
21
|
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
22
22
|
import { isSellerUser } from "../../../../features/auth/role-predicates";
|
|
23
23
|
import { getAdminAuth } from "../../../../providers/db-firebase";
|
|
24
|
+
import { recordAdminAction } from "../../features/audit-log/actions";
|
|
25
|
+
import { AdminAuditActionValues } from "../../../../features/audit-log/schemas/firestore";
|
|
24
26
|
async function cascadeAddressClusterBan(uid, banData) {
|
|
25
27
|
const userAddresses = await addressesRepository.listByOwner("user", uid);
|
|
26
28
|
const uniqueHashes = [...new Set(userAddresses.map((a) => a.addressHash).filter(Boolean))];
|
|
@@ -84,6 +86,15 @@ export async function runHardBanCascade(input, ctx) {
|
|
|
84
86
|
hardBanExpiresAt: expiresAt ?? null,
|
|
85
87
|
...(fraudOrderId ? { hardBanFraudOrderId: fraudOrderId } : {}),
|
|
86
88
|
});
|
|
89
|
+
void recordAdminAction({
|
|
90
|
+
actorUid: bannedBy,
|
|
91
|
+
action: AdminAuditActionValues.USER_HARD_BAN,
|
|
92
|
+
targetType: "user",
|
|
93
|
+
targetId: uid,
|
|
94
|
+
targetLabel: target.displayName ?? uid,
|
|
95
|
+
reason,
|
|
96
|
+
metadata: { expiresAt: expiresAt?.toISOString() ?? null, fraudOrderId: fraudOrderId ?? null },
|
|
97
|
+
});
|
|
87
98
|
// 3. Delete active sessions
|
|
88
99
|
try {
|
|
89
100
|
const sessions = await sessionRepository.findActiveByUser(uid);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JobContext } from "../runtime/types";
|
|
2
|
+
export declare const SANDBOX_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events"];
|
|
2
3
|
export interface TesterSandboxCleanupOptions {
|
|
3
4
|
/** true = wipe every isTestData:true doc regardless of testDataExpiresAt (manual force-purge). */
|
|
4
5
|
force?: boolean;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { batchDelete, getTestDataRefs } from "../handlers/_helpers";
|
|
2
2
|
import { BID_FIELDS } from "../../../../constants/field-names";
|
|
3
|
-
|
|
3
|
+
// Exported so testerSandboxRefresh.ts (the every-4h revert+prune job) reuses
|
|
4
|
+
// the exact same collection scope instead of redefining it — one source of
|
|
5
|
+
// truth for "what counts as sandbox scope."
|
|
6
|
+
export const SANDBOX_COLLECTIONS = ["categories", "stores", "products", "blogPosts", "events"];
|
|
4
7
|
const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
|
|
5
8
|
export async function runTesterSandboxCleanup(ctx, opts = {}) {
|
|
6
9
|
const cutoff = opts.force ? null : new Date();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { JobContext } from "../runtime/types";
|
|
2
|
+
/**
|
|
3
|
+
* Reverts every live tester-sandbox fixture back to its canonical seeded
|
|
4
|
+
* shape (undoing edits made through admin/seller forms) and prunes any
|
|
5
|
+
* isTestData doc that isn't one of the known fixtures (something a tester
|
|
6
|
+
* created during QA, e.g. a cloned product). Runs every 4 hours — distinct
|
|
7
|
+
* from `runTesterSandboxCleanup`, which only deletes docs whose 7-day
|
|
8
|
+
* `testDataExpiresAt` TTL has actually lapsed and never touches still-live
|
|
9
|
+
* docs' fields. Scoped to the tester sandbox only (`isTestData: true`) —
|
|
10
|
+
* the permanent Beyblade catalog real visitors browse is never touched.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runTesterSandboxRefresh(ctx: JobContext): Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { batchDelete, getTestDataRefs } from "../handlers/_helpers";
|
|
2
|
+
import { BID_FIELDS } from "../../../../constants/field-names";
|
|
3
|
+
import { SANDBOX_COLLECTIONS } from "./testerSandboxCleanup";
|
|
4
|
+
// Direct imports from each defining module (not the seed-data barrel) —
|
|
5
|
+
// this is internal appkit code, not part of the package's public API surface.
|
|
6
|
+
import { categoriesTesterSeedData } from "../../../../features/tester/seed-data/categories-tester-seed-data";
|
|
7
|
+
import { storesTesterSeedData } from "../../../../features/tester/seed-data/stores-tester-seed-data";
|
|
8
|
+
import { productsTesterSeedData } from "../../../../features/tester/seed-data/products-tester-seed-data";
|
|
9
|
+
import { blogTesterSeedData } from "../../../../features/tester/seed-data/blog-tester-seed-data";
|
|
10
|
+
import { eventsTesterSeedData } from "../../../../features/tester/seed-data/events-tester-seed-data";
|
|
11
|
+
import { bidsTesterSeedData } from "../../../../features/tester/seed-data/bids-tester-seed-data";
|
|
12
|
+
const SEED_BY_COLLECTION = {
|
|
13
|
+
categories: categoriesTesterSeedData,
|
|
14
|
+
stores: storesTesterSeedData,
|
|
15
|
+
products: productsTesterSeedData,
|
|
16
|
+
blogPosts: blogTesterSeedData,
|
|
17
|
+
events: eventsTesterSeedData,
|
|
18
|
+
};
|
|
19
|
+
const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
|
|
20
|
+
/**
|
|
21
|
+
* Reverts every live tester-sandbox fixture back to its canonical seeded
|
|
22
|
+
* shape (undoing edits made through admin/seller forms) and prunes any
|
|
23
|
+
* isTestData doc that isn't one of the known fixtures (something a tester
|
|
24
|
+
* created during QA, e.g. a cloned product). Runs every 4 hours — distinct
|
|
25
|
+
* from `runTesterSandboxCleanup`, which only deletes docs whose 7-day
|
|
26
|
+
* `testDataExpiresAt` TTL has actually lapsed and never touches still-live
|
|
27
|
+
* docs' fields. Scoped to the tester sandbox only (`isTestData: true`) —
|
|
28
|
+
* the permanent Beyblade catalog real visitors browse is never touched.
|
|
29
|
+
*/
|
|
30
|
+
export async function runTesterSandboxRefresh(ctx) {
|
|
31
|
+
let reverted = 0;
|
|
32
|
+
let deletedExtras = 0;
|
|
33
|
+
for (const collection of SANDBOX_COLLECTIONS) {
|
|
34
|
+
const seedDocs = SEED_BY_COLLECTION[collection] ?? [];
|
|
35
|
+
const validIds = new Set(seedDocs.map((d) => d.id).filter((id) => Boolean(id)));
|
|
36
|
+
if (seedDocs.length > 0) {
|
|
37
|
+
const batch = ctx.db.batch();
|
|
38
|
+
for (const doc of seedDocs) {
|
|
39
|
+
const { id, ...data } = doc;
|
|
40
|
+
if (!id)
|
|
41
|
+
continue;
|
|
42
|
+
// merge:true mirrors seed-cli.mjs's loadGeneric — the seed payload
|
|
43
|
+
// carries every canonical field, so this fully overwrites anything
|
|
44
|
+
// a tester edited through the UI.
|
|
45
|
+
batch.set(ctx.db.collection(collection).doc(id), data, { merge: true });
|
|
46
|
+
reverted++;
|
|
47
|
+
}
|
|
48
|
+
await batch.commit();
|
|
49
|
+
}
|
|
50
|
+
const testDataRefs = await getTestDataRefs(ctx.db, collection, null);
|
|
51
|
+
const extraRefs = testDataRefs.filter((ref) => !validIds.has(ref.id));
|
|
52
|
+
deletedExtras += await batchDelete(ctx, extraRefs);
|
|
53
|
+
}
|
|
54
|
+
// Bids aren't isTestData-flagged themselves — scoped by productId
|
|
55
|
+
// referencing a sandbox product instead, mirroring runTesterSandboxCleanup's
|
|
56
|
+
// cascade convention.
|
|
57
|
+
const sandboxProductIds = (SEED_BY_COLLECTION.products ?? [])
|
|
58
|
+
.map((d) => d.id)
|
|
59
|
+
.filter((id) => Boolean(id));
|
|
60
|
+
const validBidIds = new Set(bidsTesterSeedData.map((d) => d.id).filter((id) => Boolean(id)));
|
|
61
|
+
if (bidsTesterSeedData.length > 0) {
|
|
62
|
+
const bidBatch = ctx.db.batch();
|
|
63
|
+
for (const bid of bidsTesterSeedData) {
|
|
64
|
+
const { id, ...data } = bid;
|
|
65
|
+
if (!id)
|
|
66
|
+
continue;
|
|
67
|
+
bidBatch.set(ctx.db.collection("bids").doc(id), data, { merge: true });
|
|
68
|
+
reverted++;
|
|
69
|
+
}
|
|
70
|
+
await bidBatch.commit();
|
|
71
|
+
}
|
|
72
|
+
const extraBidRefs = [];
|
|
73
|
+
for (let i = 0; i < sandboxProductIds.length; i += BID_CHUNK_SIZE) {
|
|
74
|
+
const chunk = sandboxProductIds.slice(i, i + BID_CHUNK_SIZE);
|
|
75
|
+
if (chunk.length === 0)
|
|
76
|
+
continue;
|
|
77
|
+
const snap = await ctx.db.collection("bids").where(BID_FIELDS.PRODUCT_ID, "in", chunk).get();
|
|
78
|
+
for (const doc of snap.docs) {
|
|
79
|
+
if (!validBidIds.has(doc.id))
|
|
80
|
+
extraBidRefs.push(doc.ref);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
deletedExtras += await batchDelete(ctx, extraBidRefs);
|
|
84
|
+
ctx.logger.info("Tester sandbox refresh complete", { reverted, deletedExtras });
|
|
85
|
+
}
|