@stacksjs/orm 0.72.16 → 0.72.18
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/dist/index.d.ts +97 -97
- package/dist/index.js +7 -7
- package/dist/models/Activity.ts +95 -0
- package/dist/models/AnalyticsEvent.ts +70 -0
- package/dist/models/Automation.ts +23 -0
- package/dist/models/AutomationRun.ts +26 -0
- package/dist/models/Board.ts +104 -0
- package/dist/models/BoardColumn.ts +88 -0
- package/dist/models/Campaign.ts +321 -0
- package/dist/models/CampaignSend.ts +191 -0
- package/dist/models/CampaignVariant.ts +25 -0
- package/dist/models/Card.ts +118 -0
- package/dist/models/CardComment.ts +68 -0
- package/dist/models/Comment.ts +109 -0
- package/dist/models/CommunicationSuppression.ts +22 -0
- package/dist/models/ConsentEvent.ts +30 -0
- package/dist/models/Content/Author.ts +109 -0
- package/dist/models/Content/Menu.ts +56 -0
- package/dist/models/Content/MenuItem.ts +91 -0
- package/dist/models/Content/Page.ts +205 -0
- package/dist/models/Content/PageRevision.ts +86 -0
- package/dist/models/Content/Post.ts +230 -0
- package/dist/models/Content/Redirect.ts +80 -0
- package/dist/models/Deployment.ts +117 -0
- package/dist/models/EmailIdempotency.ts +64 -0
- package/dist/models/EmailList.ts +134 -0
- package/dist/models/EmailListSubscriber.ts +91 -0
- package/dist/models/EmailSuppression.ts +52 -0
- package/dist/models/EmailWebhookEvent.ts +51 -0
- package/dist/models/Error.ts +77 -0
- package/dist/models/FailedJob.ts +84 -0
- package/dist/models/Forms/Form.ts +91 -0
- package/dist/models/Forms/FormField.ts +127 -0
- package/dist/models/Forms/FormSubmission.ts +114 -0
- package/dist/models/Job.ts +69 -0
- package/dist/models/Label.ts +67 -0
- package/dist/models/Log.ts +134 -0
- package/dist/models/MagicLinkToken.ts +97 -0
- package/dist/models/MailPreference.ts +213 -0
- package/dist/models/Notification.ts +68 -0
- package/dist/models/NotificationDelivery.ts +102 -0
- package/dist/models/PaymentMethod.ts +98 -0
- package/dist/models/PaymentProduct.ts +91 -0
- package/dist/models/PaymentTransaction.ts +78 -0
- package/dist/models/QueryLog.ts +182 -0
- package/dist/models/README.md +31 -0
- package/dist/models/Release.ts +90 -0
- package/dist/models/Request.ts +133 -0
- package/dist/models/SenderDomain.ts +22 -0
- package/dist/models/Site.ts +112 -0
- package/dist/models/SiteDomain.ts +74 -0
- package/dist/models/SmsOptOut.ts +65 -0
- package/dist/models/SocialAccount.ts +70 -0
- package/dist/models/SocialPost.ts +144 -0
- package/dist/models/Subscriber.ts +89 -0
- package/dist/models/SubscriberEmail.ts +55 -0
- package/dist/models/Subscription.ts +153 -0
- package/dist/models/Tag.ts +105 -0
- package/dist/models/Team.ts +75 -0
- package/dist/models/TeamInvitation.ts +159 -0
- package/dist/models/TeamMember.ts +88 -0
- package/dist/models/UsageEvent.ts +24 -0
- package/dist/models/User.ts +142 -0
- package/dist/models/commerce/Auction.ts +173 -0
- package/dist/models/commerce/AuctionItem.ts +204 -0
- package/dist/models/commerce/Bid.ts +129 -0
- package/dist/models/commerce/Cart.ts +140 -0
- package/dist/models/commerce/CartItem.ts +138 -0
- package/dist/models/commerce/Category.ts +125 -0
- package/dist/models/commerce/Coupon.ts +186 -0
- package/dist/models/commerce/Customer.ts +132 -0
- package/dist/models/commerce/DeliveryRoute.ts +127 -0
- package/dist/models/commerce/DeliveryStop.ts +166 -0
- package/dist/models/commerce/DigitalDelivery.ts +106 -0
- package/dist/models/commerce/Driver.ts +168 -0
- package/dist/models/commerce/DriverPing.ts +103 -0
- package/dist/models/commerce/GiftCard.ts +195 -0
- package/dist/models/commerce/LicenseKey.ts +89 -0
- package/dist/models/commerce/LoyaltyPoint.ts +108 -0
- package/dist/models/commerce/LoyaltyReward.ts +129 -0
- package/dist/models/commerce/Manufacturer.ts +90 -0
- package/dist/models/commerce/Order.ts +198 -0
- package/dist/models/commerce/OrderIdempotency.ts +34 -0
- package/dist/models/commerce/OrderItem.ts +51 -0
- package/dist/models/commerce/Payment.ts +182 -0
- package/dist/models/commerce/Pledge.ts +112 -0
- package/dist/models/commerce/PrintDevice.ts +120 -0
- package/dist/models/commerce/Product.ts +153 -0
- package/dist/models/commerce/ProductUnit.ts +113 -0
- package/dist/models/commerce/ProductVariant.ts +111 -0
- package/dist/models/commerce/Receipt.ts +144 -0
- package/dist/models/commerce/Review.ts +171 -0
- package/dist/models/commerce/ShippingMethod.ts +102 -0
- package/dist/models/commerce/ShippingRate.ts +79 -0
- package/dist/models/commerce/ShippingZone.ts +116 -0
- package/dist/models/commerce/TaxRate.ts +167 -0
- package/dist/models/commerce/Transaction.ts +127 -0
- package/dist/models/commerce/WaitlistProduct.ts +151 -0
- package/dist/models/commerce/WaitlistRestaurant.ts +189 -0
- package/dist/models/realtime/Websocket.ts +78 -0
- package/dist/password-policy.ts +46 -0
- package/package.json +4 -4
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'Subscriber',
|
|
6
|
+
table: 'subscribers',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
belongsTo: ['User'],
|
|
10
|
+
hasMany: ['SubscriberEmail', 'EmailListSubscriber', 'CampaignSend'],
|
|
11
|
+
|
|
12
|
+
traits: {
|
|
13
|
+
useUuid: true,
|
|
14
|
+
useTimestamps: true,
|
|
15
|
+
useSeeder: {
|
|
16
|
+
count: 10,
|
|
17
|
+
},
|
|
18
|
+
useApi: {
|
|
19
|
+
uri: 'subscribers',
|
|
20
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
21
|
+
// Subscriber rows are PII (email + status + source). Auth-gate
|
|
22
|
+
// every CRUD route. The public signup path is the dedicated
|
|
23
|
+
// `/api/email/subscribe` action — that has its own rate-limited,
|
|
24
|
+
// CSRF-skipped handler that wraps `Subscriber.create()`. Apps
|
|
25
|
+
// that want public destroy (one-click unsubscribe) should use
|
|
26
|
+
// the existing `/api/email/unsubscribe` UUID-token route, not
|
|
27
|
+
// expose `DELETE /api/subscribers/{id}`.
|
|
28
|
+
middleware: ['auth'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
attributes: {
|
|
33
|
+
email: {
|
|
34
|
+
unique: true,
|
|
35
|
+
required: true,
|
|
36
|
+
fillable: true,
|
|
37
|
+
validation: {
|
|
38
|
+
rule: schema.string().email().max(255),
|
|
39
|
+
message: {
|
|
40
|
+
string: 'email must be a string',
|
|
41
|
+
required: 'email is required',
|
|
42
|
+
email: 'email must be a valid email address',
|
|
43
|
+
max: 'email must have a maximum of 255 characters',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
factory: faker => faker.internet.email(),
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
status: {
|
|
50
|
+
required: true,
|
|
51
|
+
fillable: true,
|
|
52
|
+
default: 'subscribed',
|
|
53
|
+
validation: {
|
|
54
|
+
rule: schema.enum(['subscribed', 'unsubscribed', 'pending', 'bounced']),
|
|
55
|
+
message: {
|
|
56
|
+
enum: 'status must be one of: subscribed, unsubscribed, pending, bounced',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
factory: faker => faker.helpers.arrayElement(['subscribed', 'subscribed', 'subscribed', 'unsubscribed', 'pending']),
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
source: {
|
|
63
|
+
required: false,
|
|
64
|
+
fillable: true,
|
|
65
|
+
default: 'homepage',
|
|
66
|
+
validation: {
|
|
67
|
+
rule: schema.string().max(100),
|
|
68
|
+
message: {
|
|
69
|
+
string: 'source must be a string',
|
|
70
|
+
max: 'source must have a maximum of 100 characters',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
factory: faker => faker.helpers.arrayElement(['homepage', 'blog', 'landing-page', 'api', 'import']),
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Set when the subscriber globally unsubscribes via the legacy
|
|
77
|
+
// /api/email/unsubscribe?token=<uuid> route. Per-list unsubscribe
|
|
78
|
+
// state (the modern flow) lives on `email_list_subscribers` so a
|
|
79
|
+
// user can drop "Weekly Digest" without losing transactional mail.
|
|
80
|
+
unsubscribedAt: {
|
|
81
|
+
required: false,
|
|
82
|
+
fillable: true,
|
|
83
|
+
validation: {
|
|
84
|
+
rule: schema.timestamp(),
|
|
85
|
+
},
|
|
86
|
+
factory: () => null,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
} as const)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'SubscriberEmail',
|
|
6
|
+
table: 'subscriber_emails',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
belongsTo: ['Subscriber'],
|
|
10
|
+
|
|
11
|
+
traits: {
|
|
12
|
+
useUuid: true,
|
|
13
|
+
useTimestamps: true,
|
|
14
|
+
useApi: {
|
|
15
|
+
uri: 'subscriber-emails',
|
|
16
|
+
routes: ['index', 'show'],
|
|
17
|
+
// SubscriberEmail rows are PII (email + signup source). Without
|
|
18
|
+
// this gate, anyone can scrape the list at GET /api/subscriber-emails.
|
|
19
|
+
// Apps that genuinely want a public list (e.g. a transparency
|
|
20
|
+
// page) can override the model in `app/Models/`.
|
|
21
|
+
middleware: ['auth'],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
attributes: {
|
|
26
|
+
email: {
|
|
27
|
+
required: true,
|
|
28
|
+
fillable: true,
|
|
29
|
+
validation: {
|
|
30
|
+
rule: schema.string().email().max(255),
|
|
31
|
+
message: {
|
|
32
|
+
string: 'email must be a string',
|
|
33
|
+
required: 'email is required',
|
|
34
|
+
email: 'email must be a valid email address',
|
|
35
|
+
max: 'email must have a maximum of 255 characters',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
factory: faker => faker.internet.email(),
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
source: {
|
|
42
|
+
required: false,
|
|
43
|
+
fillable: true,
|
|
44
|
+
default: 'homepage',
|
|
45
|
+
validation: {
|
|
46
|
+
rule: schema.string().max(100),
|
|
47
|
+
message: {
|
|
48
|
+
string: 'source must be a string',
|
|
49
|
+
max: 'source must have a maximum of 100 characters',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
factory: faker => faker.helpers.arrayElement(['homepage', 'blog', 'landing-page', 'api', 'import']),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
} as const)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'Subscription', // defaults to the sanitized file name
|
|
6
|
+
table: 'subscriptions', // defaults to the lowercase, plural name of the model name (or the name of the model file)
|
|
7
|
+
primaryKey: 'id', // defaults to `id`
|
|
8
|
+
autoIncrement: true, // defaults to true
|
|
9
|
+
belongsTo: ['User'],
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
},
|
|
13
|
+
attributes: {
|
|
14
|
+
type: {
|
|
15
|
+
fillable: true,
|
|
16
|
+
validation: {
|
|
17
|
+
rule: schema.string().required().max(512),
|
|
18
|
+
message: {
|
|
19
|
+
string: 'type must be a string',
|
|
20
|
+
required: 'type is required',
|
|
21
|
+
max: 'type must have a maximum of 512 characters',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
factory: faker => faker.lorem.lines(1),
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
plan: {
|
|
28
|
+
fillable: true,
|
|
29
|
+
validation: {
|
|
30
|
+
rule: schema.string().max(100),
|
|
31
|
+
message: {
|
|
32
|
+
string: 'plan must be a string',
|
|
33
|
+
required: 'plan is required',
|
|
34
|
+
max: 'plan must have a maximum of 100 characters',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
factory: faker => faker.helpers.arrayElement(['free', 'starter', 'pro', 'enterprise']),
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
providerId: {
|
|
41
|
+
unique: true,
|
|
42
|
+
fillable: true,
|
|
43
|
+
validation: {
|
|
44
|
+
rule: schema.string().required().max(255),
|
|
45
|
+
message: {
|
|
46
|
+
string: 'provider_id must be a string',
|
|
47
|
+
required: 'provider_id is required',
|
|
48
|
+
max: 'provider_id must have a maximum of 512 characters',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
factory: faker => faker.string.alphanumeric(10),
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
providerStatus: {
|
|
55
|
+
fillable: true,
|
|
56
|
+
validation: {
|
|
57
|
+
rule: schema.string().required(),
|
|
58
|
+
message: {
|
|
59
|
+
string: 'provider_status must be a string',
|
|
60
|
+
required: 'provider_status is required',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
factory: faker => faker.string.alpha(10),
|
|
64
|
+
},
|
|
65
|
+
unitPrice: {
|
|
66
|
+
fillable: true,
|
|
67
|
+
validation: {
|
|
68
|
+
rule: schema.number().required(),
|
|
69
|
+
message: {
|
|
70
|
+
string: 'unit_price must be a number',
|
|
71
|
+
required: 'unit_price is required',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
factory: faker => faker.number.int({ min: 1000, max: 10000 }),
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
providerType: {
|
|
78
|
+
fillable: true,
|
|
79
|
+
validation: {
|
|
80
|
+
rule: schema.string().max(255).required(),
|
|
81
|
+
message: {
|
|
82
|
+
string: 'provider_type must be a string',
|
|
83
|
+
required: 'provider_type is required',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
factory: faker => faker.string.alpha(10),
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
providerPriceId: {
|
|
90
|
+
fillable: true,
|
|
91
|
+
validation: {
|
|
92
|
+
rule: schema.string(),
|
|
93
|
+
message: {
|
|
94
|
+
string: 'provider_price_id must be a string',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
factory: faker => faker.commerce.price(),
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
quantity: {
|
|
101
|
+
fillable: true,
|
|
102
|
+
validation: {
|
|
103
|
+
rule: schema.number(),
|
|
104
|
+
message: {
|
|
105
|
+
number: 'quantity must be a number',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
factory: faker => faker.number.int(100),
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
trialEndsAt: {
|
|
112
|
+
fillable: true,
|
|
113
|
+
validation: {
|
|
114
|
+
rule: schema.timestamp(),
|
|
115
|
+
message: {
|
|
116
|
+
timestamp: 'trial_ends_at must be a valid timestamp',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
factory: (faker) => {
|
|
120
|
+
const date = faker.date.future()
|
|
121
|
+
return date.toISOString().slice(0, 19).replace('T', ' ')
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
endsAt: {
|
|
126
|
+
fillable: true,
|
|
127
|
+
validation: {
|
|
128
|
+
rule: schema.timestamp(),
|
|
129
|
+
message: {
|
|
130
|
+
timestamp: 'ends_at must be a valid timestamp',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
factory: (faker) => {
|
|
134
|
+
const date = faker.date.future()
|
|
135
|
+
return date.toISOString().slice(0, 19).replace('T', ' ')
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
lastUsedAt: {
|
|
140
|
+
validation: {
|
|
141
|
+
rule: schema.timestamp(),
|
|
142
|
+
message: {
|
|
143
|
+
timestamp: 'last_used_at must be a valid timestamp',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
fillable: true,
|
|
147
|
+
factory: (faker) => {
|
|
148
|
+
const date = faker.date.recent()
|
|
149
|
+
return date.toISOString().slice(0, 19).replace('T', ' ')
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
} as const)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
const tagNames = [
|
|
5
|
+
'javascript',
|
|
6
|
+
'typescript',
|
|
7
|
+
'stx',
|
|
8
|
+
'react',
|
|
9
|
+
'nodejs',
|
|
10
|
+
'database',
|
|
11
|
+
'performance',
|
|
12
|
+
'security',
|
|
13
|
+
'api',
|
|
14
|
+
'testing',
|
|
15
|
+
'devops',
|
|
16
|
+
'cloud',
|
|
17
|
+
'frontend',
|
|
18
|
+
'backend',
|
|
19
|
+
'mobile',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export default defineModel({
|
|
23
|
+
name: 'Tag',
|
|
24
|
+
table: 'tags',
|
|
25
|
+
primaryKey: 'id',
|
|
26
|
+
autoIncrement: true,
|
|
27
|
+
|
|
28
|
+
traits: {
|
|
29
|
+
useUuid: true,
|
|
30
|
+
useTimestamps: true,
|
|
31
|
+
useSeeder: {
|
|
32
|
+
count: tagNames.length,
|
|
33
|
+
fixtures: tagNames.map(name => ({ name, slug: name })),
|
|
34
|
+
},
|
|
35
|
+
useApi: {
|
|
36
|
+
// Public catalog: anyone may browse, only authenticated callers may
|
|
37
|
+
// write. Declared explicitly because the trait now defaults BOTH sides to
|
|
38
|
+
// `auth` — an undeclared read route is how a customer list leaks
|
|
39
|
+
// (stacksjs/stacks#2224). Behaviour here is unchanged.
|
|
40
|
+
middleware: { read: [], write: ['auth'] },
|
|
41
|
+
uri: 'tags',
|
|
42
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
belongsToMany: {
|
|
47
|
+
posts: {
|
|
48
|
+
model: 'Post',
|
|
49
|
+
table: 'taggable_models',
|
|
50
|
+
foreignKey: 'tag_id',
|
|
51
|
+
relatedKey: 'taggable_id',
|
|
52
|
+
pivot: {
|
|
53
|
+
columns: {
|
|
54
|
+
taggable_type: { default: 'posts' },
|
|
55
|
+
},
|
|
56
|
+
timestamps: true,
|
|
57
|
+
uniques: [['tag_id', 'taggable_id', 'taggable_type']],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
attributes: {
|
|
63
|
+
name: {
|
|
64
|
+
required: true,
|
|
65
|
+
unique: true,
|
|
66
|
+
fillable: true,
|
|
67
|
+
validation: {
|
|
68
|
+
rule: schema.string().min(2).max(50),
|
|
69
|
+
message: {
|
|
70
|
+
min: 'Tag name must have at least 2 characters',
|
|
71
|
+
max: 'Tag name must have at most 50 characters',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
factory: faker => `tag-${faker.string.alphanumeric(12).toLowerCase()}`,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
slug: {
|
|
78
|
+
required: true,
|
|
79
|
+
unique: true,
|
|
80
|
+
fillable: true,
|
|
81
|
+
validation: {
|
|
82
|
+
rule: schema.string().min(2).max(50),
|
|
83
|
+
},
|
|
84
|
+
factory: faker => `tag-${faker.string.alphanumeric(12).toLowerCase()}`,
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
description: {
|
|
88
|
+
required: false,
|
|
89
|
+
fillable: true,
|
|
90
|
+
validation: {
|
|
91
|
+
rule: schema.string().max(255),
|
|
92
|
+
},
|
|
93
|
+
factory: faker => faker.lorem.sentence(),
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
color: {
|
|
97
|
+
required: false,
|
|
98
|
+
fillable: true,
|
|
99
|
+
validation: {
|
|
100
|
+
rule: schema.string().max(20),
|
|
101
|
+
},
|
|
102
|
+
factory: faker => faker.helpers.arrayElement(['blue', 'green', 'red', 'purple', 'yellow', 'orange', 'pink', 'gray']),
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
} as const)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'Team',
|
|
6
|
+
table: 'teams',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
useTimestamps: true,
|
|
13
|
+
useSearch: {
|
|
14
|
+
displayable: ['id', 'name', 'description', 'memberCount', 'createdAt'],
|
|
15
|
+
searchable: ['name', 'description'],
|
|
16
|
+
sortable: ['name', 'createdAt', 'updatedAt', 'memberCount'],
|
|
17
|
+
filterable: ['status'],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
useSeeder: {
|
|
21
|
+
count: 5,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
useApi: {
|
|
25
|
+
uri: 'teams',
|
|
26
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
27
|
+
middleware: ['auth'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
hasMany: ['TeamMember', 'TeamInvitation'],
|
|
32
|
+
|
|
33
|
+
attributes: {
|
|
34
|
+
name: {
|
|
35
|
+
order: 1,
|
|
36
|
+
unique: true,
|
|
37
|
+
fillable: true,
|
|
38
|
+
validation: {
|
|
39
|
+
rule: schema.string().required().min(2).max(100),
|
|
40
|
+
},
|
|
41
|
+
factory: faker => faker.company.name(),
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
description: {
|
|
45
|
+
order: 2,
|
|
46
|
+
fillable: true,
|
|
47
|
+
validation: {
|
|
48
|
+
rule: schema.string().max(500),
|
|
49
|
+
},
|
|
50
|
+
factory: faker => faker.company.catchPhrase(),
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
memberCount: {
|
|
54
|
+
order: 3,
|
|
55
|
+
fillable: false,
|
|
56
|
+
default: 0,
|
|
57
|
+
validation: {
|
|
58
|
+
rule: schema.number().min(0),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
status: {
|
|
63
|
+
order: 4,
|
|
64
|
+
fillable: true,
|
|
65
|
+
validation: {
|
|
66
|
+
rule: schema.string(),
|
|
67
|
+
},
|
|
68
|
+
factory: faker => faker.helpers.arrayElement(['active', 'inactive']),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
dashboard: {
|
|
73
|
+
highlight: true,
|
|
74
|
+
},
|
|
75
|
+
} as const)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A single-use invitation to join a team.
|
|
6
|
+
*
|
|
7
|
+
* The raw bearer token is only returned by the dedicated invite action and
|
|
8
|
+
* sent to the recipient. This model stores its SHA-256 digest so generated
|
|
9
|
+
* API responses and database reads cannot leak a usable invitation.
|
|
10
|
+
*/
|
|
11
|
+
export default defineModel({
|
|
12
|
+
name: 'TeamInvitation',
|
|
13
|
+
table: 'team_invitations',
|
|
14
|
+
primaryKey: 'id',
|
|
15
|
+
autoIncrement: true,
|
|
16
|
+
|
|
17
|
+
indexes: [
|
|
18
|
+
{
|
|
19
|
+
name: 'team_invitations_token_hash_unique',
|
|
20
|
+
columns: ['token_hash'],
|
|
21
|
+
unique: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'team_invitations_pending_key_unique',
|
|
25
|
+
columns: ['pending_key'],
|
|
26
|
+
unique: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'team_invitations_team_email_status_index',
|
|
30
|
+
columns: ['team_id', 'email', 'status'],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
belongsTo: ['Team'],
|
|
35
|
+
|
|
36
|
+
traits: {
|
|
37
|
+
useUuid: true,
|
|
38
|
+
useTimestamps: true,
|
|
39
|
+
useSeeder: { count: 0 },
|
|
40
|
+
useSearch: {
|
|
41
|
+
displayable: ['id', 'teamId', 'email', 'role', 'status', 'deliveryStatus', 'expiresAt', 'createdAt'],
|
|
42
|
+
searchable: ['email'],
|
|
43
|
+
sortable: ['email', 'role', 'status', 'expiresAt', 'createdAt', 'updatedAt'],
|
|
44
|
+
filterable: ['teamId', 'role', 'status', 'deliveryStatus'],
|
|
45
|
+
},
|
|
46
|
+
useApi: {
|
|
47
|
+
uri: 'team-invitations',
|
|
48
|
+
routes: ['index', 'show', 'destroy'],
|
|
49
|
+
middleware: ['auth', 'team'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
attributes: {
|
|
54
|
+
teamId: {
|
|
55
|
+
required: true,
|
|
56
|
+
fillable: true,
|
|
57
|
+
validation: {
|
|
58
|
+
rule: schema.number().required(),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
email: {
|
|
63
|
+
required: true,
|
|
64
|
+
fillable: true,
|
|
65
|
+
validation: {
|
|
66
|
+
rule: schema.string().email().required().max(320),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
role: {
|
|
71
|
+
required: true,
|
|
72
|
+
fillable: true,
|
|
73
|
+
default: 'member',
|
|
74
|
+
validation: {
|
|
75
|
+
rule: schema.enum(['admin', 'member', 'viewer']),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
tokenHash: {
|
|
80
|
+
required: true,
|
|
81
|
+
hidden: true,
|
|
82
|
+
validation: {
|
|
83
|
+
rule: schema.string().required().max(64),
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
pendingKey: {
|
|
88
|
+
required: false,
|
|
89
|
+
hidden: true,
|
|
90
|
+
validation: {
|
|
91
|
+
rule: schema.string().max(384),
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
invitedByUserId: {
|
|
96
|
+
required: false,
|
|
97
|
+
validation: {
|
|
98
|
+
rule: schema.number(),
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
acceptedByUserId: {
|
|
103
|
+
required: false,
|
|
104
|
+
validation: {
|
|
105
|
+
rule: schema.number(),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
status: {
|
|
110
|
+
required: true,
|
|
111
|
+
fillable: true,
|
|
112
|
+
default: 'pending',
|
|
113
|
+
validation: {
|
|
114
|
+
rule: schema.enum(['pending', 'accepted', 'revoked', 'expired']),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
deliveryStatus: {
|
|
119
|
+
required: true,
|
|
120
|
+
default: 'pending',
|
|
121
|
+
validation: {
|
|
122
|
+
rule: schema.enum(['pending', 'sent', 'failed']),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
deliveryError: {
|
|
127
|
+
required: false,
|
|
128
|
+
hidden: true,
|
|
129
|
+
validation: {
|
|
130
|
+
rule: schema.string().max(2000),
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
expiresAt: {
|
|
135
|
+
required: true,
|
|
136
|
+
validation: {
|
|
137
|
+
rule: schema.timestamp().required(),
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
deliveredAt: {
|
|
142
|
+
required: false,
|
|
143
|
+
validation: {
|
|
144
|
+
rule: schema.timestamp(),
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
acceptedAt: {
|
|
149
|
+
required: false,
|
|
150
|
+
validation: {
|
|
151
|
+
rule: schema.timestamp(),
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
dashboard: {
|
|
157
|
+
enabled: false,
|
|
158
|
+
},
|
|
159
|
+
} as const)
|