@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
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { getDb } from '../database'
|
|
2
|
+
import { formatDate } from '@stacksjs/orm'
|
|
3
|
+
|
|
4
|
+
export interface Commentable {
|
|
5
|
+
id?: number
|
|
6
|
+
title: string
|
|
7
|
+
body: string
|
|
8
|
+
status: string
|
|
9
|
+
approved_at: number | null
|
|
10
|
+
rejected_at: number | null
|
|
11
|
+
commentables_id: number
|
|
12
|
+
commentables_type: string
|
|
13
|
+
reports_count?: number
|
|
14
|
+
reported_at?: number | null
|
|
15
|
+
upvotes_count?: number
|
|
16
|
+
downvotes_count?: number
|
|
17
|
+
user_id: number | null
|
|
18
|
+
created_at?: string
|
|
19
|
+
updated_at?: string | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type CommentStatus = 'approved' | 'pending' | 'spam'
|
|
23
|
+
|
|
24
|
+
export async function fetchComments(options: {
|
|
25
|
+
status?: Commentable['status']
|
|
26
|
+
commentables_id?: number
|
|
27
|
+
commentables_type?: string
|
|
28
|
+
limit?: number
|
|
29
|
+
offset?: number
|
|
30
|
+
} = {}): Promise<Commentable[]> {
|
|
31
|
+
const db = await getDb()
|
|
32
|
+
let query = db.selectFrom('commentables') as any
|
|
33
|
+
|
|
34
|
+
if (options.status)
|
|
35
|
+
query = query.where('status', '=', options.status)
|
|
36
|
+
|
|
37
|
+
if (options.commentables_id)
|
|
38
|
+
query = query.where('commentables_id', '=', options.commentables_id)
|
|
39
|
+
|
|
40
|
+
if (options.commentables_type)
|
|
41
|
+
query = query.where('commentables_type', '=', options.commentables_type)
|
|
42
|
+
|
|
43
|
+
if (options.limit)
|
|
44
|
+
query = query.limit(options.limit)
|
|
45
|
+
|
|
46
|
+
if (options.offset)
|
|
47
|
+
query = query.offset(options.offset)
|
|
48
|
+
|
|
49
|
+
return query.selectAll().execute() as Promise<Commentable[]>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function fetchCommentById(id: number): Promise<Commentable | undefined> {
|
|
53
|
+
const db = await getDb()
|
|
54
|
+
return db
|
|
55
|
+
.selectFrom('commentables')
|
|
56
|
+
.where('id', '=', id)
|
|
57
|
+
.selectAll()
|
|
58
|
+
.executeTakeFirst() as unknown as Promise<Commentable | undefined>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function fetchCommentsByCommentables(
|
|
62
|
+
commentables_id: number,
|
|
63
|
+
commentables_type: string,
|
|
64
|
+
options: { status?: Commentable['status'], limit?: number, offset?: number } = {},
|
|
65
|
+
): Promise<Commentable[]> {
|
|
66
|
+
const db = await getDb()
|
|
67
|
+
let query = db
|
|
68
|
+
.selectFrom('commentables')
|
|
69
|
+
.where('commentables_id', '=', commentables_id)
|
|
70
|
+
.where('commentables_type', '=', commentables_type) as any
|
|
71
|
+
|
|
72
|
+
if (options.status)
|
|
73
|
+
query = query.where('status', '=', options.status)
|
|
74
|
+
|
|
75
|
+
if (options.limit)
|
|
76
|
+
query = query.limit(options.limit)
|
|
77
|
+
|
|
78
|
+
if (options.offset)
|
|
79
|
+
query = query.offset(options.offset)
|
|
80
|
+
|
|
81
|
+
return query.selectAll().execute() as Promise<Commentable[]>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fetch comment counts for different time periods
|
|
86
|
+
*
|
|
87
|
+
* @param days The number of days to look back (e.g., 7, 14, 30, 60, 90)
|
|
88
|
+
* @returns The count of comments within the specified time period
|
|
89
|
+
*/
|
|
90
|
+
export async function fetchCommentCountByPeriod(days: number): Promise<number> {
|
|
91
|
+
const db = await getDb()
|
|
92
|
+
try {
|
|
93
|
+
const result = await db
|
|
94
|
+
.selectFrom('commentables')
|
|
95
|
+
.where('created_at', '>=', new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString())
|
|
96
|
+
.count()
|
|
97
|
+
|
|
98
|
+
return Number(result) || 0
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof Error) {
|
|
102
|
+
throw new TypeError(`Failed to fetch comment count: ${error.message}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw error
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function fetchCommentsByStatus(status: CommentStatus, options: { limit?: number, offset?: number } = {}): Promise<Commentable[]> {
|
|
110
|
+
const db = await getDb()
|
|
111
|
+
try {
|
|
112
|
+
let query = db
|
|
113
|
+
.selectFrom('commentables')
|
|
114
|
+
.where('status', '=', status) as any
|
|
115
|
+
|
|
116
|
+
if (options.limit)
|
|
117
|
+
query = query.limit(options.limit)
|
|
118
|
+
|
|
119
|
+
if (options.offset)
|
|
120
|
+
query = query.offset(options.offset)
|
|
121
|
+
|
|
122
|
+
return query.selectAll().execute() as Promise<Commentable[]>
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
throw new TypeError(`Failed to fetch comments by status: ${error.message}`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
throw error
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function calculateApprovalRate(): Promise<{ approved: number, total: number, rate: number }> {
|
|
134
|
+
const db = await getDb()
|
|
135
|
+
try {
|
|
136
|
+
const [approvedCount, totalCount] = await Promise.all([
|
|
137
|
+
db
|
|
138
|
+
.selectFrom('commentables')
|
|
139
|
+
.where('status', '=', 'approved')
|
|
140
|
+
.count(),
|
|
141
|
+
db
|
|
142
|
+
.selectFrom('commentables')
|
|
143
|
+
.count(),
|
|
144
|
+
])
|
|
145
|
+
|
|
146
|
+
const approved = Number(approvedCount || 0)
|
|
147
|
+
const total = Number(totalCount || 0)
|
|
148
|
+
const rate = total > 0 ? (approved / total) * 100 : 0
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
approved,
|
|
152
|
+
total,
|
|
153
|
+
rate,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error instanceof Error) {
|
|
158
|
+
throw new TypeError(`Failed to calculate approval rate: ${error.message}`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
throw error
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface PostWithCommentCount {
|
|
166
|
+
id: number
|
|
167
|
+
title: string
|
|
168
|
+
comment_count: number
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface DateRange {
|
|
172
|
+
startDate: Date
|
|
173
|
+
endDate: Date
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function fetchPostsWithMostComments(dateRange: DateRange, options: { limit?: number } = {}): Promise<PostWithCommentCount[]> {
|
|
177
|
+
const db = await getDb()
|
|
178
|
+
try {
|
|
179
|
+
let query = db
|
|
180
|
+
.selectFrom('posts')
|
|
181
|
+
.leftJoin('commentables', 'posts.id', '=', 'commentables.commentables_id')
|
|
182
|
+
.where('commentables.created_at', '>=', formatDate(dateRange.startDate))
|
|
183
|
+
.where('commentables.created_at', '<=', formatDate(dateRange.endDate))
|
|
184
|
+
.select([
|
|
185
|
+
'posts.id',
|
|
186
|
+
'posts.title',
|
|
187
|
+
])
|
|
188
|
+
.groupBy('posts.id', 'posts.title')
|
|
189
|
+
.orderBy('posts.id', 'desc') as any
|
|
190
|
+
|
|
191
|
+
if (options.limit)
|
|
192
|
+
query = query.limit(options.limit)
|
|
193
|
+
|
|
194
|
+
const results = await query.execute()
|
|
195
|
+
|
|
196
|
+
return (results as Record<string, unknown>[]).map((row: Record<string, unknown>) => ({
|
|
197
|
+
id: Number(row.id),
|
|
198
|
+
title: String(row.title),
|
|
199
|
+
comment_count: Number(row.comment_count || 0),
|
|
200
|
+
}))
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (error instanceof Error) {
|
|
204
|
+
throw new TypeError(`Failed to fetch posts with most comments: ${error.message}`)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
throw error
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface BarGraphData {
|
|
212
|
+
labels: string[]
|
|
213
|
+
values: number[]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function fetchCommentCountBarGraph(dateRange: DateRange, options: { limit?: number } = {}): Promise<BarGraphData> {
|
|
217
|
+
try {
|
|
218
|
+
const posts = await fetchPostsWithMostComments(dateRange, options)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
labels: posts.map((post: any) => post.title),
|
|
222
|
+
values: posts.map((post: any) => post.comment_count),
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (error instanceof Error) {
|
|
227
|
+
throw new TypeError(`Failed to fetch bar graph data: ${error.message}`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
throw error
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface DonutGraphData {
|
|
235
|
+
labels: string[]
|
|
236
|
+
values: number[]
|
|
237
|
+
percentages: number[]
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function fetchStatusDistributionDonut(dateRange: DateRange): Promise<DonutGraphData> {
|
|
241
|
+
const db = await getDb()
|
|
242
|
+
try {
|
|
243
|
+
const results = await db
|
|
244
|
+
.selectFrom('commentables')
|
|
245
|
+
.where('created_at', '>=', formatDate(dateRange.startDate))
|
|
246
|
+
.where('created_at', '<=', formatDate(dateRange.endDate))
|
|
247
|
+
.select([
|
|
248
|
+
'status',
|
|
249
|
+
])
|
|
250
|
+
.groupBy('status')
|
|
251
|
+
.execute()
|
|
252
|
+
|
|
253
|
+
const typedResults = results as Record<string, unknown>[]
|
|
254
|
+
const total = typedResults.reduce((sum: number, row: Record<string, unknown>) => sum + Number(row.count || 0), 0)
|
|
255
|
+
const labels = typedResults.map((row: Record<string, unknown>) => String(row.status))
|
|
256
|
+
const values = typedResults.map((row: Record<string, unknown>) => Number(row.count || 0))
|
|
257
|
+
const percentages = values.map((value: number) => total > 0 ? (value / total) * 100 : 0)
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
labels,
|
|
261
|
+
values,
|
|
262
|
+
percentages,
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (error instanceof Error)
|
|
267
|
+
throw new TypeError(`Failed to fetch status distribution: ${error.message}`)
|
|
268
|
+
|
|
269
|
+
throw error
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface LineGraphData {
|
|
274
|
+
labels: string[]
|
|
275
|
+
values: number[]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function fetchMonthlyCommentCounts(dateRange: DateRange): Promise<LineGraphData> {
|
|
279
|
+
const db = await getDb()
|
|
280
|
+
try {
|
|
281
|
+
const results = await db
|
|
282
|
+
.selectFrom('commentables')
|
|
283
|
+
.where('created_at', '>=', formatDate(dateRange.startDate))
|
|
284
|
+
.where('created_at', '<=', formatDate(dateRange.endDate))
|
|
285
|
+
.select([
|
|
286
|
+
'created_at',
|
|
287
|
+
])
|
|
288
|
+
.groupBy('created_at')
|
|
289
|
+
.orderBy('created_at', 'asc')
|
|
290
|
+
.execute()
|
|
291
|
+
|
|
292
|
+
// Group results by year and month
|
|
293
|
+
const monthlyCounts = new Map<string, number>()
|
|
294
|
+
;(results as Record<string, unknown>[]).forEach((row: Record<string, unknown>) => {
|
|
295
|
+
if (!row.created_at)
|
|
296
|
+
return
|
|
297
|
+
const date = new Date(row.created_at as string)
|
|
298
|
+
const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
|
299
|
+
monthlyCounts.set(key, (monthlyCounts.get(key) || 0) + Number(row.count || 0))
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// Convert to arrays for the graph
|
|
303
|
+
const labels: string[] = []
|
|
304
|
+
const values: number[] = []
|
|
305
|
+
monthlyCounts.forEach((count: any, key: any) => {
|
|
306
|
+
const [year, month] = key.split('-')
|
|
307
|
+
labels.push(new Date(Number(year), Number(month) - 1).toLocaleString('default', { month: 'short', year: 'numeric' }))
|
|
308
|
+
values.push(count)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
return { labels, values }
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (error instanceof Error)
|
|
315
|
+
throw new TypeError(`Failed to fetch monthly comment counts: ${error.message}`)
|
|
316
|
+
|
|
317
|
+
throw error
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {
|
|
2
|
+
bulkDestroy,
|
|
3
|
+
destroy,
|
|
4
|
+
} from './destroy'
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
calculateApprovalRate,
|
|
8
|
+
fetchCommentById,
|
|
9
|
+
fetchCommentCountBarGraph,
|
|
10
|
+
fetchCommentCountByPeriod,
|
|
11
|
+
fetchComments,
|
|
12
|
+
fetchCommentsByCommentables,
|
|
13
|
+
fetchCommentsByStatus,
|
|
14
|
+
fetchMonthlyCommentCounts,
|
|
15
|
+
fetchPostsWithMostComments,
|
|
16
|
+
fetchStatusDistributionDonut,
|
|
17
|
+
} from './fetch'
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
store,
|
|
21
|
+
} from './store'
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
update,
|
|
25
|
+
} from './update'
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { CommentablesTable } from '@stacksjs/orm'
|
|
2
|
+
import { getDb } from '../database'
|
|
3
|
+
|
|
4
|
+
export interface CreateCommentInput {
|
|
5
|
+
title: string
|
|
6
|
+
body: string
|
|
7
|
+
commentables_id: number
|
|
8
|
+
commentables_type: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UpdateCommentInput {
|
|
12
|
+
title?: string
|
|
13
|
+
body?: string
|
|
14
|
+
status?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function createComment(data: CreateCommentInput): Promise<CommentablesTable> {
|
|
18
|
+
const db = await getDb()
|
|
19
|
+
const now = new Date()
|
|
20
|
+
|
|
21
|
+
const commentData = {
|
|
22
|
+
title: data.title,
|
|
23
|
+
body: data.body,
|
|
24
|
+
commentables_id: data.commentables_id,
|
|
25
|
+
commentables_type: data.commentables_type,
|
|
26
|
+
status: 'pending',
|
|
27
|
+
created_at: now.toISOString(),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return db
|
|
31
|
+
.insertInto('commentables')
|
|
32
|
+
.values(commentData)
|
|
33
|
+
.returningAll()
|
|
34
|
+
.executeTakeFirstOrThrow() as unknown as Promise<CommentablesTable>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function updateComment(
|
|
38
|
+
id: number,
|
|
39
|
+
input: UpdateCommentInput,
|
|
40
|
+
): Promise<CommentablesTable> {
|
|
41
|
+
const db = await getDb()
|
|
42
|
+
return db
|
|
43
|
+
.updateTable('commentables')
|
|
44
|
+
.set({
|
|
45
|
+
...input,
|
|
46
|
+
updated_at: new Date().toISOString(),
|
|
47
|
+
})
|
|
48
|
+
.where('id', '=', id)
|
|
49
|
+
.returningAll()
|
|
50
|
+
.executeTakeFirstOrThrow() as unknown as Promise<CommentablesTable>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function approveComment(id: number): Promise<CommentablesTable> {
|
|
54
|
+
const db = await getDb()
|
|
55
|
+
return db
|
|
56
|
+
.updateTable('commentables')
|
|
57
|
+
.set({
|
|
58
|
+
status: 'approved',
|
|
59
|
+
approved_at: Date.now(),
|
|
60
|
+
updated_at: new Date().toISOString(),
|
|
61
|
+
})
|
|
62
|
+
.where('id', '=', id)
|
|
63
|
+
.returningAll()
|
|
64
|
+
.executeTakeFirstOrThrow() as unknown as Promise<CommentablesTable>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function rejectComment(id: number): Promise<CommentablesTable> {
|
|
68
|
+
const db = await getDb()
|
|
69
|
+
return db
|
|
70
|
+
.updateTable('commentables')
|
|
71
|
+
.set({
|
|
72
|
+
status: 'rejected',
|
|
73
|
+
rejected_at: Date.now(),
|
|
74
|
+
updated_at: new Date().toISOString(),
|
|
75
|
+
})
|
|
76
|
+
.where('id', '=', id)
|
|
77
|
+
.returningAll()
|
|
78
|
+
.executeTakeFirstOrThrow() as unknown as Promise<CommentablesTable>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function deleteComment(id: number): Promise<void> {
|
|
82
|
+
const db = await getDb()
|
|
83
|
+
await db
|
|
84
|
+
.deleteFrom('commentables')
|
|
85
|
+
.where('id', '=', id)
|
|
86
|
+
.execute()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface CommentStore {
|
|
90
|
+
title: string
|
|
91
|
+
body: string
|
|
92
|
+
status: string
|
|
93
|
+
user_id: number
|
|
94
|
+
commentables_id: number
|
|
95
|
+
commentables_type: string
|
|
96
|
+
is_active?: boolean | null
|
|
97
|
+
approved_at?: number | null
|
|
98
|
+
rejected_at?: number | null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create a new comment
|
|
103
|
+
*
|
|
104
|
+
* @param data The comment data to store
|
|
105
|
+
* @returns The newly created comment record
|
|
106
|
+
*/
|
|
107
|
+
export async function store(data: CommentStore): Promise<CommentablesTable> {
|
|
108
|
+
const db = await getDb()
|
|
109
|
+
try {
|
|
110
|
+
if (!data.title || data.title.trim() === '') {
|
|
111
|
+
throw new Error('Comment title is required')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!data.body || data.body.trim() === '') {
|
|
115
|
+
throw new Error('Comment body is required')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!data.commentables_type || data.commentables_type.trim() === '') {
|
|
119
|
+
throw new Error('Comment commentables_type is required')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const validStatuses = ['pending', 'approved', 'rejected']
|
|
123
|
+
if (data.status && !validStatuses.includes(data.status)) {
|
|
124
|
+
throw new Error(`Invalid comment status: ${data.status}`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const commentData = {
|
|
128
|
+
title: data.title,
|
|
129
|
+
body: data.body,
|
|
130
|
+
status: data.status,
|
|
131
|
+
commentables_id: data.commentables_id,
|
|
132
|
+
commentables_type: data.commentables_type,
|
|
133
|
+
user_id: data.user_id,
|
|
134
|
+
is_active: data.is_active,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const result = await db
|
|
138
|
+
.insertInto('commentables')
|
|
139
|
+
.values(commentData)
|
|
140
|
+
.returningAll()
|
|
141
|
+
.executeTakeFirst()
|
|
142
|
+
|
|
143
|
+
if (!result) {
|
|
144
|
+
throw new Error('Failed to create comment')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result as unknown as CommentablesTable
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error instanceof Error) {
|
|
151
|
+
throw new TypeError(`Failed to store comment: ${error.message}`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
throw error
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { CommentablesTable } from '@stacksjs/orm'
|
|
2
|
+
import { getDb } from '../database'
|
|
3
|
+
import { formatDate } from '@stacksjs/orm'
|
|
4
|
+
|
|
5
|
+
interface CommentUpdate {
|
|
6
|
+
title?: string
|
|
7
|
+
body?: string
|
|
8
|
+
status?: string
|
|
9
|
+
approved_at?: number | null
|
|
10
|
+
rejected_at?: number | null
|
|
11
|
+
commentables_id?: number
|
|
12
|
+
commentables_type?: string
|
|
13
|
+
user_id?: number | null
|
|
14
|
+
updated_at?: string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Update a comment by ID
|
|
19
|
+
*
|
|
20
|
+
* @param id The ID of the comment to update
|
|
21
|
+
* @param data The updated comment data
|
|
22
|
+
* @returns The updated comment record
|
|
23
|
+
*/
|
|
24
|
+
export async function update(id: number, data: CommentUpdate): Promise<CommentablesTable | undefined> {
|
|
25
|
+
const db = await getDb()
|
|
26
|
+
try {
|
|
27
|
+
if (data.title !== undefined && data.title.trim() === '') {
|
|
28
|
+
throw new Error('Comment title cannot be empty')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (data.body !== undefined && data.body.trim() === '') {
|
|
32
|
+
throw new Error('Comment body cannot be empty')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const validStatuses = ['pending', 'approved', 'rejected']
|
|
36
|
+
if (data.status !== undefined && !validStatuses.includes(data.status)) {
|
|
37
|
+
throw new Error(`Invalid comment status: ${data.status}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Only include fields that are explicitly provided
|
|
41
|
+
const commentData: Record<string, unknown> = {
|
|
42
|
+
updated_at: formatDate(new Date()),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (data.title !== undefined) commentData.title = data.title
|
|
46
|
+
if (data.body !== undefined) commentData.body = data.body
|
|
47
|
+
if (data.status !== undefined) commentData.status = data.status
|
|
48
|
+
if (data.commentables_id !== undefined) commentData.commentables_id = data.commentables_id
|
|
49
|
+
if (data.commentables_type !== undefined) commentData.commentables_type = data.commentables_type
|
|
50
|
+
if (data.approved_at !== undefined) commentData.approved_at = data.approved_at
|
|
51
|
+
if (data.rejected_at !== undefined) commentData.rejected_at = data.rejected_at
|
|
52
|
+
|
|
53
|
+
// Update the comment record
|
|
54
|
+
await db
|
|
55
|
+
.updateTable('commentables')
|
|
56
|
+
.set(commentData)
|
|
57
|
+
.where('id', '=', id)
|
|
58
|
+
.execute()
|
|
59
|
+
|
|
60
|
+
const updatedComment = await db
|
|
61
|
+
.selectFrom('commentables')
|
|
62
|
+
.where('id', '=', id)
|
|
63
|
+
.selectAll()
|
|
64
|
+
.executeTakeFirst()
|
|
65
|
+
|
|
66
|
+
return updatedComment as unknown as CommentablesTable | undefined
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error instanceof Error) {
|
|
70
|
+
throw new TypeError(`Failed to update comment: ${error.message}`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw error
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/database.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type StacksDatabase = typeof import('@stacksjs/database').db
|
|
2
|
+
|
|
3
|
+
let dbPromise: Promise<StacksDatabase> | undefined
|
|
4
|
+
|
|
5
|
+
export async function getDb(): Promise<StacksDatabase> {
|
|
6
|
+
dbPromise ??= import('@stacksjs/database').then(module => module.db)
|
|
7
|
+
|
|
8
|
+
return dbPromise
|
|
9
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as authors from './authors'
|
|
2
|
+
import * as postCategories from './categorizables'
|
|
3
|
+
import * as comments from './commentables'
|
|
4
|
+
import * as pages from './pages'
|
|
5
|
+
import * as posts from './posts'
|
|
6
|
+
import * as tags from './taggables'
|
|
7
|
+
|
|
8
|
+
type PostsModule = typeof posts
|
|
9
|
+
type PostCategoriesModule = typeof postCategories
|
|
10
|
+
type TagsModule = typeof tags
|
|
11
|
+
type CommentsModule = typeof comments
|
|
12
|
+
type AuthorsModule = typeof authors
|
|
13
|
+
type PagesModule = typeof pages
|
|
14
|
+
|
|
15
|
+
export interface CmsNamespace {
|
|
16
|
+
posts: PostsModule
|
|
17
|
+
postCategories: PostCategoriesModule
|
|
18
|
+
tags: TagsModule
|
|
19
|
+
comments: CommentsModule
|
|
20
|
+
authors: AuthorsModule
|
|
21
|
+
pages: PagesModule
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const cms: CmsNamespace = {
|
|
25
|
+
posts,
|
|
26
|
+
postCategories,
|
|
27
|
+
tags,
|
|
28
|
+
comments,
|
|
29
|
+
authors,
|
|
30
|
+
pages,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default cms
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
authors,
|
|
37
|
+
postCategories as categorizable,
|
|
38
|
+
comments,
|
|
39
|
+
pages,
|
|
40
|
+
posts,
|
|
41
|
+
tags,
|
|
42
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getDb } from '../database'
|
|
2
|
+
import { fetchById } from './fetch'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Delete a page by ID
|
|
6
|
+
*
|
|
7
|
+
* @param id The ID of the page to delete
|
|
8
|
+
* @returns True if the deletion was successful, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
export async function destroy(id: number): Promise<boolean> {
|
|
11
|
+
const db = await getDb()
|
|
12
|
+
try {
|
|
13
|
+
// First check if the page exists
|
|
14
|
+
const page = await fetchById(id)
|
|
15
|
+
|
|
16
|
+
if (!page) {
|
|
17
|
+
throw new Error(`Page with ID ${id} not found`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Delete the page
|
|
21
|
+
const result = await db
|
|
22
|
+
.deleteFrom('pages')
|
|
23
|
+
.where('id', '=', id)
|
|
24
|
+
.executeTakeFirst()
|
|
25
|
+
|
|
26
|
+
return result.numDeletedRows > 0
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof Error) {
|
|
30
|
+
throw new TypeError(`Failed to delete page: ${error.message}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
throw error
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Bulk delete multiple pages
|
|
39
|
+
*
|
|
40
|
+
* @param ids Array of page IDs to delete
|
|
41
|
+
* @returns Number of pages successfully deleted
|
|
42
|
+
*/
|
|
43
|
+
export async function bulkDestroy(ids: number[]): Promise<number> {
|
|
44
|
+
const db = await getDb()
|
|
45
|
+
if (!ids.length)
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Delete all pages in the array
|
|
50
|
+
const result = await db
|
|
51
|
+
.deleteFrom('pages')
|
|
52
|
+
.where('id', 'in', ids)
|
|
53
|
+
.executeTakeFirst()
|
|
54
|
+
|
|
55
|
+
return Number(result.numDeletedRows) || 0
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof Error) {
|
|
59
|
+
throw new TypeError(`Failed to delete pages in bulk: ${error.message}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw error
|
|
63
|
+
}
|
|
64
|
+
}
|