@stacksjs/defaults 0.70.356 → 0.70.357

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 (27) hide show
  1. package/ai/skills/stacks-orm/SKILL.md +6 -0
  2. package/app/Actions/Dashboard/Content/AuthorDestroyAction.ts +23 -9
  3. package/app/Actions/Dashboard/Content/AuthorIndexAction.ts +32 -26
  4. package/app/Actions/Dashboard/Content/AuthorStoreAction.ts +44 -24
  5. package/app/Actions/Dashboard/Content/AuthorUpdateAction.ts +48 -26
  6. package/app/Actions/Dashboard/Content/CategoryDestroyAction.ts +27 -8
  7. package/app/Actions/Dashboard/Content/CategoryIndexAction.ts +21 -15
  8. package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +47 -29
  9. package/app/Actions/Dashboard/Content/CommentDestroyAction.ts +17 -4
  10. package/app/Actions/Dashboard/Content/CommentIndexAction.ts +23 -17
  11. package/app/Actions/Dashboard/Content/CommentUpdateAction.ts +30 -12
  12. package/app/Actions/Dashboard/Content/ContentDashboardAction.ts +43 -37
  13. package/app/Actions/Dashboard/Content/PageDestroyAction.ts +17 -4
  14. package/app/Actions/Dashboard/Content/PageIndexAction.ts +23 -17
  15. package/app/Actions/Dashboard/Content/PageStoreAction.ts +33 -18
  16. package/app/Actions/Dashboard/Content/PageUpdateAction.ts +40 -22
  17. package/app/Actions/Dashboard/Content/PostDestroyAction.ts +24 -11
  18. package/app/Actions/Dashboard/Content/PostIndexAction.ts +55 -49
  19. package/app/Actions/Dashboard/Content/PostStoreAction.ts +43 -33
  20. package/app/Actions/Dashboard/Content/PostUpdateAction.ts +46 -34
  21. package/app/Actions/Dashboard/Content/TagDestroyAction.ts +27 -8
  22. package/app/Actions/Dashboard/Content/TagIndexAction.ts +31 -25
  23. package/app/Actions/Dashboard/Content/TagStoreAction.ts +44 -23
  24. package/app/Actions/Dashboard/Content/content-input.ts +4 -4
  25. package/app/Actions/Dashboard/Content/post-input.ts +60 -8
  26. package/ide/vscode/package.json +1 -1
  27. package/package.json +1 -1
@@ -196,6 +196,12 @@ await createUser('Alice', 'alice@example.com') // auto-wrapped
196
196
 
197
197
  Both `transaction()` and `savepoint()` delegate to `db.transaction()` and `db.savepoint()` from `@stacksjs/database`.
198
198
 
199
+ ### Transaction executor boundary
200
+
201
+ Every query that must commit or roll back together must use the callback handle (`tx` or `sp`), including validation reads, pivot writes, and the final readback. Do not mix `Model.find()`, `Model.create()`, instance `update()` / `delete()`, or instance relation calls into a raw query-builder transaction. The model executor is a separate execution surface and is not rebound to the callback handle. On SQLite it may use a separate connection, so it cannot observe an uncommitted row written through `tx`.
202
+
203
+ `runInTransactionScope()` buffers supported side effects until commit, but it does not rebind model queries. For a transaction-backed custom action, use the model definition as the schema and relationship source of truth, then execute the complete persistence workflow through `tx`. Read the created or updated row through `tx` before returning so a readback failure also rolls back the mutation.
204
+
199
205
  ## Trait Methods (traits/)
200
206
 
201
207
  ### Taggable (when `traits.taggable: true`)
@@ -1,7 +1,9 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { db } from '@stacksjs/database'
4
+ import { transaction } from '@stacksjs/orm'
4
5
  import { response } from '@stacksjs/router'
6
+ import { dashboardOperationalError } from '../dashboard-response'
5
7
  import { rowExists, rowId, timestamp } from './content-input'
6
8
 
