@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.
- package/ai/skills/stacks-orm/SKILL.md +6 -0
- package/app/Actions/Dashboard/Content/AuthorDestroyAction.ts +23 -9
- package/app/Actions/Dashboard/Content/AuthorIndexAction.ts +32 -26
- package/app/Actions/Dashboard/Content/AuthorStoreAction.ts +44 -24
- package/app/Actions/Dashboard/Content/AuthorUpdateAction.ts +48 -26
- package/app/Actions/Dashboard/Content/CategoryDestroyAction.ts +27 -8
- package/app/Actions/Dashboard/Content/CategoryIndexAction.ts +21 -15
- package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +47 -29
- package/app/Actions/Dashboard/Content/CommentDestroyAction.ts +17 -4
- package/app/Actions/Dashboard/Content/CommentIndexAction.ts +23 -17
- package/app/Actions/Dashboard/Content/CommentUpdateAction.ts +30 -12
- package/app/Actions/Dashboard/Content/ContentDashboardAction.ts +43 -37
- package/app/Actions/Dashboard/Content/PageDestroyAction.ts +17 -4
- package/app/Actions/Dashboard/Content/PageIndexAction.ts +23 -17
- package/app/Actions/Dashboard/Content/PageStoreAction.ts +33 -18
- package/app/Actions/Dashboard/Content/PageUpdateAction.ts +40 -22
- package/app/Actions/Dashboard/Content/PostDestroyAction.ts +24 -11
- package/app/Actions/Dashboard/Content/PostIndexAction.ts +55 -49
- package/app/Actions/Dashboard/Content/PostStoreAction.ts +43 -33
- package/app/Actions/Dashboard/Content/PostUpdateAction.ts +46 -34
- package/app/Actions/Dashboard/Content/TagDestroyAction.ts +27 -8
- package/app/Actions/Dashboard/Content/TagIndexAction.ts +31 -25
- package/app/Actions/Dashboard/Content/TagStoreAction.ts +44 -23
- package/app/Actions/Dashboard/Content/content-input.ts +4 -4
- package/app/Actions/Dashboard/Content/post-input.ts +60 -8
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
|
@@ -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 { COMMENT_STATUSES, parseCommentStatus } from './comment-input'
|
|
6
8
|
import { findRow, rowExists, rowId, timestamp } from './content-input'
|
|
7
9
|
|
|
@@ -30,19 +32,35 @@ export default new Action({
|
|
|
30
32
|
if (!status)
|
|
31
33
|
return response.json({ message: `Status must be one of: ${COMMENT_STATUSES.join(', ')}.` }, 422)
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
try {
|
|
36
|
+
const comment = await transaction(async (rawTrx) => {
|
|
37
|
+
const trx = rawTrx as unknown as typeof db
|
|
38
|
+
if (!await rowExists('comments', id, trx))
|
|
39
|
+
return null
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
await trx
|
|
42
|
+
.updateTable('comments')
|
|
43
|
+
.set({
|
|
44
|
+
status,
|
|
45
|
+
is_approved: status === 'approved' ? 1 : 0,
|
|
46
|
+
updated_at: timestamp(),
|
|
47
|
+
} as any)
|
|
48
|
+
.where('id', '=', id)
|
|
49
|
+
.execute()
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
const updated = await findRow('comments', id, trx)
|
|
52
|
+
if (!updated)
|
|
53
|
+
throw new Error('Updated comment could not be loaded.')
|
|
54
|
+
return updated
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (!comment)
|
|
58
|
+
return response.json({ message: 'Comment not found.' }, 404)
|
|
59
|
+
|
|
60
|
+
return response.json(comment)
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return dashboardOperationalError(error, 'Comment could not be updated.', 'CommentUpdateAction', 500)
|
|
64
|
+
}
|
|
47
65
|
},
|
|
48
66
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
4
5
|
|
|
5
6
|
const ALLOWED_RANGES = new Set([1, 7, 30, 90, 365])
|
|
6
7
|
|
|
@@ -214,42 +215,47 @@ export default new Action({
|
|
|
214
215
|
const days = requestedDays(request)
|
|
215
216
|
const since = threshold(days)
|
|
216
217
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
218
|
+
try {
|
|
219
|
+
const postQuery = db
|
|
220
|
+
.selectFrom('posts')
|
|
221
|
+
.select(['id', 'title', 'status', 'views', 'author_id', 'published_at', 'created_at', 'updated_at'])
|
|
222
|
+
const pageQuery = db
|
|
223
|
+
.selectFrom('pages')
|
|
224
|
+
.select(['id', 'title', 'views', 'published_at', 'created_at'])
|
|
225
|
+
const commentQuery = db
|
|
226
|
+
.selectFrom('comments')
|
|
227
|
+
.select(['id', 'author_name', 'author_email', 'content', 'post_id', 'post_title', 'status', 'created_at'])
|
|
228
|
+
|
|
229
|
+
const [posts, pages, comments, categories, authors] = await Promise.all([
|
|
230
|
+
(since ? postQuery.where('created_at', '>=', since) : postQuery).execute() as unknown as Promise<ContentOverviewPostRow[]>,
|
|
231
|
+
(since ? pageQuery.where('created_at', '>=', since) : pageQuery).execute() as unknown as Promise<ContentOverviewPageRow[]>,
|
|
232
|
+
(since ? commentQuery.where('created_at', '>=', since) : commentQuery).execute() as unknown as Promise<ContentOverviewCommentRow[]>,
|
|
233
|
+
db.selectFrom('categories').select(['id', 'name']).execute() as unknown as Promise<NamedRow[]>,
|
|
234
|
+
db.selectFrom('authors').select(['id', 'name']).execute() as unknown as Promise<NamedRow[]>,
|
|
235
|
+
])
|
|
236
|
+
|
|
237
|
+
const postIds = posts.map(post => Number(post.id))
|
|
238
|
+
const categoryRelations = postIds.length > 0
|
|
239
|
+
? await db
|
|
240
|
+
.selectFrom('categorizable_models')
|
|
241
|
+
.whereIn('categorizable_id', postIds)
|
|
242
|
+
.where('categorizable_type', '=', 'posts')
|
|
243
|
+
.select(['categorizable_id as postId', 'category_id as categoryId'])
|
|
244
|
+
.execute() as unknown as ContentOverviewRelationRow[]
|
|
245
|
+
: []
|
|
246
|
+
|
|
247
|
+
return buildContentOverview({
|
|
248
|
+
posts,
|
|
249
|
+
pages,
|
|
250
|
+
comments,
|
|
251
|
+
categories,
|
|
252
|
+
categoryRelations,
|
|
253
|
+
authors,
|
|
254
|
+
days,
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
return dashboardOperationalError(error, 'Content overview could not be loaded.', 'ContentDashboardAction')
|
|
259
|
+
}
|
|
254
260
|
},
|
|
255
261
|
})
|
|
@@ -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
|
/** `DELETE /api/dashboard/pages/{id}` — deletes a CMS page from the dashboard. */
|
|
@@ -15,11 +17,22 @@ export default new Action({
|
|
|
15
17
|
if (!id)
|
|
16
18
|
return response.json({ message: 'A valid page id is required.' }, 422)
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
try {
|
|
21
|
+
const deleted = await transaction(async (rawTrx) => {
|
|
22
|
+
const trx = rawTrx as unknown as typeof db
|
|
23
|
+
if (!await rowExists('pages', id, trx))
|
|
24
|
+
return false
|
|
25
|
+
await trx.deleteFrom('pages').where('id', '=', id).execute()
|
|
26
|
+
return true
|
|
27
|
+
})
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
if (!deleted)
|
|
30
|
+
return response.json({ message: 'Page not found.' }, 404)
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
return response.json({ message: 'Page deleted.', id })
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return dashboardOperationalError(error, 'Page could not be deleted.', 'PageDestroyAction', 500)
|
|
36
|
+
}
|
|
24
37
|
},
|
|
25
38
|
})
|
|
@@ -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 PageRow {
|
|
5
6
|
id: number
|
|
@@ -30,24 +31,29 @@ export default new Action({
|
|
|
30
31
|
method: 'GET',
|
|
31
32
|
apiResponse: true,
|
|
32
33
|
async handle() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
try {
|
|
35
|
+
const rows = await db
|
|
36
|
+
.selectFrom('pages')
|
|
37
|
+
.selectAll()
|
|
38
|
+
.orderBy('created_at', 'desc')
|
|
39
|
+
.execute() as unknown as PageRow[]
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
const pages = rows.map(row => ({
|
|
42
|
+
id: Number(row.id),
|
|
43
|
+
title: String(row.title || ''),
|
|
44
|
+
template: String(row.template || 'default'),
|
|
45
|
+
views: Number(row.views || 0),
|
|
46
|
+
conversions: Number(row.conversions || 0),
|
|
47
|
+
published_at: row.published_at || null,
|
|
48
|
+
author_id: row.author_id ?? null,
|
|
49
|
+
created_at: row.created_at || null,
|
|
50
|
+
updated_at: row.updated_at || null,
|
|
51
|
+
}))
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
return { pages }
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return dashboardOperationalError(error, 'Pages could not be loaded.', 'PageIndexAction')
|
|
57
|
+
}
|
|
52
58
|
},
|
|
53
59
|
})
|
|
@@ -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, timestamp } from './content-input'
|
|
7
9
|
import { parsePageInput, parsePublished } from './page-input'
|
|
8
10
|
|
|
@@ -22,27 +24,40 @@ export default new Action({
|
|
|
22
24
|
if ('message' in input)
|
|
23
25
|
return response.json({ message: input.message }, 422)
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
try {
|
|
28
|
+
const page = await transaction(async (rawTrx) => {
|
|
29
|
+
const trx = rawTrx as unknown as typeof db
|
|
30
|
+
const now = timestamp()
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const result = await trx
|
|
33
|
+
.insertInto('pages')
|
|
34
|
+
.values({
|
|
35
|
+
uuid: randomUUIDv7(),
|
|
36
|
+
title: input.data.title,
|
|
37
|
+
template: input.data.template,
|
|
38
|
+
views: 0,
|
|
39
|
+
conversions: 0,
|
|
40
|
+
published_at: parsePublished(request.get('published')) ? now : null,
|
|
41
|
+
created_at: now,
|
|
42
|
+
updated_at: now,
|
|
43
|
+
} as any)
|
|
44
|
+
.executeTakeFirst()
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
const id = insertedId(result)
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
if (!id)
|
|
49
|
+
throw new Error('Page insert did not return an id.')
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
const created = await findRow('pages', id, trx)
|
|
52
|
+
if (!created)
|
|
53
|
+
throw new Error('Created page could not be loaded.')
|
|
54
|
+
return created
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return response.json(page, 201)
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return dashboardOperationalError(error, 'Page could not be created.', 'PageStoreAction', 500)
|
|
61
|
+
}
|
|
47
62
|
},
|
|
48
63
|
})
|
|
@@ -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 { findRow, rowExists, rowId, timestamp } from './content-input'
|
|
6
8
|
import { parsePageInput, parsePublished } from './page-input'
|
|
7
9
|
|
|
@@ -15,32 +17,48 @@ export default new Action({
|
|
|
15
17
|
if (!id)
|
|
16
18
|
return response.json({ message: 'A valid page id is required.' }, 422)
|
|
17
19
|
|
|
18
|
-
if (!await rowExists('pages', id))
|
|
19
|
-
return response.json({ message: 'Page not found.' }, 404)
|
|
20
|
-
|
|
21
20
|
const input = parsePageInput(request)
|
|
22
21
|
|
|
23
22
|
if ('message' in input)
|
|
24
23
|
return response.json({ message: input.message }, 422)
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
25
|
+
try {
|
|
26
|
+
const page = await transaction(async (rawTrx) => {
|
|
27
|
+
const trx = rawTrx as unknown as typeof db
|
|
28
|
+
if (!await rowExists('pages', id, trx))
|
|
29
|
+
return null
|
|
30
|
+
|
|
31
|
+
const current = await trx
|
|
32
|
+
.selectFrom('pages')
|
|
33
|
+
.select(['published_at'])
|
|
34
|
+
.where('id', '=', id)
|
|
35
|
+
.executeTakeFirst()
|
|
36
|
+
const published = parsePublished(request.get('published'))
|
|
37
|
+
|
|
38
|
+
await trx
|
|
39
|
+
.updateTable('pages')
|
|
40
|
+
.set({
|
|
41
|
+
title: input.data.title,
|
|
42
|
+
template: input.data.template,
|
|
43
|
+
published_at: published ? current?.published_at || timestamp() : null,
|
|
44
|
+
updated_at: timestamp(),
|
|
45
|
+
} as any)
|
|
46
|
+
.where('id', '=', id)
|
|
47
|
+
.execute()
|
|
48
|
+
|
|
49
|
+
const updated = await findRow('pages', id, trx)
|
|
50
|
+
if (!updated)
|
|
51
|
+
throw new Error('Updated page could not be loaded.')
|
|
52
|
+
return updated
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (!page)
|
|
56
|
+
return response.json({ message: 'Page not found.' }, 404)
|
|
57
|
+
|
|
58
|
+
return response.json(page)
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return dashboardOperationalError(error, 'Page could not be updated.', 'PageUpdateAction', 500)
|
|
62
|
+
}
|
|
45
63
|
},
|
|
46
64
|
})
|
|
@@ -1,12 +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'
|
|
7
|
+
import { detachPostRelations } from './post-input'
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* `DELETE /api/dashboard/posts/{id}` — deletes a CMS post from the dashboard.
|
|
7
11
|
*
|
|
8
|
-
* Detaches the
|
|
9
|
-
*
|
|
12
|
+
* Detaches the model-declared category and tag pivots before removing the post,
|
|
13
|
+
* with all three writes committed by the same transaction.
|
|
10
14
|
*/
|
|
11
15
|
export default new Action({
|
|
12
16
|
name: 'PostDestroyAction',
|
|
@@ -18,17 +22,26 @@ export default new Action({
|
|
|
18
22
|
if (!Number.isInteger(id) || id <= 0)
|
|
19
23
|
return response.json({ message: 'A valid post id is required.' }, 422)
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
try {
|
|
26
|
+
const deleted = await transaction(async (rawTrx) => {
|
|
27
|
+
const trx = rawTrx as unknown as typeof db
|
|
28
|
+
const post = await trx.selectFrom('posts').select(['id']).where('id', '=', id).executeTakeFirst()
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
if (!post)
|
|
31
|
+
return false
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
await post.delete()
|
|
33
|
+
await detachPostRelations(trx, id)
|
|
34
|
+
await trx.deleteFrom('posts').where('id', '=', id).execute()
|
|
35
|
+
return true
|
|
36
|
+
})
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
if (!deleted)
|
|
39
|
+
return response.json({ message: 'Post not found.' }, 404)
|
|
40
|
+
|
|
41
|
+
return response.json({ message: 'Post deleted.', id })
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return dashboardOperationalError(error, 'Post could not be deleted.', 'PostDestroyAction', 500)
|
|
45
|
+
}
|
|
33
46
|
},
|
|
34
47
|
})
|
|
@@ -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 PostRow {
|
|
5
6
|
id: number
|
|
@@ -55,8 +56,8 @@ function counts(posts: Array<{ status: string }>) {
|
|
|
55
56
|
* `GET /api/dashboard/posts` — backs `views/dashboard/content/posts/index.stx`.
|
|
56
57
|
*
|
|
57
58
|
* Uses one bounded query per persisted dataset and groups pivot ids in maps,
|
|
58
|
-
* avoiding relation N+1 queries
|
|
59
|
-
*
|
|
59
|
+
* avoiding relation N+1 queries. Writes use the same model-declared pivots on
|
|
60
|
+
* one native database transaction.
|
|
60
61
|
*
|
|
61
62
|
* There is deliberately no mock-data fallback. This action used to catch every
|
|
62
63
|
* error and serve placeholder rows, which made a page that was 404ing against
|
|
@@ -69,56 +70,61 @@ export default new Action({
|
|
|
69
70
|
method: 'GET',
|
|
70
71
|
apiResponse: true,
|
|
71
72
|
async handle() {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
try {
|
|
74
|
+
const rows = await db
|
|
75
|
+
.selectFrom('posts')
|
|
76
|
+
.selectAll()
|
|
77
|
+
.orderBy('created_at', 'desc')
|
|
78
|
+
.execute() as unknown as PostRow[]
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
const posts = rows.map(row => ({
|
|
81
|
+
id: Number(row.id),
|
|
82
|
+
title: String(row.title || ''),
|
|
83
|
+
excerpt: String(row.excerpt || ''),
|
|
84
|
+
content: String(row.content || ''),
|
|
85
|
+
poster: String(row.poster || ''),
|
|
86
|
+
status: normalizeStatus(row.status),
|
|
87
|
+
views: Number(row.views || 0),
|
|
88
|
+
published_at: row.published_at || null,
|
|
89
|
+
created_at: row.created_at || null,
|
|
90
|
+
updated_at: row.updated_at || null,
|
|
91
|
+
author_id: row.author_id ?? null,
|
|
92
|
+
featured: Boolean(row.is_featured),
|
|
93
|
+
}))
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
const postIds = posts.map(post => post.id)
|
|
96
|
+
const [categories, tags, authors, categoryRelations, tagRelations] = await Promise.all([
|
|
97
|
+
db.selectFrom('categories').select(['id', 'name', 'slug']).orderBy('name').execute(),
|
|
98
|
+
db.selectFrom('tags').select(['id', 'name', 'slug']).orderBy('name').execute(),
|
|
99
|
+
db.selectFrom('authors').select(['id', 'name', 'email']).orderBy('name').execute(),
|
|
100
|
+
postIds.length
|
|
101
|
+
? db.selectFrom('categorizable_models')
|
|
102
|
+
.whereIn('categorizable_id', postIds)
|
|
103
|
+
.where('categorizable_type', '=', 'posts')
|
|
104
|
+
.select(['category_id as relatedId', 'categorizable_id as postId'])
|
|
105
|
+
.execute() as unknown as Promise<PivotRow[]>
|
|
106
|
+
: Promise.resolve([]),
|
|
107
|
+
postIds.length
|
|
108
|
+
? db.selectFrom('taggable_models')
|
|
109
|
+
.whereIn('taggable_id', postIds)
|
|
110
|
+
.where('taggable_type', '=', 'posts')
|
|
111
|
+
.select(['tag_id as relatedId', 'taggable_id as postId'])
|
|
112
|
+
.execute() as unknown as Promise<PivotRow[]>
|
|
113
|
+
: Promise.resolve([]),
|
|
114
|
+
])
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
const categoryIdsByPost = relatedIdsByPost(categoryRelations)
|
|
117
|
+
const tagIdsByPost = relatedIdsByPost(tagRelations)
|
|
118
|
+
const withRelations = posts.map(post => ({
|
|
119
|
+
...post,
|
|
120
|
+
categoryIds: categoryIdsByPost.get(post.id) || [],
|
|
121
|
+
tagIds: tagIdsByPost.get(post.id) || [],
|
|
122
|
+
}))
|
|
121
123
|
|
|
122
|
-
|
|
124
|
+
return { posts: withRelations, categories, tags, authors, ...counts(posts) }
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return dashboardOperationalError(error, 'Posts could not be loaded.', 'PostIndexAction')
|
|
128
|
+
}
|
|
123
129
|
},
|
|
124
130
|
})
|