@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.
- package/dist/index.d.ts +97 -97
- 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,118 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Kanban card (stacksjs/stacks#1846).
|
|
6
|
+
*
|
|
7
|
+
* A card belongs to a column (and, via denormalisation, to a board) so
|
|
8
|
+
* board-wide queries don't need a two-hop join. Cards carry labels +
|
|
9
|
+
* assignees via pivot tables (`card_labels`, `card_assignees`) — those
|
|
10
|
+
* relations land in Phase 3 alongside the card-detail modal.
|
|
11
|
+
*
|
|
12
|
+
* Hidden from the sidebar as a standalone model row — cards are seen
|
|
13
|
+
* through `/kanban/[boardId]` (Phase 2), not as a top-level model
|
|
14
|
+
* viewer. The dashboard's modular config from #1843 carries that
|
|
15
|
+
* `enabled: false` to both sidebars.
|
|
16
|
+
*/
|
|
17
|
+
export default defineModel({
|
|
18
|
+
name: 'Card',
|
|
19
|
+
table: 'cards',
|
|
20
|
+
primaryKey: 'id',
|
|
21
|
+
autoIncrement: true,
|
|
22
|
+
|
|
23
|
+
traits: {
|
|
24
|
+
useUuid: true,
|
|
25
|
+
useTimestamps: true,
|
|
26
|
+
useSearch: {
|
|
27
|
+
displayable: ['id', 'title', 'columnId', 'boardId', 'position', 'dueDate'],
|
|
28
|
+
searchable: ['title', 'description'],
|
|
29
|
+
sortable: ['position', 'createdAt', 'updatedAt', 'dueDate'],
|
|
30
|
+
filterable: ['boardId', 'columnId', 'archived'],
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
useSeeder: { count: 0 },
|
|
34
|
+
|
|
35
|
+
useApi: {
|
|
36
|
+
uri: 'cards',
|
|
37
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
38
|
+
middleware: ['auth'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
belongsTo: ['BoardColumn', 'Board', 'User'],
|
|
43
|
+
// Pivot relations land in Phase 3 (labels + assignees).
|
|
44
|
+
// belongsToMany: {
|
|
45
|
+
// labels: { model: 'Label', table: 'card_labels', foreignKey: 'card_id', relatedKey: 'label_id' },
|
|
46
|
+
// assignees: { model: 'User', table: 'card_assignees', foreignKey: 'card_id', relatedKey: 'user_id' },
|
|
47
|
+
// },
|
|
48
|
+
|
|
49
|
+
attributes: {
|
|
50
|
+
columnId: {
|
|
51
|
+
order: 1,
|
|
52
|
+
fillable: true,
|
|
53
|
+
validation: { rule: schema.number().required() },
|
|
54
|
+
factory: () => 1,
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
boardId: {
|
|
58
|
+
order: 2,
|
|
59
|
+
fillable: true,
|
|
60
|
+
// Denormalised from `column.board_id` so a "all cards on board X"
|
|
61
|
+
// query stays single-table. The kanban API keeps it in sync on
|
|
62
|
+
// card create/move (Phase 2).
|
|
63
|
+
validation: { rule: schema.number().required() },
|
|
64
|
+
factory: () => 1,
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
title: {
|
|
68
|
+
order: 3,
|
|
69
|
+
fillable: true,
|
|
70
|
+
validation: { rule: schema.string().required().min(1).max(300) },
|
|
71
|
+
factory: faker => faker.lorem.sentence({ min: 3, max: 8 }),
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
description: {
|
|
75
|
+
order: 4,
|
|
76
|
+
fillable: true,
|
|
77
|
+
// Markdown-formatted long description. Rendered in the card detail
|
|
78
|
+
// modal (Phase 3); the kanban board view shows the first line only.
|
|
79
|
+
validation: { rule: schema.string().max(10000) },
|
|
80
|
+
factory: faker => faker.lorem.paragraph(),
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
position: {
|
|
84
|
+
order: 5,
|
|
85
|
+
fillable: true,
|
|
86
|
+
// Sort order within the parent column. Drag-reorder reflows
|
|
87
|
+
// adjacent rows in a single transaction (Phase 2). Moving across
|
|
88
|
+
// columns updates both `columnId` and `position`.
|
|
89
|
+
validation: { rule: schema.number().min(0) },
|
|
90
|
+
factory: faker => faker.number.int({ min: 0, max: 100 }),
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
createdByUserId: {
|
|
94
|
+
order: 6,
|
|
95
|
+
fillable: true,
|
|
96
|
+
// The user who created the card. Set server-side from
|
|
97
|
+
// `request.user.id` on store; surfaced in the card detail.
|
|
98
|
+
validation: { rule: schema.number() },
|
|
99
|
+
factory: () => null,
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
dueDate: {
|
|
103
|
+
order: 7,
|
|
104
|
+
fillable: true,
|
|
105
|
+
validation: { rule: schema.string() },
|
|
106
|
+
factory: () => null,
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
archived: {
|
|
110
|
+
order: 8,
|
|
111
|
+
fillable: true,
|
|
112
|
+
validation: { rule: schema.boolean() },
|
|
113
|
+
factory: () => false,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
dashboard: { enabled: false },
|
|
118
|
+
} as const)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Card comment for the dashboard Kanban surface.
|
|
6
|
+
*
|
|
7
|
+
* Each comment belongs to a card and (optionally) to the user who
|
|
8
|
+
* wrote it. Anonymous comments are allowed via nullable `user_id` —
|
|
9
|
+
* makes the dev dashboard usable on localhost without an auth round-
|
|
10
|
+
* trip, and supports system-generated activity entries.
|
|
11
|
+
*
|
|
12
|
+
* Distinct from `Comment` (which is post-specific) so the schemas
|
|
13
|
+
* don't fight: post comments carry `author_name`/`author_email` for
|
|
14
|
+
* public commenters; card comments are internal-only and lean on
|
|
15
|
+
* the `user_id` FK for attribution.
|
|
16
|
+
*/
|
|
17
|
+
export default defineModel({
|
|
18
|
+
name: 'CardComment',
|
|
19
|
+
table: 'card_comments',
|
|
20
|
+
primaryKey: 'id',
|
|
21
|
+
autoIncrement: true,
|
|
22
|
+
|
|
23
|
+
traits: {
|
|
24
|
+
useUuid: true,
|
|
25
|
+
useTimestamps: true,
|
|
26
|
+
useSearch: {
|
|
27
|
+
displayable: ['id', 'cardId', 'userId', 'body', 'createdAt'],
|
|
28
|
+
searchable: ['body'],
|
|
29
|
+
sortable: ['createdAt'],
|
|
30
|
+
filterable: ['cardId'],
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
useSeeder: { count: 0 },
|
|
34
|
+
|
|
35
|
+
useApi: {
|
|
36
|
+
uri: 'card-comments',
|
|
37
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
38
|
+
middleware: ['auth'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
belongsTo: ['Card', 'User'],
|
|
43
|
+
|
|
44
|
+
attributes: {
|
|
45
|
+
cardId: {
|
|
46
|
+
order: 1,
|
|
47
|
+
fillable: true,
|
|
48
|
+
validation: { rule: schema.number().required() },
|
|
49
|
+
factory: () => 1,
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
userId: {
|
|
53
|
+
order: 2,
|
|
54
|
+
fillable: true,
|
|
55
|
+
validation: { rule: schema.number() },
|
|
56
|
+
factory: () => null,
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
body: {
|
|
60
|
+
order: 3,
|
|
61
|
+
fillable: true,
|
|
62
|
+
validation: { rule: schema.string().required().min(1).max(10000) },
|
|
63
|
+
factory: faker => faker.lorem.sentence(),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
dashboard: { enabled: false },
|
|
68
|
+
} as const)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'Comment',
|
|
6
|
+
table: 'comments',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
useTimestamps: true,
|
|
13
|
+
useSeeder: {
|
|
14
|
+
count: 25,
|
|
15
|
+
},
|
|
16
|
+
useApi: {
|
|
17
|
+
uri: 'comments',
|
|
18
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
19
|
+
middleware: ['auth'],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
belongsTo: ['Post', 'User'],
|
|
24
|
+
|
|
25
|
+
attributes: {
|
|
26
|
+
authorName: {
|
|
27
|
+
required: true,
|
|
28
|
+
fillable: true,
|
|
29
|
+
validation: {
|
|
30
|
+
rule: schema.string().min(2).max(100),
|
|
31
|
+
},
|
|
32
|
+
factory: faker => faker.person.fullName(),
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
authorEmail: {
|
|
36
|
+
required: true,
|
|
37
|
+
fillable: true,
|
|
38
|
+
validation: {
|
|
39
|
+
rule: schema.string().email(),
|
|
40
|
+
},
|
|
41
|
+
factory: faker => faker.internet.email(),
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
content: {
|
|
45
|
+
required: true,
|
|
46
|
+
fillable: true,
|
|
47
|
+
validation: {
|
|
48
|
+
rule: schema.string().min(1).max(2000),
|
|
49
|
+
},
|
|
50
|
+
factory: faker => faker.lorem.paragraph(),
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
body: {
|
|
54
|
+
required: false,
|
|
55
|
+
fillable: true,
|
|
56
|
+
validation: {
|
|
57
|
+
rule: schema.string().max(2000),
|
|
58
|
+
},
|
|
59
|
+
factory: faker => faker.lorem.paragraph(),
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
postTitle: {
|
|
63
|
+
required: false,
|
|
64
|
+
fillable: true,
|
|
65
|
+
validation: {
|
|
66
|
+
rule: schema.string().max(255),
|
|
67
|
+
},
|
|
68
|
+
factory: faker => faker.lorem.sentence(),
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
status: {
|
|
72
|
+
required: true,
|
|
73
|
+
fillable: true,
|
|
74
|
+
default: 'pending',
|
|
75
|
+
validation: {
|
|
76
|
+
rule: schema.enum(['pending', 'approved', 'spam', 'trash']),
|
|
77
|
+
},
|
|
78
|
+
factory: faker => faker.helpers.arrayElement(['pending', 'approved', 'spam', 'approved', 'approved']),
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
ipAddress: {
|
|
82
|
+
required: false,
|
|
83
|
+
fillable: true,
|
|
84
|
+
validation: {
|
|
85
|
+
rule: schema.string().max(45),
|
|
86
|
+
},
|
|
87
|
+
factory: faker => faker.internet.ip(),
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
userAgent: {
|
|
91
|
+
required: false,
|
|
92
|
+
fillable: true,
|
|
93
|
+
validation: {
|
|
94
|
+
rule: schema.string().max(500),
|
|
95
|
+
},
|
|
96
|
+
factory: faker => faker.internet.userAgent(),
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
isApproved: {
|
|
100
|
+
required: false,
|
|
101
|
+
fillable: true,
|
|
102
|
+
default: 0,
|
|
103
|
+
validation: {
|
|
104
|
+
rule: schema.number(),
|
|
105
|
+
},
|
|
106
|
+
factory: faker => faker.number.int({ min: 0, max: 1 }),
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
} 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: 'CommunicationSuppression',
|
|
6
|
+
table: 'communication_suppressions',
|
|
7
|
+
belongsTo: ['Team'],
|
|
8
|
+
traits: {
|
|
9
|
+
useUuid: true,
|
|
10
|
+
useTimestamps: true,
|
|
11
|
+
useApi: { uri: 'communication-suppressions', routes: ['index', 'store', 'show', 'destroy'], middleware: ['auth', 'team'] },
|
|
12
|
+
},
|
|
13
|
+
indexes: [{ name: 'communication_suppressions_unique', columns: ['team_id', 'channel', 'recipient'], unique: true }],
|
|
14
|
+
attributes: {
|
|
15
|
+
recipient: { required: true, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.internet.email() },
|
|
16
|
+
channel: { required: true, fillable: true, validation: { rule: schema.enum(['email', 'sms', 'push']) }, factory: () => 'email' },
|
|
17
|
+
reason: { required: true, fillable: true, validation: { rule: schema.enum(['unsubscribe', 'bounce', 'complaint', 'carrier', 'manual', 'legal']) }, factory: () => 'unsubscribe' },
|
|
18
|
+
source: { required: true, fillable: true, validation: { rule: schema.string().max(120) }, factory: () => 'preference_center' },
|
|
19
|
+
suppressedAt: { required: true, fillable: true, validation: { rule: schema.timestamp() }, factory: () => new Date().toISOString() },
|
|
20
|
+
liftedAt: { required: false, fillable: false, validation: { rule: schema.timestamp() }, factory: () => null },
|
|
21
|
+
},
|
|
22
|
+
} as const)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'ConsentEvent',
|
|
6
|
+
table: 'consent_events',
|
|
7
|
+
belongsTo: ['Team'],
|
|
8
|
+
traits: {
|
|
9
|
+
useUuid: true,
|
|
10
|
+
useTimestamps: true,
|
|
11
|
+
useApi: { uri: 'consent-events', routes: ['index', 'show'], middleware: ['auth', 'team'] },
|
|
12
|
+
},
|
|
13
|
+
indexes: [
|
|
14
|
+
{ name: 'consent_events_idempotency_unique', columns: ['team_id', 'idempotency_key'], unique: true },
|
|
15
|
+
{ name: 'consent_events_lookup', columns: ['team_id', 'channel', 'recipient', 'occurred_at'] },
|
|
16
|
+
],
|
|
17
|
+
attributes: {
|
|
18
|
+
recipient: { required: true, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.internet.email() },
|
|
19
|
+
channel: { required: true, fillable: true, validation: { rule: schema.enum(['email', 'sms', 'push']) }, factory: () => 'email' },
|
|
20
|
+
action: { required: true, fillable: true, validation: { rule: schema.enum(['requested', 'granted', 'revoked', 'confirmed', 'suppressed']) }, factory: () => 'granted' },
|
|
21
|
+
purpose: { required: true, fillable: true, validation: { rule: schema.string().max(120) }, factory: () => 'marketing' },
|
|
22
|
+
source: { required: true, fillable: true, validation: { rule: schema.string().max(120) }, factory: () => 'signup_form' },
|
|
23
|
+
jurisdiction: { required: false, fillable: true, validation: { rule: schema.string().max(80) }, factory: () => null },
|
|
24
|
+
policyVersion: { required: true, fillable: true, validation: { rule: schema.string().max(40) }, factory: () => '1.0' },
|
|
25
|
+
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() },
|
|
28
|
+
occurredAt: { required: true, fillable: true, validation: { rule: schema.timestamp() }, factory: () => new Date().toISOString() },
|
|
29
|
+
},
|
|
30
|
+
} as const)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'Author', // defaults to the sanitized file name
|
|
6
|
+
table: 'authors', // 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
|
+
|
|
10
|
+
// Define composite indexes
|
|
11
|
+
indexes: [
|
|
12
|
+
{
|
|
13
|
+
name: 'authors_email_name_index',
|
|
14
|
+
columns: ['email', 'name'],
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
traits: {
|
|
19
|
+
useAuth: {
|
|
20
|
+
usePasskey: true,
|
|
21
|
+
},
|
|
22
|
+
useUuid: true,
|
|
23
|
+
useTimestamps: true, // defaults to true, `timestampable` used as an alias
|
|
24
|
+
useSearch: {
|
|
25
|
+
displayable: ['id', 'name', 'email'], // the fields to become d (defaults to all fields)
|
|
26
|
+
searchable: ['name', 'email'], // the fields to become searchable (defaults to all fields)
|
|
27
|
+
sortable: ['created_at', 'updated_at'], // the fields to become sortable (defaults to all fields)
|
|
28
|
+
filterable: [],
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// No faker seeding. Blog authorship lives in markdown frontmatter
|
|
32
|
+
// (content/blog/*.md); this model backs the CMS dashboard only.
|
|
33
|
+
|
|
34
|
+
useApi: {
|
|
35
|
+
// Public catalog: anyone may browse, only authenticated callers may
|
|
36
|
+
// write. Declared explicitly because the trait now defaults BOTH sides to
|
|
37
|
+
// `auth` — an undeclared read route is how a customer list leaks
|
|
38
|
+
// (stacksjs/stacks#2224). Behaviour here is unchanged.
|
|
39
|
+
middleware: { read: [], write: ['auth'] },
|
|
40
|
+
uri: 'authors',
|
|
41
|
+
|
|
42
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
observe: true,
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
hasMany: ['Post'],
|
|
49
|
+
belongsTo: ['User'],
|
|
50
|
+
|
|
51
|
+
attributes: {
|
|
52
|
+
name: {
|
|
53
|
+
order: 1,
|
|
54
|
+
fillable: true,
|
|
55
|
+
validation: {
|
|
56
|
+
rule: schema.string().required().min(5).max(255),
|
|
57
|
+
message: {
|
|
58
|
+
min: 'Name must have a minimum of 3 characters',
|
|
59
|
+
max: 'Name must have a maximum of 255 characters',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
factory: faker => faker.person.fullName(),
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
email: {
|
|
67
|
+
unique: true,
|
|
68
|
+
order: 2,
|
|
69
|
+
fillable: true,
|
|
70
|
+
validation: {
|
|
71
|
+
rule: schema.string().required().email(),
|
|
72
|
+
message: {
|
|
73
|
+
email: 'Email must be a valid email address',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
factory: faker => faker.internet.email(),
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
bio: {
|
|
81
|
+
required: false,
|
|
82
|
+
order: 3,
|
|
83
|
+
fillable: true,
|
|
84
|
+
validation: {
|
|
85
|
+
rule: schema.string().max(500),
|
|
86
|
+
message: {
|
|
87
|
+
max: 'Bio must have a maximum of 500 characters',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
factory: faker => faker.lorem.sentence(),
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
avatar: {
|
|
94
|
+
required: false,
|
|
95
|
+
order: 4,
|
|
96
|
+
fillable: true,
|
|
97
|
+
validation: {
|
|
98
|
+
rule: schema.string().url(),
|
|
99
|
+
message: {
|
|
100
|
+
url: 'Avatar must be a valid URL',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
factory: faker => faker.image.avatar(),
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
dashboard: {
|
|
107
|
+
highlight: true,
|
|
108
|
+
},
|
|
109
|
+
} as const)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A named navigation slot on a site - `main`, `footer`, `portal`. The items
|
|
6
|
+
* are MenuItem rows; templates fetch the tree by handle
|
|
7
|
+
* (`fetchMenuTree(siteId, 'main')`) so navigation is content, not code.
|
|
8
|
+
*/
|
|
9
|
+
export default defineModel({
|
|
10
|
+
name: 'Menu',
|
|
11
|
+
table: 'menus',
|
|
12
|
+
primaryKey: 'id',
|
|
13
|
+
autoIncrement: true,
|
|
14
|
+
|
|
15
|
+
indexes: [
|
|
16
|
+
{
|
|
17
|
+
name: 'menus_site_handle_unique',
|
|
18
|
+
columns: ['site_id', 'handle'],
|
|
19
|
+
unique: true,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
traits: {
|
|
24
|
+
useTimestamps: true,
|
|
25
|
+
useApi: {
|
|
26
|
+
middleware: ['auth'],
|
|
27
|
+
uri: 'menus',
|
|
28
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
belongsTo: ['Site'],
|
|
33
|
+
hasMany: ['MenuItem'],
|
|
34
|
+
|
|
35
|
+
attributes: {
|
|
36
|
+
handle: {
|
|
37
|
+
required: true,
|
|
38
|
+
order: 1,
|
|
39
|
+
fillable: true,
|
|
40
|
+
validation: {
|
|
41
|
+
rule: schema.string().min(2).max(64).matches(/^[a-z0-9-]+$/),
|
|
42
|
+
},
|
|
43
|
+
factory: faker => faker.helpers.arrayElement(['main', 'footer', 'portal']),
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
name: {
|
|
47
|
+
required: true,
|
|
48
|
+
order: 2,
|
|
49
|
+
fillable: true,
|
|
50
|
+
validation: {
|
|
51
|
+
rule: schema.string().min(2).max(255),
|
|
52
|
+
},
|
|
53
|
+
factory: faker => faker.lorem.words(2),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
} as const)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One entry in a Menu: either a link to a Page (`pageId`) or a raw `url` -
|
|
6
|
+
* exactly one of the two. Nests one level via `parentId` for dropdowns.
|
|
7
|
+
*/
|
|
8
|
+
export default defineModel({
|
|
9
|
+
name: 'MenuItem',
|
|
10
|
+
table: 'menu_items',
|
|
11
|
+
primaryKey: 'id',
|
|
12
|
+
autoIncrement: true,
|
|
13
|
+
|
|
14
|
+
traits: {
|
|
15
|
+
useTimestamps: true,
|
|
16
|
+
useApi: {
|
|
17
|
+
middleware: ['auth'],
|
|
18
|
+
uri: 'menu-items',
|
|
19
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
belongsTo: ['Menu', 'Page'],
|
|
24
|
+
|
|
25
|
+
attributes: {
|
|
26
|
+
label: {
|
|
27
|
+
required: true,
|
|
28
|
+
order: 1,
|
|
29
|
+
fillable: true,
|
|
30
|
+
validation: {
|
|
31
|
+
rule: schema.string().min(1).max(120),
|
|
32
|
+
},
|
|
33
|
+
factory: faker => faker.lorem.words(2),
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/** External or hand-written link. Null when the item points at a Page. */
|
|
37
|
+
url: {
|
|
38
|
+
required: false,
|
|
39
|
+
order: 2,
|
|
40
|
+
fillable: true,
|
|
41
|
+
validation: {
|
|
42
|
+
rule: schema.string().max(2048),
|
|
43
|
+
},
|
|
44
|
+
factory: () => null,
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
target: {
|
|
48
|
+
required: false,
|
|
49
|
+
order: 3,
|
|
50
|
+
fillable: true,
|
|
51
|
+
default: '_self',
|
|
52
|
+
validation: {
|
|
53
|
+
rule: schema.enum(['_self', '_blank'] as const),
|
|
54
|
+
},
|
|
55
|
+
factory: () => '_self',
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/** Dropdown parent within the same menu. */
|
|
59
|
+
parentId: {
|
|
60
|
+
required: false,
|
|
61
|
+
order: 4,
|
|
62
|
+
fillable: true,
|
|
63
|
+
validation: {
|
|
64
|
+
rule: schema.number().min(1),
|
|
65
|
+
},
|
|
66
|
+
factory: () => null,
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
position: {
|
|
70
|
+
required: false,
|
|
71
|
+
order: 5,
|
|
72
|
+
fillable: true,
|
|
73
|
+
default: 0,
|
|
74
|
+
validation: {
|
|
75
|
+
rule: schema.number().min(0),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/** `auth` items render only for signed-in visitors (portal links). */
|
|
80
|
+
visibility: {
|
|
81
|
+
required: false,
|
|
82
|
+
order: 6,
|
|
83
|
+
fillable: true,
|
|
84
|
+
default: 'public',
|
|
85
|
+
validation: {
|
|
86
|
+
rule: schema.enum(['public', 'auth'] as const),
|
|
87
|
+
},
|
|
88
|
+
factory: () => 'public',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
} as const)
|