@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,9 +1,11 @@
|
|
|
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'
|
|
6
|
-
import {
|
|
7
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
8
|
+
import { findPost, insertedId, invalidPostContent, invalidPostReference, postPayload, publishedAtFor, syncPostRelations, timestamp } from './post-input'
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* `POST /api/dashboard/posts` — creates a CMS post from the dashboard.
|
|
@@ -23,45 +25,53 @@ export default new Action({
|
|
|
23
25
|
if (invalidContent)
|
|
24
26
|
return response.json({ message: invalidContent }, 422)
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
try {
|
|
29
|
+
const result = await transaction(async (rawTrx) => {
|
|
30
|
+
const trx = rawTrx as unknown as typeof db
|
|
31
|
+
const invalidReference = await invalidPostReference(payload, trx)
|
|
32
|
+
if (invalidReference)
|
|
33
|
+
return { kind: 'invalid', message: invalidReference } as const
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
const now = timestamp()
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
const insertResult = await trx
|
|
38
|
+
.insertInto('posts')
|
|
39
|
+
.values({
|
|
40
|
+
uuid: randomUUIDv7(),
|
|
41
|
+
title: payload.title,
|
|
42
|
+
excerpt: payload.excerpt,
|
|
43
|
+
content: payload.content,
|
|
44
|
+
poster: payload.poster,
|
|
45
|
+
status: payload.status,
|
|
46
|
+
author_id: payload.authorId,
|
|
47
|
+
is_featured: payload.featured ? 1 : 0,
|
|
48
|
+
views: 0,
|
|
49
|
+
published_at: publishedAtFor(payload.status, null, now),
|
|
50
|
+
created_at: now,
|
|
51
|
+
updated_at: now,
|
|
52
|
+
} as any)
|
|
53
|
+
.executeTakeFirst()
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
const id = insertedId(insertResult)
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
if (!id)
|
|
58
|
+
throw new Error('Post insert did not return an id.')
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
await syncPostRelations(trx, id, payload)
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
const created = await findPost(id, trx)
|
|
63
|
+
if (!created)
|
|
64
|
+
throw new Error('Created post could not be read back.')
|
|
65
|
+
return { kind: 'created', post: created } as const
|
|
66
|
+
})
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
post.tags().sync(payload.tagIds),
|
|
63
|
-
])
|
|
68
|
+
if (result.kind === 'invalid')
|
|
69
|
+
return response.json({ message: result.message }, 422)
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
return response.json(result.post, 201)
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return dashboardOperationalError(error, 'Post could not be created.', 'PostStoreAction', 500)
|
|
75
|
+
}
|
|
66
76
|
},
|
|
67
77
|
})
|
|
@@ -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
|
-
import {
|
|
6
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
7
|
+
import { findPost, invalidPostContent, invalidPostReference, postPayload, publishedAtFor, syncPostRelations, timestamp } from './post-input'
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* `PATCH /api/dashboard/posts/{id}` — updates a CMS post from the dashboard.
|
|
@@ -27,45 +29,55 @@ export default new Action({
|
|
|
27
29
|
if (invalidContent)
|
|
28
30
|
return response.json({ message: invalidContent }, 422)
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
try {
|
|
33
|
+
const result = await transaction(async (rawTrx) => {
|
|
34
|
+
const trx = rawTrx as unknown as typeof db
|
|
35
|
+
const invalidReference = await invalidPostReference(payload, trx)
|
|
36
|
+
if (invalidReference)
|
|
37
|
+
return { kind: 'invalid', message: invalidReference } as const
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
const existing = await trx
|
|
40
|
+
.selectFrom('posts')
|
|
41
|
+
.select(['id', 'published_at'])
|
|
42
|
+
.where('id', '=', id)
|
|
43
|
+
.executeTakeFirst() as { id: number, published_at: string | null } | undefined
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
if (!existing)
|
|
46
|
+
return { kind: 'not-found' } as const
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
await trx
|
|
49
|
+
.updateTable('posts')
|
|
50
|
+
.set({
|
|
51
|
+
title: payload.title,
|
|
52
|
+
excerpt: payload.excerpt,
|
|
53
|
+
content: payload.content,
|
|
54
|
+
poster: payload.poster,
|
|
55
|
+
status: payload.status,
|
|
56
|
+
author_id: payload.authorId,
|
|
57
|
+
is_featured: payload.featured ? 1 : 0,
|
|
58
|
+
published_at: publishedAtFor(payload.status, existing.published_at, timestamp()),
|
|
59
|
+
updated_at: timestamp(),
|
|
60
|
+
} as any)
|
|
61
|
+
.where('id', '=', id)
|
|
62
|
+
.execute()
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
await syncPostRelations(trx, id, payload)
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
const updated = await findPost(id, trx)
|
|
67
|
+
if (!updated)
|
|
68
|
+
throw new Error('Updated post could not be read back.')
|
|
69
|
+
return { kind: 'updated', post: updated } as const
|
|
70
|
+
})
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
if (result.kind === 'invalid')
|
|
73
|
+
return response.json({ message: result.message }, 422)
|
|
74
|
+
if (result.kind === 'not-found')
|
|
75
|
+
return response.json({ message: 'Post not found.' }, 404)
|
|
68
76
|
|
|
69
|
-
|
|
77
|
+
return response.json(result.post)
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return dashboardOperationalError(error, 'Post could not be updated.', 'PostUpdateAction', 500)
|
|
81
|
+
}
|
|
70
82
|
},
|
|
71
83
|
})
|
|
@@ -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/tags/{id}` — deletes a CMS tag.
|
|
8
11
|
*
|
|
9
|
-
* Detaches related posts through the model
|
|
10
|
-
*
|
|
12
|
+
* Detaches related posts through the model-declared pivot before removing the
|
|
13
|
+
* tag, with both writes committed by the same transaction.
|
|
11
14
|
*/
|
|
12
15
|
export default new Action({
|
|
13
16
|
name: 'TagDestroyAction',
|
|
@@ -19,13 +22,29 @@ export default new Action({
|
|
|
19
22
|
if (!id)
|
|
20
23
|
return response.json({ message: 'A valid tag id is required.' }, 422)
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
try {
|
|
26
|
+
const deleted = await transaction(async (rawTrx) => {
|
|
27
|
+
const trx = rawTrx as unknown as typeof db
|
|
28
|
+
const tag = await trx.selectFrom('tags').select(['id']).where('id', '=', id).executeTakeFirst()
|
|
29
|
+
if (!tag)
|
|
30
|
+
return false
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
await trx
|
|
33
|
+
.deleteFrom('taggable_models')
|
|
34
|
+
.where('tag_id', '=', id)
|
|
35
|
+
.where('taggable_type', '=', 'posts')
|
|
36
|
+
.execute()
|
|
37
|
+
await trx.deleteFrom('tags').where('id', '=', id).execute()
|
|
38
|
+
return true
|
|
39
|
+
})
|
|
28
40
|
|
|
29
|
-
|
|
41
|
+
if (!deleted)
|
|
42
|
+
return response.json({ message: 'Tag not found.' }, 404)
|
|
43
|
+
|
|
44
|
+
return response.json({ message: 'Tag deleted.', id })
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return dashboardOperationalError(error, 'Tag could not be deleted.', 'TagDestroyAction', 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 TagRow {
|
|
5
6
|
id: number
|
|
@@ -28,32 +29,37 @@ export default new Action({
|
|
|
28
29
|
method: 'GET',
|
|
29
30
|
apiResponse: true,
|
|
30
31
|
async handle() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
try {
|
|
33
|
+
const [rows, countRows] = await Promise.all([
|
|
34
|
+
db
|
|
35
|
+
.selectFrom('tags')
|
|
36
|
+
.select(['id', 'name', 'slug', 'description', 'color', 'created_at', 'updated_at'])
|
|
37
|
+
.orderBy('created_at', 'desc')
|
|
38
|
+
.execute() as unknown as Promise<TagRow[]>,
|
|
39
|
+
db
|
|
40
|
+
.selectFrom('taggable_models')
|
|
41
|
+
.select(['tag_id', db.fn.count('taggable_id').as('count')])
|
|
42
|
+
.where('taggable_type', '=', 'posts')
|
|
43
|
+
.groupBy('tag_id')
|
|
44
|
+
.execute() as unknown as Promise<TagCountRow[]>,
|
|
45
|
+
])
|
|
46
|
+
const countByTag = new Map(countRows.map(row => [Number(row.tag_id), Number(row.count)]))
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
const tags = rows.map(row => ({
|
|
49
|
+
id: Number(row.id),
|
|
50
|
+
name: String(row.name || ''),
|
|
51
|
+
slug: String(row.slug || ''),
|
|
52
|
+
description: String(row.description || ''),
|
|
53
|
+
color: String(row.color || ''),
|
|
54
|
+
post_count: countByTag.get(Number(row.id)) || 0,
|
|
55
|
+
created_at: row.created_at || null,
|
|
56
|
+
updated_at: row.updated_at || null,
|
|
57
|
+
}))
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
return { tags }
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return dashboardOperationalError(error, 'Tags could not be loaded.', 'TagIndexAction')
|
|
63
|
+
}
|
|
58
64
|
},
|
|
59
65
|
})
|
|
@@ -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 { findRow, insertedId, slugify, str, timestamp } from './content-input'
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -26,34 +28,53 @@ 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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
try {
|
|
32
|
+
const tag = await transaction(async (rawTrx) => {
|
|
33
|
+
const trx = rawTrx as unknown as typeof db
|
|
34
|
+
const duplicate = await trx
|
|
35
|
+
.selectFrom('tags')
|
|
36
|
+
.select(['id'])
|
|
37
|
+
.where('slug', '=', slug)
|
|
38
|
+
.orWhere('name', '=', name)
|
|
39
|
+
.executeTakeFirst()
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
if (duplicate)
|
|
42
|
+
return null
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
const now = timestamp()
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const result = await trx
|
|
47
|
+
.insertInto('tags')
|
|
48
|
+
.values({
|
|
49
|
+
uuid: randomUUIDv7(),
|
|
50
|
+
name,
|
|
51
|
+
slug,
|
|
52
|
+
description,
|
|
53
|
+
created_at: now,
|
|
54
|
+
updated_at: now,
|
|
55
|
+
} as any)
|
|
56
|
+
.executeTakeFirst()
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
const id = insertedId(result)
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
if (!id)
|
|
61
|
+
throw new Error('Tag insert did not return an id.')
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
const created = await findRow('tags', id, trx)
|
|
64
|
+
if (!created)
|
|
65
|
+
throw new Error('Created tag could not be loaded.')
|
|
66
|
+
return created
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (!tag)
|
|
70
|
+
return response.json({ message: 'A tag with that name or slug already exists.' }, 422)
|
|
71
|
+
|
|
72
|
+
return response.json(tag, 201)
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (isUniqueViolation(error))
|
|
76
|
+
return response.json({ message: 'A tag with that name or slug already exists.' }, 422)
|
|
77
|
+
return dashboardOperationalError(error, 'Tag could not be created.', 'TagStoreAction', 500)
|
|
78
|
+
}
|
|
58
79
|
},
|
|
59
80
|
})
|
|
@@ -53,13 +53,13 @@ export function slugify(value: string): string {
|
|
|
53
53
|
* `table` is a literal at every call site; the cast is only here because the
|
|
54
54
|
* query builder's types want a table union rather than a `string`.
|
|
55
55
|
*/
|
|
56
|
-
export async function findRow(table: string, id: number): Promise<unknown> {
|
|
57
|
-
return await (
|
|
56
|
+
export async function findRow(table: string, id: number, database: typeof db = db): Promise<unknown> {
|
|
57
|
+
return await (database as any).selectFrom(table).selectAll().where('id', '=', id).executeTakeFirst()
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** Whether a row with this id exists — the 404 check every write shares. */
|
|
61
|
-
export async function rowExists(table: string, id: number): Promise<boolean> {
|
|
62
|
-
const row = await (
|
|
61
|
+
export async function rowExists(table: string, id: number, database: typeof db = db): Promise<boolean> {
|
|
62
|
+
const row = await (database as any).selectFrom(table).select(['id']).where('id', '=', id).executeTakeFirst()
|
|
63
63
|
|
|
64
64
|
return Boolean(row)
|
|
65
65
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { db } from '@stacksjs/database'
|
|
3
|
-
import { str } from './content-input'
|
|
3
|
+
import { insertedId, str, timestamp } from './content-input'
|
|
4
4
|
|
|
5
|
-
export { insertedId, timestamp }
|
|
5
|
+
export { insertedId, timestamp }
|
|
6
6
|
|
|
7
7
|
export interface PostPayload {
|
|
8
8
|
title: string
|
|
@@ -42,8 +42,8 @@ export function publishedAtFor(status: string, existing: string | null, now: str
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/** Reads a post row back after a write — see `findRow` for why writes re-select. */
|
|
45
|
-
export async function findPost(id: number): Promise<unknown> {
|
|
46
|
-
return await
|
|
45
|
+
export async function findPost(id: number, database: typeof db = db): Promise<unknown> {
|
|
46
|
+
return await database.selectFrom('posts').selectAll().where('id', '=', id).executeTakeFirst()
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function ids(value: unknown): number[] {
|
|
@@ -83,16 +83,16 @@ export function invalidPostContent(payload: PostPayload): string | null {
|
|
|
83
83
|
return null
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
export async function invalidPostReference(payload: PostPayload): Promise<string | null> {
|
|
86
|
+
export async function invalidPostReference(payload: PostPayload, database: typeof db = db): Promise<string | null> {
|
|
87
87
|
const [author, categoryRows, tagRows] = await Promise.all([
|
|
88
88
|
payload.authorId
|
|
89
|
-
?
|
|
89
|
+
? database.selectFrom('authors').select(['id']).where('id', '=', payload.authorId).executeTakeFirst()
|
|
90
90
|
: Promise.resolve(undefined),
|
|
91
91
|
payload.categoryIds.length
|
|
92
|
-
?
|
|
92
|
+
? database.selectFrom('categories').whereIn('id', payload.categoryIds).select(['id']).execute()
|
|
93
93
|
: Promise.resolve([]),
|
|
94
94
|
payload.tagIds.length
|
|
95
|
-
?
|
|
95
|
+
? database.selectFrom('tags').whereIn('id', payload.tagIds).select(['id']).execute()
|
|
96
96
|
: Promise.resolve([]),
|
|
97
97
|
])
|
|
98
98
|
|
|
@@ -106,6 +106,58 @@ export async function invalidPostReference(payload: PostPayload): Promise<string
|
|
|
106
106
|
return null
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
async function replacePostPivot(
|
|
110
|
+
database: typeof db,
|
|
111
|
+
table: 'categorizable_models' | 'taggable_models',
|
|
112
|
+
foreignKey: 'category_id' | 'tag_id',
|
|
113
|
+
ownerKey: 'categorizable_id' | 'taggable_id',
|
|
114
|
+
typeKey: 'categorizable_type' | 'taggable_type',
|
|
115
|
+
postId: number,
|
|
116
|
+
relatedIds: number[],
|
|
117
|
+
now: string,
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
await (database as any)
|
|
120
|
+
.deleteFrom(table)
|
|
121
|
+
.where(ownerKey, '=', postId)
|
|
122
|
+
.where(typeKey, '=', 'posts')
|
|
123
|
+
.execute()
|
|
124
|
+
|
|
125
|
+
if (relatedIds.length === 0)
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
await (database as any)
|
|
129
|
+
.insertInto(table)
|
|
130
|
+
.values(relatedIds.map(relatedId => ({
|
|
131
|
+
[foreignKey]: relatedId,
|
|
132
|
+
[ownerKey]: postId,
|
|
133
|
+
[typeKey]: 'posts',
|
|
134
|
+
created_at: now,
|
|
135
|
+
updated_at: now,
|
|
136
|
+
})))
|
|
137
|
+
.execute()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Replaces both post pivot sets on the caller's database or transaction. */
|
|
141
|
+
export async function syncPostRelations(database: typeof db, postId: number, payload: Pick<PostPayload, 'categoryIds' | 'tagIds'>): Promise<void> {
|
|
142
|
+
const now = timestamp()
|
|
143
|
+
await replacePostPivot(database, 'categorizable_models', 'category_id', 'categorizable_id', 'categorizable_type', postId, payload.categoryIds, now)
|
|
144
|
+
await replacePostPivot(database, 'taggable_models', 'tag_id', 'taggable_id', 'taggable_type', postId, payload.tagIds, now)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Removes both post pivot sets on the caller's database or transaction. */
|
|
148
|
+
export async function detachPostRelations(database: typeof db, postId: number): Promise<void> {
|
|
149
|
+
await (database as any)
|
|
150
|
+
.deleteFrom('categorizable_models')
|
|
151
|
+
.where('categorizable_id', '=', postId)
|
|
152
|
+
.where('categorizable_type', '=', 'posts')
|
|
153
|
+
.execute()
|
|
154
|
+
await (database as any)
|
|
155
|
+
.deleteFrom('taggable_models')
|
|
156
|
+
.where('taggable_id', '=', postId)
|
|
157
|
+
.where('taggable_type', '=', 'posts')
|
|
158
|
+
.execute()
|
|
159
|
+
}
|
|
160
|
+
|
|
109
161
|
/** Maps a dashboard request body onto the writable post columns. */
|
|
110
162
|
export function postPayload(request: RequestInstance): PostPayload {
|
|
111
163
|
return {
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.357",
|
|
6
6
|
"description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"license": "MIT",
|