@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,153 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'Product',
|
|
6
|
+
table: 'products',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
useTimestamps: true,
|
|
13
|
+
useSearch: {
|
|
14
|
+
displayable: ['id', 'name', 'description', 'price', 'categoryId', 'isAvailable', 'inventoryCount'],
|
|
15
|
+
searchable: ['name', 'description', 'categoryId'],
|
|
16
|
+
sortable: ['price', 'createdAt', 'updatedAt', 'inventoryCount', 'preparationTime'],
|
|
17
|
+
filterable: ['categoryId', 'isAvailable', 'allergens'],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
useSeeder: {
|
|
21
|
+
count: 10,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
useApi: {
|
|
25
|
+
// Public catalog: anyone may browse, only authenticated callers may
|
|
26
|
+
// write. Declared explicitly because the trait now defaults BOTH sides to
|
|
27
|
+
// `auth` — an undeclared read route is how a customer list leaks
|
|
28
|
+
// (stacksjs/stacks#2224). Behaviour here is unchanged.
|
|
29
|
+
middleware: { read: [], write: ['auth'] },
|
|
30
|
+
uri: 'products',
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
observe: true,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
belongsTo: ['Category', 'Manufacturer'],
|
|
37
|
+
|
|
38
|
+
hasMany: ['Review', 'ProductUnit', 'ProductVariant', 'LicenseKey', 'WaitlistProduct', 'Coupon'],
|
|
39
|
+
|
|
40
|
+
attributes: {
|
|
41
|
+
name: {
|
|
42
|
+
order: 1,
|
|
43
|
+
fillable: true,
|
|
44
|
+
validation: {
|
|
45
|
+
rule: schema.string().required().max(100),
|
|
46
|
+
message: {
|
|
47
|
+
max: 'Name must have a maximum of 100 characters',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
factory: faker => faker.commerce.productName(),
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
description: {
|
|
54
|
+
order: 2,
|
|
55
|
+
fillable: true,
|
|
56
|
+
validation: {
|
|
57
|
+
rule: schema.string(),
|
|
58
|
+
},
|
|
59
|
+
factory: faker => faker.commerce.productDescription(),
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
price: {
|
|
63
|
+
order: 3,
|
|
64
|
+
fillable: true,
|
|
65
|
+
validation: {
|
|
66
|
+
rule: schema.number().required().min(1),
|
|
67
|
+
message: {
|
|
68
|
+
min: 'Price must be at least 1',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
factory: faker => faker.number.int({ min: 100, max: 10000 }),
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
imageUrl: {
|
|
75
|
+
order: 4,
|
|
76
|
+
fillable: true,
|
|
77
|
+
validation: {
|
|
78
|
+
rule: schema.string(),
|
|
79
|
+
message: {
|
|
80
|
+
string: 'Image URL must be a string',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
factory: faker => faker.image.url(),
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
isAvailable: {
|
|
87
|
+
order: 5,
|
|
88
|
+
fillable: true,
|
|
89
|
+
validation: {
|
|
90
|
+
rule: schema.boolean(),
|
|
91
|
+
},
|
|
92
|
+
factory: () => true,
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
inventoryCount: {
|
|
96
|
+
order: 6,
|
|
97
|
+
fillable: true,
|
|
98
|
+
validation: {
|
|
99
|
+
rule: schema.number().min(0),
|
|
100
|
+
message: {
|
|
101
|
+
min: 'Inventory count must be at least 0',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
factory: faker => faker.number.int({ min: 0, max: 100 }),
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
preparationTime: {
|
|
108
|
+
order: 8,
|
|
109
|
+
fillable: true,
|
|
110
|
+
validation: {
|
|
111
|
+
rule: schema.number().required().min(1),
|
|
112
|
+
message: {
|
|
113
|
+
min: 'Preparation time must be at least 1 minute',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
factory: faker => faker.number.int({ min: 1, max: 60 }),
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
allergens: {
|
|
120
|
+
order: 9,
|
|
121
|
+
fillable: true,
|
|
122
|
+
validation: {
|
|
123
|
+
rule: schema.string(),
|
|
124
|
+
},
|
|
125
|
+
factory: (faker) => {
|
|
126
|
+
const possibleAllergens = ['Gluten', 'Dairy', 'Nuts', 'Soy', 'Eggs', 'Fish', 'Shellfish']
|
|
127
|
+
const count = faker.number.int({ min: 0, max: 3 })
|
|
128
|
+
const allergens = faker.helpers.arrayElements(possibleAllergens, count)
|
|
129
|
+
return JSON.stringify(allergens)
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
nutritionalInfo: {
|
|
134
|
+
order: 10,
|
|
135
|
+
fillable: true,
|
|
136
|
+
validation: {
|
|
137
|
+
rule: schema.string(),
|
|
138
|
+
},
|
|
139
|
+
factory: (faker) => {
|
|
140
|
+
return JSON.stringify({
|
|
141
|
+
calories: faker.number.int({ min: 50, max: 800 }),
|
|
142
|
+
fat: faker.number.float({ min: 0, max: 50 }),
|
|
143
|
+
protein: faker.number.float({ min: 0, max: 30 }),
|
|
144
|
+
carbs: faker.number.float({ min: 0, max: 100 }),
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
dashboard: {
|
|
151
|
+
highlight: true,
|
|
152
|
+
},
|
|
153
|
+
} as const)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'ProductUnit',
|
|
6
|
+
table: 'product_units',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
useTimestamps: true,
|
|
13
|
+
useSearch: {
|
|
14
|
+
displayable: ['id', 'name', 'abbreviation', 'type', 'description', 'isDefault'],
|
|
15
|
+
searchable: ['name', 'abbreviation', 'type', 'description'],
|
|
16
|
+
sortable: ['name', 'type', 'createdAt', 'updatedAt'],
|
|
17
|
+
filterable: ['type', 'isDefault'],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
useSeeder: {
|
|
21
|
+
count: 10,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
useApi: {
|
|
25
|
+
// Public catalog: anyone may browse, only authenticated callers may
|
|
26
|
+
// write. Declared explicitly because the trait now defaults BOTH sides to
|
|
27
|
+
// `auth` — an undeclared read route is how a customer list leaks
|
|
28
|
+
// (stacksjs/stacks#2224). Behaviour here is unchanged.
|
|
29
|
+
middleware: { read: [], write: ['auth'] },
|
|
30
|
+
uri: 'product-units',
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
observe: true,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
belongsTo: ['Product'],
|
|
37
|
+
|
|
38
|
+
attributes: {
|
|
39
|
+
name: {
|
|
40
|
+
order: 1,
|
|
41
|
+
fillable: true,
|
|
42
|
+
validation: {
|
|
43
|
+
rule: schema.string().required().max(100),
|
|
44
|
+
message: {
|
|
45
|
+
max: 'Name must have a maximum of 100 characters',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
factory: (faker) => {
|
|
49
|
+
const units = ['Piece', 'Kilogram', 'Gram', 'Liter', 'Milliliter', 'Meter', 'Centimeter', 'Box', 'Pack', 'Pair']
|
|
50
|
+
return faker.helpers.arrayElement(units)
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
abbreviation: {
|
|
55
|
+
order: 2,
|
|
56
|
+
fillable: true,
|
|
57
|
+
validation: {
|
|
58
|
+
rule: schema.string().required().max(10),
|
|
59
|
+
message: {
|
|
60
|
+
max: 'Abbreviation must have a maximum of 10 characters',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
factory: (faker) => {
|
|
64
|
+
const abbrs = ['pc', 'kg', 'g', 'L', 'mL', 'm', 'cm', 'box', 'pk', 'pr']
|
|
65
|
+
return faker.helpers.arrayElement(abbrs)
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
type: {
|
|
70
|
+
order: 3,
|
|
71
|
+
fillable: true,
|
|
72
|
+
validation: {
|
|
73
|
+
rule: schema.string().required(),
|
|
74
|
+
message: {
|
|
75
|
+
string: 'Type must be a string',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
factory: (faker) => {
|
|
79
|
+
const types = ['Weight', 'Volume', 'Length', 'Quantity', 'Size']
|
|
80
|
+
return faker.helpers.arrayElement(types)
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
description: {
|
|
85
|
+
order: 4,
|
|
86
|
+
fillable: true,
|
|
87
|
+
validation: {
|
|
88
|
+
rule: schema.string().max(255),
|
|
89
|
+
message: {
|
|
90
|
+
max: 'Description must have a maximum of 255 characters',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
factory: faker => faker.lorem.sentence(),
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
isDefault: {
|
|
97
|
+
default: false,
|
|
98
|
+
order: 5,
|
|
99
|
+
fillable: true,
|
|
100
|
+
validation: {
|
|
101
|
+
rule: schema.boolean(),
|
|
102
|
+
message: {
|
|
103
|
+
boolean: 'Default status must be a boolean',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
factory: () => false,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
dashboard: {
|
|
111
|
+
highlight: true,
|
|
112
|
+
},
|
|
113
|
+
} as const)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'ProductVariant',
|
|
6
|
+
table: 'product_variants',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
useTimestamps: true,
|
|
13
|
+
useSearch: {
|
|
14
|
+
displayable: ['id', 'productId', 'variant', 'type', 'description', 'options', 'status'],
|
|
15
|
+
searchable: ['variant', 'type', 'description', 'options'],
|
|
16
|
+
sortable: ['createdAt', 'updatedAt', 'variant', 'type', 'status'],
|
|
17
|
+
filterable: ['productId', 'type', 'status'],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
useSeeder: {
|
|
21
|
+
count: 50,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
useApi: {
|
|
25
|
+
// Public catalog: anyone may browse, only authenticated callers may
|
|
26
|
+
// write. Declared explicitly because the trait now defaults BOTH sides to
|
|
27
|
+
// `auth` — an undeclared read route is how a customer list leaks
|
|
28
|
+
// (stacksjs/stacks#2224). Behaviour here is unchanged.
|
|
29
|
+
middleware: { read: [], write: ['auth'] },
|
|
30
|
+
uri: 'product-variants',
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
observe: true,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
belongsTo: ['Product'],
|
|
37
|
+
|
|
38
|
+
attributes: {
|
|
39
|
+
variant: {
|
|
40
|
+
order: 3,
|
|
41
|
+
fillable: true,
|
|
42
|
+
validation: {
|
|
43
|
+
rule: schema.string().required().max(100),
|
|
44
|
+
message: {
|
|
45
|
+
max: 'Variant name must have a maximum of 100 characters',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
factory: faker => faker.commerce.productAdjective(),
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
type: {
|
|
52
|
+
order: 4,
|
|
53
|
+
fillable: true,
|
|
54
|
+
validation: {
|
|
55
|
+
rule: schema.string().required().max(50),
|
|
56
|
+
message: {
|
|
57
|
+
max: 'Type must have a maximum of 50 characters',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
factory: faker => faker.helpers.arrayElement(['color', 'size', 'material', 'style', 'configuration']),
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
description: {
|
|
64
|
+
order: 5,
|
|
65
|
+
fillable: true,
|
|
66
|
+
validation: {
|
|
67
|
+
rule: schema.string(),
|
|
68
|
+
},
|
|
69
|
+
factory: faker => faker.commerce.productDescription(),
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
options: {
|
|
73
|
+
order: 6,
|
|
74
|
+
fillable: true,
|
|
75
|
+
validation: {
|
|
76
|
+
rule: schema.string(),
|
|
77
|
+
},
|
|
78
|
+
factory: (faker) => {
|
|
79
|
+
const optionTypes = {
|
|
80
|
+
color: ['Red', 'Blue', 'Green', 'Black', 'White', 'Yellow', 'Purple', 'Orange', 'Brown', 'Gray'],
|
|
81
|
+
size: ['XS', 'S', 'M', 'L', 'XL', 'XXL', '2XL', '3XL', '4XL'],
|
|
82
|
+
material: ['Cotton', 'Polyester', 'Wool', 'Silk', 'Leather', 'Metal', 'Wood', 'Plastic', 'Glass'],
|
|
83
|
+
style: ['Casual', 'Formal', 'Sport', 'Vintage', 'Modern', 'Classic', 'Bohemian', 'Minimalist'],
|
|
84
|
+
configuration: ['Standard', 'Deluxe', 'Premium', 'Basic', 'Pro', 'Ultimate', 'Limited Edition'],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const variantType = faker.helpers.arrayElement(Object.keys(optionTypes)) as keyof typeof optionTypes
|
|
88
|
+
const optionCount = faker.number.int({ min: 2, max: 5 })
|
|
89
|
+
const selectedOptions = faker.helpers.arrayElements(optionTypes[variantType], optionCount)
|
|
90
|
+
|
|
91
|
+
return JSON.stringify(selectedOptions)
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
status: {
|
|
96
|
+
order: 7,
|
|
97
|
+
fillable: true,
|
|
98
|
+
validation: {
|
|
99
|
+
rule: schema.enum(['active', 'inactive', 'draft']).required(),
|
|
100
|
+
message: {
|
|
101
|
+
enum: 'Status must be one of: active, inactive, draft',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
factory: faker => faker.helpers.arrayElement(['active', 'inactive', 'draft']),
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
dashboard: {
|
|
109
|
+
highlight: true,
|
|
110
|
+
},
|
|
111
|
+
} as const)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Attributes } from '@stacksjs/types'
|
|
2
|
+
import { defineModel, formatDate } from '@stacksjs/orm'
|
|
3
|
+
import { schema } from '@stacksjs/validation'
|
|
4
|
+
|
|
5
|
+
function receiptTimestamp(value: unknown): string {
|
|
6
|
+
if (typeof value === 'number')
|
|
7
|
+
return formatDate(value <= 2147483647 ? value * 1000 : value)
|
|
8
|
+
|
|
9
|
+
if (typeof value === 'string' && /^\d{10}$/.test(value))
|
|
10
|
+
return formatDate(Number(value) * 1000)
|
|
11
|
+
|
|
12
|
+
return formatDate(value as Date | string)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default defineModel({
|
|
16
|
+
name: 'Receipt',
|
|
17
|
+
table: 'receipts',
|
|
18
|
+
primaryKey: 'id',
|
|
19
|
+
autoIncrement: true,
|
|
20
|
+
belongsTo: ['PrintDevice'],
|
|
21
|
+
traits: {
|
|
22
|
+
useUuid: true,
|
|
23
|
+
useTimestamps: true,
|
|
24
|
+
useSearch: {
|
|
25
|
+
displayable: ['id', 'printer', 'document', 'timestamp', 'status', 'size', 'pages', 'duration'],
|
|
26
|
+
searchable: ['printer', 'document'],
|
|
27
|
+
sortable: ['timestamp', 'status', 'size', 'pages', 'duration'],
|
|
28
|
+
filterable: ['status'],
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
useSeeder: {
|
|
32
|
+
count: 5,
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
useApi: {
|
|
36
|
+
uri: 'print-logs',
|
|
37
|
+
middleware: ['auth'],
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
observe: true,
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
attributes: {
|
|
44
|
+
printer: {
|
|
45
|
+
order: 1,
|
|
46
|
+
fillable: true,
|
|
47
|
+
validation: {
|
|
48
|
+
rule: schema.string().max(100),
|
|
49
|
+
message: {
|
|
50
|
+
max: 'Printer name must have a maximum of 100 characters',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
factory: faker => faker.company.name(),
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
document: {
|
|
57
|
+
order: 2,
|
|
58
|
+
fillable: true,
|
|
59
|
+
validation: {
|
|
60
|
+
rule: schema.string().required().max(100),
|
|
61
|
+
message: {
|
|
62
|
+
max: 'Document name must have a maximum of 100 characters',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
factory: faker => faker.system.fileName(),
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
timestamp: {
|
|
69
|
+
order: 3,
|
|
70
|
+
fillable: true,
|
|
71
|
+
validation: {
|
|
72
|
+
rule: schema.timestampTz().required(),
|
|
73
|
+
message: {
|
|
74
|
+
invalid: 'Invalid date format',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
factory: faker => faker.date.recent().toISOString(),
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
status: {
|
|
81
|
+
order: 4,
|
|
82
|
+
fillable: true,
|
|
83
|
+
validation: {
|
|
84
|
+
rule: schema.enum(['success', 'failed', 'warning']).required(),
|
|
85
|
+
},
|
|
86
|
+
factory: faker => faker.helpers.arrayElement(['success', 'failed', 'warning']),
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
size: {
|
|
90
|
+
default: 0,
|
|
91
|
+
order: 5,
|
|
92
|
+
fillable: true,
|
|
93
|
+
validation: {
|
|
94
|
+
rule: schema.number().min(0).max(100),
|
|
95
|
+
message: {
|
|
96
|
+
max: 'Size must be less than or equal to 100',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
factory: faker => faker.number.int({ min: 1, max: 100 }),
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
pages: {
|
|
103
|
+
default: 0,
|
|
104
|
+
order: 6,
|
|
105
|
+
fillable: true,
|
|
106
|
+
validation: {
|
|
107
|
+
rule: schema.number().min(0).max(50),
|
|
108
|
+
message: {
|
|
109
|
+
max: 'Pages must be less than or equal to 50',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
factory: faker => faker.number.int({ min: 1, max: 50 }),
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
duration: {
|
|
116
|
+
default: 0,
|
|
117
|
+
order: 7,
|
|
118
|
+
fillable: true,
|
|
119
|
+
validation: {
|
|
120
|
+
rule: schema.number().min(0).max(50),
|
|
121
|
+
message: {
|
|
122
|
+
max: 'Duration must be less than or equal to 50',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
factory: faker => faker.number.int({ min: 1, max: 50 }),
|
|
126
|
+
},
|
|
127
|
+
metadata: {
|
|
128
|
+
order: 8,
|
|
129
|
+
fillable: true,
|
|
130
|
+
default: '{}',
|
|
131
|
+
validation: {
|
|
132
|
+
rule: schema.string(),
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
set: {
|
|
138
|
+
timestamp: (attributes: Attributes) => receiptTimestamp(attributes.timestamp),
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
dashboard: {
|
|
142
|
+
highlight: true,
|
|
143
|
+
},
|
|
144
|
+
} as const)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'Review',
|
|
6
|
+
table: 'reviews',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
useTimestamps: true,
|
|
13
|
+
useSearch: {
|
|
14
|
+
displayable: ['id', 'productId', 'rating', 'title', 'content', 'isVerifiedPurchase', 'isApproved'],
|
|
15
|
+
searchable: ['title', 'content', 'productId'],
|
|
16
|
+
sortable: ['rating', 'createdAt', 'updatedAt', 'helpfulVotes'],
|
|
17
|
+
filterable: ['productId', 'rating', 'isVerifiedPurchase', 'isApproved'],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
useSeeder: {
|
|
21
|
+
count: 50,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
useApi: {
|
|
25
|
+
uri: 'product-reviews',
|
|
26
|
+
middleware: ['auth'],
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
observe: true,
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
belongsTo: ['Product', 'Customer'],
|
|
33
|
+
|
|
34
|
+
attributes: {
|
|
35
|
+
/**
|
|
36
|
+
* One to five stars, or absent.
|
|
37
|
+
*
|
|
38
|
+
* A review is a rating, a written comment, or both, and which of those a
|
|
39
|
+
* shop asks for is the shop's decision - some want a star out of a
|
|
40
|
+
* one-tap prompt, some want prose from a verified buyer, most want either.
|
|
41
|
+
* `required()` here made the first and third impossible: a comment with no
|
|
42
|
+
* star failed validation, so a store that wanted written feedback had to
|
|
43
|
+
* invent a rating to carry it and then filter that invention back out of
|
|
44
|
+
* its own averages.
|
|
45
|
+
*
|
|
46
|
+
* The column has always been nullable; only this rule disagreed. Null means
|
|
47
|
+
* "not rated" and is excluded from an average rather than counted as zero -
|
|
48
|
+
* see `fetchStats` in commerce/products/reviews.
|
|
49
|
+
*
|
|
50
|
+
* `store()` enforces the other half of the rule: a review with neither a
|
|
51
|
+
* rating nor content is not a review, and is rejected there because a
|
|
52
|
+
* cross-field check cannot be expressed on a single attribute.
|
|
53
|
+
*/
|
|
54
|
+
rating: {
|
|
55
|
+
order: 3,
|
|
56
|
+
fillable: true,
|
|
57
|
+
validation: {
|
|
58
|
+
rule: schema.number().min(1).max(5),
|
|
59
|
+
message: {
|
|
60
|
+
min: 'Rating must be at least 1',
|
|
61
|
+
max: 'Rating cannot be more than 5',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
// Roughly one in six is a comment with no star, so seeded data exercises
|
|
65
|
+
// the unrated path rather than only ever the happy one.
|
|
66
|
+
factory: faker => faker.datatype.boolean({ probability: 0.84 })
|
|
67
|
+
? faker.number.int({ min: 1, max: 5 })
|
|
68
|
+
: null,
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
title: {
|
|
72
|
+
order: 4,
|
|
73
|
+
fillable: true,
|
|
74
|
+
validation: {
|
|
75
|
+
rule: schema.string().max(100),
|
|
76
|
+
message: {
|
|
77
|
+
max: 'Title must have a maximum of 100 characters',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
factory: faker => faker.lorem.sentence({ min: 3, max: 8 }),
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
content: {
|
|
84
|
+
order: 5,
|
|
85
|
+
fillable: true,
|
|
86
|
+
validation: {
|
|
87
|
+
rule: schema.string().max(2000),
|
|
88
|
+
message: {
|
|
89
|
+
max: 'Review content must have a maximum of 2000 characters',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
// And roughly one in five is a bare star with nothing written, which is
|
|
93
|
+
// what a one-tap rating prompt produces.
|
|
94
|
+
factory: faker => faker.datatype.boolean({ probability: 0.8 })
|
|
95
|
+
? faker.lorem.paragraphs({ min: 1, max: 3 })
|
|
96
|
+
: '',
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
isVerifiedPurchase: {
|
|
100
|
+
default: false,
|
|
101
|
+
order: 6,
|
|
102
|
+
fillable: true,
|
|
103
|
+
validation: {
|
|
104
|
+
rule: schema.boolean(),
|
|
105
|
+
},
|
|
106
|
+
factory: faker => faker.datatype.boolean({ probability: 0.7 }),
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
isApproved: {
|
|
110
|
+
default: false,
|
|
111
|
+
order: 7,
|
|
112
|
+
fillable: true,
|
|
113
|
+
validation: {
|
|
114
|
+
rule: schema.boolean(),
|
|
115
|
+
},
|
|
116
|
+
factory: faker => faker.datatype.boolean({ probability: 0.9 }),
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
isFeatured: {
|
|
120
|
+
default: false,
|
|
121
|
+
order: 8,
|
|
122
|
+
fillable: true,
|
|
123
|
+
validation: {
|
|
124
|
+
rule: schema.boolean(),
|
|
125
|
+
},
|
|
126
|
+
factory: faker => faker.datatype.boolean({ probability: 0.9 }),
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
helpfulVotes: {
|
|
130
|
+
order: 9,
|
|
131
|
+
default: 0,
|
|
132
|
+
fillable: true,
|
|
133
|
+
validation: {
|
|
134
|
+
rule: schema.number().min(0),
|
|
135
|
+
},
|
|
136
|
+
factory: faker => faker.number.int({ min: 0, max: 200 }),
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
unhelpfulVotes: {
|
|
140
|
+
order: 10,
|
|
141
|
+
default: 0,
|
|
142
|
+
fillable: true,
|
|
143
|
+
validation: {
|
|
144
|
+
rule: schema.number().min(0),
|
|
145
|
+
},
|
|
146
|
+
factory: faker => faker.number.int({ min: 0, max: 50 }),
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
purchaseDate: {
|
|
150
|
+
order: 11,
|
|
151
|
+
fillable: true,
|
|
152
|
+
validation: {
|
|
153
|
+
rule: schema.string(),
|
|
154
|
+
},
|
|
155
|
+
factory: faker => faker.date.past({ years: 1 }).toISOString(),
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
images: {
|
|
159
|
+
order: 12,
|
|
160
|
+
fillable: true,
|
|
161
|
+
validation: {
|
|
162
|
+
rule: schema.string(),
|
|
163
|
+
},
|
|
164
|
+
factory: () => 'test',
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
dashboard: {
|
|
169
|
+
highlight: true,
|
|
170
|
+
},
|
|
171
|
+
} as const)
|