@stacksjs/defaults 0.70.57 → 0.70.59
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/app/Actions/AI/AskAction.ts +40 -0
- package/app/Actions/AI/SummaryAction.ts +40 -0
- package/app/Actions/Auth/AuthUserAction.ts +13 -0
- package/app/Actions/Auth/CreateTokenAction.ts +64 -0
- package/app/Actions/Auth/DisableTwoFactorAction.ts +37 -0
- package/app/Actions/Auth/EnableTwoFactorAction.ts +37 -0
- package/app/Actions/Auth/GenerateAuthenticationAction.ts +51 -0
- package/app/Actions/Auth/GenerateRegistrationAction.ts +53 -0
- package/app/Actions/Auth/GenerateTwoFactorSecretAction.ts +26 -0
- package/app/Actions/Auth/ListTokensAction.ts +30 -0
- package/app/Actions/Auth/LoginAction.ts +73 -0
- package/app/Actions/Auth/LogoutAction.ts +16 -0
- package/app/Actions/Auth/LogoutAllAction.ts +28 -0
- package/app/Actions/Auth/RefreshTokenAction.ts +39 -0
- package/app/Actions/Auth/RegisterAction.ts +57 -0
- package/app/Actions/Auth/ResendVerificationAction.ts +24 -0
- package/app/Actions/Auth/RevokeTokenAction.ts +43 -0
- package/app/Actions/Auth/TestAbilitiesAction.ts +61 -0
- package/app/Actions/Auth/VerifyAuthenticationAction.ts +94 -0
- package/app/Actions/Auth/VerifyEmailAction.ts +25 -0
- package/app/Actions/Auth/VerifyRegistrationAction.ts +59 -0
- package/app/Actions/Auth/VerifyTwoFactorLoginAction.ts +57 -0
- package/app/Actions/Billable/CheckoutAction.ts +25 -0
- package/app/Actions/Buddy/BuddyBrowseAction.ts +34 -0
- package/app/Actions/Buddy/BuddyCancelAction.ts +32 -0
- package/app/Actions/Buddy/BuddyCommitAction.ts +25 -0
- package/app/Actions/Buddy/BuddyGitHubConnectAction.ts +40 -0
- package/app/Actions/Buddy/BuddyGitHubDisconnectAction.ts +19 -0
- package/app/Actions/Buddy/BuddyProcessAction.ts +65 -0
- package/app/Actions/Buddy/BuddyProcessStreamAction.ts +128 -0
- package/app/Actions/Buddy/BuddyPushAction.ts +21 -0
- package/app/Actions/Buddy/BuddyRepoOpenAction.ts +44 -0
- package/app/Actions/Buddy/BuddyRepoValidateAction.ts +35 -0
- package/app/Actions/Buddy/BuddySettingsAction.ts +21 -0
- package/app/Actions/Buddy/BuddySettingsUpdateAction.ts +46 -0
- package/app/Actions/Buddy/BuddyStateAction.ts +28 -0
- package/app/Actions/Buddy/BuddyTitleAction.ts +49 -0
- package/app/Actions/Buddy/CommandsAction.ts +802 -0
- package/app/Actions/Buddy/JobCancelAction.ts +32 -0
- package/app/Actions/Buddy/JobRetryAction.ts +49 -0
- package/app/Actions/Buddy/JobsListAction.ts +141 -0
- package/app/Actions/Buddy/ScheduleListAction.ts +52 -0
- package/app/Actions/Buddy/VersionsAction.ts +12 -0
- package/app/Actions/Cms/AuthorDestroyAction.ts +21 -0
- package/app/Actions/Cms/AuthorIndexAction.ts +14 -0
- package/app/Actions/Cms/AuthorShowAction.ts +19 -0
- package/app/Actions/Cms/AuthorStoreAction.ts +20 -0
- package/app/Actions/Cms/AuthorUpdateAction.ts +24 -0
- package/app/Actions/Cms/CategorizableDestroyAction.ts +16 -0
- package/app/Actions/Cms/CategorizableIndexAction.ts +14 -0
- package/app/Actions/Cms/CategorizableShowAction.ts +16 -0
- package/app/Actions/Cms/CategorizableStoreAction.ts +42 -0
- package/app/Actions/Cms/CategorizableUpdateAction.ts +38 -0
- package/app/Actions/Cms/CommentDestroyAction.ts +16 -0
- package/app/Actions/Cms/CommentIndexAction.ts +17 -0
- package/app/Actions/Cms/CommentShowAction.ts +16 -0
- package/app/Actions/Cms/CommentStoreAction.ts +39 -0
- package/app/Actions/Cms/CommentUpdateAction.ts +37 -0
- package/app/Actions/Cms/PageDestroyAction.ts +16 -0
- package/app/Actions/Cms/PageIndexAction.ts +14 -0
- package/app/Actions/Cms/PageStoreAction.ts +31 -0
- package/app/Actions/Cms/PageUpdateAction.ts +27 -0
- package/app/Actions/Cms/PostDestroyAction.ts +17 -0
- package/app/Actions/Cms/PostIndexAction.ts +14 -0
- package/app/Actions/Cms/PostShowAction.ts +16 -0
- package/app/Actions/Cms/PostStoreAction.ts +37 -0
- package/app/Actions/Cms/PostUpdateAction.ts +32 -0
- package/app/Actions/Cms/PostViewsUpdateAction.ts +34 -0
- package/app/Actions/Cms/RssFeedAction.ts +59 -0
- package/app/Actions/Cms/SitemapAction.ts +60 -0
- package/app/Actions/Cms/TaggableDestroyAction.ts +16 -0
- package/app/Actions/Cms/TaggableIndexAction.ts +14 -0
- package/app/Actions/Cms/TaggableShowAction.ts +16 -0
- package/app/Actions/Cms/TaggableStoreAction.ts +36 -0
- package/app/Actions/Cms/TaggableUpdateAction.ts +37 -0
- package/app/Actions/Commerce/CouponDestroyAction.ts +18 -0
- package/app/Actions/Commerce/CouponIndexAction.ts +16 -0
- package/app/Actions/Commerce/CouponShowAction.ts +18 -0
- package/app/Actions/Commerce/CouponStoreAction.ts +21 -0
- package/app/Actions/Commerce/CouponUpdateAction.ts +31 -0
- package/app/Actions/Commerce/CustomerDestroyAction.ts +16 -0
- package/app/Actions/Commerce/CustomerIndexAction.ts +15 -0
- package/app/Actions/Commerce/CustomerShowAction.ts +18 -0
- package/app/Actions/Commerce/CustomerStoreAction.ts +29 -0
- package/app/Actions/Commerce/CustomerUpdateAction.ts +27 -0
- package/app/Actions/Commerce/DriverDestroyAction.ts +18 -0
- package/app/Actions/Commerce/GiftCardDestroyAction.ts +18 -0
- package/app/Actions/Commerce/GiftCardIndexAction.ts +16 -0
- package/app/Actions/Commerce/GiftCardShowAction.ts +18 -0
- package/app/Actions/Commerce/GiftCardStatsAction.ts +30 -0
- package/app/Actions/Commerce/GiftCardStoreAction.ts +35 -0
- package/app/Actions/Commerce/GiftCardUpdateAction.ts +26 -0
- package/app/Actions/Commerce/GiftCardUpdateBalanceAction.ts +36 -0
- package/app/Actions/Commerce/OrderDestroyAction.ts +18 -0
- package/app/Actions/Commerce/OrderExportAction.ts +15 -0
- package/app/Actions/Commerce/OrderIndexAction.ts +15 -0
- package/app/Actions/Commerce/OrderShowAction.ts +18 -0
- package/app/Actions/Commerce/OrderStoreAction.ts +32 -0
- package/app/Actions/Commerce/OrderUpdateAction.ts +34 -0
- package/app/Actions/Commerce/PaymentDestroyAction.ts +16 -0
- package/app/Actions/Commerce/PaymentFetchStatsAction.ts +31 -0
- package/app/Actions/Commerce/PaymentIndexAction.ts +15 -0
- package/app/Actions/Commerce/PaymentMonthlyTrendsAction.ts +21 -0
- package/app/Actions/Commerce/PaymentShowAction.ts +17 -0
- package/app/Actions/Commerce/PaymentStoreAction.ts +24 -0
- package/app/Actions/Commerce/PaymentUpdateAction.ts +35 -0
- package/app/Actions/Commerce/PrintDeviceDestroyAction.ts +18 -0
- package/app/Actions/Commerce/PrintDeviceIndexAction.ts +14 -0
- package/app/Actions/Commerce/PrintDeviceShowAction.ts +17 -0
- package/app/Actions/Commerce/PrintDeviceStoreAction.ts +27 -0
- package/app/Actions/Commerce/PrintDeviceUpdateAction.ts +27 -0
- package/app/Actions/Commerce/Product/ManufacturerDestroyAction.ts +20 -0
- package/app/Actions/Commerce/Product/ManufacturerIndexAction.ts +16 -0
- package/app/Actions/Commerce/Product/ManufacturerShowAction.ts +18 -0
- package/app/Actions/Commerce/Product/ManufacturerStoreAction.ts +14 -0
- package/app/Actions/Commerce/Product/ProductCategoryIndexAction.ts +16 -0
- package/app/Actions/Commerce/Product/ProductCategoryStoreAction.ts +16 -0
- package/app/Actions/Commerce/Product/ProductDestroyAction.ts +16 -0
- package/app/Actions/Commerce/Product/ProductIndexAction.ts +14 -0
- package/app/Actions/Commerce/Product/ProductManufacturerDestroyAction.ts +18 -0
- package/app/Actions/Commerce/Product/ProductManufacturerIndexAction.ts +14 -0
- package/app/Actions/Commerce/Product/ProductManufacturerShowAction.ts +16 -0
- package/app/Actions/Commerce/Product/ProductManufacturerStoreAction.ts +14 -0
- package/app/Actions/Commerce/Product/ProductManufacturerUpdateAction.ts +18 -0
- package/app/Actions/Commerce/Product/ProductShowAction.ts +18 -0
- package/app/Actions/Commerce/Product/ProductStoreAction.ts +17 -0
- package/app/Actions/Commerce/Product/ProductUnitDestroyAction.ts +18 -0
- package/app/Actions/Commerce/Product/ProductUnitIndexAction.ts +14 -0
- package/app/Actions/Commerce/Product/ProductUnitShowAction.ts +18 -0
- package/app/Actions/Commerce/Product/ProductUnitStoreAction.ts +15 -0
- package/app/Actions/Commerce/Product/ProductUpdateAction.ts +17 -0
- package/app/Actions/Commerce/Product/ProductVariantDestroyAction.ts +18 -0
- package/app/Actions/Commerce/Product/ProductVariantIndexAction.ts +14 -0
- package/app/Actions/Commerce/Product/ProductVariantShowAction.ts +17 -0
- package/app/Actions/Commerce/Product/ProductVariantStoreAction.ts +14 -0
- package/app/Actions/Commerce/Product/ProductVariantUpdateAction.ts +16 -0
- package/app/Actions/Commerce/ReceiptDestroyAction.ts +18 -0
- package/app/Actions/Commerce/ReceiptIndexAction.ts +14 -0
- package/app/Actions/Commerce/ReceiptShowAction.ts +18 -0
- package/app/Actions/Commerce/ReceiptStoreAction.ts +28 -0
- package/app/Actions/Commerce/ReceiptUpdateAction.ts +30 -0
- package/app/Actions/Commerce/ReviewIndexAction.ts +14 -0
- package/app/Actions/Commerce/ReviewShowAction.ts +16 -0
- package/app/Actions/Commerce/ReviewStoreAction.ts +26 -0
- package/app/Actions/Commerce/ReviewUpdateAction.ts +28 -0
- package/app/Actions/Commerce/Shipping/DeliveryRouteDestroyAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/DeliveryRouteIndexAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/DeliveryRouteShowAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/DeliveryRouteStoreAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/DeliveryRouteUpdateAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/DigitalDeliveryDestroyAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/DigitalDeliveryIndexAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/DigitalDeliveryShowAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/DigitalDeliveryStoreAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/DigitalDeliveryUpdateAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/DriverIndexAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/DriverShowAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/DriverStoreAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/DriverUpdateAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/LicenseKeyDestroyAction.ts +20 -0
- package/app/Actions/Commerce/Shipping/LicenseKeyIndexAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/LicenseKeyShowAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/LicenseKeyStoreAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/LicenseKeyUpdateAction.ts +18 -0
- package/app/Actions/Commerce/Shipping/ShippingMethodDestroyAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingMethodIndexAction.ts +14 -0
- package/app/Actions/Commerce/Shipping/ShippingMethodShowAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingMethodStoreAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingMethodUpdateAction.ts +15 -0
- package/app/Actions/Commerce/Shipping/ShippingRateDestroyAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingRateIndexAction.ts +14 -0
- package/app/Actions/Commerce/Shipping/ShippingRateShowAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingRateStoreAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingRateUpdateAction.ts +17 -0
- package/app/Actions/Commerce/Shipping/ShippingZoneDestroyAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingZoneIndexAction.ts +14 -0
- package/app/Actions/Commerce/Shipping/ShippingZoneShowAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingZoneStoreAction.ts +16 -0
- package/app/Actions/Commerce/Shipping/ShippingZoneUpdateAction.ts +15 -0
- package/app/Actions/Commerce/TaxRateDestroyAction.ts +16 -0
- package/app/Actions/Commerce/TaxRateIndexAction.ts +14 -0
- package/app/Actions/Commerce/TaxRateShowAction.ts +17 -0
- package/app/Actions/Commerce/TaxRateStoreAction.ts +24 -0
- package/app/Actions/Commerce/TaxRateUpdateAction.ts +24 -0
- package/app/Actions/Commerce/WaitlistProductAnalyticsAction.ts +29 -0
- package/app/Actions/Commerce/WaitlistProductDestroyAction.ts +16 -0
- package/app/Actions/Commerce/WaitlistProductIndexAction.ts +14 -0
- package/app/Actions/Commerce/WaitlistProductQuantityDistributionAction.ts +32 -0
- package/app/Actions/Commerce/WaitlistProductShowAction.ts +16 -0
- package/app/Actions/Commerce/WaitlistProductStatusAction.ts +37 -0
- package/app/Actions/Commerce/WaitlistProductStoreAction.ts +27 -0
- package/app/Actions/Commerce/WaitlistProductTimeSeriesAction.ts +25 -0
- package/app/Actions/Commerce/WaitlistProductUpdateAction.ts +29 -0
- package/app/Actions/Commerce/WaitlistRestaurantDashboardAction.ts +29 -0
- package/app/Actions/Commerce/WaitlistRestaurantDestroyAction.ts +16 -0
- package/app/Actions/Commerce/WaitlistRestaurantIndexAction.ts +14 -0
- package/app/Actions/Commerce/WaitlistRestaurantShowAction.ts +16 -0
- package/app/Actions/Commerce/WaitlistRestaurantStoreAction.ts +31 -0
- package/app/Actions/Commerce/WaitlistRestaurantUpdateAction.ts +33 -0
- package/app/Actions/ContactAction.ts +72 -0
- package/app/Actions/Dashboard/Actions/CreateAction.ts +11 -0
- package/app/Actions/Dashboard/Actions/GetActionCount.ts +12 -0
- package/app/Actions/Dashboard/Actions/GetActions.ts +19 -0
- package/app/Actions/Dashboard/Analytics/BlogAnalyticsAction.ts +58 -0
- package/app/Actions/Dashboard/Analytics/BrowserAnalyticsAction.ts +42 -0
- package/app/Actions/Dashboard/Analytics/CommerceAnalyticsAction.ts +64 -0
- package/app/Actions/Dashboard/Analytics/CountryAnalyticsAction.ts +41 -0
- package/app/Actions/Dashboard/Analytics/DeviceAnalyticsAction.ts +58 -0
- package/app/Actions/Dashboard/Analytics/EventAnalyticsAction.ts +90 -0
- package/app/Actions/Dashboard/Analytics/MarketingAnalyticsAction.ts +76 -0
- package/app/Actions/Dashboard/Analytics/PageAnalyticsAction.ts +45 -0
- package/app/Actions/Dashboard/Analytics/ReferrerAnalyticsAction.ts +72 -0
- package/app/Actions/Dashboard/Analytics/SalesAnalyticsAction.ts +73 -0
- package/app/Actions/Dashboard/Analytics/WebAnalyticsAction.ts +114 -0
- package/app/Actions/Dashboard/Auth/MeAction.ts +84 -0
- package/app/Actions/Dashboard/BuddyDashboardAction.ts +34 -0
- package/app/Actions/Dashboard/Ci/RepoRunJobsAction.ts +60 -0
- package/app/Actions/Dashboard/Ci/RepoRunsAction.ts +67 -0
- package/app/Actions/Dashboard/Ci/RunnerHistoryAction.ts +58 -0
- package/app/Actions/Dashboard/Ci/StatusAction.ts +73 -0
- package/app/Actions/Dashboard/Ci/failure-notifier.ts +214 -0
- package/app/Actions/Dashboard/Ci/runner-pressure-monitor.ts +259 -0
- package/app/Actions/Dashboard/Cloud/CloudIndexAction.ts +61 -0
- package/app/Actions/Dashboard/Commands/CreateCommand.ts +11 -0
- package/app/Actions/Dashboard/Commands/GetAverageCommandTime.ts +12 -0
- package/app/Actions/Dashboard/Commands/GetCommandCount.ts +12 -0
- package/app/Actions/Dashboard/Commands/GetCommandSuccessRate.ts +12 -0
- package/app/Actions/Dashboard/Commands/GetCommands.ts +12 -0
- package/app/Actions/Dashboard/Commerce/CommerceCouponsAction.ts +48 -0
- package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +48 -0
- package/app/Actions/Dashboard/Commerce/CommerceDashboardAction.ts +98 -0
- package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +65 -0
- package/app/Actions/Dashboard/Commerce/CommerceGiftCardsAction.ts +48 -0
- package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +51 -0
- package/app/Actions/Dashboard/Commerce/CommercePaymentsAction.ts +59 -0
- package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +50 -0
- package/app/Actions/Dashboard/Commerce/CommerceTaxesAction.ts +46 -0
- package/app/Actions/Dashboard/Commerce/PosIndexAction.ts +57 -0
- package/app/Actions/Dashboard/Commerce/ReviewIndexAction.ts +51 -0
- package/app/Actions/Dashboard/Content/AuthorIndexAction.ts +51 -0
- package/app/Actions/Dashboard/Content/CategoryIndexAction.ts +27 -0
- package/app/Actions/Dashboard/Content/CommentIndexAction.ts +41 -0
- package/app/Actions/Dashboard/Content/ContentDashboardAction.ts +54 -0
- package/app/Actions/Dashboard/Content/FileIndexAction.ts +24 -0
- package/app/Actions/Dashboard/Content/PageIndexAction.ts +25 -0
- package/app/Actions/Dashboard/Content/PostIndexAction.ts +102 -0
- package/app/Actions/Dashboard/Content/SeoIndexAction.ts +17 -0
- package/app/Actions/Dashboard/Content/TagIndexAction.ts +23 -0
- package/app/Actions/Dashboard/CreateDnsRecords.ts +11 -0
- package/app/Actions/Dashboard/CreateMailbox.ts +11 -0
- package/app/Actions/Dashboard/DashboardActivityAction.ts +58 -0
- package/app/Actions/Dashboard/DashboardHealthAction.ts +52 -0
- package/app/Actions/Dashboard/DashboardHomeAction.ts +83 -0
- package/app/Actions/Dashboard/DashboardStatsAction.ts +48 -0
- package/app/Actions/Dashboard/Data/AccessTokenIndexAction.ts +65 -0
- package/app/Actions/Dashboard/Data/ActivityIndexAction.ts +28 -0
- package/app/Actions/Dashboard/Data/DataDashboardAction.ts +67 -0
- package/app/Actions/Dashboard/Data/SubscriberIndexAction.ts +48 -0
- package/app/Actions/Dashboard/Data/TeamIndexAction.ts +43 -0
- package/app/Actions/Dashboard/Data/UserIndexAction.ts +48 -0
- package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +11 -0
- package/app/Actions/Dashboard/Deployments/GetAverageDeploymentTime.ts +15 -0
- package/app/Actions/Dashboard/Deployments/GetDeployScript.ts +12 -0
- package/app/Actions/Dashboard/Deployments/GetDeploymentCount.ts +12 -0
- package/app/Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput.ts +12 -0
- package/app/Actions/Dashboard/Deployments/GetDeployments.ts +20 -0
- package/app/Actions/Dashboard/Deployments/GetRecentDeployments.ts +12 -0
- package/app/Actions/Dashboard/Deployments/UpdateDeployScript.ts +11 -0
- package/app/Actions/Dashboard/GetDnsRecords.ts +12 -0
- package/app/Actions/Dashboard/GetEmailInboxes.ts +12 -0
- package/app/Actions/Dashboard/GetProjects.ts +13 -0
- package/app/Actions/Dashboard/GetRecentCommits.ts +13 -0
- package/app/Actions/Dashboard/Inbox/InboxIndexAction.ts +144 -0
- package/app/Actions/Dashboard/Inbox/InboxShowAction.ts +89 -0
- package/app/Actions/Dashboard/Infrastructure/CommandIndexAction.ts +18 -0
- package/app/Actions/Dashboard/Infrastructure/DnsIndexAction.ts +101 -0
- package/app/Actions/Dashboard/Infrastructure/EnvironmentIndexAction.ts +67 -0
- package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +29 -0
- package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +50 -0
- package/app/Actions/Dashboard/Infrastructure/MailboxIndexAction.ts +85 -0
- package/app/Actions/Dashboard/Infrastructure/PermissionIndexAction.ts +54 -0
- package/app/Actions/Dashboard/Infrastructure/RequestIndexAction.ts +44 -0
- package/app/Actions/Dashboard/Infrastructure/ServerIndexAction.ts +101 -0
- package/app/Actions/Dashboard/Jobs/CreateJob.ts +11 -0
- package/app/Actions/Dashboard/Jobs/GetAverageJobTime.ts +12 -0
- package/app/Actions/Dashboard/Jobs/GetJobHistory.ts +12 -0
- package/app/Actions/Dashboard/Jobs/GetJobs.ts +11 -0
- package/app/Actions/Dashboard/Jobs/GetJobsPerMinute.ts +12 -0
- package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +136 -0
- package/app/Actions/Dashboard/Jobs/JobRetryAction.ts +23 -0
- package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +49 -0
- package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +84 -0
- package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +215 -0
- package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +98 -0
- package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +96 -0
- package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +81 -0
- package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +80 -0
- package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +103 -0
- package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +33 -0
- package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +82 -0
- package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +48 -0
- package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +103 -0
- package/app/Actions/Dashboard/Kanban/CardShowAction.ts +122 -0
- package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +112 -0
- package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +91 -0
- package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +148 -0
- package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +59 -0
- package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +94 -0
- package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +89 -0
- package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +85 -0
- package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +41 -0
- package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +70 -0
- package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +83 -0
- package/app/Actions/Dashboard/Kanban/UsersListAction.ts +36 -0
- package/app/Actions/Dashboard/Library/CreateComponent.ts +11 -0
- package/app/Actions/Dashboard/Library/CreateFunction.ts +11 -0
- package/app/Actions/Dashboard/Library/DependencyIndexAction.ts +92 -0
- package/app/Actions/Dashboard/Library/GetAverageReleaseTime.ts +12 -0
- package/app/Actions/Dashboard/Library/GetComponents.ts +24 -0
- package/app/Actions/Dashboard/Library/GetComponentsDownloadCount.ts +12 -0
- package/app/Actions/Dashboard/Library/GetDownloadCount.ts +12 -0
- package/app/Actions/Dashboard/Library/GetFunctions.ts +22 -0
- package/app/Actions/Dashboard/Library/GetFunctionsDownloadCount.ts +12 -0
- package/app/Actions/Dashboard/Library/GetReleaseCount.ts +12 -0
- package/app/Actions/Dashboard/Library/GetReleases.ts +28 -0
- package/app/Actions/Dashboard/Library/PackageIndexAction.ts +26 -0
- package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +45 -0
- package/app/Actions/Dashboard/Marketing/ListIndexAction.ts +43 -0
- package/app/Actions/Dashboard/Marketing/SocialPostIndexAction.ts +47 -0
- package/app/Actions/Dashboard/Models/GetModels.ts +39 -0
- package/app/Actions/Dashboard/Models/GetSubscriberCount.ts +12 -0
- package/app/Actions/Dashboard/Models/GetUserCount.ts +12 -0
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +131 -0
- package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +169 -0
- package/app/Actions/Dashboard/Notifications/CreateNotification.ts +11 -0
- package/app/Actions/Dashboard/Notifications/GetAverageNotificationTime.ts +12 -0
- package/app/Actions/Dashboard/Notifications/GetNotificationCount.ts +12 -0
- package/app/Actions/Dashboard/Notifications/GetNotificationDeliveryRate.ts +12 -0
- package/app/Actions/Dashboard/Notifications/GetNotificationHistory.ts +12 -0
- package/app/Actions/Dashboard/Notifications/GetNotifications.ts +12 -0
- package/app/Actions/Dashboard/Notifications/NotificationDashboardAction.ts +43 -0
- package/app/Actions/Dashboard/Queries/QueryIndexAction.ts +22 -0
- package/app/Actions/Dashboard/Queue/QueueRetryFailedAction.ts +22 -0
- package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +98 -0
- package/app/Actions/Dashboard/Queue/QueueWorkersAction.ts +46 -0
- package/app/Actions/Dashboard/Rbac/PermissionDestroyAction.ts +33 -0
- package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +53 -0
- package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +34 -0
- package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +35 -0
- package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +45 -0
- package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +63 -0
- package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +58 -0
- package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +36 -0
- package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +40 -0
- package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +65 -0
- package/app/Actions/Dashboard/Rbac/UsersListAction.ts +39 -0
- package/app/Actions/Dashboard/Realtime/RealtimeStatsAction.ts +50 -0
- package/app/Actions/Dashboard/Releases/ReleaseIndexAction.ts +21 -0
- package/app/Actions/Dashboard/Requests/GetAverageRequestTime.ts +12 -0
- package/app/Actions/Dashboard/Requests/GetRequestCount.ts +12 -0
- package/app/Actions/Dashboard/Requests/GetRequestSuccessRate.ts +12 -0
- package/app/Actions/Dashboard/Requests/GetRequests.ts +12 -0
- package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +26 -0
- package/app/Actions/Dashboard/ServiceHealthAction.ts +243 -0
- package/app/Actions/Dashboard/Settings/MailSettingsGetAction.ts +20 -0
- package/app/Actions/Dashboard/Settings/MailSettingsUpdateAction.ts +10 -0
- package/app/Actions/Dashboard/Settings/SettingsIndexAction.ts +40 -0
- package/app/Actions/Dashboard/Settings/UpdateAiConfig.ts +27 -0
- package/app/Actions/Dashboard/Settings/UpdateAnalyticsConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateAppConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateCacheConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateCliConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateCloudConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateDatabaseConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateDnsConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateDocsConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateEmailConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateEnvConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateErrorsConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateFileSystemsConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateGitConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateHashingConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateLibraryConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateLoggerConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateNotificationConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdatePaymentConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdatePortsConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateQueueConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateSearchEngineConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateSecurityConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateServicesConfig.ts +38 -0
- package/app/Actions/Dashboard/Settings/UpdateStorageConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateTeamConfig.ts +11 -0
- package/app/Actions/Dashboard/Settings/UpdateUiConfig.ts +11 -0
- package/app/Actions/HealthAction.ts +112 -0
- package/app/Actions/InstallAction.ts +83 -0
- package/app/Actions/Locale/SetLocaleAction.ts +51 -0
- package/app/Actions/LogAction.ts +42 -0
- package/app/Actions/Logs/LogFetchStatsAction.ts +50 -0
- package/app/Actions/Logs/LogIndexOrmAction.ts +13 -0
- package/app/Actions/Logs/LogShowOrmAction.ts +17 -0
- package/app/Actions/Logs/LogStoreOrmAction.ts +16 -0
- package/app/Actions/Logs/LogUpdateOrmAction.ts +20 -0
- package/app/Actions/Monitoring/ErrorDestroyAction.ts +25 -0
- package/app/Actions/Monitoring/ErrorGroupAction.ts +21 -0
- package/app/Actions/Monitoring/ErrorIgnoreAction.ts +25 -0
- package/app/Actions/Monitoring/ErrorIndexAction.ts +14 -0
- package/app/Actions/Monitoring/ErrorResolveAction.ts +25 -0
- package/app/Actions/Monitoring/ErrorShowAction.ts +19 -0
- package/app/Actions/Monitoring/ErrorStatsAction.ts +14 -0
- package/app/Actions/Monitoring/ErrorTimelineAction.ts +14 -0
- package/app/Actions/Monitoring/ErrorUnresolveAction.ts +25 -0
- package/app/Actions/NotifyUser.ts +10 -0
- package/app/Actions/Password/PasswordResetAction.ts +54 -0
- package/app/Actions/Password/SendPasswordResetEmailAction.ts +66 -0
- package/app/Actions/Password/VerifyResetTokenAction.ts +27 -0
- package/app/Actions/Payment/CancelSubscriptionAction.ts +20 -0
- package/app/Actions/Payment/CreateCheckoutAction.ts +28 -0
- package/app/Actions/Payment/CreateInvoiceSubscription.ts +18 -0
- package/app/Actions/Payment/CreatePaymentIntentAction.ts +31 -0
- package/app/Actions/Payment/CreateSetupIntentAction.ts +20 -0
- package/app/Actions/Payment/CreateSubscriptionAction.ts +23 -0
- package/app/Actions/Payment/DeleteDefaultPaymentAction.ts +18 -0
- package/app/Actions/Payment/FetchActiveSubscriptionAction.ts +18 -0
- package/app/Actions/Payment/FetchDefaultPaymentMethodAction.ts +18 -0
- package/app/Actions/Payment/FetchPaymentCustomerAction.ts +18 -0
- package/app/Actions/Payment/FetchPaymentMethodsAction.ts +18 -0
- package/app/Actions/Payment/FetchProductAction.ts +15 -0
- package/app/Actions/Payment/FetchTransactionHistoryAction.ts +18 -0
- package/app/Actions/Payment/FetchUserSubscriptionsAction.ts +18 -0
- package/app/Actions/Payment/SetDefaultPaymentAction.ts +20 -0
- package/app/Actions/Payment/SetUserDefaultPaymentAction.ts +20 -0
- package/app/Actions/Payment/StorePaymentMethodAction.ts +20 -0
- package/app/Actions/Payment/StoreTransactionAction.ts +20 -0
- package/app/Actions/Payment/UpdateCustomerAction.ts +24 -0
- package/app/Actions/Payment/UpdateDefaultPaymentMethodAction.ts +18 -0
- package/app/Actions/Payment/UpdateSubscriptionAction.ts +22 -0
- package/app/Actions/Queue/FetchQueuesAction.ts +14 -0
- package/app/Actions/README.md +61 -0
- package/app/Actions/Realtime/FetchWebsocketsAction.ts +14 -0
- package/app/Actions/RobotsAction.ts +61 -0
- package/app/Actions/SendWelcomeEmail.ts +40 -0
- package/app/Actions/SitemapAction.ts +234 -0
- package/app/Actions/Storefront/AddToCartAction.ts +153 -0
- package/app/Actions/Storefront/CheckoutContactAction.ts +50 -0
- package/app/Actions/Storefront/CheckoutShippingAction.ts +61 -0
- package/app/Actions/Storefront/GetCartAction.ts +58 -0
- package/app/Actions/Storefront/PlaceOrderAction.ts +195 -0
- package/app/Actions/Storefront/SubmitReviewAction.ts +140 -0
- package/app/Actions/Storefront/UpdateCartItemAction.ts +83 -0
- package/app/Actions/Storefront/_shipping.ts +64 -0
- package/app/Actions/SubscriberEmailAction.ts +60 -0
- package/app/Actions/TestErrorAction.ts +163 -0
- package/app/Actions/UnsubscribeAction.ts +107 -0
- package/app/Actions/UploadTestAction.ts +57 -0
- package/app/Broadcasts/OrderShipped.ts +57 -0
- package/app/Commands/Inspire.ts +57 -0
- package/app/Commands/README.md +84 -0
- package/app/Commands.ts +38 -0
- package/app/Controllers/ComingSoonController.ts +9 -0
- package/app/Controllers/QueryController.ts +373 -0
- package/app/Events.ts +14 -0
- package/app/Gates.ts +107 -0
- package/app/Jobs/ExampleJob.ts +20 -0
- package/app/Jobs/ExampleTwoJob.ts +12 -0
- package/app/Jobs/PruneModelsJob.ts +61 -0
- package/app/Jobs/PruneQueryLogsJob.ts +51 -0
- package/app/Jobs/README.md +79 -0
- package/app/Jobs/SendCampaignJob.ts +201 -0
- package/app/Jobs/SendEmailJob.ts +52 -0
- package/app/Jobs/SendPasswordResetEmailJob.ts +44 -0
- package/app/Jobs/SyncSearchIndexJob.ts +64 -0
- package/app/Listener.ts +84 -0
- package/app/Listeners/Console.ts +22 -0
- package/app/Listeners/README.md +47 -0
- package/app/Mail/OrderConfirmation.ts +60 -0
- package/app/Mail/SubscriptionConfirmation.ts +41 -0
- package/app/Middleware/Abilities.ts +53 -0
- package/app/Middleware/Api.ts +26 -0
- package/app/Middleware/Auth.ts +59 -0
- package/app/Middleware/Can.ts +68 -0
- package/app/Middleware/Compress.ts +213 -0
- package/app/Middleware/Cors.ts +320 -0
- package/app/Middleware/Csrf.ts +224 -0
- package/app/Middleware/EnsureEmailIsVerified.ts +28 -0
- package/app/Middleware/Env.ts +24 -0
- package/app/Middleware/EnvDevelopment.ts +19 -0
- package/app/Middleware/EnvLocal.ts +19 -0
- package/app/Middleware/EnvNotDevelopment.ts +19 -0
- package/app/Middleware/EnvNotLocal.ts +19 -0
- package/app/Middleware/EnvNotProduction.ts +19 -0
- package/app/Middleware/EnvNotStaging.ts +19 -0
- package/app/Middleware/EnvProduction.ts +19 -0
- package/app/Middleware/EnvStaging.ts +19 -0
- package/app/Middleware/Guest.ts +25 -0
- package/app/Middleware/Logger.ts +20 -0
- package/app/Middleware/Maintenance.ts +36 -0
- package/app/Middleware/Permission.ts +42 -0
- package/app/Middleware/README.md +48 -0
- package/app/Middleware/Role.ts +42 -0
- package/app/Middleware/Signed.ts +31 -0
- package/app/Middleware/Team.ts +36 -0
- package/app/Middleware/Throttle.ts +96 -0
- package/app/Middleware.ts +35 -0
- package/app/Models/Activity.ts +94 -0
- package/app/Models/Board.ts +103 -0
- package/app/Models/BoardColumn.ts +87 -0
- package/app/Models/Campaign.ts +239 -0
- package/app/Models/CampaignSend.ts +107 -0
- package/app/Models/Card.ts +117 -0
- package/app/Models/CardComment.ts +68 -0
- package/app/Models/Comment.ts +108 -0
- package/app/Models/Content/Author.ts +104 -0
- package/app/Models/Content/Page.ts +99 -0
- package/app/Models/Content/Post.ts +144 -0
- package/app/Models/Deployment.ts +116 -0
- package/app/Models/EmailList.ts +126 -0
- package/app/Models/EmailListSubscriber.ts +91 -0
- package/app/Models/Error.ts +70 -0
- package/app/Models/FailedJob.ts +62 -0
- package/app/Models/Job.ts +63 -0
- package/app/Models/Label.ts +66 -0
- package/app/Models/Log.ts +110 -0
- package/app/Models/Notification.ts +118 -0
- package/app/Models/PaymentMethod.ts +98 -0
- package/app/Models/PaymentProduct.ts +91 -0
- package/app/Models/PaymentTransaction.ts +78 -0
- package/app/Models/README.md +31 -0
- package/app/Models/Release.ts +89 -0
- package/app/Models/Request.ts +114 -0
- package/app/Models/SocialPost.ts +136 -0
- package/app/Models/Subscriber.ts +89 -0
- package/app/Models/SubscriberEmail.ts +55 -0
- package/app/Models/Subscription.ts +153 -0
- package/app/Models/Tag.ts +82 -0
- package/app/Models/Team.ts +72 -0
- package/app/Models/User.ts +134 -0
- package/app/Models/commerce/Cart.ts +139 -0
- package/app/Models/commerce/CartItem.ts +137 -0
- package/app/Models/commerce/Category.ts +105 -0
- package/app/Models/commerce/Coupon.ts +181 -0
- package/app/Models/commerce/Customer.ts +132 -0
- package/app/Models/commerce/DeliveryRoute.ts +93 -0
- package/app/Models/commerce/DigitalDelivery.ts +105 -0
- package/app/Models/commerce/Driver.ts +97 -0
- package/app/Models/commerce/GiftCard.ts +191 -0
- package/app/Models/commerce/LicenseKey.ts +87 -0
- package/app/Models/commerce/LoyaltyPoint.ts +107 -0
- package/app/Models/commerce/LoyaltyReward.ts +124 -0
- package/app/Models/commerce/Manufacturer.ts +84 -0
- package/app/Models/commerce/Order.ts +142 -0
- package/app/Models/commerce/OrderItem.ts +51 -0
- package/app/Models/commerce/Payment.ts +163 -0
- package/app/Models/commerce/PrintDevice.ts +117 -0
- package/app/Models/commerce/Product.ts +148 -0
- package/app/Models/commerce/ProductUnit.ts +107 -0
- package/app/Models/commerce/ProductVariant.ts +106 -0
- package/app/Models/commerce/Receipt.ts +128 -0
- package/app/Models/commerce/Review.ts +140 -0
- package/app/Models/commerce/ShippingMethod.ts +97 -0
- package/app/Models/commerce/ShippingRate.ts +74 -0
- package/app/Models/commerce/ShippingZone.ts +111 -0
- package/app/Models/commerce/TaxRate.ts +114 -0
- package/app/Models/commerce/Transaction.ts +126 -0
- package/app/Models/commerce/WaitlistProduct.ts +149 -0
- package/app/Models/commerce/WaitlistRestaurant.ts +187 -0
- package/app/Models/realtime/Websocket.ts +77 -0
- package/app/Notifications/README.md +31 -0
- package/app/Notifications/Welcome.ts +32 -0
- package/app/Policies/PostPolicy.ts +131 -0
- package/app/README.md +23 -0
- package/app/Routes.ts +47 -0
- package/app/Scheduler.ts +28 -0
- package/app/Storefront/CartCookie.ts +109 -0
- package/package.json +3 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { db } from '@stacksjs/database'
|
|
3
|
+
import { response } from '@stacksjs/router'
|
|
4
|
+
import { randomUUIDv7 } from 'bun'
|
|
5
|
+
import { readCartCookie, writeCartCookie } from '../../Storefront/CartCookie'
|
|
6
|
+
|
|
7
|
+
const CART_COOKIE = 'stacks_cart'
|
|
8
|
+
const COOKIE_OPTS = {
|
|
9
|
+
path: '/',
|
|
10
|
+
httpOnly: true,
|
|
11
|
+
sameSite: 'lax' as const,
|
|
12
|
+
maxAge: 60 * 60 * 24 * 30, // 30 days
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Add a product to the shopper's cart.
|
|
17
|
+
*
|
|
18
|
+
* - Anonymous, cookie-keyed: a `stacks_cart` cookie holds a random
|
|
19
|
+
* `session_token` that points at a row in `carts`. We create the cart
|
|
20
|
+
* on first add so empty visitors don't litter the table.
|
|
21
|
+
* - Idempotent per product: if the same slug is already in the cart we
|
|
22
|
+
* bump the existing line's quantity instead of inserting a duplicate.
|
|
23
|
+
* - Form vs XHR: a normal HTML form post redirects to /cart so the
|
|
24
|
+
* back button works; XHR callers (the cart drawer) get JSON and
|
|
25
|
+
* stay on the current page.
|
|
26
|
+
*/
|
|
27
|
+
export default new Action({
|
|
28
|
+
name: 'AddToCartAction',
|
|
29
|
+
description: 'Add a product to the anonymous cart, keyed by cookie session.',
|
|
30
|
+
method: 'POST',
|
|
31
|
+
|
|
32
|
+
async handle(request: any) {
|
|
33
|
+
const slug = String(request.get('slug') ?? '').trim()
|
|
34
|
+
const quantity = Math.max(1, Math.min(99, Number(request.get('quantity') ?? 1) || 1))
|
|
35
|
+
|
|
36
|
+
if (!slug)
|
|
37
|
+
return response.json({ error: 'A product slug is required.' }, { status: 422 })
|
|
38
|
+
|
|
39
|
+
const product = await (db as any)
|
|
40
|
+
.selectFrom('products')
|
|
41
|
+
.where('slug', '=', slug)
|
|
42
|
+
.where('is_available', '=', 1)
|
|
43
|
+
.selectAll()
|
|
44
|
+
.executeTakeFirst()
|
|
45
|
+
|
|
46
|
+
if (!product)
|
|
47
|
+
return response.json({ error: `Product "${slug}" not found` }, { status: 404 })
|
|
48
|
+
|
|
49
|
+
// readCartCookie verifies the HMAC; tampered cookies return null
|
|
50
|
+
// and the action falls through to the "create new cart" branch as
|
|
51
|
+
// if no cookie were present. Legacy unsigned tokens are accepted
|
|
52
|
+
// once and re-signed on the next write below.
|
|
53
|
+
let token = readCartCookie(request, CART_COOKIE)
|
|
54
|
+
let cart = token
|
|
55
|
+
? await (db as any).selectFrom('carts').where('session_token', '=', token).selectAll().executeTakeFirst()
|
|
56
|
+
: null
|
|
57
|
+
|
|
58
|
+
if (!cart) {
|
|
59
|
+
token = randomUUIDv7()
|
|
60
|
+
await (db as any)
|
|
61
|
+
.insertInto('carts')
|
|
62
|
+
.values({
|
|
63
|
+
status: 'active',
|
|
64
|
+
total_items: 0,
|
|
65
|
+
subtotal: 0,
|
|
66
|
+
total: 0,
|
|
67
|
+
currency: 'USD',
|
|
68
|
+
session_token: token,
|
|
69
|
+
checkout_step: 'cart',
|
|
70
|
+
})
|
|
71
|
+
.execute()
|
|
72
|
+
// Re-read by session_token: bun-query-builder's `returningAll()`
|
|
73
|
+
// returns metadata (lastInsertRowid) on sqlite rather than the
|
|
74
|
+
// full row, so we look up by token to grab a real id we can use
|
|
75
|
+
// for the cart_items FK.
|
|
76
|
+
cart = await (db as any)
|
|
77
|
+
.selectFrom('carts')
|
|
78
|
+
.where('session_token', '=', token)
|
|
79
|
+
.selectAll()
|
|
80
|
+
.executeTakeFirst()
|
|
81
|
+
writeCartCookie(request, CART_COOKIE, token, COOKIE_OPTS)
|
|
82
|
+
}
|
|
83
|
+
else if (!(request.cookies?.get?.(CART_COOKIE) ?? '').includes('.')) {
|
|
84
|
+
// Re-sign legacy carts opportunistically so the next request
|
|
85
|
+
// round-trips with the HMAC variant. Cheap — one cookie set per
|
|
86
|
+
// legacy shopper, then never again.
|
|
87
|
+
writeCartCookie(request, CART_COOKIE, token!, COOKIE_OPTS)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Look for an existing line for the same product to bump quantity.
|
|
91
|
+
const existingItem = await (db as any)
|
|
92
|
+
.selectFrom('cart_items')
|
|
93
|
+
.where('cart_id', '=', cart.id)
|
|
94
|
+
.where('product_sku', '=', product.slug)
|
|
95
|
+
.selectAll()
|
|
96
|
+
.executeTakeFirst()
|
|
97
|
+
|
|
98
|
+
if (existingItem) {
|
|
99
|
+
const newQty = Number(existingItem.quantity) + quantity
|
|
100
|
+
await (db as any)
|
|
101
|
+
.updateTable('cart_items')
|
|
102
|
+
.set({
|
|
103
|
+
quantity: newQty,
|
|
104
|
+
total_price: newQty * Number(existingItem.unit_price),
|
|
105
|
+
})
|
|
106
|
+
.where('id', '=', existingItem.id)
|
|
107
|
+
.execute()
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
await (db as any)
|
|
111
|
+
.insertInto('cart_items')
|
|
112
|
+
.values({
|
|
113
|
+
cart_id: cart.id,
|
|
114
|
+
quantity,
|
|
115
|
+
unit_price: Number(product.price),
|
|
116
|
+
total_price: quantity * Number(product.price),
|
|
117
|
+
product_name: product.name,
|
|
118
|
+
product_sku: product.slug,
|
|
119
|
+
product_image: product.image_url,
|
|
120
|
+
})
|
|
121
|
+
.execute()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await recomputeCartTotals(cart.id)
|
|
125
|
+
|
|
126
|
+
// Browser form post → redirect; XHR (cart drawer) → JSON.
|
|
127
|
+
const wantsHtml = (request.headers?.get?.('accept') || '').includes('text/html')
|
|
128
|
+
const xhr = request.headers?.get?.('x-requested-with') === 'XMLHttpRequest'
|
|
129
|
+
const redirectTarget = String(request.get('redirect') ?? '/cart')
|
|
130
|
+
|
|
131
|
+
if (wantsHtml && !xhr)
|
|
132
|
+
return response.redirect(redirectTarget)
|
|
133
|
+
|
|
134
|
+
return response.json({ ok: true, cart_id: cart.id })
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
async function recomputeCartTotals(cartId: number): Promise<void> {
|
|
139
|
+
const items = await (db as any)
|
|
140
|
+
.selectFrom('cart_items')
|
|
141
|
+
.where('cart_id', '=', cartId)
|
|
142
|
+
.selectAll()
|
|
143
|
+
.execute()
|
|
144
|
+
|
|
145
|
+
const totalItems = items.reduce((s: number, i: any) => s + Number(i.quantity), 0)
|
|
146
|
+
const subtotal = items.reduce((s: number, i: any) => s + Number(i.total_price || 0), 0)
|
|
147
|
+
|
|
148
|
+
await (db as any)
|
|
149
|
+
.updateTable('carts')
|
|
150
|
+
.set({ total_items: totalItems, subtotal, total: subtotal })
|
|
151
|
+
.where('id', '=', cartId)
|
|
152
|
+
.execute()
|
|
153
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { db } from '@stacksjs/database'
|
|
3
|
+
import { response } from '@stacksjs/router'
|
|
4
|
+
import { readCartCookie } from '../../Storefront/CartCookie'
|
|
5
|
+
|
|
6
|
+
const CART_COOKIE = 'stacks_cart'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Step 1 of multi-step checkout. Persists the shopper's email on the
|
|
10
|
+
* cart row and bumps the cart's `checkout_step` so a refresh on the
|
|
11
|
+
* shipping page knows which step the shopper is actually on.
|
|
12
|
+
*/
|
|
13
|
+
export default new Action({
|
|
14
|
+
name: 'CheckoutContactAction',
|
|
15
|
+
description: 'Save the shopper email on the active cart.',
|
|
16
|
+
method: 'POST',
|
|
17
|
+
|
|
18
|
+
async handle(request: any) {
|
|
19
|
+
const email = String(request.get('email') ?? '').trim().toLowerCase()
|
|
20
|
+
if (!email || !email.includes('@'))
|
|
21
|
+
return response.json({ error: 'A valid email is required.' }, { status: 422 })
|
|
22
|
+
|
|
23
|
+
const token = readCartCookie(request, CART_COOKIE)
|
|
24
|
+
if (!token)
|
|
25
|
+
return response.json({ error: 'Your cart is empty.' }, { status: 400 })
|
|
26
|
+
|
|
27
|
+
const cart = await (db as any)
|
|
28
|
+
.selectFrom('carts')
|
|
29
|
+
.where('session_token', '=', token)
|
|
30
|
+
.where('status', '=', 'active')
|
|
31
|
+
.selectAll()
|
|
32
|
+
.executeTakeFirst()
|
|
33
|
+
|
|
34
|
+
if (!cart)
|
|
35
|
+
return response.json({ error: 'Your cart is empty.' }, { status: 400 })
|
|
36
|
+
|
|
37
|
+
await (db as any)
|
|
38
|
+
.updateTable('carts')
|
|
39
|
+
.set({ email, checkout_step: 'shipping' })
|
|
40
|
+
.where('id', '=', cart.id)
|
|
41
|
+
.execute()
|
|
42
|
+
|
|
43
|
+
const wantsHtml = (request.headers?.get?.('accept') || '').includes('text/html')
|
|
44
|
+
const xhr = request.headers?.get?.('x-requested-with') === 'XMLHttpRequest'
|
|
45
|
+
if (wantsHtml && !xhr)
|
|
46
|
+
return response.redirect('/checkout/shipping')
|
|
47
|
+
|
|
48
|
+
return response.json({ ok: true, next: '/checkout/shipping' })
|
|
49
|
+
},
|
|
50
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { db } from '@stacksjs/database'
|
|
3
|
+
import { response } from '@stacksjs/router'
|
|
4
|
+
import { readCartCookie } from '../../Storefront/CartCookie'
|
|
5
|
+
|
|
6
|
+
const CART_COOKIE = 'stacks_cart'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Step 2 of multi-step checkout. Saves shipping address fields on the
|
|
10
|
+
* cart and advances to the payment step.
|
|
11
|
+
*/
|
|
12
|
+
export default new Action({
|
|
13
|
+
name: 'CheckoutShippingAction',
|
|
14
|
+
description: 'Save shipping address on the active cart.',
|
|
15
|
+
method: 'POST',
|
|
16
|
+
|
|
17
|
+
async handle(request: any) {
|
|
18
|
+
const name = String(request.get('shipping_name') ?? '').trim()
|
|
19
|
+
const address = String(request.get('shipping_address') ?? '').trim()
|
|
20
|
+
const city = String(request.get('shipping_city') ?? '').trim()
|
|
21
|
+
const state = String(request.get('shipping_state') ?? '').trim()
|
|
22
|
+
const zip = String(request.get('shipping_zip') ?? '').trim()
|
|
23
|
+
|
|
24
|
+
if (!name || !address || !city || !state || !zip)
|
|
25
|
+
return response.json({ error: 'All shipping fields are required.' }, { status: 422 })
|
|
26
|
+
|
|
27
|
+
const token = readCartCookie(request, CART_COOKIE)
|
|
28
|
+
if (!token)
|
|
29
|
+
return response.json({ error: 'Your cart is empty.' }, { status: 400 })
|
|
30
|
+
|
|
31
|
+
const cart = await (db as any)
|
|
32
|
+
.selectFrom('carts')
|
|
33
|
+
.where('session_token', '=', token)
|
|
34
|
+
.where('status', '=', 'active')
|
|
35
|
+
.selectAll()
|
|
36
|
+
.executeTakeFirst()
|
|
37
|
+
|
|
38
|
+
if (!cart)
|
|
39
|
+
return response.json({ error: 'Your cart is empty.' }, { status: 400 })
|
|
40
|
+
|
|
41
|
+
await (db as any)
|
|
42
|
+
.updateTable('carts')
|
|
43
|
+
.set({
|
|
44
|
+
shipping_name: name,
|
|
45
|
+
shipping_address: address,
|
|
46
|
+
shipping_city: city,
|
|
47
|
+
shipping_state: state,
|
|
48
|
+
shipping_zip: zip,
|
|
49
|
+
checkout_step: 'payment',
|
|
50
|
+
})
|
|
51
|
+
.where('id', '=', cart.id)
|
|
52
|
+
.execute()
|
|
53
|
+
|
|
54
|
+
const wantsHtml = (request.headers?.get?.('accept') || '').includes('text/html')
|
|
55
|
+
const xhr = request.headers?.get?.('x-requested-with') === 'XMLHttpRequest'
|
|
56
|
+
if (wantsHtml && !xhr)
|
|
57
|
+
return response.redirect('/checkout/payment')
|
|
58
|
+
|
|
59
|
+
return response.json({ ok: true, next: '/checkout/payment' })
|
|
60
|
+
},
|
|
61
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { db } from '@stacksjs/database'
|
|
3
|
+
import { response } from '@stacksjs/router'
|
|
4
|
+
import { readCartCookie } from '../../Storefront/CartCookie'
|
|
5
|
+
|
|
6
|
+
const CART_COOKIE = 'stacks_cart'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read-only cart endpoint used by the slide-in CartDrawer to refresh
|
|
10
|
+
* its contents after an add/remove. Returns the same shape the drawer
|
|
11
|
+
* renders directly so the client doesn't have to do any reshaping.
|
|
12
|
+
*/
|
|
13
|
+
export default new Action({
|
|
14
|
+
name: 'GetCartAction',
|
|
15
|
+
description: 'Return the current cart for the caller cookie.',
|
|
16
|
+
method: 'GET',
|
|
17
|
+
|
|
18
|
+
async handle(request: any) {
|
|
19
|
+
// Verify HMAC; tampered cookies behave the same as no cookie at
|
|
20
|
+
// all — return an empty cart shell instead of looking up the
|
|
21
|
+
// forged token. Legacy unsigned tokens parse through.
|
|
22
|
+
const token = readCartCookie(request, CART_COOKIE)
|
|
23
|
+
if (!token)
|
|
24
|
+
return response.json({ items: [], subtotal: 0, total_items: 0 })
|
|
25
|
+
|
|
26
|
+
const cart = await (db as any)
|
|
27
|
+
.selectFrom('carts')
|
|
28
|
+
.where('session_token', '=', token)
|
|
29
|
+
.where('status', '=', 'active')
|
|
30
|
+
.selectAll()
|
|
31
|
+
.executeTakeFirst()
|
|
32
|
+
|
|
33
|
+
if (!cart)
|
|
34
|
+
return response.json({ items: [], subtotal: 0, total_items: 0 })
|
|
35
|
+
|
|
36
|
+
const rawItems = await (db as any)
|
|
37
|
+
.selectFrom('cart_items')
|
|
38
|
+
.where('cart_id', '=', cart.id)
|
|
39
|
+
.selectAll()
|
|
40
|
+
.execute()
|
|
41
|
+
|
|
42
|
+
const items = rawItems.map((row: any) => ({
|
|
43
|
+
id: row.id,
|
|
44
|
+
slug: row.product_sku,
|
|
45
|
+
name: row.product_name,
|
|
46
|
+
image: row.product_image,
|
|
47
|
+
qty: Number(row.quantity),
|
|
48
|
+
price: Number(row.unit_price),
|
|
49
|
+
lineTotal: Number(row.total_price || 0),
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
return response.json({
|
|
53
|
+
items,
|
|
54
|
+
total_items: Number(cart.total_items || 0),
|
|
55
|
+
subtotal: Number(cart.subtotal || 0),
|
|
56
|
+
})
|
|
57
|
+
},
|
|
58
|
+
})
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { config } from '@stacksjs/config'
|
|
3
|
+
import { db } from '@stacksjs/database'
|
|
4
|
+
import { response } from '@stacksjs/router'
|
|
5
|
+
import { sendOrderConfirmation } from '../../Mail/OrderConfirmation'
|
|
6
|
+
import { readCartCookie } from '../../Storefront/CartCookie'
|
|
7
|
+
import { totalsFor } from './_shipping'
|
|
8
|
+
|
|
9
|
+
const CART_COOKIE = 'stacks_cart'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Step 3 of multi-step checkout — the actual "place order" call.
|
|
13
|
+
*
|
|
14
|
+
* Pulls everything we collected in earlier steps off the cart row
|
|
15
|
+
* (email + shipping_*), creates a Customer if we don't have one for
|
|
16
|
+
* this email yet, materializes an Order + OrderItems, decrements
|
|
17
|
+
* inventory, marks the cart as `converted`, and clears the cookie so
|
|
18
|
+
* a refresh starts fresh.
|
|
19
|
+
*
|
|
20
|
+
* No real card is charged — a Stripe call would slot in between the
|
|
21
|
+
* cart lookup and the order creation. The mock card form on the
|
|
22
|
+
* payment view doesn't actually submit any sensitive data.
|
|
23
|
+
*/
|
|
24
|
+
export default new Action({
|
|
25
|
+
name: 'PlaceOrderAction',
|
|
26
|
+
description: 'Convert the active cart into an Order + OrderItems.',
|
|
27
|
+
method: 'POST',
|
|
28
|
+
|
|
29
|
+
async handle(request: any) {
|
|
30
|
+
const token = readCartCookie(request, CART_COOKIE)
|
|
31
|
+
if (!token)
|
|
32
|
+
return response.json({ error: 'Your cart is empty.' }, { status: 400 })
|
|
33
|
+
|
|
34
|
+
const cart = await (db as any)
|
|
35
|
+
.selectFrom('carts')
|
|
36
|
+
.where('session_token', '=', token)
|
|
37
|
+
.where('status', '=', 'active')
|
|
38
|
+
.selectAll()
|
|
39
|
+
.executeTakeFirst()
|
|
40
|
+
|
|
41
|
+
if (!cart)
|
|
42
|
+
return response.json({ error: 'Your cart is empty.' }, { status: 400 })
|
|
43
|
+
|
|
44
|
+
const items = await (db as any)
|
|
45
|
+
.selectFrom('cart_items')
|
|
46
|
+
.where('cart_id', '=', cart.id)
|
|
47
|
+
.selectAll()
|
|
48
|
+
.execute()
|
|
49
|
+
|
|
50
|
+
if (items.length === 0)
|
|
51
|
+
return response.json({ error: 'Your cart is empty.' }, { status: 400 })
|
|
52
|
+
|
|
53
|
+
if (!cart.email || !cart.shipping_name || !cart.shipping_address)
|
|
54
|
+
return response.json({ error: 'Checkout details are incomplete.' }, { status: 422 })
|
|
55
|
+
|
|
56
|
+
// One customer per email — repeat shoppers reuse the same row so
|
|
57
|
+
// the dashboard's customer total isn't inflated by checkouts.
|
|
58
|
+
let customer = await (db as any)
|
|
59
|
+
.selectFrom('customers')
|
|
60
|
+
.where('email', '=', cart.email)
|
|
61
|
+
.selectAll()
|
|
62
|
+
.executeTakeFirst()
|
|
63
|
+
|
|
64
|
+
if (!customer) {
|
|
65
|
+
await (db as any)
|
|
66
|
+
.insertInto('customers')
|
|
67
|
+
.values({ name: cart.shipping_name, email: cart.email })
|
|
68
|
+
.execute()
|
|
69
|
+
customer = await (db as any)
|
|
70
|
+
.selectFrom('customers')
|
|
71
|
+
.where('email', '=', cart.email)
|
|
72
|
+
.selectAll()
|
|
73
|
+
.executeTakeFirst()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const itemsSubtotal = items.reduce((s: number, i: any) => s + Number(i.total_price || 0), 0)
|
|
77
|
+
const { subtotal, shipping, total } = totalsFor(itemsSubtotal)
|
|
78
|
+
|
|
79
|
+
const fullAddress = [
|
|
80
|
+
cart.shipping_address,
|
|
81
|
+
[cart.shipping_city, cart.shipping_state, cart.shipping_zip].filter(Boolean).join(', '),
|
|
82
|
+
].filter(Boolean).join('\n')
|
|
83
|
+
|
|
84
|
+
await (db as any)
|
|
85
|
+
.insertInto('orders')
|
|
86
|
+
.values({
|
|
87
|
+
customer_id: customer.id,
|
|
88
|
+
status: 'paid',
|
|
89
|
+
order_type: 'shipping',
|
|
90
|
+
total_amount: total,
|
|
91
|
+
delivery_fee: shipping,
|
|
92
|
+
delivery_address: fullAddress,
|
|
93
|
+
})
|
|
94
|
+
.execute()
|
|
95
|
+
// Re-read latest order for this customer — RETURNING * doesn't
|
|
96
|
+
// round-trip the row reliably on sqlite via bun-query-builder.
|
|
97
|
+
const order = await (db as any)
|
|
98
|
+
.selectFrom('orders')
|
|
99
|
+
.where('customer_id', '=', customer.id)
|
|
100
|
+
.orderBy('id', 'desc')
|
|
101
|
+
.limit(1)
|
|
102
|
+
.selectAll()
|
|
103
|
+
.executeTakeFirst()
|
|
104
|
+
|
|
105
|
+
// Map cart_items → order_items, resolving product_id by SKU.
|
|
106
|
+
for (const item of items) {
|
|
107
|
+
const product = await (db as any)
|
|
108
|
+
.selectFrom('products')
|
|
109
|
+
.where('slug', '=', item.product_sku)
|
|
110
|
+
.selectAll()
|
|
111
|
+
.executeTakeFirst()
|
|
112
|
+
|
|
113
|
+
await (db as any)
|
|
114
|
+
.insertInto('order_items')
|
|
115
|
+
.values({
|
|
116
|
+
order_id: order.id,
|
|
117
|
+
product_id: product?.id ?? null,
|
|
118
|
+
quantity: Number(item.quantity),
|
|
119
|
+
price: Number(item.unit_price),
|
|
120
|
+
})
|
|
121
|
+
.execute()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Decrement inventory for what we just sold. Best-effort — we
|
|
125
|
+
// already accepted the order, so a stock-row miss shouldn't
|
|
126
|
+
// fail the response.
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
try {
|
|
129
|
+
const product = await (db as any)
|
|
130
|
+
.selectFrom('products')
|
|
131
|
+
.where('slug', '=', item.product_sku)
|
|
132
|
+
.selectAll()
|
|
133
|
+
.executeTakeFirst()
|
|
134
|
+
if (product) {
|
|
135
|
+
const next = Math.max(0, Number(product.inventory_count || 0) - Number(item.quantity))
|
|
136
|
+
await (db as any)
|
|
137
|
+
.updateTable('products')
|
|
138
|
+
.set({ inventory_count: next })
|
|
139
|
+
.where('id', '=', product.id)
|
|
140
|
+
.execute()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch { /* keep going */ }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await (db as any)
|
|
147
|
+
.updateTable('carts')
|
|
148
|
+
.set({ status: 'converted', checkout_step: 'placed' })
|
|
149
|
+
.where('id', '=', cart.id)
|
|
150
|
+
.execute()
|
|
151
|
+
|
|
152
|
+
request.cookies?.delete?.(CART_COOKIE, { path: '/' })
|
|
153
|
+
|
|
154
|
+
// Fire the receipt email asynchronously so a mailer hiccup
|
|
155
|
+
// never fails an otherwise-good checkout. The customer can still
|
|
156
|
+
// see their order on the post-checkout page even if the email
|
|
157
|
+
// never lands.
|
|
158
|
+
const baseUrl = (() => {
|
|
159
|
+
let raw = process.env.APP_URL || config.app.url || ''
|
|
160
|
+
if (raw && !/^https?:\/\//i.test(raw)) raw = `https://${raw}`
|
|
161
|
+
return raw.replace(/\/$/, '')
|
|
162
|
+
})()
|
|
163
|
+
sendOrderConfirmation({
|
|
164
|
+
to: cart.email,
|
|
165
|
+
orderId: order.id,
|
|
166
|
+
customerName: cart.shipping_name || customer.name,
|
|
167
|
+
items: items.map((row: any) => ({
|
|
168
|
+
name: row.product_name,
|
|
169
|
+
qty: Number(row.quantity),
|
|
170
|
+
lineTotal: Number(row.total_price || 0),
|
|
171
|
+
})),
|
|
172
|
+
subtotal,
|
|
173
|
+
shipping,
|
|
174
|
+
total,
|
|
175
|
+
shippingAddress: fullAddress,
|
|
176
|
+
orderUrl: `${baseUrl}/orders/${order.id}`,
|
|
177
|
+
}).catch((err: unknown) => {
|
|
178
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
179
|
+
console.error(`[PlaceOrderAction] order receipt email failed for order #${order.id}:`, message)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const wantsHtml = (request.headers?.get?.('accept') || '').includes('text/html')
|
|
183
|
+
const xhr = request.headers?.get?.('x-requested-with') === 'XMLHttpRequest'
|
|
184
|
+
if (wantsHtml && !xhr)
|
|
185
|
+
return response.redirect(`/orders/${order.id}`)
|
|
186
|
+
|
|
187
|
+
return response.json({
|
|
188
|
+
ok: true,
|
|
189
|
+
order_id: order.id,
|
|
190
|
+
total,
|
|
191
|
+
subtotal,
|
|
192
|
+
shipping,
|
|
193
|
+
})
|
|
194
|
+
},
|
|
195
|
+
})
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { db } from '@stacksjs/database'
|
|
3
|
+
import { rateLimit, response } from '@stacksjs/router'
|
|
4
|
+
import { readCartCookie } from '../../Storefront/CartCookie'
|
|
5
|
+
|
|
6
|
+
const CART_COOKIE = 'stacks_cart'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Public review-submission endpoint. Posted from `/orders/{id}` by
|
|
10
|
+
* the buyer after their order ships.
|
|
11
|
+
*
|
|
12
|
+
* Trust model:
|
|
13
|
+
* - Verifies the submitter actually owns the order by matching the
|
|
14
|
+
* `email` on the order against the `email` they're posting with,
|
|
15
|
+
* and additionally accepts a still-valid cart cookie path for
|
|
16
|
+
* guest shoppers who haven't enrolled in account auth.
|
|
17
|
+
* - Marks `is_verified_purchase=1` only when that email-on-order
|
|
18
|
+
* check passes.
|
|
19
|
+
* - Always inserts with `is_approved=0`. The dashboard's reviews
|
|
20
|
+
* index shows pending entries to admins for moderation.
|
|
21
|
+
*
|
|
22
|
+
* Spam handling: rate-limit per IP (5/min) so a hostile actor can't
|
|
23
|
+
* dump 1000 boilerplate reviews from one address. Honeypot field
|
|
24
|
+
* (`website`) catches drive-by bots — a non-empty value short-circuits
|
|
25
|
+
* with a fake-success response.
|
|
26
|
+
*/
|
|
27
|
+
export default new Action({
|
|
28
|
+
name: 'SubmitReviewAction',
|
|
29
|
+
description: 'Buyer-submitted review tied to a shipped order. Marked pending until moderated.',
|
|
30
|
+
method: 'POST',
|
|
31
|
+
|
|
32
|
+
async handle(request: any) {
|
|
33
|
+
await rateLimit('review-submit', 5).per('minute')
|
|
34
|
+
|
|
35
|
+
const orderId = Number(request.get('order_id') ?? 0)
|
|
36
|
+
const productSlug = String(request.get('product_slug') ?? '').trim()
|
|
37
|
+
const rating = Math.max(1, Math.min(5, Number(request.get('rating') ?? 0)))
|
|
38
|
+
const title = String(request.get('title') ?? '').trim()
|
|
39
|
+
const content = String(request.get('content') ?? '').trim()
|
|
40
|
+
const email = String(request.get('email') ?? '').trim().toLowerCase()
|
|
41
|
+
// Honeypot. Bots fill every field; a real form keeps this hidden
|
|
42
|
+
// and untouched. Pretend to succeed so the bot moves on without
|
|
43
|
+
// retrying with a different shape.
|
|
44
|
+
const honeypot = String(request.get('website') ?? '').trim()
|
|
45
|
+
if (honeypot)
|
|
46
|
+
return response.json({ success: true })
|
|
47
|
+
|
|
48
|
+
if (!orderId || !productSlug || !rating || !title || !content || !email)
|
|
49
|
+
return response.json({ success: false, message: 'All fields are required.' }, { status: 422 })
|
|
50
|
+
|
|
51
|
+
if (title.length > 100)
|
|
52
|
+
return response.json({ success: false, message: 'Title is too long.' }, { status: 422 })
|
|
53
|
+
if (content.length > 2000)
|
|
54
|
+
return response.json({ success: false, message: 'Review is too long.' }, { status: 422 })
|
|
55
|
+
|
|
56
|
+
// Verify the order + ownership. Two acceptable proofs of ownership:
|
|
57
|
+
// (a) email on the order row matches the submitted email
|
|
58
|
+
// (b) shopper still has the cart cookie that placed the order
|
|
59
|
+
// Either is enough — (a) is the common case for a returning buyer
|
|
60
|
+
// clicking the link in their receipt; (b) covers a buyer who never
|
|
61
|
+
// closes the post-checkout tab.
|
|
62
|
+
const order = await (db as any)
|
|
63
|
+
.selectFrom('orders')
|
|
64
|
+
.where('id', '=', orderId)
|
|
65
|
+
.selectAll()
|
|
66
|
+
.executeTakeFirst()
|
|
67
|
+
if (!order)
|
|
68
|
+
return response.json({ success: false, message: 'Order not found.' }, { status: 404 })
|
|
69
|
+
|
|
70
|
+
let ownsOrder = false
|
|
71
|
+
if (order.customer_id) {
|
|
72
|
+
const customer = await (db as any)
|
|
73
|
+
.selectFrom('customers')
|
|
74
|
+
.where('id', '=', order.customer_id)
|
|
75
|
+
.selectAll()
|
|
76
|
+
.executeTakeFirst()
|
|
77
|
+
if (customer && String(customer.email || '').toLowerCase() === email)
|
|
78
|
+
ownsOrder = true
|
|
79
|
+
}
|
|
80
|
+
if (!ownsOrder) {
|
|
81
|
+
const cookieToken = readCartCookie(request, CART_COOKIE)
|
|
82
|
+
if (cookieToken) {
|
|
83
|
+
const cart = await (db as any)
|
|
84
|
+
.selectFrom('carts')
|
|
85
|
+
.where('session_token', '=', cookieToken)
|
|
86
|
+
.selectAll()
|
|
87
|
+
.executeTakeFirst()
|
|
88
|
+
if (cart && cart.email && String(cart.email).toLowerCase() === email)
|
|
89
|
+
ownsOrder = true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!ownsOrder)
|
|
93
|
+
return response.json({ success: false, message: 'We could not match you to that order.' }, { status: 403 })
|
|
94
|
+
|
|
95
|
+
// Resolve the product row by slug. Reviews use `product_id` as a
|
|
96
|
+
// FK; the storefront submits the slug because it's what the URL
|
|
97
|
+
// and cart row already carry.
|
|
98
|
+
const product = await (db as any)
|
|
99
|
+
.selectFrom('products')
|
|
100
|
+
.where('slug', '=', productSlug)
|
|
101
|
+
.selectAll()
|
|
102
|
+
.executeTakeFirst()
|
|
103
|
+
if (!product)
|
|
104
|
+
return response.json({ success: false, message: 'Unknown product.' }, { status: 422 })
|
|
105
|
+
|
|
106
|
+
// Block duplicates per (order, product) pair so a curious shopper
|
|
107
|
+
// can't refresh-spam the form and clobber the moderation queue.
|
|
108
|
+
const dup = await (db as any)
|
|
109
|
+
.selectFrom('reviews')
|
|
110
|
+
.where('product_id', '=', product.id)
|
|
111
|
+
.where('order_id', '=', orderId)
|
|
112
|
+
.selectAll()
|
|
113
|
+
.executeTakeFirst()
|
|
114
|
+
if (dup)
|
|
115
|
+
return response.json({ success: false, message: 'You already submitted a review for this product on this order.' }, { status: 409 })
|
|
116
|
+
|
|
117
|
+
await (db as any)
|
|
118
|
+
.insertInto('reviews')
|
|
119
|
+
.values({
|
|
120
|
+
product_id: product.id,
|
|
121
|
+
customer_id: order.customer_id ?? null,
|
|
122
|
+
order_id: orderId,
|
|
123
|
+
rating,
|
|
124
|
+
title,
|
|
125
|
+
content,
|
|
126
|
+
is_verified_purchase: 1,
|
|
127
|
+
is_approved: 0,
|
|
128
|
+
is_featured: 0,
|
|
129
|
+
helpful_votes: 0,
|
|
130
|
+
unhelpful_votes: 0,
|
|
131
|
+
purchase_date: order.created_at || new Date().toISOString(),
|
|
132
|
+
})
|
|
133
|
+
.execute()
|
|
134
|
+
|
|
135
|
+
return response.json({
|
|
136
|
+
success: true,
|
|
137
|
+
message: "Thanks — your review is in. We'll publish it once a human gives it a quick look.",
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
})
|