@stacksjs/defaults 0.75.40 → 0.75.42
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-actions/SKILL.md +1 -1
- package/ai/skills/stacks-auto-imports/SKILL.md +1 -1
- package/ai/skills/stacks-chat/SKILL.md +43 -1
- package/ai/skills/stacks-cloud/SKILL.md +36 -0
- package/ai/skills/stacks-dashboard/SKILL.md +16 -2
- package/ai/skills/stacks-database/SKILL.md +24 -3
- package/ai/skills/stacks-deploy/SKILL.md +7 -1
- package/ai/skills/stacks-desktop/SKILL.md +22 -0
- package/ai/skills/stacks-development/SKILL.md +1 -1
- package/ai/skills/stacks-migrations/SKILL.md +2 -2
- package/ai/skills/stacks-models/SKILL.md +38 -2
- package/ai/skills/stacks-orm/SKILL.md +2 -2
- package/ai/skills/stacks-security/SKILL.md +28 -1
- package/app/Actions/Auth/DataExportAction.test.ts +84 -0
- package/app/Actions/Auth/DataExportAction.ts +40 -0
- package/app/Actions/Dashboard/Content/FileReprocessAction.ts +1 -1
- package/app/Actions/Dashboard/Content/file-manager.ts +120 -8
- package/app/Actions/Dashboard/Content/file-metadata.ts +108 -12
- package/app/Actions/Dashboard/Content/file-pipeline.test.ts +13 -9
- package/app/Actions/Dashboard/Content/file-preview.test.ts +487 -0
- package/app/Actions/Dashboard/Content/file-preview.ts +548 -0
- package/app/Actions/Payment/CreatePaymentIntentAction.ts +1 -1
- package/app/Actions/Payment/CreateSetupIntentAction.ts +1 -1
- package/app/Jobs/GenerateStoragePreviewJob.ts +47 -0
- package/app/Jobs/OptimizeStorageImageJob.ts +7 -3
- package/app/Jobs/PruneRetainedDataJob.ts +46 -0
- package/app/Models/Activity.ts +2 -0
- package/app/Models/CampaignSend.ts +2 -0
- package/app/Models/Card.ts +1 -0
- package/app/Models/CardComment.ts +1 -0
- package/app/Models/Comment.ts +5 -0
- package/app/Models/CommunicationSuppression.ts +2 -1
- package/app/Models/ConsentEvent.ts +4 -3
- package/app/Models/Content/Author.ts +5 -0
- package/app/Models/EmailListSubscriber.ts +1 -0
- package/app/Models/EmailSuppression.ts +2 -0
- package/app/Models/Forms/FormSubmission.ts +5 -0
- package/app/Models/GdprRequest.ts +77 -0
- package/app/Models/MagicLinkToken.ts +2 -0
- package/app/Models/Notification.ts +2 -0
- package/app/Models/NotificationDelivery.ts +4 -0
- package/app/Models/PaymentMethod.ts +5 -0
- package/app/Models/PaymentTransaction.ts +1 -0
- package/app/Models/PersonalAccessToken.ts +3 -0
- package/app/Models/Referral.ts +4 -1
- package/app/Models/ReferralCode.ts +4 -1
- package/app/Models/Request.ts +3 -0
- package/app/Models/SmsOptOut.ts +2 -0
- package/app/Models/SocialAccount.ts +3 -0
- package/app/Models/SocialPost.ts +2 -0
- package/app/Models/StorageItemTask.ts +14 -8
- package/app/Models/Subscriber.ts +2 -0
- package/app/Models/SubscriberEmail.ts +2 -0
- package/app/Models/Subscription.ts +1 -0
- package/app/Models/TeamInvitation.ts +2 -0
- package/app/Models/TeamMember.ts +1 -0
- package/app/Models/User.ts +5 -0
- package/app/Models/commerce/Bid.ts +3 -0
- package/app/Models/commerce/Cart.ts +2 -0
- package/app/Models/commerce/Courier.ts +7 -0
- package/app/Models/commerce/CourierPing.ts +3 -0
- package/app/Models/commerce/Customer.ts +5 -0
- package/app/Models/commerce/DeliveryStop.ts +7 -0
- package/app/Models/commerce/GiftCard.ts +4 -0
- package/app/Models/commerce/Order.ts +5 -0
- package/app/Models/commerce/OrderItem.ts +2 -0
- package/app/Models/commerce/Payment.ts +5 -0
- package/app/Models/commerce/Pledge.ts +3 -0
- package/app/Models/commerce/Transaction.ts +2 -0
- package/app/Models/commerce/WaitlistProduct.ts +5 -0
- package/app/Models/commerce/WaitlistRestaurant.ts +4 -0
- package/ide/vscode/.vscode/extensions.json +1 -1
- package/ide/vscode/.vscode/settings.json +8 -149
- package/ide/vscode/README.md +92 -22
- package/ide/vscode/build.ts +43 -0
- package/ide/vscode/package.json +368 -29
- package/ide/vscode/scripts/stx-contributes.ts +66 -0
- package/ide/vscode/src/env-file.ts +103 -0
- package/ide/vscode/src/env.ts +241 -0
- package/ide/vscode/src/extension.ts +153 -0
- package/ide/vscode/src/pickier-protocol.ts +51 -0
- package/ide/vscode/src/pickier-worker.ts +92 -0
- package/ide/vscode/src/pickier.ts +433 -0
- package/ide/vscode/src/project.ts +450 -0
- package/ide/vscode/src/stacks.ts +482 -0
- package/ide/vscode/tests/build.test.ts +58 -0
- package/ide/vscode/tests/env.test.ts +166 -0
- package/ide/vscode/tests/extension.test.ts +428 -0
- package/ide/vscode/tests/fake-vscode.ts +228 -0
- package/ide/vscode/tests/manifest.test.ts +88 -0
- package/ide/vscode/tests/pickier.test.ts +202 -0
- package/ide/vscode/tests/project.test.ts +268 -0
- package/ide/vscode/tsconfig.json +19 -0
- package/package.json +4 -4
- package/resources/components/Dashboard/Content/FileManagerDashboard.stx +2 -0
- package/resources/components/Dashboard/FileViewer/MediaCard.stx +14 -3
- package/routes/auth.ts +4 -0
- package/vcs/github/workflows/buddy-bot.yml +2 -2
- package/vcs/github/workflows/ci.yml +3 -3
- package/vcs/github/workflows/release.yml +1 -1
package/app/Models/Activity.ts
CHANGED
|
@@ -8,6 +8,7 @@ export default defineModel({
|
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
10
|
traits: {
|
|
11
|
+
gdpr: { erasure: 'anonymize', basis: 'legitimate_interests', purpose: 'Activity feed' },
|
|
11
12
|
useUuid: true,
|
|
12
13
|
useTimestamps: true,
|
|
13
14
|
useSeeder: {
|
|
@@ -84,6 +85,7 @@ export default defineModel({
|
|
|
84
85
|
},
|
|
85
86
|
|
|
86
87
|
ipAddress: {
|
|
88
|
+
personal: true,
|
|
87
89
|
required: false,
|
|
88
90
|
fillable: true,
|
|
89
91
|
validation: {
|
|
@@ -17,6 +17,7 @@ export default defineModel({
|
|
|
17
17
|
],
|
|
18
18
|
|
|
19
19
|
traits: {
|
|
20
|
+
gdpr: { subject: { via: 'Subscriber' }, erasure: 'anonymize', basis: 'legitimate_interests', purpose: 'Campaign delivery statistics' },
|
|
20
21
|
useUuid: true,
|
|
21
22
|
useTimestamps: true,
|
|
22
23
|
useApi: {
|
|
@@ -81,6 +82,7 @@ export default defineModel({
|
|
|
81
82
|
},
|
|
82
83
|
|
|
83
84
|
recipient: {
|
|
85
|
+
personal: true,
|
|
84
86
|
required: true,
|
|
85
87
|
fillable: true,
|
|
86
88
|
validation: {
|
package/app/Models/Card.ts
CHANGED
|
@@ -21,6 +21,7 @@ export default defineModel({
|
|
|
21
21
|
autoIncrement: true,
|
|
22
22
|
|
|
23
23
|
traits: {
|
|
24
|
+
gdpr: { erasure: 'keep', basis: 'legitimate_interests', purpose: 'Shared board discussion, attributed to the (anonymized) author' },
|
|
24
25
|
useUuid: true,
|
|
25
26
|
useTimestamps: true,
|
|
26
27
|
useSearch: {
|
package/app/Models/Comment.ts
CHANGED
|
@@ -8,6 +8,7 @@ export default defineModel({
|
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
10
|
traits: {
|
|
11
|
+
gdpr: { erasure: 'anonymize', basis: 'legitimate_interests', purpose: 'Comments on posts' },
|
|
11
12
|
useUuid: true,
|
|
12
13
|
useTimestamps: true,
|
|
13
14
|
useSeeder: {
|
|
@@ -24,6 +25,7 @@ export default defineModel({
|
|
|
24
25
|
|
|
25
26
|
attributes: {
|
|
26
27
|
authorName: {
|
|
28
|
+
personal: true,
|
|
27
29
|
required: true,
|
|
28
30
|
fillable: true,
|
|
29
31
|
validation: {
|
|
@@ -33,6 +35,7 @@ export default defineModel({
|
|
|
33
35
|
},
|
|
34
36
|
|
|
35
37
|
authorEmail: {
|
|
38
|
+
personal: true,
|
|
36
39
|
required: true,
|
|
37
40
|
fillable: true,
|
|
38
41
|
validation: {
|
|
@@ -79,6 +82,7 @@ export default defineModel({
|
|
|
79
82
|
},
|
|
80
83
|
|
|
81
84
|
ipAddress: {
|
|
85
|
+
personal: true,
|
|
82
86
|
required: false,
|
|
83
87
|
fillable: true,
|
|
84
88
|
validation: {
|
|
@@ -88,6 +92,7 @@ export default defineModel({
|
|
|
88
92
|
},
|
|
89
93
|
|
|
90
94
|
userAgent: {
|
|
95
|
+
personal: true,
|
|
91
96
|
required: false,
|
|
92
97
|
fillable: true,
|
|
93
98
|
validation: {
|
|
@@ -6,13 +6,14 @@ export default defineModel({
|
|
|
6
6
|
table: 'communication_suppressions',
|
|
7
7
|
belongsTo: ['Team'],
|
|
8
8
|
traits: {
|
|
9
|
+
gdpr: { subject: { email: 'recipient' }, erasure: 'keep', basis: 'legal_obligation', purpose: 'Honouring opt-outs, which needs the address it suppresses' },
|
|
9
10
|
useUuid: true,
|
|
10
11
|
useTimestamps: true,
|
|
11
12
|
useApi: { uri: 'communication-suppressions', routes: ['index', 'store', 'show', 'destroy'], middleware: ['auth', 'team'] },
|
|
12
13
|
},
|
|
13
14
|
indexes: [{ name: 'communication_suppressions_unique', columns: ['team_id', 'channel', 'recipient'], unique: true }],
|
|
14
15
|
attributes: {
|
|
15
|
-
recipient: { required: true, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.internet.email() },
|
|
16
|
+
recipient: { personal: true, required: true, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.internet.email() },
|
|
16
17
|
channel: { required: true, fillable: true, validation: { rule: schema.enum(['email', 'sms', 'push']) }, factory: () => 'email' },
|
|
17
18
|
reason: { required: true, fillable: true, validation: { rule: schema.enum(['unsubscribe', 'bounce', 'complaint', 'carrier', 'manual', 'legal']) }, factory: () => 'unsubscribe' },
|
|
18
19
|
source: { required: true, fillable: true, validation: { rule: schema.string().max(120) }, factory: () => 'preference_center' },
|
|
@@ -6,6 +6,7 @@ export default defineModel({
|
|
|
6
6
|
table: 'consent_events',
|
|
7
7
|
belongsTo: ['Team'],
|
|
8
8
|
traits: {
|
|
9
|
+
gdpr: { subject: { email: 'recipient' }, erasure: 'keep', basis: 'legal_obligation', purpose: 'Proof of consent and its withdrawal (Article 7(1))' },
|
|
9
10
|
useUuid: true,
|
|
10
11
|
useTimestamps: true,
|
|
11
12
|
useApi: { uri: 'consent-events', routes: ['index', 'show'], middleware: ['auth', 'team'] },
|
|
@@ -15,7 +16,7 @@ export default defineModel({
|
|
|
15
16
|
{ name: 'consent_events_lookup', columns: ['team_id', 'channel', 'recipient', 'occurred_at'] },
|
|
16
17
|
],
|
|
17
18
|
attributes: {
|
|
18
|
-
recipient: { required: true, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.internet.email() },
|
|
19
|
+
recipient: { personal: true, required: true, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.internet.email() },
|
|
19
20
|
channel: { required: true, fillable: true, validation: { rule: schema.enum(['email', 'sms', 'push']) }, factory: () => 'email' },
|
|
20
21
|
action: { required: true, fillable: true, validation: { rule: schema.enum(['requested', 'granted', 'revoked', 'confirmed', 'suppressed']) }, factory: () => 'granted' },
|
|
21
22
|
purpose: { required: true, fillable: true, validation: { rule: schema.string().max(120) }, factory: () => 'marketing' },
|
|
@@ -23,8 +24,8 @@ export default defineModel({
|
|
|
23
24
|
jurisdiction: { required: false, fillable: true, validation: { rule: schema.string().max(80) }, factory: () => null },
|
|
24
25
|
policyVersion: { required: true, fillable: true, validation: { rule: schema.string().max(40) }, factory: () => '1.0' },
|
|
25
26
|
idempotencyKey: { required: false, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.string.uuid() },
|
|
26
|
-
proof: { required: false, fillable: true, validation: { rule: schema.json() }, factory: () => JSON.stringify({}) },
|
|
27
|
-
ipAddress: { required: false, fillable: true, validation: { rule: schema.string().max(45) }, factory: faker => faker.internet.ip() },
|
|
27
|
+
proof: { personal: true, required: false, fillable: true, validation: { rule: schema.json() }, factory: () => JSON.stringify({}) },
|
|
28
|
+
ipAddress: { personal: true, required: false, fillable: true, validation: { rule: schema.string().max(45) }, factory: faker => faker.internet.ip() },
|
|
28
29
|
occurredAt: { required: true, fillable: true, validation: { rule: schema.timestamp() }, factory: () => new Date().toISOString() },
|
|
29
30
|
},
|
|
30
31
|
} as const)
|
|
@@ -16,6 +16,7 @@ export default defineModel({
|
|
|
16
16
|
],
|
|
17
17
|
|
|
18
18
|
traits: {
|
|
19
|
+
gdpr: { erasure: 'anonymize', basis: 'legitimate_interests', purpose: 'Byline on published content' },
|
|
19
20
|
useAuth: {
|
|
20
21
|
usePasskey: true,
|
|
21
22
|
},
|
|
@@ -51,6 +52,7 @@ export default defineModel({
|
|
|
51
52
|
|
|
52
53
|
attributes: {
|
|
53
54
|
name: {
|
|
55
|
+
personal: true,
|
|
54
56
|
order: 1,
|
|
55
57
|
fillable: true,
|
|
56
58
|
validation: {
|
|
@@ -65,6 +67,7 @@ export default defineModel({
|
|
|
65
67
|
},
|
|
66
68
|
|
|
67
69
|
email: {
|
|
70
|
+
personal: true,
|
|
68
71
|
unique: true,
|
|
69
72
|
order: 2,
|
|
70
73
|
fillable: true,
|
|
@@ -79,6 +82,7 @@ export default defineModel({
|
|
|
79
82
|
},
|
|
80
83
|
|
|
81
84
|
bio: {
|
|
85
|
+
personal: true,
|
|
82
86
|
required: false,
|
|
83
87
|
order: 3,
|
|
84
88
|
fillable: true,
|
|
@@ -92,6 +96,7 @@ export default defineModel({
|
|
|
92
96
|
},
|
|
93
97
|
|
|
94
98
|
avatar: {
|
|
99
|
+
personal: true,
|
|
95
100
|
required: false,
|
|
96
101
|
order: 4,
|
|
97
102
|
fillable: true,
|
|
@@ -21,6 +21,7 @@ export default defineModel({
|
|
|
21
21
|
ownership: false,
|
|
22
22
|
|
|
23
23
|
traits: {
|
|
24
|
+
gdpr: { subject: { email: 'email' }, erasure: 'keep', basis: 'legal_obligation', purpose: 'Honouring bounces and complaints, which needs the address it suppresses' },
|
|
24
25
|
useTimestamps: true,
|
|
25
26
|
useApi: {
|
|
26
27
|
uri: 'email-suppressions',
|
|
@@ -34,6 +35,7 @@ export default defineModel({
|
|
|
34
35
|
|
|
35
36
|
attributes: {
|
|
36
37
|
email: {
|
|
38
|
+
personal: true,
|
|
37
39
|
required: true,
|
|
38
40
|
fillable: true,
|
|
39
41
|
validation: {
|
|
@@ -18,6 +18,7 @@ export default defineModel({
|
|
|
18
18
|
autoIncrement: true,
|
|
19
19
|
|
|
20
20
|
traits: {
|
|
21
|
+
gdpr: { subject: { email: 'email' }, erasure: 'anonymize', basis: 'consent', purpose: 'Form submissions' },
|
|
21
22
|
useUuid: true,
|
|
22
23
|
useTimestamps: true,
|
|
23
24
|
},
|
|
@@ -27,6 +28,7 @@ export default defineModel({
|
|
|
27
28
|
attributes: {
|
|
28
29
|
/** Validated { [fieldName]: value } JSON. Named data, not values - values is a SQL keyword. */
|
|
29
30
|
data: {
|
|
31
|
+
personal: true,
|
|
30
32
|
required: true,
|
|
31
33
|
order: 1,
|
|
32
34
|
fillable: true,
|
|
@@ -37,6 +39,7 @@ export default defineModel({
|
|
|
37
39
|
},
|
|
38
40
|
|
|
39
41
|
email: {
|
|
42
|
+
personal: true,
|
|
40
43
|
required: false,
|
|
41
44
|
order: 2,
|
|
42
45
|
fillable: true,
|
|
@@ -47,6 +50,7 @@ export default defineModel({
|
|
|
47
50
|
},
|
|
48
51
|
|
|
49
52
|
name: {
|
|
53
|
+
personal: true,
|
|
50
54
|
required: false,
|
|
51
55
|
order: 3,
|
|
52
56
|
fillable: true,
|
|
@@ -89,6 +93,7 @@ export default defineModel({
|
|
|
89
93
|
},
|
|
90
94
|
|
|
91
95
|
ip: {
|
|
96
|
+
personal: true,
|
|
92
97
|
required: false,
|
|
93
98
|
order: 7,
|
|
94
99
|
fillable: true,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The ledger of data-subject requests (stacksjs/stacks#365).
|
|
6
|
+
*
|
|
7
|
+
* One row per access export, per erasure, and per retention run that changed
|
|
8
|
+
* something - written by `exportSubjectData`, `eraseSubject` and
|
|
9
|
+
* `pruneRetainedData`, inside the erasure's own transaction, so an erasure is
|
|
10
|
+
* recorded exactly when it happened. It is the answer to "when did we act on
|
|
11
|
+
* this request, and what did we do", which a regulator can ask and which the
|
|
12
|
+
* data itself can no longer answer once it is erased.
|
|
13
|
+
*
|
|
14
|
+
* It holds no personal data of its own: `summary` carries per-model counts,
|
|
15
|
+
* never values. `subject_id` has no foreign key on purpose - the user row it
|
|
16
|
+
* names may be anonymized or gone, and the record of having erased it must
|
|
17
|
+
* outlive it.
|
|
18
|
+
*/
|
|
19
|
+
export default defineModel({
|
|
20
|
+
name: 'GdprRequest',
|
|
21
|
+
table: 'gdpr_requests',
|
|
22
|
+
primaryKey: 'id',
|
|
23
|
+
autoIncrement: true,
|
|
24
|
+
|
|
25
|
+
indexes: [
|
|
26
|
+
{ name: 'gdpr_requests_subject_lookup', columns: ['subject_id', 'type', 'occurred_at'] },
|
|
27
|
+
],
|
|
28
|
+
|
|
29
|
+
// System-written; there is no caller whose rows these are, and no API.
|
|
30
|
+
ownership: false,
|
|
31
|
+
|
|
32
|
+
traits: {
|
|
33
|
+
useUuid: true,
|
|
34
|
+
useTimestamps: true,
|
|
35
|
+
gdpr: { subject: 'subject_id', erasure: 'keep', basis: 'legal_obligation', purpose: 'Record of the data-subject requests acted on' },
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
attributes: {
|
|
39
|
+
type: {
|
|
40
|
+
required: true,
|
|
41
|
+
fillable: true,
|
|
42
|
+
validation: { rule: schema.enum(['access', 'erasure', 'retention']) },
|
|
43
|
+
factory: () => 'access',
|
|
44
|
+
},
|
|
45
|
+
subjectId: {
|
|
46
|
+
required: false,
|
|
47
|
+
fillable: true,
|
|
48
|
+
validation: { rule: schema.number().integer().min(1) },
|
|
49
|
+
factory: () => 1,
|
|
50
|
+
},
|
|
51
|
+
actor: {
|
|
52
|
+
required: false,
|
|
53
|
+
fillable: true,
|
|
54
|
+
validation: { rule: schema.string().max(120) },
|
|
55
|
+
factory: () => 'cli',
|
|
56
|
+
},
|
|
57
|
+
status: {
|
|
58
|
+
required: true,
|
|
59
|
+
fillable: true,
|
|
60
|
+
default: 'completed',
|
|
61
|
+
validation: { rule: schema.enum(['completed', 'failed']) },
|
|
62
|
+
factory: () => 'completed',
|
|
63
|
+
},
|
|
64
|
+
summary: {
|
|
65
|
+
required: false,
|
|
66
|
+
fillable: true,
|
|
67
|
+
validation: { rule: schema.json() },
|
|
68
|
+
factory: () => JSON.stringify({}),
|
|
69
|
+
},
|
|
70
|
+
occurredAt: {
|
|
71
|
+
required: true,
|
|
72
|
+
fillable: true,
|
|
73
|
+
validation: { rule: schema.timestamp() },
|
|
74
|
+
factory: () => new Date().toISOString(),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
} as const)
|
|
@@ -24,6 +24,7 @@ export default defineModel({
|
|
|
24
24
|
],
|
|
25
25
|
|
|
26
26
|
traits: {
|
|
27
|
+
gdpr: { erasure: 'delete', basis: 'contract', purpose: 'Passwordless sign-in links' },
|
|
27
28
|
useTimestamps: true,
|
|
28
29
|
},
|
|
29
30
|
|
|
@@ -31,6 +32,7 @@ export default defineModel({
|
|
|
31
32
|
|
|
32
33
|
attributes: {
|
|
33
34
|
email: {
|
|
35
|
+
personal: true,
|
|
34
36
|
required: true,
|
|
35
37
|
order: 1,
|
|
36
38
|
fillable: true,
|
|
@@ -8,6 +8,7 @@ export default defineModel({
|
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
10
|
traits: {
|
|
11
|
+
gdpr: { erasure: 'delete', basis: 'contract', purpose: 'In-app notifications' },
|
|
11
12
|
useUuid: true,
|
|
12
13
|
useTimestamps: true,
|
|
13
14
|
useSeeder: {
|
|
@@ -38,6 +39,7 @@ export default defineModel({
|
|
|
38
39
|
},
|
|
39
40
|
|
|
40
41
|
data: {
|
|
42
|
+
personal: true,
|
|
41
43
|
type: 'text',
|
|
42
44
|
required: true,
|
|
43
45
|
fillable: true,
|
|
@@ -8,6 +8,7 @@ export default defineModel({
|
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
10
|
traits: {
|
|
11
|
+
gdpr: { subject: 'user_id', erasure: 'delete', basis: 'contract', purpose: 'Notification delivery log' },
|
|
11
12
|
useTimestamps: true,
|
|
12
13
|
useSearch: {
|
|
13
14
|
displayable: ['id', 'channel', 'recipient', 'subject', 'status', 'sentAt'],
|
|
@@ -41,6 +42,7 @@ export default defineModel({
|
|
|
41
42
|
},
|
|
42
43
|
|
|
43
44
|
recipient: {
|
|
45
|
+
personal: true,
|
|
44
46
|
required: true,
|
|
45
47
|
fillable: true,
|
|
46
48
|
validation: {
|
|
@@ -49,6 +51,7 @@ export default defineModel({
|
|
|
49
51
|
},
|
|
50
52
|
|
|
51
53
|
subject: {
|
|
54
|
+
personal: true,
|
|
52
55
|
required: false,
|
|
53
56
|
fillable: true,
|
|
54
57
|
validation: {
|
|
@@ -57,6 +60,7 @@ export default defineModel({
|
|
|
57
60
|
},
|
|
58
61
|
|
|
59
62
|
body: {
|
|
63
|
+
personal: true,
|
|
60
64
|
required: true,
|
|
61
65
|
fillable: true,
|
|
62
66
|
validation: {
|
|
@@ -11,6 +11,7 @@ export default defineModel({
|
|
|
11
11
|
belongsTo: ['User'],
|
|
12
12
|
hasMany: ['PaymentTransaction'],
|
|
13
13
|
traits: {
|
|
14
|
+
gdpr: { erasure: 'anonymize', basis: 'contract', purpose: 'Saved payment methods' },
|
|
14
15
|
useSeeder: {
|
|
15
16
|
count: 5,
|
|
16
17
|
},
|
|
@@ -31,6 +32,7 @@ export default defineModel({
|
|
|
31
32
|
},
|
|
32
33
|
|
|
33
34
|
lastFour: {
|
|
35
|
+
personal: true,
|
|
34
36
|
fillable: true,
|
|
35
37
|
validation: {
|
|
36
38
|
rule: schema.number().required(),
|
|
@@ -43,6 +45,7 @@ export default defineModel({
|
|
|
43
45
|
},
|
|
44
46
|
|
|
45
47
|
brand: {
|
|
48
|
+
personal: true,
|
|
46
49
|
fillable: true,
|
|
47
50
|
validation: {
|
|
48
51
|
rule: schema.string().required().max(50),
|
|
@@ -55,6 +58,7 @@ export default defineModel({
|
|
|
55
58
|
},
|
|
56
59
|
|
|
57
60
|
expMonth: {
|
|
61
|
+
personal: true,
|
|
58
62
|
fillable: true,
|
|
59
63
|
validation: {
|
|
60
64
|
rule: schema.number().required(),
|
|
@@ -67,6 +71,7 @@ export default defineModel({
|
|
|
67
71
|
},
|
|
68
72
|
|
|
69
73
|
expYear: {
|
|
74
|
+
personal: true,
|
|
70
75
|
fillable: true,
|
|
71
76
|
validation: {
|
|
72
77
|
rule: schema.number().required(),
|
|
@@ -27,6 +27,7 @@ export default defineModel({
|
|
|
27
27
|
autoIncrement: true,
|
|
28
28
|
|
|
29
29
|
traits: {
|
|
30
|
+
gdpr: { subject: { column: 'tokenable_id', where: { tokenable_type: 'users' } }, erasure: 'anonymize', basis: 'contract', purpose: 'API tokens, revoked on erasure' },
|
|
30
31
|
useTimestamps: true,
|
|
31
32
|
// Deliberately no `useApi`.
|
|
32
33
|
//
|
|
@@ -136,6 +137,7 @@ export default defineModel({
|
|
|
136
137
|
* nothing authorises on either.
|
|
137
138
|
*/
|
|
138
139
|
userAgent: {
|
|
140
|
+
personal: true,
|
|
139
141
|
order: 8,
|
|
140
142
|
fillable: true,
|
|
141
143
|
validation: {
|
|
@@ -144,6 +146,7 @@ export default defineModel({
|
|
|
144
146
|
},
|
|
145
147
|
|
|
146
148
|
ipAddress: {
|
|
149
|
+
personal: true,
|
|
147
150
|
order: 9,
|
|
148
151
|
fillable: true,
|
|
149
152
|
validation: {
|
package/app/Models/Referral.ts
CHANGED
|
@@ -4,7 +4,10 @@ import { schema } from '@stacksjs/validation'
|
|
|
4
4
|
export default defineModel({
|
|
5
5
|
name: 'Referral',
|
|
6
6
|
table: 'referrals',
|
|
7
|
-
traits: {
|
|
7
|
+
traits: {
|
|
8
|
+
useTimestamps: true,
|
|
9
|
+
gdpr: { subject: ['referrer_id', 'referred_user_id'], erasure: 'keep', basis: 'contract', purpose: 'Referral rewards owed' },
|
|
10
|
+
},
|
|
8
11
|
indexes: [{ name: 'referrals_referrer_status', columns: ['referrer_id', 'status'] }],
|
|
9
12
|
attributes: {
|
|
10
13
|
referrerId: { required: true, validation: { rule: schema.number().integer().min(1) } },
|
|
@@ -4,7 +4,10 @@ import { schema } from '@stacksjs/validation'
|
|
|
4
4
|
export default defineModel({
|
|
5
5
|
name: 'ReferralCode',
|
|
6
6
|
table: 'referral_codes',
|
|
7
|
-
traits: {
|
|
7
|
+
traits: {
|
|
8
|
+
useTimestamps: true,
|
|
9
|
+
gdpr: { subject: 'user_id', erasure: 'delete', basis: 'contract', purpose: 'Referral codes' },
|
|
10
|
+
},
|
|
8
11
|
attributes: {
|
|
9
12
|
userId: { required: true, unique: true, validation: { rule: schema.number().integer().min(1) } },
|
|
10
13
|
code: { required: true, unique: true, validation: { rule: schema.string().max(24) } },
|
package/app/Models/Request.ts
CHANGED
|
@@ -28,6 +28,7 @@ export default defineModel({
|
|
|
28
28
|
ownership: false,
|
|
29
29
|
|
|
30
30
|
traits: {
|
|
31
|
+
gdpr: { basis: 'legitimate_interests', purpose: 'Request log for debugging and abuse prevention' },
|
|
31
32
|
useTimestamps: true,
|
|
32
33
|
useSoftDeletes: true,
|
|
33
34
|
useSeeder: {
|
|
@@ -93,6 +94,7 @@ export default defineModel({
|
|
|
93
94
|
},
|
|
94
95
|
|
|
95
96
|
ip_address: {
|
|
97
|
+
personal: true,
|
|
96
98
|
fillable: true,
|
|
97
99
|
validation: {
|
|
98
100
|
rule: schema.string(),
|
|
@@ -117,6 +119,7 @@ export default defineModel({
|
|
|
117
119
|
},
|
|
118
120
|
|
|
119
121
|
user_agent: {
|
|
122
|
+
personal: true,
|
|
120
123
|
fillable: true,
|
|
121
124
|
validation: {
|
|
122
125
|
rule: schema.string(),
|
package/app/Models/SmsOptOut.ts
CHANGED
|
@@ -22,12 +22,14 @@ export default defineModel({
|
|
|
22
22
|
],
|
|
23
23
|
|
|
24
24
|
traits: {
|
|
25
|
+
gdpr: { erasure: 'keep', basis: 'legal_obligation', purpose: 'Honouring SMS STOP replies, keyed by phone number' },
|
|
25
26
|
useTimestamps: true,
|
|
26
27
|
},
|
|
27
28
|
|
|
28
29
|
attributes: {
|
|
29
30
|
/** E.164 where derivable (`+13105550199`). */
|
|
30
31
|
phone: {
|
|
32
|
+
personal: true,
|
|
31
33
|
required: true,
|
|
32
34
|
order: 1,
|
|
33
35
|
fillable: true,
|
|
@@ -33,6 +33,7 @@ export default defineModel({
|
|
|
33
33
|
],
|
|
34
34
|
|
|
35
35
|
traits: {
|
|
36
|
+
gdpr: { erasure: 'delete', basis: 'contract', purpose: 'Linked sign-in providers' },
|
|
36
37
|
useTimestamps: true,
|
|
37
38
|
},
|
|
38
39
|
|
|
@@ -49,6 +50,7 @@ export default defineModel({
|
|
|
49
50
|
},
|
|
50
51
|
|
|
51
52
|
providerUserId: {
|
|
53
|
+
personal: true,
|
|
52
54
|
required: true,
|
|
53
55
|
fillable: true,
|
|
54
56
|
validation: {
|
|
@@ -60,6 +62,7 @@ export default defineModel({
|
|
|
60
62
|
// The address the provider reported at link time. Informational — sign-in
|
|
61
63
|
// resolves through (provider, provider_user_id), never through this.
|
|
62
64
|
providerEmail: {
|
|
65
|
+
personal: true,
|
|
63
66
|
fillable: true,
|
|
64
67
|
validation: {
|
|
65
68
|
rule: schema.string().max(255),
|
package/app/Models/SocialPost.ts
CHANGED
|
@@ -8,6 +8,7 @@ export default defineModel({
|
|
|
8
8
|
autoIncrement: true,
|
|
9
9
|
|
|
10
10
|
traits: {
|
|
11
|
+
gdpr: { erasure: 'delete', basis: 'contract', purpose: 'Social posts the user scheduled' },
|
|
11
12
|
useUuid: true,
|
|
12
13
|
useTimestamps: true,
|
|
13
14
|
useSeeder: {
|
|
@@ -31,6 +32,7 @@ export default defineModel({
|
|
|
31
32
|
|
|
32
33
|
attributes: {
|
|
33
34
|
content: {
|
|
35
|
+
personal: true,
|
|
34
36
|
required: true,
|
|
35
37
|
fillable: true,
|
|
36
38
|
validation: {
|
|
@@ -5,7 +5,8 @@ import { schema } from '@stacksjs/validation'
|
|
|
5
5
|
* One unit of background processing for one file (stacksjs/stacks#2578).
|
|
6
6
|
*
|
|
7
7
|
* The three things #245 asked for that could not happen inside a request -
|
|
8
|
-
* image optimization, video transcode, AI tagging -
|
|
8
|
+
* image optimization, video transcode, AI tagging - and the preview #308 asked
|
|
9
|
+
* for share the same shape:
|
|
9
10
|
* dispatch work and show what happened to it. Transcoding is minutes, not
|
|
10
11
|
* milliseconds; a vision call is a round trip to a third party. An upload
|
|
11
12
|
* handler that waits for either is an upload handler that times out.
|
|
@@ -81,10 +82,11 @@ export default defineModel({
|
|
|
81
82
|
kind: {
|
|
82
83
|
order: 3,
|
|
83
84
|
fillable: true,
|
|
84
|
-
// `optimize` (images), `transcode` (video), `tag` (AI)
|
|
85
|
-
//
|
|
86
|
-
// a
|
|
87
|
-
|
|
85
|
+
// `optimize` (images), `transcode` (video), `tag` (AI), `preview` (the
|
|
86
|
+
// file manager's thumbnail, stacksjs/stacks#308). An enum rather than
|
|
87
|
+
// free text so a typo in a dispatch is a validation error instead of a
|
|
88
|
+
// task nothing will ever run.
|
|
89
|
+
validation: { rule: schema.enum(['optimize', 'transcode', 'tag', 'preview']) },
|
|
88
90
|
factory: () => 'optimize',
|
|
89
91
|
},
|
|
90
92
|
|
|
@@ -92,7 +94,10 @@ export default defineModel({
|
|
|
92
94
|
order: 4,
|
|
93
95
|
fillable: true,
|
|
94
96
|
default: 'queued',
|
|
95
|
-
|
|
97
|
+
// `skipped` is terminal and not a failure: the work does not apply to
|
|
98
|
+
// this file here - a PDF with no renderer to draw it - and `error`
|
|
99
|
+
// carries the reason, so the dashboard can say why.
|
|
100
|
+
validation: { rule: schema.enum(['queued', 'running', 'done', 'failed', 'skipped']) },
|
|
96
101
|
factory: () => 'queued',
|
|
97
102
|
},
|
|
98
103
|
|
|
@@ -109,8 +114,9 @@ export default defineModel({
|
|
|
109
114
|
error: {
|
|
110
115
|
order: 6,
|
|
111
116
|
fillable: true,
|
|
112
|
-
// The failure as the worker saw it
|
|
113
|
-
// by the column, so a stack trace
|
|
117
|
+
// The failure as the worker saw it, or why a skipped task did not apply.
|
|
118
|
+
// Truncated by the writer rather than by the column, so a stack trace
|
|
119
|
+
// does not silently lose its first line.
|
|
114
120
|
validation: { rule: schema.string().max(2000) },
|
|
115
121
|
factory: () => '',
|
|
116
122
|
},
|
package/app/Models/Subscriber.ts
CHANGED
|
@@ -10,6 +10,7 @@ export default defineModel({
|
|
|
10
10
|
hasMany: ['SubscriberEmail', 'EmailListSubscriber', 'CampaignSend'],
|
|
11
11
|
|
|
12
12
|
traits: {
|
|
13
|
+
gdpr: { subject: { email: 'email' }, erasure: 'anonymize', basis: 'consent', purpose: 'Newsletter subscription' },
|
|
13
14
|
useUuid: true,
|
|
14
15
|
useTimestamps: true,
|
|
15
16
|
useSeeder: {
|
|
@@ -31,6 +32,7 @@ export default defineModel({
|
|
|
31
32
|
|
|
32
33
|
attributes: {
|
|
33
34
|
email: {
|
|
35
|
+
personal: true,
|
|
34
36
|
unique: true,
|
|
35
37
|
required: true,
|
|
36
38
|
fillable: true,
|
|
@@ -9,6 +9,7 @@ export default defineModel({
|
|
|
9
9
|
belongsTo: ['Subscriber'],
|
|
10
10
|
|
|
11
11
|
traits: {
|
|
12
|
+
gdpr: { subject: { via: 'Subscriber' }, erasure: 'delete', basis: 'consent', purpose: 'Newsletter subscription addresses' },
|
|
12
13
|
useUuid: true,
|
|
13
14
|
useTimestamps: true,
|
|
14
15
|
useApi: {
|
|
@@ -24,6 +25,7 @@ export default defineModel({
|
|
|
24
25
|
|
|
25
26
|
attributes: {
|
|
26
27
|
email: {
|
|
28
|
+
personal: true,
|
|
27
29
|
required: true,
|
|
28
30
|
fillable: true,
|
|
29
31
|
validation: {
|
|
@@ -34,6 +34,7 @@ export default defineModel({
|
|
|
34
34
|
belongsTo: ['Team'],
|
|
35
35
|
|
|
36
36
|
traits: {
|
|
37
|
+
gdpr: { subject: { email: 'email' }, erasure: 'delete', basis: 'contract', purpose: 'Team invitations' },
|
|
37
38
|
useUuid: true,
|
|
38
39
|
useTimestamps: true,
|
|
39
40
|
useSeeder: { count: 0 },
|
|
@@ -60,6 +61,7 @@ export default defineModel({
|
|
|
60
61
|
},
|
|
61
62
|
|
|
62
63
|
email: {
|
|
64
|
+
personal: true,
|
|
63
65
|
required: true,
|
|
64
66
|
fillable: true,
|
|
65
67
|
validation: {
|