@stacksjs/cms 0.70.53 → 0.70.55
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/package.json +3 -2
- package/src/authors/destroy.ts +55 -0
- package/src/authors/fetch.ts +70 -0
- package/src/authors/index.ts +21 -0
- package/src/authors/store.ts +150 -0
- package/src/authors/update.ts +42 -0
- package/src/build.ts +1382 -0
- package/src/categorizables/destroy.ts +64 -0
- package/src/categorizables/fetch.ts +121 -0
- package/src/categorizables/index.ts +22 -0
- package/src/categorizables/store.ts +140 -0
- package/src/categorizables/update.ts +57 -0
- package/src/commentables/destroy.ts +64 -0
- package/src/commentables/fetch.ts +319 -0
- package/src/commentables/index.ts +25 -0
- package/src/commentables/store.ts +156 -0
- package/src/commentables/update.ts +75 -0
- package/src/database.ts +9 -0
- package/src/index.ts +42 -0
- package/src/pages/destroy.ts +64 -0
- package/src/pages/fetch.ts +82 -0
- package/src/pages/index.ts +21 -0
- package/src/pages/store.ts +42 -0
- package/src/pages/update.ts +39 -0
- package/src/posts/destroy.ts +90 -0
- package/src/posts/fetch.ts +82 -0
- package/src/posts/index.ts +25 -0
- package/src/posts/store.ts +213 -0
- package/src/posts/update.ts +46 -0
- package/src/taggables/destroy.ts +49 -0
- package/src/taggables/fetch.ts +282 -0
- package/src/taggables/index.ts +17 -0
- package/src/taggables/store.ts +122 -0
- package/src/taggables/update.ts +74 -0
- package/src/tests/categorizables.test.ts +66 -0
- package/src/tests/setup.ts +120 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/cms",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.55",
|
|
5
5
|
"description": "Stacks cms utilities.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"types": "dist/index.d.ts",
|
|
41
41
|
"files": [
|
|
42
42
|
"README.md",
|
|
43
|
-
"dist"
|
|
43
|
+
"dist",
|
|
44
|
+
"src"
|
|
44
45
|
],
|
|
45
46
|
"scripts": {
|
|
46
47
|
"build": "bun build.ts",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
type AuthorJsonResponse = ModelRow<typeof Author>
|
|
2
|
+
import { getDb } from '../database'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Delete an author by ID
|
|
6
|
+
*
|
|
7
|
+
* @param id The ID of the author to delete
|
|
8
|
+
* @returns The deleted author record
|
|
9
|
+
*/
|
|
10
|
+
export async function destroy(id: number): Promise<AuthorJsonResponse> {
|
|
11
|
+
const db = await getDb()
|
|
12
|
+
try {
|
|
13
|
+
const result = await db
|
|
14
|
+
.deleteFrom('authors')
|
|
15
|
+
.where('id', '=', id)
|
|
16
|
+
.returningAll()
|
|
17
|
+
.executeTakeFirst()
|
|
18
|
+
|
|
19
|
+
if (!result) {
|
|
20
|
+
throw new Error(`Author with ID ${id} not found`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result as AuthorJsonResponse
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof Error)
|
|
27
|
+
throw new TypeError(`Author with ID ${id} not found`)
|
|
28
|
+
|
|
29
|
+
throw error
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Delete multiple authors by IDs
|
|
35
|
+
*
|
|
36
|
+
* @param ids Array of author IDs to delete
|
|
37
|
+
* @returns The number of deleted authors
|
|
38
|
+
*/
|
|
39
|
+
export async function destroyMany(ids: number[]): Promise<number> {
|
|
40
|
+
const db = await getDb()
|
|
41
|
+
try {
|
|
42
|
+
const result = await db
|
|
43
|
+
.deleteFrom('authors')
|
|
44
|
+
.where('id', 'in', ids)
|
|
45
|
+
.execute()
|
|
46
|
+
|
|
47
|
+
return Number(result) || 0
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof Error)
|
|
51
|
+
throw new TypeError(`Failed to delete authors: ${error.message}`)
|
|
52
|
+
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
type AuthorJsonResponse = ModelRow<typeof Author>
|
|
2
|
+
import { getDb } from '../database'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fetch an author by ID
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchById(id: number): Promise<AuthorJsonResponse | undefined> {
|
|
8
|
+
const db = await getDb()
|
|
9
|
+
return await db
|
|
10
|
+
.selectFrom('authors')
|
|
11
|
+
.where('id', '=', id)
|
|
12
|
+
.selectAll()
|
|
13
|
+
.executeTakeFirst() as AuthorJsonResponse | undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fetch all authors
|
|
18
|
+
*/
|
|
19
|
+
export async function fetchAll(): Promise<AuthorJsonResponse[]> {
|
|
20
|
+
const db = await getDb()
|
|
21
|
+
return await db.selectFrom('authors').selectAll().execute() as AuthorJsonResponse[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fetch authors by name
|
|
26
|
+
*/
|
|
27
|
+
export async function findByName(name: string): Promise<AuthorJsonResponse | undefined> {
|
|
28
|
+
const db = await getDb()
|
|
29
|
+
return await db
|
|
30
|
+
.selectFrom('authors')
|
|
31
|
+
.where('name', '=', name)
|
|
32
|
+
.selectAll()
|
|
33
|
+
.executeTakeFirst() as AuthorJsonResponse | undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Fetch authors by email
|
|
38
|
+
*/
|
|
39
|
+
export async function findByEmail(email: string): Promise<AuthorJsonResponse | undefined> {
|
|
40
|
+
const db = await getDb()
|
|
41
|
+
return await db
|
|
42
|
+
.selectFrom('authors')
|
|
43
|
+
.where('email', '=', email)
|
|
44
|
+
.selectAll()
|
|
45
|
+
.executeTakeFirst() as AuthorJsonResponse | undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fetch author by UUID
|
|
50
|
+
*/
|
|
51
|
+
export async function findByUuid(uuid: string): Promise<AuthorJsonResponse | undefined> {
|
|
52
|
+
const db = await getDb()
|
|
53
|
+
return await db
|
|
54
|
+
.selectFrom('authors')
|
|
55
|
+
.where('uuid', '=', uuid)
|
|
56
|
+
.selectAll()
|
|
57
|
+
.executeTakeFirst() as AuthorJsonResponse | undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fetch authors by user ID
|
|
62
|
+
*/
|
|
63
|
+
export async function findByUserId(userId: number): Promise<AuthorJsonResponse | undefined> {
|
|
64
|
+
const db = await getDb()
|
|
65
|
+
return await db
|
|
66
|
+
.selectFrom('authors')
|
|
67
|
+
.where('user_id', '=', userId)
|
|
68
|
+
.selectAll()
|
|
69
|
+
.executeTakeFirst() as AuthorJsonResponse | undefined
|
|
70
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export {
|
|
2
|
+
destroy,
|
|
3
|
+
} from './destroy'
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
fetchAll,
|
|
7
|
+
fetchById,
|
|
8
|
+
findByEmail,
|
|
9
|
+
findByName,
|
|
10
|
+
findByUserId,
|
|
11
|
+
findByUuid,
|
|
12
|
+
} from './fetch'
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
findOrCreate,
|
|
16
|
+
store,
|
|
17
|
+
} from './store'
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
update,
|
|
21
|
+
} from './update'
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
type AuthorJsonResponse = ModelRow<typeof Author>
|
|
2
|
+
type NewAuthor = NewModelData<typeof Author>
|
|
3
|
+
import { randomUUIDv7 } from 'bun'
|
|
4
|
+
import { getDb } from '../database'
|
|
5
|
+
import { HttpError } from '@stacksjs/error-handling'
|
|
6
|
+
import { formatDate, isUniqueViolation } from '@stacksjs/orm'
|
|
7
|
+
|
|
8
|
+
interface AuthorData {
|
|
9
|
+
name: string
|
|
10
|
+
email: string
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Find an existing author by name or email, or create a new one if not found
|
|
14
|
+
*
|
|
15
|
+
* @param data The author data to find or create
|
|
16
|
+
* @returns The found or created author record
|
|
17
|
+
*/
|
|
18
|
+
export async function findOrCreate(data: AuthorData): Promise<AuthorJsonResponse> {
|
|
19
|
+
const db = await getDb()
|
|
20
|
+
try {
|
|
21
|
+
// First, try to find an existing author by email or name
|
|
22
|
+
const existingAuthor = await db
|
|
23
|
+
.selectFrom('authors')
|
|
24
|
+
.where((eb: any) => eb.or([
|
|
25
|
+
eb('email', '=', data.email),
|
|
26
|
+
eb('name', '=', data.name),
|
|
27
|
+
]))
|
|
28
|
+
.selectAll()
|
|
29
|
+
.executeTakeFirst()
|
|
30
|
+
|
|
31
|
+
// If author exists, return it
|
|
32
|
+
if (existingAuthor)
|
|
33
|
+
return existingAuthor as AuthorJsonResponse
|
|
34
|
+
|
|
35
|
+
// Look up or create the associated user
|
|
36
|
+
let user = await db
|
|
37
|
+
.selectFrom('users')
|
|
38
|
+
.where('email', '=', data.email)
|
|
39
|
+
.selectAll()
|
|
40
|
+
.executeTakeFirst()
|
|
41
|
+
|
|
42
|
+
if (!user) {
|
|
43
|
+
// Create a new user if one doesn't exist
|
|
44
|
+
const result = await db
|
|
45
|
+
.insertInto('users')
|
|
46
|
+
.values({
|
|
47
|
+
email: data.email,
|
|
48
|
+
name: data.name,
|
|
49
|
+
password: randomUUIDv7(),
|
|
50
|
+
uuid: randomUUIDv7(),
|
|
51
|
+
created_at: formatDate(new Date()),
|
|
52
|
+
updated_at: formatDate(new Date()),
|
|
53
|
+
})
|
|
54
|
+
.returningAll()
|
|
55
|
+
.executeTakeFirst()
|
|
56
|
+
|
|
57
|
+
if (!result)
|
|
58
|
+
throw new Error('Failed to create user')
|
|
59
|
+
|
|
60
|
+
user = result
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Create a new author with the user_id
|
|
64
|
+
const authorData = {
|
|
65
|
+
user_id: (user as Record<string, unknown>).id as number,
|
|
66
|
+
name: data.name,
|
|
67
|
+
email: data.email,
|
|
68
|
+
created_at: formatDate(new Date()),
|
|
69
|
+
updated_at: formatDate(new Date()),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await db
|
|
73
|
+
.insertInto('authors')
|
|
74
|
+
.values(authorData)
|
|
75
|
+
.returningAll()
|
|
76
|
+
.executeTakeFirst()
|
|
77
|
+
|
|
78
|
+
if (!result)
|
|
79
|
+
throw new Error('Failed to create author')
|
|
80
|
+
|
|
81
|
+
return result as AuthorJsonResponse
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof HttpError)
|
|
85
|
+
throw error
|
|
86
|
+
// A concurrent insert can lose the find-then-insert race on the
|
|
87
|
+
// authors.email / users.email unique index (#1957).
|
|
88
|
+
if (isUniqueViolation(error))
|
|
89
|
+
throw new HttpError(409, 'An author with this email already exists')
|
|
90
|
+
if (error instanceof Error)
|
|
91
|
+
throw new TypeError(`Failed to find or create author: ${error.message}`)
|
|
92
|
+
|
|
93
|
+
throw error
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Find or create an author by name or email
|
|
99
|
+
*
|
|
100
|
+
* @param data The author data to find or create
|
|
101
|
+
* @returns The found or created author record
|
|
102
|
+
*/
|
|
103
|
+
export async function store(data: NewAuthor): Promise<AuthorJsonResponse> {
|
|
104
|
+
const db = await getDb()
|
|
105
|
+
try {
|
|
106
|
+
// First, try to find an existing author by email or name
|
|
107
|
+
const existingAuthor = await db
|
|
108
|
+
.selectFrom('authors')
|
|
109
|
+
.where((eb: any) => eb.or([
|
|
110
|
+
eb('email', '=', data.email),
|
|
111
|
+
eb('name', '=', data.name),
|
|
112
|
+
]))
|
|
113
|
+
.selectAll()
|
|
114
|
+
.executeTakeFirst()
|
|
115
|
+
|
|
116
|
+
// If author exists, return it
|
|
117
|
+
if (existingAuthor)
|
|
118
|
+
return existingAuthor as AuthorJsonResponse
|
|
119
|
+
|
|
120
|
+
// If no existing author, create a new one
|
|
121
|
+
const authorData = {
|
|
122
|
+
user_id: data.user_id,
|
|
123
|
+
name: data.name,
|
|
124
|
+
email: data.email,
|
|
125
|
+
created_at: formatDate(new Date()),
|
|
126
|
+
updated_at: formatDate(new Date()),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const result = await db
|
|
130
|
+
.insertInto('authors')
|
|
131
|
+
.values(authorData)
|
|
132
|
+
.returningAll()
|
|
133
|
+
.executeTakeFirst()
|
|
134
|
+
|
|
135
|
+
if (!result)
|
|
136
|
+
throw new Error('Failed to create author')
|
|
137
|
+
|
|
138
|
+
return result as AuthorJsonResponse
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (error instanceof HttpError)
|
|
142
|
+
throw error
|
|
143
|
+
if (isUniqueViolation(error))
|
|
144
|
+
throw new HttpError(409, 'An author with this email already exists')
|
|
145
|
+
if (error instanceof Error)
|
|
146
|
+
throw new TypeError(`Failed to find or create author: ${error.message}`)
|
|
147
|
+
|
|
148
|
+
throw error
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type AuthorJsonResponse = ModelRow<typeof Author>
|
|
2
|
+
type NewAuthor = NewModelData<typeof Author>
|
|
3
|
+
import { getDb } from '../database'
|
|
4
|
+
import { HttpError } from '@stacksjs/error-handling'
|
|
5
|
+
import { formatDate, isUniqueViolation } from '@stacksjs/orm'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Update an existing author
|
|
9
|
+
*
|
|
10
|
+
* @param id The ID of the author to update
|
|
11
|
+
* @param data The author data to update
|
|
12
|
+
* @returns The updated author record
|
|
13
|
+
*/
|
|
14
|
+
export async function update(id: number, data: Partial<NewAuthor>): Promise<AuthorJsonResponse | undefined> {
|
|
15
|
+
const db = await getDb()
|
|
16
|
+
try {
|
|
17
|
+
const updateData = {
|
|
18
|
+
...data,
|
|
19
|
+
updated_at: formatDate(new Date()),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await db
|
|
23
|
+
.updateTable('authors')
|
|
24
|
+
.set(updateData)
|
|
25
|
+
.where('id', '=', id)
|
|
26
|
+
.returningAll()
|
|
27
|
+
.executeTakeFirst()
|
|
28
|
+
|
|
29
|
+
return result as AuthorJsonResponse | undefined
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof HttpError)
|
|
33
|
+
throw error
|
|
34
|
+
// Duplicate authors.email on update (#1957).
|
|
35
|
+
if (isUniqueViolation(error))
|
|
36
|
+
throw new HttpError(409, 'An author with this email already exists')
|
|
37
|
+
if (error instanceof Error)
|
|
38
|
+
throw new TypeError(`Failed to update author: ${error.message}`)
|
|
39
|
+
|
|
40
|
+
throw error
|
|
41
|
+
}
|
|
42
|
+
}
|