7
9
  /**
@@ -22,17 +24,29 @@ export default new Action({
22
24
  if (!id)
23
25
  return response.json({ message: 'A valid author id is required.' }, 422)
24
26
 
25
- if (!await rowExists('authors', id))
26
- return response.json({ message: 'Author not found.' }, 404)
27
+ try {
28
+ const deleted = await transaction(async (rawTrx) => {
29
+ const trx = rawTrx as unknown as typeof db
30
+ if (!await rowExists('authors', id, trx))
31
+ return false
27
32
 
28
- await db
29
- .updateTable('posts')
30
- .set({ author_id: null, updated_at: timestamp() } as any)
31
- .where('author_id', '=', id)
32
- .execute()
33
+ await trx
34
+ .updateTable('posts')
35
+ .set({ author_id: null, updated_at: timestamp() } as any)
36
+ .where('author_id', '=', id)
37
+ .execute()
33
38
 
34
- await db.deleteFrom('authors').where('id', '=', id).execute()
39
+ await trx.deleteFrom('authors').where('id', '=', id).execute()
40
+ return true
41
+ })
35
42
 
36
- return response.json({ message: 'Author deleted.', id })
43
+ if (!deleted)
44
+ return response.json({ message: 'Author not found.' }, 404)
45
+
46
+ return response.json({ message: 'Author deleted.', id })
47
+ }
48
+ catch (error) {
49
+ return dashboardOperationalError(error, 'Author could not be deleted.', 'AuthorDestroyAction', 500)
50
+ }
37
51
  },
38
52
  })
@@ -1,5 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { db } from '@stacksjs/database'
3
+ import { dashboardOperationalError } from '../dashboard-response'
3
4
 
4
5
  interface AuthorRow {
5
6
  id: number
@@ -30,36 +31,41 @@ export default new Action({
30
31
  method: 'GET',
31
32
  apiResponse: true,
32
33
  async handle() {
33
- const [rows, postAuthors] = await Promise.all([
34
- db.selectFrom('authors').selectAll().orderBy('created_at', 'desc').execute() as unknown as Promise<AuthorRow[]>,
35
- db.selectFrom('posts').select(['author_id']).execute() as unknown as Promise<Array<{ author_id: number | null }>>,
36
- ])
34
+ try {
35
+ const [rows, postAuthors] = await Promise.all([
36
+ db.selectFrom('authors').selectAll().orderBy('created_at', 'desc').execute() as unknown as Promise<AuthorRow[]>,
37
+ db.selectFrom('posts').select(['author_id']).execute() as unknown as Promise<Array<{ author_id: number | null }>>,
38
+ ])
37
39
 
38
- const postCounts = new Map<number, number>()
39
- for (const post of postAuthors) {
40
- if (post.author_id == null)
41
- continue
40
+ const postCounts = new Map<number, number>()
41
+ for (const post of postAuthors) {
42
+ if (post.author_id == null)
43
+ continue
42
44
 
43
- const id = Number(post.author_id)
44
- postCounts.set(id, (postCounts.get(id) || 0) + 1)
45
- }
45
+ const id = Number(post.author_id)
46
+ postCounts.set(id, (postCounts.get(id) || 0) + 1)
47
+ }
46
48
 
47
- const authors = rows.map(row => ({
48
- id: Number(row.id),
49
- name: String(row.name || ''),
50
- email: String(row.email || ''),
51
- bio: String(row.bio || ''),
52
- avatar: String(row.avatar || ''),
53
- postCount: postCounts.get(Number(row.id)) || 0,
54
- created_at: row.created_at || null,
55
- updated_at: row.updated_at || null,
56
- }))
49
+ const authors = rows.map(row => ({
50
+ id: Number(row.id),
51
+ name: String(row.name || ''),
52
+ email: String(row.email || ''),
53
+ bio: String(row.bio || ''),
54
+ avatar: String(row.avatar || ''),
55
+ postCount: postCounts.get(Number(row.id)) || 0,
56
+ created_at: row.created_at || null,
57
+ updated_at: row.updated_at || null,
58
+ }))
57
59
 
58
- // Posts whose author no longer exists are not counted: the stat means
59
- // "posts reachable from this list", which is what the page's Assigned Posts
60
- // tile claims to show.
61
- const assignedPostCount = authors.reduce((sum, author) => sum + author.postCount, 0)
60
+ // Posts whose author no longer exists are not counted: the stat means
61
+ // "posts reachable from this list", which is what the page's Assigned Posts
62
+ // tile claims to show.
63
+ const assignedPostCount = authors.reduce((sum, author) => sum + author.postCount, 0)
62
64
 
63
- return { authors, assignedPostCount }
65
+ return { authors, assignedPostCount }
66
+ }
67
+ catch (error) {
68
+ return dashboardOperationalError(error, 'Authors could not be loaded.', 'AuthorIndexAction')
69
+ }
64
70
  },
65
71
  })
@@ -1,8 +1,10 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { db } from '@stacksjs/database'
4
+ import { isUniqueViolation, transaction } from '@stacksjs/orm'
4
5
  import { response } from '@stacksjs/router'
5
6
  import { randomUUIDv7 } from 'bun'
7
+ import { dashboardOperationalError } from '../dashboard-response'
6
8
  import { parseAuthorInput } from './author-input'
7
9
  import { findRow, insertedId, timestamp } from './content-input'
8
10
 
@@ -25,35 +27,53 @@ export default new Action({
25
27
  if ('message' in input)
26
28
  return response.json({ message: input.message }, 422)
27
29
 
28
- const duplicate = await db
29
- .selectFrom('authors')
30
- .select(['id'])
31
- .where('email', '=', input.data.email)
32
- .executeTakeFirst()
30
+ try {
31
+ const author = await transaction(async (rawTrx) => {
32
+ const trx = rawTrx as unknown as typeof db
33
+ const duplicate = await trx
34
+ .selectFrom('authors')
35
+ .select(['id'])
36
+ .where('email', '=', input.data.email)
37
+ .executeTakeFirst()
33
38
 
34
- if (duplicate)
35
- return response.json({ message: 'An author with that email already exists.' }, 422)
39
+ if (duplicate)
40
+ return null
36
41
 
37
- const now = timestamp()
42
+ const now = timestamp()
38
43
 
39
- const result = await db
40
- .insertInto('authors')
41
- .values({
42
- uuid: randomUUIDv7(),
43
- name: input.data.name,
44
- email: input.data.email,
45
- bio: input.data.bio || null,
46
- avatar: input.data.avatar || null,
47
- created_at: now,
48
- updated_at: now,
49
- } as any)
50
- .executeTakeFirst()
44
+ const result = await trx
45
+ .insertInto('authors')
46
+ .values({
47
+ uuid: randomUUIDv7(),
48
+ name: input.data.name,
49
+ email: input.data.email,
50
+ bio: input.data.bio || null,
51
+ avatar: input.data.avatar || null,
52
+ created_at: now,
53
+ updated_at: now,
54
+ } as any)
55
+ .executeTakeFirst()
51
56
 
52
- const id = insertedId(result)
57
+ const id = insertedId(result)
53
58
 
54
- if (!id)
55
- return response.json({ message: 'Could not create author.' }, 500)
59
+ if (!id)
60
+ throw new Error('Author insert did not return an id.')
56
61
 
57
- return response.json(await findRow('authors', id), 201)
62
+ const created = await findRow('authors', id, trx)
63
+ if (!created)
64
+ throw new Error('Created author could not be loaded.')
65
+ return created
66
+ })
67
+
68
+ if (!author)
69
+ return response.json({ message: 'An author with that email already exists.' }, 422)
70
+
71
+ return response.json(author, 201)
72
+ }
73
+ catch (error) {
74
+ if (isUniqueViolation(error))
75
+ return response.json({ message: 'An author with that email already exists.' }, 422)
76
+ return dashboardOperationalError(error, 'Author could not be created.', 'AuthorStoreAction', 500)
77
+ }
58
78
  },
59
79
  })
@@ -1,7 +1,9 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { db } from '@stacksjs/database'
4
+ import { isUniqueViolation, transaction } from '@stacksjs/orm'
4
5
  import { response } from '@stacksjs/router'
6
+ import { dashboardOperationalError } from '../dashboard-response'
5
7
  import { parseAuthorInput } from './author-input'
6
8
  import { findRow, rowExists, rowId, timestamp } from './content-input'
7
9
 
@@ -15,36 +17,56 @@ export default new Action({
15
17
  if (!id)
16
18
  return response.json({ message: 'A valid author id is required.' }, 422)
17
19
 
18
- if (!await rowExists('authors', id))
19
- return response.json({ message: 'Author not found.' }, 404)
20
-
21
20
  const input = parseAuthorInput(request)
22
21
 
23
22
  if ('message' in input)
24
23
  return response.json({ message: input.message }, 422)
25
24
 
26
- const duplicate = await db
27
- .selectFrom('authors')
28
- .select(['id'])
29
- .where('email', '=', input.data.email)
30
- .where('id', '!=', id)
31
- .executeTakeFirst()
32
-
33
- if (duplicate)
34
- return response.json({ message: 'An author with that email already exists.' }, 422)
35
-
36
- await db
37
- .updateTable('authors')
38
- .set({
39
- name: input.data.name,
40
- email: input.data.email,
41
- bio: input.data.bio || null,
42
- avatar: input.data.avatar || null,
43
- updated_at: timestamp(),
44
- } as any)
45
- .where('id', '=', id)
46
- .execute()
47
-
48
- return response.json(await findRow('authors', id))
25
+ try {
26
+ const result = await transaction(async (rawTrx) => {
27
+ const trx = rawTrx as unknown as typeof db
28
+ if (!await rowExists('authors', id, trx))
29
+ return { kind: 'not-found' } as const
30
+
31
+ const duplicate = await trx
32
+ .selectFrom('authors')
33
+ .select(['id'])
34
+ .where('email', '=', input.data.email)
35
+ .where('id', '!=', id)
36
+ .executeTakeFirst()
37
+
38
+ if (duplicate)
39
+ return { kind: 'duplicate' } as const
40
+
41
+ await trx
42
+ .updateTable('authors')
43
+ .set({
44
+ name: input.data.name,
45
+ email: input.data.email,
46
+ bio: input.data.bio || null,
47
+ avatar: input.data.avatar || null,
48
+ updated_at: timestamp(),
49
+ } as any)
50
+ .where('id', '=', id)
51
+ .execute()
52
+
53
+ const author = await findRow('authors', id, trx)
54
+ if (!author)
55
+ throw new Error('Updated author could not be loaded.')
56
+ return { kind: 'updated', author } as const
57
+ })
58
+
59
+ if (result.kind === 'not-found')
60
+ return response.json({ message: 'Author not found.' }, 404)
61
+ if (result.kind === 'duplicate')
62
+ return response.json({ message: 'An author with that email already exists.' }, 422)
63
+
64
+ return response.json(result.author)
65
+ }
66
+ catch (error) {
67
+ if (isUniqueViolation(error))
68
+ return response.json({ message: 'An author with that email already exists.' }, 422)
69
+ return dashboardOperationalError(error, 'Author could not be updated.', 'AuthorUpdateAction', 500)
70
+ }
49
71
  },
50
72
  })
@@ -1,13 +1,16 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
+ import { db } from '@stacksjs/database'
4
+ import { transaction } from '@stacksjs/orm'
3
5
  import { response } from '@stacksjs/router'
6
+ import { dashboardOperationalError } from '../dashboard-response'
4
7
  import { rowId } from './content-input'
5
8
 
6
9
  /**
7
10
  * `DELETE /api/dashboard/categories/{id}` — deletes a CMS category.
8
11
  *
9
- * Detaches related posts through the model relation before removing the
10
- * category so no pivot rows are orphaned.
12
+ * Detaches related posts through the model-declared pivot before removing the
13
+ * category, with both writes committed by the same transaction.
11
14
  */
