@stacksjs/defaults 0.75.480 → 0.75.483

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 (55) hide show
  1. package/ai/skills/stacks-actions/SKILL.md +1 -1
  2. package/ai/skills/stacks-native/CATALOG.md +7 -7
  3. package/ai/skills/stacks-queue/DURABLE-WORKFLOWS.md +12 -0
  4. package/ai/skills/stacks-server/SKILL.md +13 -0
  5. package/app/Actions/Dashboard/Content/CommentIndexAction.ts +22 -112
  6. package/app/Actions/Dashboard/Content/PageIndexAction.ts +26 -25
  7. package/app/Actions/Dashboard/Content/PostIndexAction.ts +30 -35
  8. package/app/Actions/Dashboard/Content/PostShowAction.ts +41 -0
  9. package/app/Actions/Dashboard/Content/comment-input.ts +25 -1
  10. package/app/Actions/Dashboard/Content/comment-list.ts +311 -0
  11. package/app/Actions/Dashboard/Content/content-list.ts +69 -0
  12. package/app/Actions/Dashboard/Content/page-list.ts +108 -0
  13. package/app/Actions/Dashboard/Content/post-list.ts +140 -0
  14. package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +12 -24
  15. package/app/Actions/Dashboard/Jobs/job-list.ts +168 -0
  16. package/app/Actions/Dashboard/Jobs/job-records.ts +8 -3
  17. package/app/Actions/Dashboard/Models/ModelShowAction.ts +8 -2
  18. package/app/Actions/Dashboard/Queries/QueryDashboardAction.ts +20 -10
  19. package/app/Actions/Dashboard/Queries/query-dashboard.ts +56 -9
  20. package/app/Actions/Dashboard/Queries/query-list.ts +161 -0
  21. package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +14 -52
  22. package/app/Actions/Dashboard/Queue/queue-stats.ts +69 -0
  23. package/app/Actions/Monitoring/ErrorIndexAction.ts +9 -4
  24. package/app/Actions/Monitoring/error-provider.ts +85 -7
  25. package/functions/api.ts +14 -10
  26. package/functions/commerce/coupons.ts +4 -6
  27. package/functions/commerce/customers.ts +4 -6
  28. package/functions/commerce/gift-cards.ts +4 -2
  29. package/functions/commerce/orders.ts +4 -2
  30. package/functions/commerce/payments.ts +4 -2
  31. package/functions/commerce/products/categories.ts +4 -6
  32. package/functions/commerce/products/manufacturers.ts +4 -6
  33. package/functions/commerce/products/products.ts +4 -6
  34. package/functions/commerce/products/reviews.ts +4 -6
  35. package/functions/commerce/products/units.ts +4 -6
  36. package/functions/commerce/products/variants.ts +4 -6
  37. package/functions/commerce/taxes.ts +4 -2
  38. package/functions/commerce/waitlist/products.ts +4 -6
  39. package/functions/commerce/waitlist/restaurants.ts +4 -6
  40. package/functions/monitoring/errors.ts +77 -49
  41. package/functions/queries.ts +34 -2
  42. package/ide/vscode/package.json +1 -1
  43. package/package.json +4 -4
  44. package/resources/components/Dashboard/Analytics/AnalyticsHub.stx +4 -0
  45. package/resources/components/Dashboard/Auth/MagicLink.stx +6 -1
  46. package/resources/components/Dashboard/Content/CommentsDashboard.stx +90 -12
  47. package/resources/components/Dashboard/Content/PagesDashboard.stx +100 -16
  48. package/resources/components/Dashboard/Content/PostEditorDialog.stx +28 -8
  49. package/resources/components/Dashboard/Content/PostsDashboard.stx +179 -21
  50. package/resources/components/Dashboard/Monitoring/ErrorDashboard.stx +121 -36
  51. package/resources/components/Dashboard/Monitoring/ErrorTable.stx +29 -8
  52. package/resources/components/Dashboard/Queries/QueryDashboard.stx +73 -64
  53. package/resources/components/Dashboard/UI/Table.stx +30 -7
  54. package/routes/dashboard-api.ts +1 -0
  55. package/app/Actions/Buddy/JobsListAction.ts +0 -141
