@stacksjs/defaults 0.75.318 → 0.75.320
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/ai/skills/stacks-browse/SKILL.md +8 -0
- package/ai/skills/stacks-browse/scripts/browse.ts +16 -6
- package/ai/skills/stacks-browse/scripts/chrome.ts +4 -0
- package/ai/skills/stacks-database/SKILL.md +1 -1
- package/ai/skills/stacks-migrations/SKILL.md +2 -2
- package/ai/skills/stacks-native/CATALOG.md +6 -6
- package/app/Actions/Cms/AuthorUpdateAction.ts +4 -4
- package/app/Actions/Cms/CategorizableUpdateAction.ts +4 -4
- package/app/Actions/Cms/PostUpdateAction.ts +4 -5
- package/app/Actions/Cms/TaggableUpdateAction.ts +4 -2
- package/app/Actions/Commerce/CustomerStoreAction.ts +4 -6
- package/app/Actions/Commerce/CustomerUpdateAction.ts +4 -6
- package/app/Actions/Commerce/PaymentStoreAction.ts +5 -8
- package/app/Actions/Commerce/PaymentUpdateAction.ts +20 -17
- package/app/Actions/Dashboard/Commerce/commerce-product-records.test.ts +7 -0
- package/app/Actions/Dashboard/Commerce/commerce-product-records.ts +14 -3
- package/app/Actions/Dashboard/Commerce/review-records.test.ts +12 -0
- package/app/Actions/Dashboard/Commerce/review-records.ts +10 -5
- package/app/Actions/Dashboard/Content/AuthorStoreAction.ts +2 -0
- package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +4 -1
- package/app/Actions/Dashboard/Content/PageStoreAction.ts +2 -0
- package/app/Actions/Dashboard/Content/PostStoreAction.ts +2 -0
- package/app/Actions/Dashboard/Content/TagStoreAction.ts +2 -0
- package/app/Actions/Dashboard/Content/content-input.ts +9 -4
- package/app/Actions/Dashboard/Content/content-record-input.test.ts +20 -1
- package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +1 -1
- package/app/Actions/Dashboard/Marketing/ListIndexAction.ts +1 -1
- package/app/Actions/Dashboard/Models/ModelStoreAction.ts +5 -1
- package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +5 -1
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +1 -1
- package/app/Actions/Storefront/AddToCartAction.ts +5 -2
- package/app/Actions/Storefront/PlaceOrderAction.ts +6 -11
- package/app/Actions/Storefront/SubmitReviewAction.ts +6 -5
- package/app/Models/Activity.ts +1 -0
- package/app/Models/AnalyticsEvent.ts +1 -0
- package/app/Models/AutomationRun.ts +1 -1
- package/app/Models/BoardColumn.ts +0 -1
- package/app/Models/Campaign.ts +1 -1
- package/app/Models/CampaignSend.ts +3 -3
- package/app/Models/Card.ts +0 -1
- package/app/Models/CardComment.ts +0 -2
- package/app/Models/Content/Author.ts +2 -0
- package/app/Models/Content/Categorizable.ts +1 -0
- package/app/Models/Content/Menu.ts +2 -0
- package/app/Models/Content/MenuItem.ts +2 -0
- package/app/Models/Content/PageRevision.ts +2 -0
- package/app/Models/Content/Post.ts +2 -0
- package/app/Models/Content/Redirect.ts +2 -0
- package/app/Models/EmailListSubscriber.ts +2 -2
- package/app/Models/Error.ts +5 -0
- package/app/Models/Forms/Form.ts +2 -0
- package/app/Models/Forms/FormField.ts +2 -0
- package/app/Models/Forms/FormSubmission.ts +2 -0
- package/app/Models/Label.ts +0 -1
- package/app/Models/Log.ts +3 -1
- package/app/Models/MagicLinkToken.ts +0 -7
- package/app/Models/PaymentProduct.ts +1 -0
- package/app/Models/PaymentTransaction.ts +1 -0
- package/app/Models/Release.ts +3 -0
- package/app/Models/Request.ts +1 -0
- package/app/Models/SenderDomain.ts +2 -0
- package/app/Models/SmsOptOut.ts +0 -7
- package/app/Models/Subscription.ts +2 -0
- package/app/Models/commerce/Auction.ts +1 -0
- package/app/Models/commerce/AuctionItem.ts +1 -0
- package/app/Models/commerce/Cart.ts +83 -2
- package/app/Models/commerce/Category.ts +5 -1
- package/app/Models/commerce/Coupon.ts +4 -1
- package/app/Models/commerce/DigitalDelivery.ts +1 -0
- package/app/Models/commerce/GiftCard.ts +1 -0
- package/app/Models/commerce/LoyaltyPoint.ts +1 -0
- package/app/Models/commerce/LoyaltyReward.ts +3 -1
- package/app/Models/commerce/Order.ts +4 -1
- package/app/Models/commerce/OrderItem.ts +1 -0
- package/app/Models/commerce/Payment.ts +1 -0
- package/app/Models/commerce/Product.ts +15 -0
- package/app/Models/commerce/ProductVariant.ts +2 -0
- package/app/Models/commerce/Receipt.ts +1 -0
- package/app/Models/commerce/Review.ts +3 -1
- package/app/Models/commerce/Transaction.ts +1 -0
- package/app/Models/realtime/Websocket.ts +4 -1
- package/functions/dashboard-form.test.ts +10 -1
- package/functions/dashboard-form.ts +17 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +4 -4
- package/resources/components/Dashboard/Analytics/AnalyticsPageHeader.stx +1 -1
- package/resources/components/Dashboard/App/RequestDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +1 -1
- package/resources/components/Dashboard/Auth/ForgotPassword.stx +1 -1
- package/resources/components/Dashboard/Auth/Login.stx +1 -1
- package/resources/components/Dashboard/Auth/Register.stx +1 -1
- package/resources/components/Dashboard/Auth/ResetPassword.stx +1 -1
- package/resources/components/Dashboard/Auth/TwoFactorLogin.stx +1 -1
- package/resources/components/Dashboard/CodeEditor.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCategoriesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +4 -13
- package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCouponsTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceCustomersTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceGiftCardsTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceManufacturersTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceOrdersTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommercePaymentsTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommercePosCart.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommercePosCatalog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +27 -2
- package/resources/components/Dashboard/Commerce/CommerceProductsTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +7 -5
- package/resources/components/Dashboard/Commerce/CommerceReviewsDashboard.stx +12 -4
- package/resources/components/Dashboard/Commerce/CommerceReviewsTable.stx +3 -3
- package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitsTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/CourierDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/CourierDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/CouriersTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryPagination.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/PrintDevicesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/PrintLogsTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/ProductWaitlistTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistTable.stx +1 -1
- package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +1 -1
- package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +1 -1
- package/resources/components/Dashboard/Content/CreateFolderDialog.stx +1 -1
- package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +1 -1
- package/resources/components/Dashboard/Content/FileUploadDialog.stx +1 -1
- package/resources/components/Dashboard/Content/PageEditorDialog.stx +1 -1
- package/resources/components/Dashboard/Content/PostEditorDialog.stx +1 -1
- package/resources/components/Dashboard/Content/SeoEditorDialog.stx +1 -1
- package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +1 -1
- package/resources/components/Dashboard/CreateRecordModal.stx +1 -1
- package/resources/components/Dashboard/Deployments/DeploymentPreviewDialog.stx +1 -1
- package/resources/components/Dashboard/Elements/DropdownNavigation.stx +1 -1
- package/resources/components/Dashboard/Email/CapturedMailList.stx +1 -1
- package/resources/components/Dashboard/Email/EmailSettingsList.stx +1 -1
- package/resources/components/Dashboard/Email/EmailSettingsNavigation.stx +1 -1
- package/resources/components/Dashboard/Email/EmailSidebar.stx +1 -1
- package/resources/components/Dashboard/Email/InboxComposer.stx +1 -1
- package/resources/components/Dashboard/Email/InboxMessageDetail.stx +1 -1
- package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
- package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Infrastructure/CloudResourceButton.stx +1 -1
- package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +1 -1
- package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +1 -1
- package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +1 -1
- package/resources/components/Dashboard/Jobs/JobTable.stx +1 -1
- package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +1 -1
- package/resources/components/Dashboard/Management/AssignmentDrawer.stx +1 -1
- package/resources/components/Dashboard/Marketing/CampaignDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +1 -1
- package/resources/components/Dashboard/Marketing/CampaignDialog.stx +1 -1
- package/resources/components/Dashboard/Marketing/CampaignsTable.stx +1 -1
- package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +1 -1
- package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
- package/resources/components/Dashboard/Marketing/RecoveryCampaignDialog.stx +1 -1
- package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +1 -1
- package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +1 -1
- package/resources/components/Dashboard/Marketing/SocialPostsTable.stx +1 -1
- package/resources/components/Dashboard/MobileSidebar.stx +1 -1
- package/resources/components/Dashboard/Modals/BaseModal.stx +1 -1
- package/resources/components/Dashboard/Modals/Popups/Alert.stx +1 -1
- package/resources/components/Dashboard/Modals/Popups/Toast.stx +1 -1
- package/resources/components/Dashboard/Modals/ToastWrapper.stx +1 -1
- package/resources/components/Dashboard/Models/EditRecordModal.stx +1 -1
- package/resources/components/Dashboard/Models/ModelRecordActions.stx +1 -1
- package/resources/components/Dashboard/Monitoring/ErrorTable.stx +1 -1
- package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +1 -1
- package/resources/components/Dashboard/Notifications/NotificationDeliveryTable.stx +1 -1
- package/resources/components/Dashboard/Operations/RecoveryDestinationDialog.stx +1 -1
- package/resources/components/Dashboard/Operations/RecoveryPointTable.stx +1 -1
- package/resources/components/Dashboard/Operations/RecoveryPolicyDialog.stx +1 -1
- package/resources/components/Dashboard/Operations/RecoveryRestoreDialog.stx +1 -1
- package/resources/components/Dashboard/Operations/SchedulerTaskTable.stx +1 -1
- package/resources/components/Dashboard/UI/Checkbox.stx +1 -1
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +1 -1
- package/resources/components/Dashboard/UI/Drawer.stx +1 -1
- package/resources/components/Dashboard/UI/Dropdown.stx +1 -1
- package/resources/components/Dashboard/UI/Input.stx +1 -1
- package/resources/components/Dashboard/UI/Modal.stx +1 -1
- package/resources/components/Dashboard/UI/Pagination.stx +1 -1
- package/resources/components/Dashboard/UI/SearchFilter.stx +1 -1
- package/resources/components/Dashboard/UI/Select.stx +1 -1
- package/resources/components/Dashboard/UI/Table.stx +1 -1
- package/resources/components/Dashboard/UI/Tabs.stx +1 -1
- package/resources/components/Dashboard/UI/Textarea.stx +1 -1
- package/resources/components/Dashboard/UI/Toggle.stx +1 -1
- package/resources/components/Dashboard/UI/WindowControls.stx +1 -1
- package/resources/components/NativeActionSheet.stx +1 -1
- package/resources/components/NativeContextMenu.stx +1 -1
- package/resources/components/NativeHealthButton.stx +1 -1
- package/resources/components/NativePermissionButton.stx +1 -1
- package/resources/components/NativePullToRefresh.stx +1 -1
- package/resources/components/NativeSegmentedControl.stx +1 -1
- package/resources/components/NativeSheet.stx +1 -1
|
@@ -20,11 +20,16 @@ export function timestamp(): string {
|
|
|
20
20
|
return new Date().toISOString().slice(0, 19).replace('T', ' ')
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* The new row's id, across the driver shapes an insert answers with: the row
|
|
25
|
+
* itself from `returningAll()` (PostgreSQL, which reports no id at all without
|
|
26
|
+
* RETURNING, and gives a BIGSERIAL id back as a string), `lastInsertRowid`
|
|
27
|
+
* (SQLite) or `insertId` (MySQL). 0 when none of them names a row.
|
|
28
|
+
*/
|
|
24
29
|
export function insertedId(result: unknown): number {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
return Number(
|
|
30
|
+
const answer = (Array.isArray(result) ? result[0] : result) as { id?: unknown, lastInsertRowid?: unknown, insertId?: unknown } | undefined
|
|
31
|
+
const id = Number(answer?.id ?? answer?.lastInsertRowid ?? answer?.insertId ?? 0)
|
|
32
|
+
return Number.isSafeInteger(id) && id > 0 ? id : 0
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
/** The `{id}` route param, or 0 when it is not a usable row id. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { describe, expect, test } from 'bun:test'
|
|
3
|
-
import { rowId } from './content-input'
|
|
3
|
+
import { insertedId, rowId } from './content-input'
|
|
4
4
|
import { parseAuthorInput } from './author-input'
|
|
5
5
|
import { parsePageInput, parsePublished } from './page-input'
|
|
6
6
|
|
|
@@ -50,3 +50,22 @@ test('content identifiers use the shared safe-integer contract', () => {
|
|
|
50
50
|
for (const value of [true, {}, [], Number.MAX_SAFE_INTEGER + 1]) expect(rowId({ getParam: () => value } as unknown as RequestInstance)).toBe(0)
|
|
51
51
|
expect(rowId({ getParam: () => '42' } as unknown as RequestInstance)).toBe(42)
|
|
52
52
|
})
|
|
53
|
+
|
|
54
|
+
// What an insert answers with on each driver (stacksjs/stacks#3004).
|
|
55
|
+
describe('the id of an inserted content row', () => {
|
|
56
|
+
test('reads the row returningAll() gives back, with PostgreSQL\'s string id', () => {
|
|
57
|
+
expect(insertedId({ id: '64', name: 'Example' })).toBe(64)
|
|
58
|
+
expect(insertedId([{ id: 7 }])).toBe(7)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('reads SQLite\'s and MySQL\'s insert reports', () => {
|
|
62
|
+
expect(insertedId({ changes: 1, lastInsertRowid: 12 })).toBe(12)
|
|
63
|
+
expect(insertedId({ insertId: 9n })).toBe(9)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('is 0 when nothing names a row, as PostgreSQL answers without RETURNING', () => {
|
|
67
|
+
expect(insertedId([])).toBe(0)
|
|
68
|
+
expect(insertedId(undefined)).toBe(0)
|
|
69
|
+
expect(insertedId({ id: 'not-an-id' })).toBe(0)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -32,7 +32,7 @@ export default new Action({
|
|
|
32
32
|
db
|
|
33
33
|
.selectFrom('campaign_sends')
|
|
34
34
|
.select(['campaign_id', 'status', db.fn.count('id').as('count')])
|
|
35
|
-
.groupBy(
|
|
35
|
+
.groupBy('campaign_id', 'status')
|
|
36
36
|
.execute(),
|
|
37
37
|
db
|
|
38
38
|
.selectFrom('campaign_sends')
|
|
@@ -22,7 +22,7 @@ export default new Action({
|
|
|
22
22
|
db
|
|
23
23
|
.selectFrom('email_list_subscribers')
|
|
24
24
|
.select(['email_list_id', 'status', db.fn.count('id').as('count')])
|
|
25
|
-
.groupBy(
|
|
25
|
+
.groupBy('email_list_id', 'status')
|
|
26
26
|
.execute(),
|
|
27
27
|
db
|
|
28
28
|
.selectFrom('email_list_subscribers')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions/runtime'
|
|
3
|
-
import { toSnakeCaseKeys } from '@stacksjs/orm'
|
|
3
|
+
import { ModelValidationError, toSnakeCaseKeys } from '@stacksjs/orm'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
5
|
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
6
|
import { prepareModelFields, resolveWritableModel } from './model-write'
|
|
@@ -35,6 +35,10 @@ export default new Action({
|
|
|
35
35
|
return { ok: true, id }
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
|
+
// The model's own rules rejected the row: the caller's mistake, named
|
|
39
|
+
// field by field, not a server failure (stacksjs/stacks#3007).
|
|
40
|
+
if (error instanceof ModelValidationError)
|
|
41
|
+
return response.json({ message: 'Validation failed.', errors: error.errors }, 422)
|
|
38
42
|
return dashboardOperationalError(error, 'The model record could not be created.', 'ModelStoreAction', 500)
|
|
39
43
|
}
|
|
40
44
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions/runtime'
|
|
3
|
-
import { toSnakeCaseKeys } from '@stacksjs/orm'
|
|
3
|
+
import { ModelValidationError, toSnakeCaseKeys } from '@stacksjs/orm'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
5
|
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
6
|
import { parseRowId, prepareModelFields, resolveWritableModel } from './model-write'
|
|
@@ -55,6 +55,10 @@ export default new Action({
|
|
|
55
55
|
return { ok: true, id, changed: Object.keys(changes) }
|
|
56
56
|
}
|
|
57
57
|
catch (error) {
|
|
58
|
+
// The model's own rules rejected the row: the caller's mistake, named
|
|
59
|
+
// field by field, not a server failure (stacksjs/stacks#3007).
|
|
60
|
+
if (error instanceof ModelValidationError)
|
|
61
|
+
return response.json({ message: 'Validation failed.', errors: error.errors }, 422)
|
|
58
62
|
return dashboardOperationalError(error, 'The model record could not be updated.', 'ModelUpdateAction', 500)
|
|
59
63
|
}
|
|
60
64
|
},
|
|
@@ -42,7 +42,7 @@ async function notificationDeliveryOverview() {
|
|
|
42
42
|
db
|
|
43
43
|
.selectFrom('notification_deliveries')
|
|
44
44
|
.select(['channel', 'status', db.fn.count('id').as('count')])
|
|
45
|
-
.groupBy(
|
|
45
|
+
.groupBy('channel', 'status')
|
|
46
46
|
.execute() as unknown as Promise<ChannelStatusCountRow[]>,
|
|
47
47
|
db
|
|
48
48
|
.selectFrom('notification_deliveries')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions/runtime'
|
|
2
2
|
import { config } from '@stacksjs/config'
|
|
3
|
-
import { db } from '@stacksjs/database/runtime'
|
|
3
|
+
import { db, sqlDateTime } from '@stacksjs/database/runtime'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
5
|
import { randomUUIDv7 } from 'bun'
|
|
6
6
|
import { readCartCookie, writeCartCookie } from '../../Storefront/CartCookie'
|
|
@@ -41,7 +41,8 @@ export default new Action({
|
|
|
41
41
|
const product = await (db as any)
|
|
42
42
|
.selectFrom('products')
|
|
43
43
|
.where('slug', '=', slug)
|
|
44
|
-
|
|
44
|
+
// A boolean, not 1: PostgreSQL has no boolean = integer (#3005).
|
|
45
|
+
.where('is_available', '=', true)
|
|
45
46
|
.selectAll()
|
|
46
47
|
.executeTakeFirst()
|
|
47
48
|
|
|
@@ -70,6 +71,8 @@ export default new Action({
|
|
|
70
71
|
currency: storefrontCurrency(config.commerce?.currency),
|
|
71
72
|
session_token: token,
|
|
72
73
|
checkout_step: 'cart',
|
|
74
|
+
// The cart lasts as long as the cookie that finds it (#3017).
|
|
75
|
+
expires_at: sqlDateTime(new Date(Date.now() + COOKIE_OPTS.maxAge * 1000)),
|
|
73
76
|
})
|
|
74
77
|
.execute()
|
|
75
78
|
// Re-read by session_token: bun-query-builder's `returningAll()`
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { randomUUIDv7 } from 'bun'
|
|
1
2
|
import { Action } from '@stacksjs/actions/runtime'
|
|
3
|
+
import { customers } from '@stacksjs/commerce'
|
|
2
4
|
import { config } from '@stacksjs/config'
|
|
3
5
|
import { db } from '@stacksjs/database/runtime'
|
|
4
6
|
import { response } from '@stacksjs/router'
|
|
@@ -62,17 +64,10 @@ export default new Action({
|
|
|
62
64
|
.selectAll()
|
|
63
65
|
.executeTakeFirst()
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.execute()
|
|
70
|
-
customer = await (db as any)
|
|
71
|
-
.selectFrom('customers')
|
|
72
|
-
.where('email', '=', cart.email)
|
|
73
|
-
.selectAll()
|
|
74
|
-
.executeTakeFirst()
|
|
75
|
-
}
|
|
67
|
+
// Through commerce's store, which fills what the table requires and the
|
|
68
|
+
// checkout does not ask for, such as the avatar (#3017).
|
|
69
|
+
if (!customer)
|
|
70
|
+
customer = await customers.store({ name: cart.shipping_name, email: cart.email, uuid: randomUUIDv7() } as never)
|
|
76
71
|
|
|
77
72
|
// Integer minor units of the cart's currency, like every amount here.
|
|
78
73
|
const currency = storefrontCurrency(cart.currency)
|
|
@@ -14,9 +14,9 @@ const CART_COOKIE = 'stacks_cart'
|
|
|
14
14
|
* `email` on the order against the `email` they're posting with,
|
|
15
15
|
* and additionally accepts a still-valid cart cookie path for
|
|
16
16
|
* guest shoppers who haven't enrolled in account auth.
|
|
17
|
-
* - Marks `is_verified_purchase
|
|
17
|
+
* - Marks `is_verified_purchase` true only when that email-on-order
|
|
18
18
|
* check passes.
|
|
19
|
-
* - Always inserts with `is_approved
|
|
19
|
+
* - Always inserts with `is_approved` false. The dashboard's reviews
|
|
20
20
|
* index shows pending entries to admins for moderation.
|
|
21
21
|
*
|
|
22
22
|
* Spam handling: rate-limit per IP (5/min) so a hostile actor can't
|
|
@@ -123,9 +123,10 @@ export default new Action({
|
|
|
123
123
|
rating,
|
|
124
124
|
title,
|
|
125
125
|
content,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
// Booleans, which PostgreSQL requires of these columns (#3005).
|
|
127
|
+
is_verified_purchase: true,
|
|
128
|
+
is_approved: false,
|
|
129
|
+
is_featured: false,
|
|
129
130
|
helpful_votes: 0,
|
|
130
131
|
unhelpful_votes: 0,
|
|
131
132
|
purchase_date: order.created_at || new Date().toISOString(),
|
package/app/Models/Activity.ts
CHANGED
|
@@ -21,6 +21,6 @@ export default defineModel({
|
|
|
21
21
|
idempotencyKey: { required: true, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.string.uuid() },
|
|
22
22
|
startedAt: { required: false, fillable: true, validation: { rule: schema.timestamp() }, factory: () => null },
|
|
23
23
|
finishedAt: { required: false, fillable: true, validation: { rule: schema.timestamp() }, factory: () => null },
|
|
24
|
-
error: { required: false, fillable: true, validation: { rule: schema.string() }, factory: () => null },
|
|
24
|
+
error: { type: 'text', required: false, fillable: true, validation: { rule: schema.string() }, factory: () => null },
|
|
25
25
|
},
|
|
26
26
|
} as const)
|
package/app/Models/Campaign.ts
CHANGED
|
@@ -103,6 +103,7 @@ export default defineModel({
|
|
|
103
103
|
text: {
|
|
104
104
|
required: false,
|
|
105
105
|
fillable: true,
|
|
106
|
+
type: 'text',
|
|
106
107
|
validation: {
|
|
107
108
|
rule: schema.string(),
|
|
108
109
|
},
|
|
@@ -197,7 +198,6 @@ export default defineModel({
|
|
|
197
198
|
validation: {
|
|
198
199
|
rule: schema.number(),
|
|
199
200
|
},
|
|
200
|
-
factory: faker => faker.number.int({ min: 1, max: 8 }),
|
|
201
201
|
},
|
|
202
202
|
|
|
203
203
|
scheduledAt: {
|
|
@@ -17,6 +17,8 @@ export default defineModel({
|
|
|
17
17
|
],
|
|
18
18
|
|
|
19
19
|
traits: {
|
|
20
|
+
// So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
|
|
21
|
+
useSeeder: { count: 20 },
|
|
20
22
|
gdpr: { subject: { via: 'Subscriber' }, erasure: 'anonymize', basis: 'legitimate_interests', purpose: 'Campaign delivery statistics' },
|
|
21
23
|
useUuid: true,
|
|
22
24
|
useTimestamps: true,
|
|
@@ -37,7 +39,6 @@ export default defineModel({
|
|
|
37
39
|
validation: {
|
|
38
40
|
rule: schema.number(),
|
|
39
41
|
},
|
|
40
|
-
factory: faker => faker.number.int({ min: 1, max: 10 }),
|
|
41
42
|
},
|
|
42
43
|
|
|
43
44
|
subscriberId: {
|
|
@@ -46,7 +47,6 @@ export default defineModel({
|
|
|
46
47
|
validation: {
|
|
47
48
|
rule: schema.number(),
|
|
48
49
|
},
|
|
49
|
-
factory: () => null,
|
|
50
50
|
},
|
|
51
51
|
|
|
52
52
|
emailListId: {
|
|
@@ -55,7 +55,6 @@ export default defineModel({
|
|
|
55
55
|
validation: {
|
|
56
56
|
rule: schema.number(),
|
|
57
57
|
},
|
|
58
|
-
factory: () => null,
|
|
59
58
|
},
|
|
60
59
|
|
|
61
60
|
status: {
|
|
@@ -112,6 +111,7 @@ export default defineModel({
|
|
|
112
111
|
error: {
|
|
113
112
|
required: false,
|
|
114
113
|
fillable: true,
|
|
114
|
+
type: 'text',
|
|
115
115
|
validation: {
|
|
116
116
|
rule: schema.string(),
|
|
117
117
|
},
|
package/app/Models/Card.ts
CHANGED
|
@@ -47,14 +47,12 @@ export default defineModel({
|
|
|
47
47
|
order: 1,
|
|
48
48
|
fillable: true,
|
|
49
49
|
validation: { rule: schema.number().required() },
|
|
50
|
-
factory: () => 1,
|
|
51
50
|
},
|
|
52
51
|
|
|
53
52
|
userId: {
|
|
54
53
|
order: 2,
|
|
55
54
|
fillable: true,
|
|
56
55
|
validation: { rule: schema.number() },
|
|
57
|
-
factory: () => null,
|
|
58
56
|
},
|
|
59
57
|
|
|
60
58
|
body: {
|
|
@@ -16,6 +16,8 @@ export default defineModel({
|
|
|
16
16
|
],
|
|
17
17
|
|
|
18
18
|
traits: {
|
|
19
|
+
// So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
|
|
20
|
+
useSeeder: { count: 5 },
|
|
19
21
|
gdpr: { erasure: 'anonymize', basis: 'legitimate_interests', purpose: 'Byline on published content' },
|
|
20
22
|
useAuth: {
|
|
21
23
|
usePasskey: true,
|
|
@@ -18,6 +18,8 @@ export default defineModel({
|
|
|
18
18
|
belongsTo: ['EmailList', 'Subscriber'],
|
|
19
19
|
|
|
20
20
|
traits: {
|
|
21
|
+
// So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
|
|
22
|
+
useSeeder: { count: 20 },
|
|
21
23
|
gdpr: { subject: { via: 'Subscriber' }, erasure: 'delete', basis: 'consent', purpose: 'Mailing list membership' },
|
|
22
24
|
useUuid: true,
|
|
23
25
|
useTimestamps: true,
|
|
@@ -39,7 +41,6 @@ export default defineModel({
|
|
|
39
41
|
validation: {
|
|
40
42
|
rule: schema.number(),
|
|
41
43
|
},
|
|
42
|
-
factory: faker => faker.number.int({ min: 1, max: 8 }),
|
|
43
44
|
},
|
|
44
45
|
|
|
45
46
|
subscriberId: {
|
|
@@ -48,7 +49,6 @@ export default defineModel({
|
|
|
48
49
|
validation: {
|
|
49
50
|
rule: schema.number(),
|
|
50
51
|
},
|
|
51
|
-
factory: faker => faker.number.int({ min: 1, max: 100 }),
|
|
52
52
|
},
|
|
53
53
|
|
|
54
54
|
status: {
|
package/app/Models/Error.ts
CHANGED
|
@@ -15,6 +15,8 @@ export default defineModel({
|
|
|
15
15
|
],
|
|
16
16
|
|
|
17
17
|
traits: {
|
|
18
|
+
// So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
|
|
19
|
+
useSeeder: { count: 10 },
|
|
18
20
|
useTimestamps: true,
|
|
19
21
|
},
|
|
20
22
|
|
|
@@ -32,6 +34,7 @@ export default defineModel({
|
|
|
32
34
|
|
|
33
35
|
message: {
|
|
34
36
|
fillable: true,
|
|
37
|
+
type: 'text',
|
|
35
38
|
validation: {
|
|
36
39
|
rule: schema.string().required(),
|
|
37
40
|
message: {
|
|
@@ -43,6 +46,7 @@ export default defineModel({
|
|
|
43
46
|
|
|
44
47
|
stack: {
|
|
45
48
|
fillable: true,
|
|
49
|
+
type: 'text',
|
|
46
50
|
validation: {
|
|
47
51
|
rule: schema.string(),
|
|
48
52
|
message: {
|
|
@@ -86,6 +90,7 @@ export default defineModel({
|
|
|
86
90
|
|
|
87
91
|
additional_info: {
|
|
88
92
|
fillable: true,
|
|
93
|
+
type: 'text',
|
|
89
94
|
validation: {
|
|
90
95
|
rule: schema.string(),
|
|
91
96
|
message: {
|
package/app/Models/Forms/Form.ts
CHANGED
|
@@ -18,6 +18,8 @@ export default defineModel({
|
|
|
18
18
|
autoIncrement: true,
|
|
19
19
|
|
|
20
20
|
traits: {
|
|
21
|
+
// So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
|
|
22
|
+
useSeeder: { count: 10 },
|
|
21
23
|
gdpr: { subject: { email: 'email' }, erasure: 'anonymize', basis: 'consent', purpose: 'Form submissions' },
|
|
22
24
|
useUuid: true,
|
|
23
25
|
useTimestamps: true,
|
package/app/Models/Label.ts
CHANGED
package/app/Models/Log.ts
CHANGED
|
@@ -49,7 +49,9 @@ export default defineModel({
|
|
|
49
49
|
order: 1,
|
|
50
50
|
fillable: true,
|
|
51
51
|
validation: {
|
|
52
|
-
|
|
52
|
+
// Milliseconds since the epoch, so a bigint column, as on Websocket.time
|
|
53
|
+
// (stacksjs/stacks#2996).
|
|
54
|
+
rule: schema.unix().required(),
|
|
53
55
|
message: {
|
|
54
56
|
date: 'Timestamp must be a valid date',
|
|
55
57
|
},
|
|
@@ -15,13 +15,6 @@ export default defineModel({
|
|
|
15
15
|
primaryKey: 'id',
|
|
16
16
|
autoIncrement: true,
|
|
17
17
|
|
|
18
|
-
indexes: [
|
|
19
|
-
{
|
|
20
|
-
name: 'magic_link_tokens_token_unique',
|
|
21
|
-
columns: ['token'],
|
|
22
|
-
unique: true,
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
18
|
|
|
26
19
|
traits: {
|
|
27
20
|
gdpr: { erasure: 'delete', basis: 'contract', purpose: 'Passwordless sign-in links' },
|
package/app/Models/Release.ts
CHANGED
|
@@ -8,6 +8,8 @@ export default defineModel({
|
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
10
|
traits: {
|
|
11
|
+
// So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
|
|
12
|
+
useSeeder: { count: 5 },
|
|
11
13
|
useUuid: true,
|
|
12
14
|
useTimestamps: true,
|
|
13
15
|
useSearch: {
|
|
@@ -59,6 +61,7 @@ export default defineModel({
|
|
|
59
61
|
notes: {
|
|
60
62
|
order: 4,
|
|
61
63
|
fillable: true,
|
|
64
|
+
type: 'text',
|
|
62
65
|
validation: {
|
|
63
66
|
rule: schema.string(),
|
|
64
67
|
},
|
package/app/Models/Request.ts
CHANGED
|
@@ -6,6 +6,8 @@ export default defineModel({
|
|
|
6
6
|
table: 'sender_domains',
|
|
7
7
|
belongsTo: ['Team'],
|
|
8
8
|
traits: {
|
|
9
|
+
// So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
|
|
10
|
+
useSeeder: { count: 3 },
|
|
9
11
|
useUuid: true,
|
|
10
12
|
useTimestamps: true,
|
|
11
13
|
useApi: { uri: 'sender-domains', routes: ['index', 'store', 'show', 'update', 'destroy'], middleware: ['auth', 'team'] },
|
package/app/Models/SmsOptOut.ts
CHANGED
|
@@ -13,13 +13,6 @@ export default defineModel({
|
|
|
13
13
|
primaryKey: 'id',
|
|
14
14
|
autoIncrement: true,
|
|
15
15
|
|
|
16
|
-
indexes: [
|
|
17
|
-
{
|
|
18
|
-
name: 'sms_opt_outs_phone_unique',
|
|
19
|
-
columns: ['phone'],
|
|
20
|
-
unique: true,
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
16
|
|
|
24
17
|
traits: {
|
|
25
18
|
gdpr: { erasure: 'keep', basis: 'legal_obligation', purpose: 'Honouring SMS STOP replies, keyed by phone number' },
|
|
@@ -8,6 +8,8 @@ export default defineModel({
|
|
|
8
8
|
autoIncrement: true, // defaults to true
|
|
9
9
|
belongsTo: ['User'],
|
|
10
10
|
traits: {
|
|
11
|
+
// So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
|
|
12
|
+
useSeeder: { count: 10 },
|
|
11
13
|
gdpr: { erasure: 'keep', basis: 'legal_obligation', purpose: 'Billing records' },
|
|
12
14
|
useUuid: true,
|
|
13
15
|
},
|