12
15
  export default new Action({
13
16
  name: 'CategoryDestroyAction',
@@ -19,13 +22,29 @@ export default new Action({
19
22
  if (!id)
20
23
  return response.json({ message: 'A valid category id is required.' }, 422)
21
24
 
22
- const category = await Category.find(id)
23
- if (!category)
24
- return response.json({ message: 'Category not found.' }, 404)
25
+ try {
26
+ const deleted = await transaction(async (rawTrx) => {
27
+ const trx = rawTrx as unknown as typeof db
28
+ const category = await trx.selectFrom('categories').select(['id']).where('id', '=', id).executeTakeFirst()
29
+ if (!category)
30
+ return false
25
31
 
26
- await category.posts().detach()
27
- await category.delete()
32
+ await trx
33
+ .deleteFrom('categorizable_models')
34
+ .where('category_id', '=', id)
35
+ .where('categorizable_type', '=', 'posts')
36
+ .execute()
37
+ await trx.deleteFrom('categories').where('id', '=', id).execute()
38
+ return true
39
+ })
28
40
 
29
- return response.json({ message: 'Category deleted.', id })
41
+ if (!deleted)
42
+ return response.json({ message: 'Category not found.' }, 404)
43
+
44
+ return response.json({ message: 'Category deleted.', id })
45
+ }
46
+ catch (error) {
47
+ return dashboardOperationalError(error, 'Category could not be deleted.', 'CategoryDestroyAction', 500)
48
+ }
30
49
  },
31
50
  })
@@ -1,5 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { db } from '@stacksjs/database'
3
+ import { dashboardOperationalError } from '../dashboard-response'
3
4
 
4
5
  interface CategoryRow {
5
6
  id: number
@@ -29,22 +30,27 @@ export default new Action({
29
30
  method: 'GET',
30
31
  apiResponse: true,
31
32
  async handle() {
32
- const rows = await db
33
- .selectFrom('categories')
34
- .selectAll()
35
- .orderBy('created_at', 'desc')
36
- .execute() as unknown as CategoryRow[]
33
+ try {
34
+ const rows = await db
35
+ .selectFrom('categories')
36
+ .selectAll()
37
+ .orderBy('created_at', 'desc')
38
+ .execute() as unknown as CategoryRow[]
37
39
 
38
- const categories = rows.map(row => ({
39
- id: Number(row.id),
40
- name: String(row.name || ''),
41
- slug: String(row.slug || ''),
42
- description: String(row.description || ''),
43
- is_active: row.is_active == null ? true : Boolean(row.is_active),
44
- created_at: row.created_at || null,
45
- updated_at: row.updated_at || null,
46
- }))
40
+ const categories = rows.map(row => ({
41
+ id: Number(row.id),
42
+ name: String(row.name || ''),
43
+ slug: String(row.slug || ''),
44
+ description: String(row.description || ''),
45
+ is_active: row.is_active == null ? true : Boolean(row.is_active),
46
+ created_at: row.created_at || null,
47
+ updated_at: row.updated_at || null,
48
+ }))
47
49
 
48
- return { categories }
50
+ return { categories }
51
+ }
52
+ catch (error) {
53
+ return dashboardOperationalError(error, 'Categories could not be loaded.', 'CategoryIndexAction')
54
+ }
49
55
  },
50
56
  })
@@ -1,8 +1,10 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { db } from '@stacksjs/database'
4
+ import { transaction } from '@stacksjs/orm'
4
5
  import { response } from '@stacksjs/router'
5
6
  import { randomUUIDv7 } from 'bun'
7
+ import { dashboardOperationalError } from '../dashboard-response'
6
8
  import { findRow, insertedId, slugify, str, timestamp } from './content-input'
7
9
 
8
10
  /**
@@ -26,40 +28,56 @@ export default new Action({
26
28
  if (!slug)
27
29
  return response.json({ message: 'Slug could not be derived from the name; enter one.' }, 422)
28
30
 
29
- const duplicate = await db
30
- .selectFrom('categories')
31
- .select(['id'])
32
- .where('slug', '=', slug)
33
- .executeTakeFirst()
31
+ try {
32
+ const category = await transaction(async (rawTrx) => {
33
+ const trx = rawTrx as unknown as typeof db
34
+ const duplicate = await trx
35
+ .selectFrom('categories')
36
+ .select(['id'])
37
+ .where('slug', '=', slug)
38
+ .executeTakeFirst()
34
39
 
35
- if (duplicate)
36
- return response.json({ message: 'A category with that slug already exists.' }, 422)
40
+ if (duplicate)
41
+ return null
37
42
 
38
- const now = timestamp()
39
- const displayOrder = await db
40
- .selectFrom('categories')
41
- .select(db.fn.max('display_order').as('max_display_order'))
42
- .executeTakeFirst()
43
+ const now = timestamp()
44
+ const displayOrder = await trx
45
+ .selectFrom('categories')
46
+ .select(db.fn.max('display_order').as('max_display_order'))
47
+ .executeTakeFirst()
43
48
 
44
- const result = await db
45
- .insertInto('categories')
46
- .values({
47
- uuid: randomUUIDv7(),
48
- name,
49
- slug,
50
- description,
51
- is_active: 1,
52
- display_order: Number(displayOrder?.max_display_order || 0) + 1,
53
- created_at: now,
54
- updated_at: now,
55
- } as any)
56
- .executeTakeFirst()
49
+ const result = await trx
50
+ .insertInto('categories')
51
+ .values({
52
+ uuid: randomUUIDv7(),
53
+ name,
54
+ slug,
55
+ description,
56
+ is_active: 1,
57
+ display_order: Number(displayOrder?.max_display_order || 0) + 1,
58
+ created_at: now,
59
+ updated_at: now,
60
+ } as any)
61
+ .executeTakeFirst()
57
62
 
58
- const id = insertedId(result)
63
+ const id = insertedId(result)
59
64
 
60
- if (!id)
61
- return response.json({ message: 'Could not create category.' }, 500)
65
+ if (!id)
66
+ throw new Error('Category insert did not return an id.')
62
67
 
63
- return response.json(await findRow('categories', id), 201)
68
+ const created = await findRow('categories', id, trx)
69
+ if (!created)
70
+ throw new Error('Created category could not be loaded.')
71
+ return created
72
+ })
73
+
74
+ if (!category)
75
+ return response.json({ message: 'A category with that slug already exists.' }, 422)
76
+
77
+ return response.json(category, 201)
78
+ }
79
+ catch (error) {
80
+ return dashboardOperationalError(error, 'Category could not be created.', 'CategoryStoreAction', 500)
81
+ }
64
82
  },
65
83
  })
@@ -1,7 +1,9 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { db } from '@stacksjs/database'
4
+ import { transaction } from '@stacksjs/orm'
4
5
  import { response } from '@stacksjs/router'
6
+ import { dashboardOperationalError } from '../dashboard-response'
5
7
  import { rowExists, rowId } from './content-input'
6
8
 
7
9
  /**
@@ -20,11 +22,22 @@ export default new Action({
20
22
  if (!id)
21
23
  return response.json({ message: 'A valid comment id is required.' }, 422)
22
24
 
23
- if (!await rowExists('comments', id))
24
- return response.json({ message: 'Comment not found.' }, 404)
25
+ try {
26
+ const deleted = await transaction(async (rawTrx) => {
27
+ const trx = rawTrx as unknown as typeof db
28
+ if (!await rowExists('comments', id, trx))
29
+ return false
30
+ await trx.deleteFrom('comments').where('id', '=', id).execute()
31
+ return true
32
+ })
25
33
 
26
- await db.deleteFrom('comments').where('id', '=', id).execute()
34
+ if (!deleted)
35
+ return response.json({ message: 'Comment not found.' }, 404)
27
36
 
28
- return response.json({ message: 'Comment deleted.', id })
37
+ return response.json({ message: 'Comment deleted.', id })
38
+ }
39
+ catch (error) {
40
+ return dashboardOperationalError(error, 'Comment could not be deleted.', 'CommentDestroyAction', 500)
41
+ }
29
42
  },
30
43
  })
@@ -1,5 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { db } from '@stacksjs/database'
3
+ import { dashboardOperationalError } from '../dashboard-response'
3
4
  import { normalizeCommentStatus } from './comment-input'
4
5
 
5
6
  interface CommentRow {
@@ -33,24 +34,29 @@ export default new Action({
33
34
  method: 'GET',
34
35
  apiResponse: true,
35
36
  async handle() {
36
- const rows = await db
37
- .selectFrom('comments')
38
- .selectAll()
39
- .orderBy('created_at', 'desc')
40
- .execute() as unknown as CommentRow[]
37
+ try {
38
+ const rows = await db
39
+ .selectFrom('comments')
40
+ .selectAll()
41
+ .orderBy('created_at', 'desc')
42
+ .execute() as unknown as CommentRow[]
41
43
 
42
- const comments = rows.map(row => ({
43
- id: Number(row.id),
44
- author_name: String(row.author_name || ''),
45
- author_email: String(row.author_email || ''),
46
- content: String(row.content || row.body || ''),
47
- post_title: String(row.post_title || ''),
48
- status: normalizeCommentStatus(row.status),
49
- is_approved: Boolean(row.is_approved),
50
- created_at: row.created_at || null,
51
- updated_at: row.updated_at || null,
52
- }))
44
+ const comments = rows.map(row => ({
45
+ id: Number(row.id),
46
+ author_name: String(row.author_name || ''),
47
+ author_email: String(row.author_email || ''),
48
+ content: String(row.content || row.body || ''),
49
+ post_title: String(row.post_title || ''),
50
+ status: normalizeCommentStatus(row.status),
51
+ is_approved: Boolean(row.is_approved),
52
+ created_at: row.created_at || null,
53
+ updated_at: row.updated_at || null,
54
+ }))
53
55
 
54
- return { comments }
56
+ return { comments }
57
+ }
58
+ catch (error) {
59
+ return dashboardOperationalError(error, 'Comments could not be loaded.', 'CommentIndexAction')
60
+ }
55
61
  },
56
62
  })