@@ -0,0 +1,69 @@
1
+ import { db } from '@stacksjs/database/runtime'
2
+
3
+ /** One queue's jobs, as the Queue page shows them. */
4
+ export interface QueueCounts {
5
+ name: string
6
+ /** Waiting for a worker, delayed or not. */
7
+ pending: number
8
+ /** Reserved by a worker. */
9
+ active: number
10
+ /** Rows in `failed_jobs` for this queue. */
11
+ failed: number
12
+ total: number
13
+ }
14
+
15
+ const queueName = (value: unknown): string => String(value || 'default')
16
+
17
+ /**
18
+ * Every queue that has jobs, counted by the database (stacksjs/stacks#3200).
19
+ *
20
+ * This read every row of `jobs` and `failed_jobs`, payloads included, to count
21
+ * them per queue, on every visit to the Queue page. A job is active when a
22
+ * worker has reserved it, which is `queuedJobState`'s rule in
23
+ * `@stacksjs/queue`: a `reserved_at` that is set and not 0, so the filter
24
+ * below is `!= 0`, which NULL never matches. Queues are listed in the order of
25
+ * their first job, and a queue with only failures is not listed, as before.
26
+ */
27
+ export async function readQueueCounts(database: typeof db = db): Promise<QueueCounts[]> {
28
+ const [jobRows, failedRows] = await Promise.all([
29
+ database.selectFrom('jobs')
30
+ .select([
31
+ 'queue',
32
+ database.fn.count('id').as('total'),
33
+ database.fn.count('id').filterWhere('reserved_at', '!=', 0).as('active'),
34
+ database.fn.min('id').as('first_id'),
35
+ ])
36
+ .groupBy('queue')
37
+ .execute() as Promise<Array<{ queue: unknown, total: unknown, active: unknown, first_id: unknown }>>,
38
+ database.selectFrom('failed_jobs')
39
+ .select(['queue', database.fn.count('id').as('failed')])
40
+ .groupBy('queue')
41
+ .execute() as Promise<Array<{ queue: unknown, failed: unknown }>>,
42
+ ])
43
+
44
+ const failed = new Map<string, number>()
45
+ for (const row of failedRows)
46
+ failed.set(queueName(row.queue), (failed.get(queueName(row.queue)) ?? 0) + Number(row.failed))
47
+
48
+ // A NULL, an empty and a 'default' queue are all the default queue.
49
+ const queues = new Map<string, { total: number, active: number, firstId: number }>()
50
+ for (const row of jobRows) {
51
+ const name = queueName(row.queue)
52
+ const current = queues.get(name) ?? { total: 0, active: 0, firstId: Number.POSITIVE_INFINITY }
53
+ queues.set(name, {
54
+ total: current.total + Number(row.total),
55
+ active: current.active + Number(row.active),
56
+ firstId: Math.min(current.firstId, Number(row.first_id)),
57
+ })
58
+ }
59
+
60
+ return [...queues.entries()]
61
+ .sort(([, a], [, b]) => a.firstId - b.firstId)
62
+ .map(([name, counts]) => ({
63
+ name,
64
+ pending: counts.total - counts.active,
65
+ active: counts.active,
66
+ failed: failed.get(name) ?? 0,
67
+ total: counts.total,
68
+ }))
69
+ }
@@ -1,12 +1,17 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions/runtime'
2
3
  import { response } from '@stacksjs/router'
3
- import { readGroupedErrors } from './error-provider'
4
+ import { parseErrorGroupQuery, readGroupedErrors } from './error-provider'
4
5
 
