@stacksjs/orm 0.72.16 → 0.72.17

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.
Files changed (101) hide show
  1. package/dist/index.d.ts +97 -97
  2. package/dist/models/Activity.ts +95 -0
  3. package/dist/models/AnalyticsEvent.ts +70 -0
  4. package/dist/models/Automation.ts +23 -0
  5. package/dist/models/AutomationRun.ts +26 -0
  6. package/dist/models/Board.ts +104 -0
  7. package/dist/models/BoardColumn.ts +88 -0
  8. package/dist/models/Campaign.ts +321 -0
  9. package/dist/models/CampaignSend.ts +191 -0
  10. package/dist/models/CampaignVariant.ts +25 -0
  11. package/dist/models/Card.ts +118 -0
  12. package/dist/models/CardComment.ts +68 -0
  13. package/dist/models/Comment.ts +109 -0
  14. package/dist/models/CommunicationSuppression.ts +22 -0
  15. package/dist/models/ConsentEvent.ts +30 -0
  16. package/dist/models/Content/Author.ts +109 -0
  17. package/dist/models/Content/Menu.ts +56 -0
  18. package/dist/models/Content/MenuItem.ts +91 -0
  19. package/dist/models/Content/Page.ts +205 -0
  20. package/dist/models/Content/PageRevision.ts +86 -0
  21. package/dist/models/Content/Post.ts +230 -0
  22. package/dist/models/Content/Redirect.ts +80 -0
  23. package/dist/models/Deployment.ts +117 -0
  24. package/dist/models/EmailIdempotency.ts +64 -0
  25. package/dist/models/EmailList.ts +134 -0
  26. package/dist/models/EmailListSubscriber.ts +91 -0
  27. package/dist/models/EmailSuppression.ts +52 -0
  28. package/dist/models/EmailWebhookEvent.ts +51 -0
  29. package/dist/models/Error.ts +77 -0
  30. package/dist/models/FailedJob.ts +84 -0
  31. package/dist/models/Forms/Form.ts +91 -0
  32. package/dist/models/Forms/FormField.ts +127 -0
  33. package/dist/models/Forms/FormSubmission.ts +114 -0
  34. package/dist/models/Job.ts +69 -0
  35. package/dist/models/Label.ts +67 -0
  36. package/dist/models/Log.ts +134 -0
  37. package/dist/models/MagicLinkToken.ts +97 -0
  38. package/dist/models/MailPreference.ts +213 -0
  39. package/dist/models/Notification.ts +68 -0
  40. package/dist/models/NotificationDelivery.ts +102 -0
  41. package/dist/models/PaymentMethod.ts +98 -0
  42. package/dist/models/PaymentProduct.ts +91 -0
  43. package/dist/models/PaymentTransaction.ts +78 -0
  44. package/dist/models/QueryLog.ts +182 -0
  45. package/dist/models/README.md +31 -0
  46. package/dist/models/Release.ts +90 -0
  47. package/dist/models/Request.ts +133 -0
  48. package/dist/models/SenderDomain.ts +22 -0
  49. package/dist/models/Site.ts +112 -0
  50. package/dist/models/SiteDomain.ts +74 -0
  51. package/dist/models/SmsOptOut.ts +65 -0
  52. package/dist/models/SocialAccount.ts +70 -0
  53. package/dist/models/SocialPost.ts +144 -0
  54. package/dist/models/Subscriber.ts +89 -0
  55. package/dist/models/SubscriberEmail.ts +55 -0
  56. package/dist/models/Subscription.ts +153 -0
  57. package/dist/models/Tag.ts +105 -0
  58. package/dist/models/Team.ts +75 -0
  59. package/dist/models/TeamInvitation.ts +159 -0
  60. package/dist/models/TeamMember.ts +88 -0
  61. package/dist/models/UsageEvent.ts +24 -0
  62. package/dist/models/User.ts +142 -0
  63. package/dist/models/commerce/Auction.ts +173 -0
  64. package/dist/models/commerce/AuctionItem.ts +204 -0
  65. package/dist/models/commerce/Bid.ts +129 -0
  66. package/dist/models/commerce/Cart.ts +140 -0
  67. package/dist/models/commerce/CartItem.ts +138 -0
  68. package/dist/models/commerce/Category.ts +125 -0
  69. package/dist/models/commerce/Coupon.ts +186 -0
  70. package/dist/models/commerce/Customer.ts +132 -0
  71. package/dist/models/commerce/DeliveryRoute.ts +127 -0
  72. package/dist/models/commerce/DeliveryStop.ts +166 -0
  73. package/dist/models/commerce/DigitalDelivery.ts +106 -0
  74. package/dist/models/commerce/Driver.ts +168 -0
  75. package/dist/models/commerce/DriverPing.ts +103 -0
  76. package/dist/models/commerce/GiftCard.ts +195 -0
  77. package/dist/models/commerce/LicenseKey.ts +89 -0
  78. package/dist/models/commerce/LoyaltyPoint.ts +108 -0
  79. package/dist/models/commerce/LoyaltyReward.ts +129 -0
  80. package/dist/models/commerce/Manufacturer.ts +90 -0
  81. package/dist/models/commerce/Order.ts +198 -0
  82. package/dist/models/commerce/OrderIdempotency.ts +34 -0
  83. package/dist/models/commerce/OrderItem.ts +51 -0
  84. package/dist/models/commerce/Payment.ts +182 -0
  85. package/dist/models/commerce/Pledge.ts +112 -0
  86. package/dist/models/commerce/PrintDevice.ts +120 -0
  87. package/dist/models/commerce/Product.ts +153 -0
  88. package/dist/models/commerce/ProductUnit.ts +113 -0
  89. package/dist/models/commerce/ProductVariant.ts +111 -0
  90. package/dist/models/commerce/Receipt.ts +144 -0
  91. package/dist/models/commerce/Review.ts +171 -0
  92. package/dist/models/commerce/ShippingMethod.ts +102 -0
  93. package/dist/models/commerce/ShippingRate.ts +79 -0
  94. package/dist/models/commerce/ShippingZone.ts +116 -0
  95. package/dist/models/commerce/TaxRate.ts +167 -0
  96. package/dist/models/commerce/Transaction.ts +127 -0
  97. package/dist/models/commerce/WaitlistProduct.ts +151 -0
  98. package/dist/models/commerce/WaitlistRestaurant.ts +189 -0
  99. package/dist/models/realtime/Websocket.ts +78 -0
  100. package/dist/password-policy.ts +46 -0
  101. package/package.json +4 -4
