@stacksjs/defaults 0.74.0 → 0.74.2
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/Models/AnalyticsEvent.ts +5 -0
- package/app/Models/Content/Menu.ts +5 -1
- package/app/Models/Content/MenuItem.ts +6 -1
- package/app/Models/Content/Page.ts +5 -1
- package/app/Models/Content/Post.ts +5 -1
- package/app/Models/Content/Redirect.ts +5 -1
- package/app/Models/EmailIdempotency.ts +5 -0
- package/app/Models/EmailList.ts +6 -0
- package/app/Models/EmailSuppression.ts +5 -0
- package/app/Models/EmailWebhookEvent.ts +5 -0
- package/app/Models/Forms/Form.ts +5 -1
- package/app/Models/Forms/FormField.ts +6 -1
- package/app/Models/MailPreference.ts +5 -0
- package/app/Models/Request.ts +5 -0
- package/app/Models/SiteDomain.ts +5 -1
- package/app/Models/Tag.ts +6 -0
- package/app/Models/Team.ts +6 -1
- package/app/Models/User.ts +6 -1
- package/app/Models/commerce/Auction.ts +6 -0
- package/app/Models/commerce/AuctionItem.ts +6 -0
- package/app/Models/commerce/Cart.ts +6 -1
- package/app/Models/commerce/CartItem.ts +6 -1
- package/app/Models/commerce/Category.ts +6 -0
- package/app/Models/commerce/Coupon.ts +6 -0
- package/app/Models/commerce/DeliveryRoute.ts +6 -1
- package/app/Models/commerce/DeliveryStop.ts +6 -1
- package/app/Models/commerce/GiftCard.ts +6 -1
- package/app/Models/commerce/LicenseKey.ts +6 -1
- package/app/Models/commerce/LoyaltyReward.ts +6 -0
- package/app/Models/commerce/Manufacturer.ts +6 -0
- package/app/Models/commerce/Order.ts +6 -1
- package/app/Models/commerce/Payment.ts +6 -1
- package/app/Models/commerce/PrintDevice.ts +6 -0
- package/app/Models/commerce/Product.ts +6 -0
- package/app/Models/commerce/ProductUnit.ts +6 -0
- package/app/Models/commerce/ProductVariant.ts +6 -0
- package/app/Models/commerce/Review.ts +6 -1
- package/app/Models/commerce/ShippingMethod.ts +6 -0
- package/app/Models/commerce/ShippingRate.ts +6 -0
- package/app/Models/commerce/ShippingZone.ts +6 -0
- package/app/Models/commerce/TaxRate.ts +6 -0
- package/app/Models/commerce/Transaction.ts +6 -1
- package/app/Models/commerce/WaitlistProduct.ts +6 -1
- package/app/Models/commerce/WaitlistRestaurant.ts +6 -1
- package/app/Models/realtime/Websocket.ts +5 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/project/storage/framework/tsconfig.app.json +4 -1
- package/resources/views/cms/blocks/form.stx +92 -1
- package/routes/forms.ts +46 -0
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// An infrastructure table: rows are written by the system, not on behalf of a
|
|
11
|
+
// caller, so no row has a per-caller owner to scope by. Writes are gated by
|
|
12
|
+
// `middleware` instead. Declared rather than left silent (stacksjs/stacks#2375).
|
|
13
|
+
ownership: false,
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, siteOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -20,6 +20,10 @@ export default defineModel({
|
|
|
20
20
|
},
|
|
21
21
|
],
|
|
22
22
|
|
|
23
|
+
// Owned through the site, which belongs to a team, so the owner is the set of
|
|
24
|
+
// site ids the caller's team owns rather than a single id (stacksjs/stacks#2375).
|
|
25
|
+
ownership: siteOwnership(),
|
|
26
|
+
|
|
23
27
|
traits: {
|
|
24
28
|
useTimestamps: true,
|
|
25
29
|
useApi: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, parentOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -11,6 +11,11 @@ export default defineModel({
|
|
|
11
11
|
primaryKey: 'id',
|
|
12
12
|
autoIncrement: true,
|
|
13
13
|
|
|
14
|
+
// No owner of its own: these rows are owned by whoever owns the menu's site, and so its team
|
|
15
|
+
// (stacksjs/stacks#2375). Resolved through the parent so it follows any change
|
|
16
|
+
// to how Menu decides ownership.
|
|
17
|
+
ownership: parentOwnership('Menu', 'menu_id'),
|
|
18
|
+
|
|
14
19
|
traits: {
|
|
15
20
|
useTimestamps: true,
|
|
16
21
|
useApi: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, siteOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -28,6 +28,10 @@ export default defineModel({
|
|
|
28
28
|
},
|
|
29
29
|
],
|
|
30
30
|
|
|
31
|
+
// Owned through the site, which belongs to a team, so the owner is the set of
|
|
32
|
+
// site ids the caller's team owns rather than a single id (stacksjs/stacks#2375).
|
|
33
|
+
ownership: siteOwnership(),
|
|
34
|
+
|
|
31
35
|
traits: {
|
|
32
36
|
useUuid: true,
|
|
33
37
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, siteOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,10 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// Owned through the site, which belongs to a team, so the owner is the set of
|
|
11
|
+
// site ids the caller's team owns rather than a single id (stacksjs/stacks#2375).
|
|
12
|
+
ownership: siteOwnership(),
|
|
13
|
+
|
|
10
14
|
traits: {
|
|
11
15
|
useUuid: true,
|
|
12
16
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, siteOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -21,6 +21,10 @@ export default defineModel({
|
|
|
21
21
|
},
|
|
22
22
|
],
|
|
23
23
|
|
|
24
|
+
// Owned through the site, which belongs to a team, so the owner is the set of
|
|
25
|
+
// site ids the caller's team owns rather than a single id (stacksjs/stacks#2375).
|
|
26
|
+
ownership: siteOwnership(),
|
|
27
|
+
|
|
24
28
|
traits: {
|
|
25
29
|
useTimestamps: true,
|
|
26
30
|
useApi: {
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// An infrastructure table: rows are written by the system, not on behalf of a
|
|
11
|
+
// caller, so no row has a per-caller owner to scope by. Writes are gated by
|
|
12
|
+
// `middleware` instead. Declared rather than left silent (stacksjs/stacks#2375).
|
|
13
|
+
ownership: false,
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useTimestamps: true,
|
|
12
17
|
useApi: {
|
package/app/Models/EmailList.ts
CHANGED
|
@@ -8,6 +8,12 @@ export default defineModel({
|
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
hasMany: ['EmailListSubscriber', 'Campaign', 'CampaignSend'],
|
|
10
10
|
|
|
11
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
12
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
13
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
14
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
15
|
+
ownership: false,
|
|
16
|
+
|
|
11
17
|
traits: {
|
|
12
18
|
useUuid: true,
|
|
13
19
|
useTimestamps: true,
|
|
@@ -15,6 +15,11 @@ export default defineModel({
|
|
|
15
15
|
},
|
|
16
16
|
],
|
|
17
17
|
|
|
18
|
+
// An infrastructure table: rows are written by the system, not on behalf of a
|
|
19
|
+
// caller, so no row has a per-caller owner to scope by. Writes are gated by
|
|
20
|
+
// `middleware` instead. Declared rather than left silent (stacksjs/stacks#2375).
|
|
21
|
+
ownership: false,
|
|
22
|
+
|
|
18
23
|
traits: {
|
|
19
24
|
useTimestamps: true,
|
|
20
25
|
useApi: {
|
|
@@ -15,6 +15,11 @@ export default defineModel({
|
|
|
15
15
|
},
|
|
16
16
|
],
|
|
17
17
|
|
|
18
|
+
// An infrastructure table: rows are written by the system, not on behalf of a
|
|
19
|
+
// caller, so no row has a per-caller owner to scope by. Writes are gated by
|
|
20
|
+
// `middleware` instead. Declared rather than left silent (stacksjs/stacks#2375).
|
|
21
|
+
ownership: false,
|
|
22
|
+
|
|
18
23
|
traits: {
|
|
19
24
|
useTimestamps: true,
|
|
20
25
|
useApi: {
|
package/app/Models/Forms/Form.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, siteOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -23,6 +23,10 @@ export default defineModel({
|
|
|
23
23
|
},
|
|
24
24
|
],
|
|
25
25
|
|
|
26
|
+
// Owned through the site, which belongs to a team, so the owner is the set of
|
|
27
|
+
// site ids the caller's team owns rather than a single id (stacksjs/stacks#2375).
|
|
28
|
+
ownership: siteOwnership(),
|
|
29
|
+
|
|
26
30
|
traits: {
|
|
27
31
|
useUuid: true,
|
|
28
32
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, parentOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -21,6 +21,11 @@ export default defineModel({
|
|
|
21
21
|
},
|
|
22
22
|
],
|
|
23
23
|
|
|
24
|
+
// No owner of its own: these rows are owned by whoever owns the form's site, and so its team
|
|
25
|
+
// (stacksjs/stacks#2375). Resolved through the parent so it follows any change
|
|
26
|
+
// to how Form decides ownership.
|
|
27
|
+
ownership: parentOwnership('Form', 'form_id'),
|
|
28
|
+
|
|
24
29
|
traits: {
|
|
25
30
|
useTimestamps: true,
|
|
26
31
|
useApi: {
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// An infrastructure table: rows are written by the system, not on behalf of a
|
|
11
|
+
// caller, so no row has a per-caller owner to scope by. Writes are gated by
|
|
12
|
+
// `middleware` instead. Declared rather than left silent (stacksjs/stacks#2375).
|
|
13
|
+
ownership: false,
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useTimestamps: true,
|
|
12
17
|
useApi: {
|
package/app/Models/Request.ts
CHANGED
|
@@ -22,6 +22,11 @@ export default defineModel({
|
|
|
22
22
|
},
|
|
23
23
|
],
|
|
24
24
|
|
|
25
|
+
// An infrastructure table: rows are written by the system, not on behalf of a
|
|
26
|
+
// caller, so no row has a per-caller owner to scope by. Writes are gated by
|
|
27
|
+
// `middleware` instead. Declared rather than left silent (stacksjs/stacks#2375).
|
|
28
|
+
ownership: false,
|
|
29
|
+
|
|
25
30
|
traits: {
|
|
26
31
|
useTimestamps: true,
|
|
27
32
|
useSoftDeletes: true,
|
package/app/Models/SiteDomain.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, siteOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -14,6 +14,10 @@ export default defineModel({
|
|
|
14
14
|
primaryKey: 'id',
|
|
15
15
|
autoIncrement: true,
|
|
16
16
|
|
|
17
|
+
// Owned through the site, which belongs to a team, so the owner is the set of
|
|
18
|
+
// site ids the caller's team owns rather than a single id (stacksjs/stacks#2375).
|
|
19
|
+
ownership: siteOwnership(),
|
|
20
|
+
|
|
17
21
|
traits: {
|
|
18
22
|
useTimestamps: true,
|
|
19
23
|
observe: true, // domain changes clear the host-resolution cache
|
package/app/Models/Tag.ts
CHANGED
|
@@ -25,6 +25,12 @@ export default defineModel({
|
|
|
25
25
|
primaryKey: 'id',
|
|
26
26
|
autoIncrement: true,
|
|
27
27
|
|
|
28
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
29
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
30
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
31
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
32
|
+
ownership: false,
|
|
33
|
+
|
|
28
34
|
traits: {
|
|
29
35
|
useUuid: true,
|
|
30
36
|
useTimestamps: true,
|
package/app/Models/Team.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, teamMembershipOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// The owner column is the primary key: a caller may write the team they are
|
|
11
|
+
// actually a member of, resolved by the same auth path every other
|
|
12
|
+
// team-scoped model uses (stacksjs/stacks#2375).
|
|
13
|
+
ownership: teamMembershipOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
package/app/Models/User.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Attributes } from '@stacksjs/types'
|
|
2
|
-
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { defineModel, selfOwnership } from '@stacksjs/orm'
|
|
3
3
|
import { makeHash } from '@stacksjs/security'
|
|
4
4
|
// soon, these will be auto-imported
|
|
5
5
|
import { schema } from '@stacksjs/validation'
|
|
@@ -19,6 +19,11 @@ export default defineModel({
|
|
|
19
19
|
},
|
|
20
20
|
],
|
|
21
21
|
|
|
22
|
+
// A caller may write their own row and no other (stacksjs/stacks#2375). The
|
|
23
|
+
// owner column IS the primary key here, so without this `User` is the worst
|
|
24
|
+
// case the issue describes: an authenticated caller able to PATCH any user.
|
|
25
|
+
ownership: selfOwnership(),
|
|
26
|
+
|
|
22
27
|
traits: {
|
|
23
28
|
useAuth: {
|
|
24
29
|
usePasskey: true,
|
|
@@ -17,6 +17,12 @@ export default defineModel({
|
|
|
17
17
|
primaryKey: 'id',
|
|
18
18
|
autoIncrement: true,
|
|
19
19
|
|
|
20
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
21
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
22
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
23
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
24
|
+
ownership: false,
|
|
25
|
+
|
|
20
26
|
traits: {
|
|
21
27
|
useUuid: true,
|
|
22
28
|
useTimestamps: true,
|
|
@@ -13,6 +13,12 @@ export default defineModel({
|
|
|
13
13
|
primaryKey: 'id',
|
|
14
14
|
autoIncrement: true,
|
|
15
15
|
|
|
16
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
17
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
18
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
19
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
20
|
+
ownership: false,
|
|
21
|
+
|
|
16
22
|
traits: {
|
|
17
23
|
useUuid: true,
|
|
18
24
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { customerOwnership, defineModel } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// Rows belong to the caller's customer record, one hop from the user
|
|
11
|
+
// (stacksjs/stacks#2375). Without this the generated writes are reachable by
|
|
12
|
+
// any authenticated caller for any row.
|
|
13
|
+
ownership: customerOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, parentOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// No owner of its own: these rows are owned by whoever owns the cart's customer
|
|
11
|
+
// (stacksjs/stacks#2375). Resolved through the parent so it follows any change
|
|
12
|
+
// to how Cart decides ownership.
|
|
13
|
+
ownership: parentOwnership('Cart', 'cart_id'),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -8,6 +8,12 @@ export default defineModel({
|
|
|
8
8
|
primaryKey: 'id',
|
|
9
9
|
autoIncrement: true,
|
|
10
10
|
|
|
11
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
12
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
13
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
14
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
15
|
+
ownership: false,
|
|
16
|
+
|
|
11
17
|
traits: {
|
|
12
18
|
useUuid: true,
|
|
13
19
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, parentOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// No owner of its own: these rows are owned by whoever owns the courier, who is a user
|
|
11
|
+
// (stacksjs/stacks#2375). Resolved through the parent so it follows any change
|
|
12
|
+
// to how Courier decides ownership.
|
|
13
|
+
ownership: parentOwnership('Courier', 'courier_id'),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, parentOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -18,6 +18,11 @@ export default defineModel({
|
|
|
18
18
|
primaryKey: 'id',
|
|
19
19
|
autoIncrement: true,
|
|
20
20
|
|
|
21
|
+
// No owner of its own: these rows are owned by whoever owns the order's customer
|
|
22
|
+
// (stacksjs/stacks#2375). Resolved through the parent so it follows any change
|
|
23
|
+
// to how Order decides ownership.
|
|
24
|
+
ownership: parentOwnership('Order', 'order_id'),
|
|
25
|
+
|
|
21
26
|
traits: {
|
|
22
27
|
useUuid: true,
|
|
23
28
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { customerOwnership, defineModel } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// Rows belong to the caller's customer record, one hop from the user
|
|
11
|
+
// (stacksjs/stacks#2375). Without this the generated writes are reachable by
|
|
12
|
+
// any authenticated caller for any row.
|
|
13
|
+
ownership: customerOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { customerOwnership, defineModel } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// Rows belong to the caller's customer record, one hop from the user
|
|
11
|
+
// (stacksjs/stacks#2375). Without this the generated writes are reachable by
|
|
12
|
+
// any authenticated caller for any row.
|
|
13
|
+
ownership: customerOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { customerOwnership, defineModel } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// Rows belong to the caller's customer record, one hop from the user
|
|
11
|
+
// (stacksjs/stacks#2375). Without this the generated writes are reachable by
|
|
12
|
+
// any authenticated caller for any row.
|
|
13
|
+
ownership: customerOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { customerOwnership, defineModel } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// Rows belong to the caller's customer record, one hop from the user
|
|
11
|
+
// (stacksjs/stacks#2375). Without this the generated writes are reachable by
|
|
12
|
+
// any authenticated caller for any row.
|
|
13
|
+
ownership: customerOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
hasMany: ['Receipt'],
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { customerOwnership, defineModel } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// Rows belong to the caller's customer record, one hop from the user
|
|
11
|
+
// (stacksjs/stacks#2375). Without this the generated writes are reachable by
|
|
12
|
+
// any authenticated caller for any row.
|
|
13
|
+
ownership: customerOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -7,6 +7,12 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// A reference table: no row here has a per-caller owner, so there is nothing
|
|
11
|
+
// to scope by and writes are an administrative concern gated by `middleware`.
|
|
12
|
+
// Declared rather than left silent so `security.api.rowScoping: 'deny'` can
|
|
13
|
+
// tell "considered" from "nobody thought about it" (stacksjs/stacks#2375).
|
|
14
|
+
ownership: false,
|
|
15
|
+
|
|
10
16
|
traits: {
|
|
11
17
|
useUuid: true,
|
|
12
18
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { defineModel, parentOwnership } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// No owner of its own: these rows are owned by whoever owns the order's customer
|
|
11
|
+
// (stacksjs/stacks#2375). Resolved through the parent so it follows any change
|
|
12
|
+
// to how Order decides ownership.
|
|
13
|
+
ownership: parentOwnership('Order', 'order_id'),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { customerOwnership, defineModel } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
belongsTo: ['Product', 'Customer'],
|
|
10
|
+
// Rows belong to the caller's customer record, one hop from the user
|
|
11
|
+
// (stacksjs/stacks#2375). Without this the generated writes are reachable by
|
|
12
|
+
// any authenticated caller for any row.
|
|
13
|
+
ownership: customerOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineModel } from '@stacksjs/orm'
|
|
1
|
+
import { customerOwnership, defineModel } from '@stacksjs/orm'
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
export default defineModel({
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
belongsTo: ['Customer'],
|
|
10
|
+
// Rows belong to the caller's customer record, one hop from the user
|
|
11
|
+
// (stacksjs/stacks#2375). Without this the generated writes are reachable by
|
|
12
|
+
// any authenticated caller for any row.
|
|
13
|
+
ownership: customerOwnership(),
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useUuid: true,
|
|
12
17
|
useTimestamps: true,
|
|
@@ -7,6 +7,11 @@ export default defineModel({
|
|
|
7
7
|
primaryKey: 'id',
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
|
+
// An infrastructure table: rows are written by the system, not on behalf of a
|
|
11
|
+
// caller, so no row has a per-caller owner to scope by. Writes are gated by
|
|
12
|
+
// `middleware` instead. Declared rather than left silent (stacksjs/stacks#2375).
|
|
13
|
+
ownership: false,
|
|
14
|
+
|
|
10
15
|
traits: {
|
|
11
16
|
useTimestamps: true,
|
|
12
17
|
useSeeder: {
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.2",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@iconify-json/f7": "^1.2.2",
|
|
56
56
|
"@iconify-json/hugeicons": "^1.2.27",
|
|
57
|
-
"@stacksjs/mobile": "^0.74.
|
|
57
|
+
"@stacksjs/mobile": "^0.74.2",
|
|
58
58
|
"@stacksjs/sanitizer": "^0.2.113",
|
|
59
59
|
"ts-qr-codes": "^0.1.8"
|
|
60
60
|
}
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
"compilerOptions": {
|
|
7
7
|
"lib": ["esnext", "dom"],
|
|
8
8
|
"rootDir": "../..",
|
|
9
|
-
|
|
9
|
+
// Not incremental, for the reason spelled out in tsconfig.app.json: the
|
|
10
|
+
// cache reports a false green after the auto-import barrels are
|
|
11
|
+
// regenerated. stacksjs/stacks#2405.
|
|
12
|
+
"incremental": false,
|
|
10
13
|
"paths": {
|
|
11
14
|
"Actions/*": ["../../app/Actions/*"],
|
|
12
15
|
"Commands/*": ["../../app/Commands/*"],
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
const sending = state(false)
|
|
19
19
|
const done = state('')
|
|
20
20
|
const openedAt = Date.now()
|
|
21
|
+
// Field names with an upload in flight, so each file input can disable
|
|
22
|
+
// itself and the submit button can wait for all of them.
|
|
23
|
+
const uploading = reactive<Record<string, boolean>>({})
|
|
24
|
+
// The chosen file's name per field, so the person can see what was attached
|
|
25
|
+
// rather than only the storage path the server answered with.
|
|
26
|
+
const attached = reactive<Record<string, string>>({})
|
|
21
27
|
|
|
22
28
|
effect(() => {
|
|
23
29
|
if (!host() || definition())
|
|
@@ -52,10 +58,87 @@
|
|
|
52
58
|
return conditions.action === 'show' ? matched : !matched
|
|
53
59
|
}
|
|
54
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Upload one file and record the storage path it returns.
|
|
63
|
+
*
|
|
64
|
+
* The submitted value for a `file` field is that path, not the file: the
|
|
65
|
+
* server checks it sits under this form's upload prefix before accepting the
|
|
66
|
+
* submission, so a path this endpoint did not issue is rejected.
|
|
67
|
+
*
|
|
68
|
+
* Size and type are enforced server-side from `config/forms.ts`; `accept` on
|
|
69
|
+
* the input is only a picker convenience, since a file dialog filter is a
|
|
70
|
+
* suggestion and not a check.
|
|
71
|
+
*/
|
|
72
|
+
async function uploadFile(field: PublicFormField, event: Event): Promise<void> {
|
|
73
|
+
const input = event.target as HTMLInputElement
|
|
74
|
+
const file = input.files?.[0]
|
|
75
|
+
if (!file)
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
uploading[field.name] = true
|
|
79
|
+
errors.set({ ...errors(), [field.name]: '' })
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const uuid = host()?.dataset.formUuid
|
|
83
|
+
const payload = new FormData()
|
|
84
|
+
payload.append('field', field.name)
|
|
85
|
+
payload.append('file', file)
|
|
86
|
+
|
|
87
|
+
const reply = await fetch(`/api/forms/${uuid}/uploads`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
credentials: 'same-origin',
|
|
90
|
+
body: payload,
|
|
91
|
+
})
|
|
92
|
+
const body = await reply.json().catch(() => ({}))
|
|
93
|
+
|
|
94
|
+
if (!reply.ok) {
|
|
95
|
+
// Clear the input as well as the value: leaving a rejected file
|
|
96
|
+
// showing next to its own error message reads as though it was taken.
|
|
97
|
+
input.value = ''
|
|
98
|
+
delete values[field.name]
|
|
99
|
+
delete attached[field.name]
|
|
100
|
+
errors.set({ ...errors(), [field.name]: body.message ?? 'That file could not be uploaded.' })
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
values[field.name] = body.path
|
|
105
|
+
attached[field.name] = file.name
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
input.value = ''
|
|
109
|
+
errors.set({ ...errors(), [field.name]: 'That file could not be uploaded.' })
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
uploading[field.name] = false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The `accept` attribute for a field's picker.
|
|
118
|
+
*
|
|
119
|
+
* A convenience only - a file dialog filter is a suggestion, and the server
|
|
120
|
+
* enforces the real allowlist from `config/forms.ts`. Built here rather than
|
|
121
|
+
* inline in the template so the extension parameter has a type.
|
|
122
|
+
*/
|
|
123
|
+
function acceptAttr(field: PublicFormField): string {
|
|
124
|
+
return (field.options.accept ?? []).map((extension: string) => `.${extension}`).join(',')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** True while any upload is still in flight. */
|
|
128
|
+
function anyUploading(): boolean {
|
|
129
|
+
return Object.values(uploading).some(Boolean)
|
|
130
|
+
}
|
|
131
|
+
|
|
55
132
|
async function submit(event: Event): Promise<void> {
|
|
56
133
|
event.preventDefault()
|
|
57
134
|
if (sending())
|
|
58
135
|
return
|
|
136
|
+
// A submission sent mid-upload would carry no path for that field and read
|
|
137
|
+
// as a missing required answer, which is not what happened.
|
|
138
|
+
if (anyUploading()) {
|
|
139
|
+
errors.set({ _form: 'Please wait for the upload to finish.' })
|
|
140
|
+
return
|
|
141
|
+
}
|
|
59
142
|
sending.set(true)
|
|
60
143
|
errors.set({})
|
|
61
144
|
|
|
@@ -119,6 +202,14 @@
|
|
|
119
202
|
<input type="checkbox" @change="values[field.name] = $event.target.checked" class="h-4 w-4 rounded">
|
|
120
203
|
{{ field.options.placeholder ?? 'Yes' }}
|
|
121
204
|
</label>
|
|
205
|
+
{{-- A file field used to fall through to the text input below, so
|
|
206
|
+
a person typed prose into what should have been a picker and
|
|
207
|
+
the submission could never satisfy it. --}}
|
|
208
|
+
<div :else-if="field.type === 'file'" class="mt-1.5">
|
|
209
|
+
<input type="file" :required="field.required && !values[field.name]" :disabled="uploading[field.name]" accept="{{ acceptAttr(field) }}" @change="uploadFile(field, $event)" class="block w-full text-[15px] file:mr-3 file:rounded-lg file:border-0 file:bg-gray-100 file:px-3.5 file:py-2 file:text-[14px] file:font-medium">
|
|
210
|
+
<p :if="uploading[field.name]" class="mt-1.5 text-[13px] text-gray-500">Uploading...</p>
|
|
211
|
+
<p :else-if="attached[field.name]" class="mt-1.5 text-[13px] text-green-700">Attached {{ attached[field.name] }}</p>
|
|
212
|
+
</div>
|
|
122
213
|
<input :else type="{{ field.type === 'email' ? 'email' : (field.type === 'phone' ? 'tel' : (field.type === 'date' ? 'date' : (field.type === 'currency' ? 'number' : 'text'))) }}" :required="field.required" placeholder="{{ field.options.placeholder ?? '' }}" @input="values[field.name] = field.type === 'currency' ? Math.round(Number($event.target.value) * 100) : $event.target.value" class="mt-1.5 w-full rounded-lg border border-gray-300 px-3.5 py-2.5 text-[15px]">
|
|
123
214
|
<p :if="errors()[field.name]" class="mt-1.5 text-[13px] text-red-600">{{ errors()[field.name] }}</p>
|
|
124
215
|
</template>
|
|
@@ -128,7 +219,7 @@
|
|
|
128
219
|
|
|
129
220
|
<input type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true" class="hidden" @input="values._hp = $event.target.value">
|
|
130
221
|
|
|
131
|
-
<button type="submit" :disabled="sending()" class="mt-7 rounded-lg px-6 py-3 font-medium bg-gray-900 text-white transition-transform active:scale-[0.98] disabled:opacity-60">
|
|
222
|
+
<button type="submit" :disabled="sending() || anyUploading()" class="mt-7 rounded-lg px-6 py-3 font-medium bg-gray-900 text-white transition-transform active:scale-[0.98] disabled:opacity-60">
|
|
132
223
|
{{ sending() ? 'Sending...' : (definition()?.submitLabel ?? 'Submit') }}
|
|
133
224
|
</button>
|
|
134
225
|
</form>
|
package/routes/forms.ts
CHANGED
|
@@ -35,6 +35,52 @@ route.get('/api/forms/{uuid}', async (request: any) => {
|
|
|
35
35
|
return response.json(publicDefinition(form))
|
|
36
36
|
}).rateLimit(60, 'minute')
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* File uploads for a form's `file` fields.
|
|
40
|
+
*
|
|
41
|
+
* Proxied through the server rather than presigned, because this is where the
|
|
42
|
+
* ceilings in `config/forms.ts` can actually be applied: a presigned policy can
|
|
43
|
+
* express a size cap but not a real type check (S3 sees only the Content-Type
|
|
44
|
+
* the client declared), and a proxied upload works on a local disk too. A
|
|
45
|
+
* public form is not a bulk-upload surface. stacksjs/stacks#2406.
|
|
46
|
+
*
|
|
47
|
+
* The stored path is returned for the submit step, which checks it sits under
|
|
48
|
+
* this form's prefix before accepting it.
|
|
49
|
+
*/
|
|
50
|
+
route.post('/api/forms/{uuid}/uploads', async (request: any) => {
|
|
51
|
+
const { checkUpload, fileFieldNamed, formUploadPrefix, loadFormByUuid, resolveUploadLimits } = await import('@stacksjs/forms')
|
|
52
|
+
|
|
53
|
+
const form = await loadFormByUuid(String(request.params?.uuid ?? request.param?.('uuid') ?? ''), await siteIdForRequest(request))
|
|
54
|
+
if (!form || form.status === 'draft')
|
|
55
|
+
return response.notFound('Form not found')
|
|
56
|
+
|
|
57
|
+
// Same gate the submit endpoint applies: a closed form does not take files
|
|
58
|
+
// either, and accepting them would leave orphans nothing ever references.
|
|
59
|
+
if (form.status !== 'active')
|
|
60
|
+
return response.json({ message: 'This form is not accepting responses.' }, { status: 409 })
|
|
61
|
+
|
|
62
|
+
const field = fileFieldNamed(form, request.get?.('field') ?? request.input?.('field'))
|
|
63
|
+
if (!field)
|
|
64
|
+
return response.json({ message: 'Unknown upload field.' }, { status: 422 })
|
|
65
|
+
|
|
66
|
+
const file = request.file?.('file')
|
|
67
|
+
if (!file)
|
|
68
|
+
return response.json({ message: `${field.label} is required.` }, { status: 422 })
|
|
69
|
+
|
|
70
|
+
const limits = resolveUploadLimits(field)
|
|
71
|
+
const rejection = checkUpload(field, { name: file.name, size: file.size }, limits)
|
|
72
|
+
if (rejection)
|
|
73
|
+
return response.json({ message: rejection.message }, { status: rejection.status })
|
|
74
|
+
|
|
75
|
+
const { Storage } = await import('@stacksjs/storage')
|
|
76
|
+
const stored = await Storage.put(file, {
|
|
77
|
+
...(limits.disk ? { disk: limits.disk } : {}),
|
|
78
|
+
dir: formUploadPrefix(form),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return response.json({ path: stored.path }, { status: 201 })
|
|
82
|
+
}).rateLimit(20, 'minute')
|
|
83
|
+
|
|
38
84
|
route.post('/api/forms/{uuid}/submissions', async (request: any) => {
|
|
39
85
|
const { dispatchSubmissionNotifications, loadFormByUuid, submitForm } = await import('@stacksjs/forms')
|
|
40
86
|
|