5
6
  export default new Action({
6
7
  name: 'Error Index',
7
- description: 'Fetch all grouped errors (Sentry-like aggregation)',
8
+ description: 'Fetch one page of grouped errors (Sentry-like aggregation)',
8
9
  method: 'GET',
9
- async handle() {
10
- return response.json(await readGroupedErrors())
10
+ async handle(request: RequestInstance) {
11
+ const query = parseErrorGroupQuery(key => request.get<unknown>(key, ''))
12
+ if ('error' in query)
13
+ return response.json({ message: query.error }, 422)
14
+
15
+ return response.json(await readGroupedErrors(query))
11
16
  },
12
17
  })
@@ -1,5 +1,7 @@
1
1
  import type { DashboardProviderUnavailable } from '../Dashboard/dashboard-provider'
2
2
  import { errors } from '@stacksjs/commerce'
3
+ import type { QueryReader } from '../Dashboard/Content/content-list'
4
+ import { LIST_PAGE_SIZES, queryText } from '../Dashboard/Content/content-list'
3
5
  import { HQ_READ_UNAVAILABLE, readThroughProvider, resolveDashboardDriver } from '../Dashboard/dashboard-provider'
4
6
 
5
7
  /**
@@ -81,7 +83,39 @@ export interface DashboardGroupedError {
81
83
  count: number
82
84
  first_seen: string
83
85
  last_seen: string
84
- error_ids: number[]
86
+ }
87
+
88
+ /**
89
+ * What a page of error groups asks for. A group's state is its newest
90
+ * occurrence's, so `status` filters groups, not rows.
91
+ */
92
+ export interface DashboardErrorGroupQuery {
93
+ page: number
94
+ perPage: number
95
+ search: string
96
+ status: DashboardErrorStatus | 'all'
97
+ type: string
98
+ sort: typeof ERROR_GROUP_SORTS[number]
99
+ direction: 'asc' | 'desc'
100
+ }
101
+
102
+ export const ERROR_GROUP_SORTS = ['last_seen', 'count', 'type', 'status'] as const
103
+
104
+ /**
105
+ * One page of error groups, most recently seen first (stacksjs/stacks#3195).
106
+ *
107
+ * The section used to take every group, each with the id of every one of its
108
+ * occurrences, to show ten. Nothing read those ids, and a busy group has
109
+ * thousands, so a page carries the groups' counts instead.
110
+ */
111
+ export interface DashboardGroupedErrorPage {
112
+ groups: DashboardGroupedError[]
113
+ /** Groups matching the query, which the pager counts. */
114
+ total: number
115
+ /** The page the groups are, moved back to the last one when it ran past it. */
116
+ page: number
117
+ /** Every recorded error type, for the type filter. */
118
+ types: string[]
85
119
  }
86
120
 
87
121
  export interface DashboardErrorStats {
@@ -99,7 +133,7 @@ export interface DashboardErrorTimelinePoint {
99
133
  }
100
134
 
101
135
  export interface DashboardErrorsProvider {
102
- grouped: () => Promise<DashboardGroupedError[]>
136
+ grouped: (query: DashboardErrorGroupQuery) => Promise<DashboardGroupedErrorPage>
103
137
  stats: () => Promise<DashboardErrorStats>
104
138
  timeline: () => Promise<DashboardErrorTimelinePoint[]>
105
139
  byGroup: (type: string, message: string) => Promise<DashboardErrorRecord[]>
@@ -141,7 +175,7 @@ function toErrorStatus(value: string): DashboardErrorStatus {
141
175
  * next moves, this file stops compiling instead of quietly changing what a
142
176
  * remote provider is required to match.
143
177
  */
144
- function toGroupedError(row: Awaited<ReturnType<typeof errors.fetchGrouped>>[number]): DashboardGroupedError {
178
+ function toGroupedError(row: Awaited<ReturnType<typeof errors.fetchGroupedPage>>['groups'][number]): DashboardGroupedError {
145
179
  return {
146
180
  id: row.id,
147
181
  type: row.type,
@@ -151,7 +185,6 @@ function toGroupedError(row: Awaited<ReturnType<typeof errors.fetchGrouped>>[num
151
185
  count: row.count,
152
186
  first_seen: row.first_seen,
153
187
  last_seen: row.last_seen,
154
- error_ids: row.error_ids,
155
188
  }
156
189
  }
157
190
 
@@ -169,7 +202,10 @@ export function localErrorId(id: string | number): number | undefined {
169
202
  }
170
203
 
171
204
  export const localErrorsProvider: DashboardErrorsProvider = {
172
- grouped: async () => (await errors.fetchGrouped()).map(toGroupedError),
205
+ grouped: async (query) => {
206
+ const page = await errors.fetchGroupedPage(query)
207
+ return { ...page, groups: page.groups.map(toGroupedError) }
208
+ },
173
209
  stats: () => errors.fetchStats(),
174
210
  timeline: () => errors.fetchTimeline(),
175
211
  byGroup: (type, message) => errors.fetchByGroup(type, message),
@@ -226,8 +262,50 @@ async function readErrors<T>(
226
262
  return payload
227
263
  }
228
264
 
229
- export function readGroupedErrors(): Promise<DashboardErrorsEnvelope<DashboardGroupedError[]>> {
230
- return readErrors([], provider => provider.grouped(), Array.isArray)
265
+ const EMPTY_GROUP_PAGE: DashboardGroupedErrorPage = { groups: [], total: 0, page: 1, types: [] }
266
+
267
+ export function readGroupedErrors(query: DashboardErrorGroupQuery): Promise<DashboardErrorsEnvelope<DashboardGroupedErrorPage>> {
268
+ return readErrors(
269
+ EMPTY_GROUP_PAGE,
270
+ provider => provider.grouped(query),
271
+ payload => Boolean(payload) && Array.isArray((payload as DashboardGroupedErrorPage).groups),
272
+ )
273
+ }
274
+
275
+ /** The page query, or the reason it is invalid. Absent values: page 1 of 25, every group. */
276
+ export function parseErrorGroupQuery(read: QueryReader): DashboardErrorGroupQuery | { error: string } {
277
+ const pageText = queryText(read, 'page')
278
+ const page = pageText === '' ? 1 : Number(pageText)
279
+ if (!Number.isInteger(page) || page < 1)
280
+ return { error: 'page must be a whole number of 1 or more.' }
281
+
282
+ const sizeText = queryText(read, 'per_page')
283
+ const perPage = sizeText === '' ? 25 : Number(sizeText)
284
+ if (!(LIST_PAGE_SIZES as readonly number[]).includes(perPage))
285
+ return { error: `per_page must be one of ${LIST_PAGE_SIZES.join(', ')}.` }
286
+
287
+ const status = queryText(read, 'status') || 'all'
288
+ if (status !== 'all' && !ERROR_STATUSES.includes(status as DashboardErrorStatus))
289
+ return { error: `status must be all or one of ${ERROR_STATUSES.join(', ')}.` }
290
+
291
+ const search = queryText(read, 'search')
292
+ if (search.length > 200)
293
+ return { error: 'search must be 200 characters or fewer.' }
294
+
295
+ const type = queryText(read, 'type')
296
+ if (type.length > 255)
297
+ return { error: 'type must be 255 characters or fewer.' }
298
+
299
+ const sort = queryText(read, 'sort') || 'last_seen'
300
+ if (!(ERROR_GROUP_SORTS as readonly string[]).includes(sort))
301
+ return { error: `sort must be one of ${ERROR_GROUP_SORTS.join(', ')}.` }
302
+
303
+ const directionText = queryText(read, 'direction').toLowerCase()
304
+ const direction = directionText === '' ? (sort === 'last_seen' || sort === 'count' ? 'desc' : 'asc') : directionText
305
+ if (direction !== 'asc' && direction !== 'desc')
306
+ return { error: 'direction must be asc or desc.' }
307
+
308
+ return { page, perPage, search, status: status as DashboardErrorGroupQuery['status'], type, sort: sort as DashboardErrorGroupQuery['sort'], direction }
231
309
  }
232
310
 
233
311
  export function readErrorStats(): Promise<DashboardErrorsEnvelope<DashboardErrorStats>> {
package/functions/api.ts CHANGED
@@ -5,6 +5,7 @@ import { resolveApiBaseUrl } from './api-url'
5
5
  * Provides a simple fetch wrapper with authentication and error handling.
6
6
  */
7
7
 
8
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
8
9
  import { useAuth } from './auth'
9
10
 
10
11
  const baseUrl = resolveApiBaseUrl()
@@ -69,14 +70,17 @@ export interface ApiError {
69
70
  }
70
71
 
71
72
  /**
72
- * Create headers with authentication
73
+ * Create headers with authentication.
74
+ *
75
+ * A write also carries the CSRF token: the server checks it on every write
76
+ * that rides the session cookie rather than a Bearer token, so without it
77
+ * each post, put, patch and del from a signed-in browser answered 403
78
+ * (stacksjs/stacks#3197), as `dashboardApi` already knew.
73
79
  */
74
- function createHeaders(contentType = 'application/json'): Headers {
80
+ function createHeaders(contentType = 'application/json', write = false): Headers {
75
81
  const { getToken } = useAuth()
76
- const headers = new Headers({
77
- 'Accept': 'application/json',
78
- 'Content-Type': contentType,
79
- })
82
+ const base = { 'Accept': 'application/json', 'Content-Type': contentType }
83
+ const headers = new Headers(write ? withCsrfHeader(base) : base)
80
84
 
81
85
  const token = getToken()
82
86
  if (token) {
@@ -118,7 +122,7 @@ export async function get<T>(endpoint: string): Promise<T> {
118
122
  export async function post<T>(endpoint: string, body?: unknown): Promise<T> {
119
123
  const response = await fetch(requestUrl(endpoint), {
120
124
  method: 'POST',
121
- headers: createHeaders(),
125
+ headers: createHeaders('application/json', true),
122
126
  body: body ? JSON.stringify(body) : undefined,
123
127
  })
124
128
 
@@ -131,7 +135,7 @@ export async function post<T>(endpoint: string, body?: unknown): Promise<T> {
131
135
  export async function put<T>(endpoint: string, body?: unknown): Promise<T> {
132
136
  const response = await fetch(requestUrl(endpoint), {
133
137
  method: 'PUT',
134
- headers: createHeaders(),
138
+ headers: createHeaders('application/json', true),
135
139
  body: body ? JSON.stringify(body) : undefined,
136
140
  })
137
141
 
@@ -144,7 +148,7 @@ export async function put<T>(endpoint: string, body?: unknown): Promise<T> {
144
148
  export async function patch<T>(endpoint: string, body?: unknown): Promise<T> {
145
149
  const response = await fetch(requestUrl(endpoint), {
146
150
  method: 'PATCH',
147
- headers: createHeaders(),
151
+ headers: createHeaders('application/json', true),
148
152
  body: body ? JSON.stringify(body) : undefined,
149
153
  })
150
154
 
@@ -157,7 +161,7 @@ export async function patch<T>(endpoint: string, body?: unknown): Promise<T> {
157
161
  export async function del<T>(endpoint: string): Promise<T> {
158
162
  const response = await fetch(requestUrl(endpoint), {
159
163
  method: 'DELETE',
160
- headers: createHeaders(),
164
+ headers: createHeaders('application/json', true),
161
165
  })
162
166
 
163
167
  return handleResponse<T>(response)
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../api-url'
2
2
  import type { Coupons, NewCoupon } from '../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../toasts'
5
6
 
@@ -38,9 +39,7 @@ async function createCoupon(coupon: Omit<Coupons, 'id'>) {
38
39
  try {
39
40
  const response = await fetch(`${baseURL}/commerce/coupons`, {
40
41
  method: 'POST',
41
- headers: {
42
- 'Content-Type': 'application/json',
43
- },
42
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
44
43
  body: JSON.stringify(coupon),
45
44
  })
46
45
 
@@ -66,9 +65,7 @@ async function updateCoupon(id: number, coupon: NewCoupon) {
66
65
  try {
67
66
  const response = await fetch(`${baseURL}/commerce/coupons/${id}`, {
68
67
  method: 'PATCH',
69
- headers: {
70
- 'Content-Type': 'application/json',
71
- },
68
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
72
69
  body: JSON.stringify(coupon),
73
70
  })
74
71
 
@@ -101,6 +98,7 @@ async function deleteCoupon(id: number) {
101
98
  try {
102
99
  const response = await fetch(`${baseURL}/commerce/coupons/${id}`, {
103
100
  method: 'DELETE',
101
+ headers: withCsrfHeader(),
104
102
  })
105
103
 
106
104
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../api-url'
2
2
  import type { Customers } from '../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../toasts'
5
6
 
@@ -38,9 +39,7 @@ async function createCustomer(customer: Omit<Customers, 'id'>) {
38
39
  try {
39
40
  const response = await fetch(`${baseURL}/commerce/customers`, {
40
41
  method: 'POST',
41
- headers: {
42
- 'Content-Type': 'application/json',
43
- },
42
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
44
43
  body: JSON.stringify(customer),
45
44
  })
46
45
 
@@ -66,9 +65,7 @@ async function updateCustomer(customer: Customers) {
66
65
  try {
67
66
  const response = await fetch(`${baseURL}/commerce/customers/${customer.id}`, {
68
67
  method: 'PATCH',
69
- headers: {
70
- 'Content-Type': 'application/json',
71
- },
68
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
72
69
  body: JSON.stringify(customer),
73
70
  })
74
71
 
@@ -101,6 +98,7 @@ async function deleteCustomer(id: number) {
101
98
  try {
102
99
  const response = await fetch(`${baseURL}/commerce/customers/${id}`, {
103
100
  method: 'DELETE',
101
+ headers: withCsrfHeader(),
104
102
  })
105
103
 
106
104
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../api-url'
2
2
  import type { GiftCards } from '../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../toasts'
5
6
 
@@ -35,7 +36,7 @@ async function createGiftCard(giftCard: GiftCards): Promise<GiftCards | null> {
35
36
  try {
36
37
  const response = await fetch(`${baseURL}/commerce/gift-cards`, {
37
38
  method: 'POST',
38
- headers: { 'Content-Type': 'application/json' },
39
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
39
40
  body: JSON.stringify(giftCard),
40
41
  })
41
42
 
@@ -61,7 +62,7 @@ async function updateGiftCard(giftCard: GiftCards): Promise<GiftCards | null> {
61
62
  try {
62
63
  const response = await fetch(`${baseURL}/commerce/gift-cards/${giftCard.id}`, {
63
64
  method: 'PATCH',
64
- headers: { 'Content-Type': 'application/json' },
65
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
65
66
  body: JSON.stringify(giftCard),
66
67
  })
67
68
 
@@ -90,6 +91,7 @@ async function deleteGiftCard(id: number): Promise<boolean> {
90
91
  try {
91
92
  const response = await fetch(`${baseURL}/commerce/gift-cards/${id}`, {
92
93
  method: 'DELETE',
94
+ headers: withCsrfHeader(),
93
95
  })
94
96
 
95
97
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../api-url'
2
2
  import type { Orders } from '../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../toasts'
5
6
 
@@ -28,7 +29,7 @@ async function createOrder(order: Orders) {
28
29
  try {
29
30
  const response = await fetch(`${baseURL}/commerce/orders`, {
30
31
  method: 'POST',
31
- headers: { 'Content-Type': 'application/json' },
32
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
32
33
  body: JSON.stringify(order),
33
34
  })
34
35
 
@@ -54,7 +55,7 @@ async function updateOrder(order: Orders) {
54
55
  try {
55
56
  const response = await fetch(`${baseURL}/commerce/orders/${order.id}`, {
56
57
  method: 'PATCH',
57
- headers: { 'Content-Type': 'application/json' },
58
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
58
59
  body: JSON.stringify(order),
59
60
  })
60
61
 
@@ -83,6 +84,7 @@ async function deleteOrder(id: number) {
83
84
  try {
84
85
  const response = await fetch(`${baseURL}/commerce/orders/${id}`, {
85
86
  method: 'DELETE',
87
+ headers: withCsrfHeader(),
86
88
  })
87
89
 
88
90
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../api-url'
2
2
  import type { Payments } from '../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../toasts'
5
6
 
@@ -35,7 +36,7 @@ async function createPayment(payment: Payments): Promise<Payments | null> {
35
36
  try {
36
37
  const response = await fetch(`${baseURL}/commerce/payments`, {
37
38
  method: 'POST',
38
- headers: { 'Content-Type': 'application/json' },
39
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
39
40
  body: JSON.stringify(payment),
40
41
  })
41
42
 
@@ -61,7 +62,7 @@ async function updatePayment(payment: Payments): Promise<Payments | null> {
61
62
  try {
62
63
  const response = await fetch(`${baseURL}/commerce/payments/${payment.id}`, {
63
64
  method: 'PATCH',
64
- headers: { 'Content-Type': 'application/json' },
65
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
65
66
  body: JSON.stringify(payment),
66
67
  })
67
68
 
@@ -90,6 +91,7 @@ async function deletePayment(id: number): Promise<boolean> {
90
91
  try {
91
92
  const response = await fetch(`${baseURL}/commerce/payments/${id}`, {
92
93
  method: 'DELETE',
94
+ headers: withCsrfHeader(),
93
95
  })
94
96
 
95
97
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../../api-url'
2
2
  import type { Categories } from '../../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../../toasts'
5
6
 
@@ -36,9 +37,7 @@ async function createCategory(category: Categories): Promise<Categories | null>
36
37
  try {
37
38
  const response = await fetch(`${baseURL}/commerce/product-categories`, {
38
39
  method: 'POST',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- },
40
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
42
41
  body: JSON.stringify(category),
43
42
  })
44
43
 
@@ -63,9 +62,7 @@ async function updateCategory(category: Categories): Promise<Categories | null>
63
62
  try {
64
63
  const response = await fetch(`${baseURL}/commerce/product-categories/${category.id}`, {
65
64
  method: 'PATCH',
66
- headers: {
67
- 'Content-Type': 'application/json',
68
- },
65
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
69
66
  body: JSON.stringify(category),
70
67
  })
71
68
 
@@ -93,6 +90,7 @@ async function deleteCategory(id: number): Promise<boolean> {
93
90
  try {
94
91
  const response = await fetch(`${baseURL}/commerce/product-categories/${id}`, {
95
92
  method: 'DELETE',
93
+ headers: withCsrfHeader(),
96
94
  })
97
95
 
98
96
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../../api-url'
2
2
  import type { Manufacturers } from '../../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../../toasts'
5
6
 
@@ -36,9 +37,7 @@ async function createManufacturer(manufacturer: Manufacturers): Promise<Manufact
36
37
  try {
37
38
  const response = await fetch(`${baseURL}/commerce/product-manufacturers`, {
38
39
  method: 'POST',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- },
40
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
42
41
  body: JSON.stringify(manufacturer),
43
42
  })
44
43
 
@@ -63,9 +62,7 @@ async function updateManufacturer(manufacturer: Manufacturers): Promise<Manufact
63
62
  try {
64
63
  const response = await fetch(`${baseURL}/commerce/product-manufacturers/${manufacturer.id}`, {
65
64
  method: 'PATCH',
66
- headers: {
67
- 'Content-Type': 'application/json',
68
- },
65
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
69
66
  body: JSON.stringify(manufacturer),
70
67
  })
71
68
 
@@ -93,6 +90,7 @@ async function deleteManufacturer(id: number): Promise<boolean> {
93
90
  try {
94
91
  const response = await fetch(`${baseURL}/commerce/product-manufacturers/${id}`, {
95
92
  method: 'DELETE',
93
+ headers: withCsrfHeader(),
96
94
  })
97
95
 
98
96
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../../api-url'
2
2
  import type { Products } from '../../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../../toasts'
5
6
 
@@ -36,9 +37,7 @@ async function createProduct(product: Products): Promise<Products | null> {
36
37
  try {
37
38
  const response = await fetch(`${baseURL}/commerce/products`, {
38
39
  method: 'POST',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- },
40
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
42
41
  body: JSON.stringify(product),
43
42
  })
44
43
 
@@ -63,9 +62,7 @@ async function updateProduct(product: Products): Promise<Products | null> {
63
62
  try {
64
63
  const response = await fetch(`${baseURL}/commerce/products/${product.id}`, {
65
64
  method: 'PATCH',
66
- headers: {
67
- 'Content-Type': 'application/json',
68
- },
65
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
69
66
  body: JSON.stringify(product),
70
67
  })
71
68
 
@@ -93,6 +90,7 @@ async function deleteProduct(id: number): Promise<boolean> {
93
90
  try {
94
91
  const response = await fetch(`${baseURL}/commerce/products/${id}`, {
95
92
  method: 'DELETE',
93
+ headers: withCsrfHeader(),
96
94
  })
97
95
 
98
96
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../../api-url'
2
2
  import type { Reviews } from '../../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../../toasts'
5
6
 
@@ -36,9 +37,7 @@ async function createReview(review: Reviews): Promise<Reviews | null> {
36
37
  try {
37
38
  const response = await fetch(`${baseURL}/commerce/products/reviews`, {
38
39
  method: 'POST',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- },
40
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
42
41
  body: JSON.stringify(review),
43
42
  })
44
43
 
@@ -63,9 +62,7 @@ async function updateReview(review: Reviews): Promise<Reviews | null> {
63
62
  try {
64
63
  const response = await fetch(`${baseURL}/commerce/products/reviews/${review.id}`, {
65
64
  method: 'PATCH',
66
- headers: {
67
- 'Content-Type': 'application/json',
68
- },
65
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
69
66
  body: JSON.stringify(review),
70
67
  })
71
68
 
@@ -93,6 +90,7 @@ async function deleteReview(id: number): Promise<boolean> {
93
90
  try {
94
91
  const response = await fetch(`${baseURL}/commerce/products/reviews/${id}`, {
95
92
  method: 'DELETE',
93
+ headers: withCsrfHeader(),
96
94
  })
97
95
 
98
96
  if (!response.ok) {
@@ -1,5 +1,6 @@
1
1
  import { resolveApiBaseUrl } from '../../api-url'
2
2
  import type { Units } from '../../../types/defaults'
3
+ import { withCsrfHeader } from '@stacksjs/browser/composables/csrf'
3
4
  import { useStorage } from '@stacksjs/browser/composables/useStorage'
4
5
  import { pushToast } from '../../toasts'
5
6
 
@@ -36,9 +37,7 @@ async function createUnit(unit: Units): Promise<Units | null> {
36
37
  try {
37
38
  const response = await fetch(`${baseURL}/commerce/products/units`, {
38
39
  method: 'POST',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- },
40
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
42
41
  body: JSON.stringify(unit),
43
42
  })
44
43
 
@@ -63,9 +62,7 @@ async function updateUnit(unit: Units): Promise<Units | null> {
63
62
  try {
64
63
  const response = await fetch(`${baseURL}/commerce/products/units/${unit.id}`, {
65
64
  method: 'PATCH',
66
- headers: {
67
- 'Content-Type': 'application/json',
68
- },
65
+ headers: withCsrfHeader({ 'Content-Type': 'application/json' }),
69
66
  body: JSON.stringify(unit),
70
67
  })
71
68
 
@@ -93,6 +90,7 @@ async function deleteUnit(id: number): Promise<boolean> {
93
90
  try {
94
91
  const response = await fetch(`${baseURL}/commerce/products/units/${id}`, {
95
92
  method: 'DELETE',
93
+ headers: withCsrfHeader(),
96
94
  })
97
95
 
98
96
  if (!response.ok) {