@@ -0,0 +1,182 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'QueryLog',
6
+ table: 'query_logs',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ indexes: [
11
+ {
12
+ name: 'query_logs_executed_at_index',
13
+ columns: ['executed_at'],
14
+ },
15
+ {
16
+ name: 'query_logs_status_index',
17
+ columns: ['status'],
18
+ },
19
+ {
20
+ name: 'query_logs_duration_index',
21
+ columns: ['duration'],
22
+ },
23
+ ],
24
+
25
+ traits: {
26
+ useTimestamps: true,
27
+ useApi: {
28
+ uri: 'query-logs',
29
+ routes: ['index', 'show'],
30
+ middleware: ['auth'],
31
+ },
32
+ },
33
+
34
+ attributes: {
35
+ query: {
36
+ required: true,
37
+ fillable: true,
38
+ type: 'text',
39
+ validation: {
40
+ rule: schema.string().required(),
41
+ },
42
+ },
43
+ normalized_query: {
44
+ fillable: true,
45
+ type: 'text',
46
+ validation: {
47
+ rule: schema.string(),
48
+ },
49
+ },
50
+ duration: {
51
+ fillable: true,
52
+ default: 0,
53
+ validation: {
54
+ rule: schema.number().min(0),
55
+ },
56
+ },
57
+ connection: {
58
+ fillable: true,
59
+ default: 'unknown',
60
+ validation: {
61
+ rule: schema.string().max(255),
62
+ },
63
+ },
64
+ status: {
65
+ fillable: true,
66
+ default: 'completed',
67
+ validation: {
68
+ rule: schema.enum(['completed', 'failed', 'slow']),
69
+ },
70
+ },
71
+ error: {
72
+ fillable: true,
73
+ type: 'text',
74
+ validation: {
75
+ rule: schema.string(),
76
+ },
77
+ },
78
+ executed_at: {
79
+ required: true,
80
+ fillable: true,
81
+ validation: {
82
+ rule: schema.string().required(),
83
+ },
84
+ },
85
+ bindings: {
86
+ hidden: true,
87
+ fillable: true,
88
+ type: 'text',
89
+ validation: {
90
+ rule: schema.string(),
91
+ },
92
+ },
93
+ trace: {
94
+ hidden: true,
95
+ fillable: true,
96
+ type: 'text',
97
+ validation: {
98
+ rule: schema.string(),
99
+ },
100
+ },
101
+ model: {
102
+ fillable: true,
103
+ validation: {
104
+ rule: schema.string().max(255),
105
+ },
106
+ },
107
+ method: {
108
+ fillable: true,
109
+ validation: {
110
+ rule: schema.string().max(255),
111
+ },
112
+ },
113
+ file: {
114
+ hidden: true,
115
+ fillable: true,
116
+ validation: {
117
+ rule: schema.string(),
118
+ },
119
+ },
120
+ line: {
121
+ fillable: true,
122
+ validation: {
123
+ rule: schema.number().min(0),
124
+ },
125
+ },
126
+ memory_usage: {
127
+ fillable: true,
128
+ validation: {
129
+ rule: schema.number().min(0),
130
+ },
131
+ },
132
+ rows_affected: {
133
+ fillable: true,
134
+ validation: {
135
+ rule: schema.number().min(0),
136
+ },
137
+ },
138
+ transaction_id: {
139
+ fillable: true,
140
+ foreignKey: false,
141
+ validation: {
142
+ rule: schema.string().max(255),
143
+ },
144
+ },
145
+ tags: {
146
+ fillable: true,
147
+ validation: {
148
+ rule: schema.string(),
149
+ },
150
+ },
151
+ affected_tables: {
152
+ fillable: true,
153
+ validation: {
154
+ rule: schema.string(),
155
+ },
156
+ },
157
+ indexes_used: {
158
+ fillable: true,
159
+ validation: {
160
+ rule: schema.string(),
161
+ },
162
+ },
163
+ missing_indexes: {
164
+ fillable: true,
165
+ validation: {
166
+ rule: schema.string(),
167
+ },
168
+ },
169
+ explain_plan: {
170
+ fillable: true,
171
+ validation: {
172
+ rule: schema.string(),
173
+ },
174
+ },
175
+ optimization_suggestions: {
176
+ fillable: true,
177
+ validation: {
178
+ rule: schema.string(),
179
+ },
180
+ },
181
+ },
182
+ } as const)
@@ -0,0 +1,31 @@
1
+ # What are Models
2
+
3
+ Models are a way to define the structure of your data. They are used to interact with your database, and can be used to define relationships between different types of data.
4
+
5
+ ## Get Started
6
+
7
+ The following command will bootstrap a new model in the `app/Models` directory.
8
+
9
+ ```sh
10
+ buddy make:model User
11
+ ```
12
+
13
+ ## 🚜 Contributing
14
+
15
+ Please review the [Contributing Guide](https://github.com/stacksjs/contributing) for details.
16
+
17
+ ## 🏝 Community
18
+
19
+ For help, discussion about best practices, or any other conversation that would benefit from being searchable:
20
+
21
+ [Discussions on GitHub](https://github.com/stacksjs/stacks/discussions)
22
+
23
+ For casual chit-chat with others using this package:
24
+
25
+ [Join the Stacks Discord Server](https://stacksjs.com/discord)
26
+
27
+ ## 📄 License
28
+
29
+ The MIT License (MIT). Please see [LICENSE](../../LICENSE.md) for more information.
30
+
31
+ Made with 💙
@@ -0,0 +1,90 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'Release',
6
+ table: 'releases',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ traits: {
11
+ useUuid: true,
12
+ useTimestamps: true,
13
+ useSearch: {
14
+ displayable: ['id', 'version', 'type', 'status', 'createdAt'],
15
+ searchable: ['version', 'type', 'notes'],
16
+ sortable: ['createdAt', 'version'],
17
+ filterable: ['type', 'status'],
18
+ },
19
+
20
+ useApi: {
21
+ // Public catalog: anyone may browse, only authenticated callers may
22
+ // write. Declared explicitly because the trait now defaults BOTH sides to
23
+ // `auth` — an undeclared read route is how a customer list leaks
24
+ // (stacksjs/stacks#2224). Behaviour here is unchanged.
25
+ middleware: { read: [], write: ['auth'] },
26
+ uri: 'releases',
27
+ routes: ['index', 'show'],
28
+ },
29
+ },
30
+
31
+ attributes: {
32
+ version: {
33
+ order: 1,
34
+ fillable: true,
35
+ validation: {
36
+ rule: schema.string().required().max(50),
37
+ },
38
+ factory: faker => faker.system.semver(),
39
+ },
40
+
41
+ type: {
42
+ order: 2,
43
+ fillable: true,
44
+ validation: {
45
+ rule: schema.string().required(),
46
+ },
47
+ factory: faker => faker.helpers.arrayElement(['major', 'minor', 'patch']),
48
+ },
49
+
50
+ status: {
51
+ order: 3,
52
+ fillable: true,
53
+ validation: {
54
+ rule: schema.string().required(),
55
+ },
56
+ factory: faker => faker.helpers.arrayElement(['published', 'draft', 'scheduled']),
57
+ },
58
+
59
+ notes: {
60
+ order: 4,
61
+ fillable: true,
62
+ validation: {
63
+ rule: schema.string(),
64
+ },
65
+ factory: faker => faker.lorem.paragraph(),
66
+ },
67
+
68
+ downloads: {
69
+ order: 5,
70
+ fillable: true,
71
+ validation: {
72
+ rule: schema.number().min(0),
73
+ },
74
+ factory: faker => faker.number.int({ min: 0, max: 50000 }),
75
+ },
76
+
77
+ author: {
78
+ order: 6,
79
+ fillable: true,
80
+ validation: {
81
+ rule: schema.string().max(255),
82
+ },
83
+ factory: faker => faker.person.fullName(),
84
+ },
85
+ },
86
+
87
+ dashboard: {
88
+ highlight: true,
89
+ },
90
+ } as const)
@@ -0,0 +1,133 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'Request',
6
+ table: 'requests',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ indexes: [
11
+ {
12
+ name: 'requests_created_at_index',
13
+ columns: ['created_at'],
14
+ },
15
+ {
16
+ name: 'requests_duration_ms_index',
17
+ columns: ['duration_ms'],
18
+ },
19
+ {
20
+ name: 'requests_status_code_index',
21
+ columns: ['status_code'],
22
+ },
23
+ ],
24
+
25
+ traits: {
26
+ useTimestamps: true,
27
+ useSoftDeletes: true,
28
+ useSeeder: {
29
+ count: 50,
30
+ },
31
+ useApi: {
32
+ uri: 'requests',
33
+ routes: ['index', 'store', 'show', 'update', 'destroy'],
34
+ middleware: ['auth'],
35
+ },
36
+ },
37
+
38
+ attributes: {
39
+ method: {
40
+ fillable: true,
41
+ validation: {
42
+ rule: schema.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']),
43
+ message: {
44
+ enum: 'method must be a valid HTTP method',
45
+ required: 'method is required',
46
+ },
47
+ },
48
+ factory: faker => faker.helpers.arrayElement(['GET', 'POST', 'PUT', 'DELETE']),
49
+ },
50
+
51
+ path: {
52
+ fillable: true,
53
+ validation: {
54
+ rule: schema.string(),
55
+ message: {
56
+ string: 'path must be a string',
57
+ required: 'path is required',
58
+ },
59
+ },
60
+ factory: faker => faker.internet.url(),
61
+ },
62
+
63
+ status_code: {
64
+ fillable: true,
65
+ validation: {
66
+ rule: schema.number(),
67
+ message: {
68
+ number: 'status_code must be a number',
69
+ required: 'status_code is required',
70
+ },
71
+ },
72
+ factory: faker => faker.helpers.arrayElement([200, 201, 400, 401, 403, 404, 500]),
73
+ },
74
+
75
+ duration_ms: {
76
+ fillable: true,
77
+ validation: {
78
+ rule: schema.number(),
79
+ message: {
80
+ number: 'duration_ms must be a number',
81
+ required: 'duration_ms is required',
82
+ },
83
+ },
84
+ factory: faker => faker.number.int({ min: 50, max: 1000 }),
85
+ },
86
+
87
+ ip_address: {
88
+ fillable: true,
89
+ validation: {
90
+ rule: schema.string(),
91
+ message: {
92
+ string: 'ip_address must be a string',
93
+ required: 'ip_address is required',
94
+ },
95
+ },
96
+ factory: faker => faker.internet.ip(),
97
+ },
98
+
99
+ memory_usage: {
100
+ fillable: true,
101
+ validation: {
102
+ rule: schema.number(),
103
+ message: {
104
+ number: 'memory_usage must be a number in MB',
105
+ required: 'memory_usage is required',
106
+ },
107
+ },
108
+ factory: faker => faker.number.int({ min: 50, max: 512 }),
109
+ },
110
+
111
+ user_agent: {
112
+ fillable: true,
113
+ validation: {
114
+ rule: schema.string(),
115
+ message: {
116
+ string: 'user_agent must be a string',
117
+ },
118
+ },
119
+ factory: faker => faker.internet.userAgent(),
120
+ },
121
+
122
+ error_message: {
123
+ fillable: true,
124
+ validation: {
125
+ rule: schema.string(),
126
+ message: {
127
+ string: 'error_message must be a string',
128
+ },
129
+ },
130
+ factory: faker => faker.helpers.maybe(() => faker.lorem.sentence(), { probability: 0.2 }),
131
+ },
132
+ },
133
+ } as const)
@@ -0,0 +1,22 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'SenderDomain',
6
+ table: 'sender_domains',
7
+ belongsTo: ['Team'],
8
+ traits: {
9
+ useUuid: true,
10
+ useTimestamps: true,
11
+ useApi: { uri: 'sender-domains', routes: ['index', 'store', 'show', 'update', 'destroy'], middleware: ['auth', 'team'] },
12
+ },
13
+ indexes: [{ name: 'sender_domains_domain_unique', columns: ['domain'], unique: true }],
14
+ attributes: {
15
+ domain: { required: true, fillable: true, validation: { rule: schema.string().max(253) }, factory: faker => faker.internet.domainName() },
16
+ status: { required: true, fillable: true, default: 'pending', validation: { rule: schema.enum(['pending', 'verified', 'failed', 'disabled']) }, factory: () => 'pending' },
17
+ selector: { required: true, fillable: true, default: 'mail', validation: { rule: schema.string().max(63) }, factory: () => 'mail' },
18
+ dnsRecords: { required: false, fillable: true, validation: { rule: schema.json() }, factory: () => JSON.stringify([]) },
19
+ verifiedAt: { required: false, fillable: true, validation: { rule: schema.timestamp() }, factory: () => null },
20
+ lastCheckedAt: { required: false, fillable: true, validation: { rule: schema.timestamp() }, factory: () => null },
21
+ },
22
+ } as const)
@@ -0,0 +1,112 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ /**
5
+ * A public web property served by this app: one host (subdomain of
6
+ * `config.sites.baseDomain`, plus any verified custom domains via
7
+ * SiteDomain), one content tree, one theme. A team may own several - a main
8
+ * site, a microsite, a campaign landing page - each with its own domain.
9
+ *
10
+ * The `useApi` surface is the ADMIN surface (auth'd, team-scoped). Public
11
+ * visitors never read sites through it; the serving layer resolves the site
12
+ * from the Host header (`@stacksjs/sites`).
13
+ */
14
+ export default defineModel({
15
+ name: 'Site',
16
+ table: 'sites',
17
+ primaryKey: 'id',
18
+ autoIncrement: true,
19
+
20
+ traits: {
21
+ useUuid: true,
22
+ useTimestamps: true,
23
+ observe: true, // site:updated clears the host-resolution cache
24
+
25
+ useSeeder: {
26
+ count: 2,
27
+ },
28
+
29
+ useApi: {
30
+ uri: 'sites',
31
+ routes: ['index', 'store', 'show', 'update', 'destroy'],
32
+ middleware: ['auth', 'team'],
33
+ },
34
+
35
+ useSearch: {
36
+ displayable: ['id', 'name', 'subdomain', 'status'],
37
+ searchable: ['name', 'subdomain'],
38
+ sortable: ['name', 'createdAt'],
39
+ filterable: ['status'],
40
+ },
41
+ },
42
+
43
+ belongsTo: ['Team'],
44
+ hasMany: ['SiteDomain'],
45
+
46
+ attributes: {
47
+ name: {
48
+ required: true,
49
+ order: 1,
50
+ fillable: true,
51
+ validation: {
52
+ rule: schema.string().min(2).max(255),
53
+ message: {
54
+ min: 'Site name must have a minimum of 2 characters',
55
+ max: 'Site name must have a maximum of 255 characters',
56
+ },
57
+ },
58
+ factory: faker => `${faker.company.name()} School`,
59
+ },
60
+
61
+ subdomain: {
62
+ required: true,
63
+ order: 2,
64
+ fillable: true,
65
+ unique: true,
66
+ validation: {
67
+ rule: schema.string().min(2).max(63).matches(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/),
68
+ message: {
69
+ matches: 'Subdomain may only contain lowercase letters, numbers and hyphens',
70
+ },
71
+ },
72
+ factory: faker => faker.internet.domainWord(),
73
+ },
74
+
75
+ status: {
76
+ required: true,
77
+ order: 3,
78
+ fillable: true,
79
+ default: 'active',
80
+ validation: {
81
+ rule: schema.enum(['active', 'suspended', 'archived'] as const),
82
+ },
83
+ factory: () => 'active',
84
+ },
85
+
86
+ /** Theme tokens, locale, analytics id - render-time knobs, JSON. */
87
+ settings: {
88
+ required: false,
89
+ order: 4,
90
+ fillable: true,
91
+ validation: {
92
+ rule: schema.json(),
93
+ },
94
+ factory: () => JSON.stringify({}),
95
+ },
96
+
97
+ timezone: {
98
+ required: false,
99
+ order: 5,
100
+ fillable: true,
101
+ default: 'America/New_York',
102
+ validation: {
103
+ rule: schema.string().max(64),
104
+ },
105
+ factory: () => 'America/New_York',
106
+ },
107
+ },
108
+
109
+ dashboard: {
110
+ highlight: true,
111
+ },
112
+ })
@@ -0,0 +1,74 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ /**
5
+ * A custom domain routed to a Site. Only rows with `verifiedAt` set resolve
6
+ * traffic - verification proves the school controls the DNS before we serve
7
+ * their content on it. `isPrimary` names the canonical host: sessions are
8
+ * host-scoped cookies, so every other host 301s there and portal logins
9
+ * always land on one origin.
10
+ */
11
+ export default defineModel({
12
+ name: 'SiteDomain',
13
+ table: 'site_domains',
14
+ primaryKey: 'id',
15
+ autoIncrement: true,
16
+
17
+ traits: {
18
+ useTimestamps: true,
19
+ observe: true, // domain changes clear the host-resolution cache
20
+
21
+ useApi: {
22
+ uri: 'site-domains',
23
+ routes: ['index', 'store', 'show', 'update', 'destroy'],
24
+ middleware: ['auth'],
25
+ },
26
+ },
27
+
28
+ belongsTo: ['Site'],
29
+
30
+ attributes: {
31
+ domain: {
32
+ required: true,
33
+ order: 1,
34
+ fillable: true,
35
+ unique: true,
36
+ validation: {
37
+ rule: schema.string().min(4).max(253),
38
+ },
39
+ factory: faker => faker.internet.domainName(),
40
+ },
41
+
42
+ isPrimary: {
43
+ required: false,
44
+ order: 2,
45
+ fillable: true,
46
+ default: false,
47
+ validation: {
48
+ rule: schema.boolean(),
49
+ },
50
+ factory: () => false,
51
+ },
52
+
53
+ verifiedAt: {
54
+ required: false,
55
+ order: 3,
56
+ fillable: true,
57
+ validation: {
58
+ rule: schema.timestamp(),
59
+ },
60
+ factory: () => null,
61
+ },
62
+
63
+ sslStatus: {
64
+ required: false,
65
+ order: 4,
66
+ fillable: true,
67
+ default: 'pending',
68
+ validation: {
69
+ rule: schema.enum(['pending', 'issued', 'failed'] as const),
70
+ },
71
+ factory: () => 'pending',
72
+ },
73
+ },
74
+ })
@@ -0,0 +1,65 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ /**
5
+ * A phone number that texted STOP. Keyed by NUMBER, not user - the reply
6
+ * comes from a handset, and whoever holds it has spoken for it. Checked by
7
+ * `notify()`'s sms channel on every non-emergency send; written and cleared
8
+ * by `handleInboundSms()` (`@stacksjs/sms`).
9
+ */
10
+ export default defineModel({
11
+ name: 'SmsOptOut',
12
+ table: 'sms_opt_outs',
13
+ primaryKey: 'id',
14
+ autoIncrement: true,
15
+
16
+ indexes: [
17
+ {
18
+ name: 'sms_opt_outs_phone_unique',
19
+ columns: ['phone'],
20
+ unique: true,
21
+ },
22
+ ],
23
+
24
+ traits: {
25
+ useTimestamps: true,
26
+ },
27
+
28
+ attributes: {
29
+ /** E.164 where derivable (`+13105550199`). */
30
+ phone: {
31
+ required: true,
32
+ order: 1,
33
+ fillable: true,
34
+ unique: true,
35
+ validation: {
36
+ rule: schema.string().min(7).max(20),
37
+ },
38
+ factory: faker => faker.phone.number(),
39
+ },
40
+
41
+ reason: {
42
+ required: false,
43
+ order: 2,
44
+ fillable: true,
45
+ default: 'stop-keyword',
46
+ validation: {
47
+ rule: schema.string().max(64),
48
+ },
49
+ factory: () => 'stop-keyword',
50
+ },
51
+
52
+ optedOutAt: {
53
+ required: true,
54
+ order: 3,
55
+ fillable: true,
56
+ validation: {
57
+ rule: schema.timestamp(),
58
+ },
59
+ factory: (faker) => {
60
+ const date = faker.date.recent()
61
+ return date.toISOString().slice(0, 19).replace('T', ' ')
62
+ },
63
+ },
64
+ },
65
+ } as const)