create-atsdc-stack 1.0.1 → 1.2.0
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/.claude/settings.local.json +3 -1
- package/CLAUDE.md +236 -0
- package/CONTRIBUTING.md +342 -342
- package/INSTALLATION.md +359 -359
- package/LICENSE +201 -201
- package/README.md +405 -405
- package/app/.env.example +17 -17
- package/app/.github/labeler.yml +61 -0
- package/app/.github/workflows/browser-tests.yml +101 -0
- package/app/.github/workflows/check.yml +24 -0
- package/app/.github/workflows/greetings.yml +16 -0
- package/app/.github/workflows/label.yml +22 -0
- package/app/.github/workflows/stale.yml +27 -0
- package/app/.github/workflows/summary.yml +34 -0
- package/app/.stylelintrc.json +8 -0
- package/app/README.md +251 -251
- package/app/astro.config.mjs +83 -83
- package/app/drizzle.config.ts +16 -16
- package/app/package.json +66 -52
- package/app/playwright.config.ts +27 -0
- package/app/public/manifest.webmanifest +36 -36
- package/app/pwa-assets.config.ts +8 -0
- package/app/src/components/Card.astro +36 -36
- package/app/src/db/initialize.ts +107 -107
- package/app/src/db/schema.ts +72 -72
- package/app/src/db/validations.ts +158 -158
- package/app/src/layouts/Layout.astro +63 -63
- package/app/src/lib/config.ts +36 -36
- package/app/src/lib/content-converter.ts +141 -141
- package/app/src/lib/dom-utils.ts +230 -230
- package/app/src/lib/exa-search.ts +269 -269
- package/app/src/pages/api/chat.ts +91 -91
- package/app/src/pages/api/posts.ts +350 -350
- package/app/src/pages/index.astro +87 -87
- package/app/src/styles/components/button.scss +152 -152
- package/app/src/styles/components/card.scss +180 -180
- package/app/src/styles/components/form.scss +240 -240
- package/app/src/styles/global.scss +141 -141
- package/app/src/styles/pages/index.scss +80 -80
- package/app/src/styles/reset.scss +83 -83
- package/app/src/styles/variables/globals.scss +96 -96
- package/app/src/styles/variables/mixins.scss +238 -238
- package/app/tests/browser.test.nopause.ts +10 -0
- package/app/tests/browser.test.ts +13 -0
- package/bin/cli.js +1151 -1138
- package/package.json +8 -6
- package/app/.astro/settings.json +0 -5
- package/app/.astro/types.d.ts +0 -1
|
@@ -1,350 +1,350 @@
|
|
|
1
|
-
import type { APIRoute } from 'astro';
|
|
2
|
-
import { db } from '@/db/initialize';
|
|
3
|
-
import { posts } from '@/db/schema';
|
|
4
|
-
import {
|
|
5
|
-
createPostSchema,
|
|
6
|
-
updatePostSchema,
|
|
7
|
-
postQuerySchema,
|
|
8
|
-
type CreatePostInput,
|
|
9
|
-
type UpdatePostInput,
|
|
10
|
-
} from '@/db/validations';
|
|
11
|
-
import { eq, desc, and, ilike, or } from 'drizzle-orm';
|
|
12
|
-
import { z } from 'zod';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Posts API Routes
|
|
16
|
-
* Demonstrates CRUD operations with Drizzle ORM, Zod validation, and NanoID
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// GET /api/posts - List posts with pagination and filtering
|
|
20
|
-
export const GET: APIRoute = async ({ request, url }) => {
|
|
21
|
-
try {
|
|
22
|
-
// Parse and validate query parameters
|
|
23
|
-
const queryParams = {
|
|
24
|
-
page: url.searchParams.get('page'),
|
|
25
|
-
limit: url.searchParams.get('limit'),
|
|
26
|
-
published: url.searchParams.get('published'),
|
|
27
|
-
featured: url.searchParams.get('featured'),
|
|
28
|
-
authorId: url.searchParams.get('authorId'),
|
|
29
|
-
search: url.searchParams.get('search'),
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const validated = postQuerySchema.parse(queryParams);
|
|
33
|
-
const offset = (validated.page - 1) * validated.limit;
|
|
34
|
-
|
|
35
|
-
// Build query conditions
|
|
36
|
-
const conditions = [];
|
|
37
|
-
|
|
38
|
-
if (validated.published !== undefined) {
|
|
39
|
-
conditions.push(eq(posts.published, validated.published));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (validated.featured !== undefined) {
|
|
43
|
-
conditions.push(eq(posts.featured, validated.featured));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (validated.authorId) {
|
|
47
|
-
conditions.push(eq(posts.authorId, validated.authorId));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (validated.search) {
|
|
51
|
-
conditions.push(
|
|
52
|
-
or(
|
|
53
|
-
ilike(posts.title, `%${validated.search}%`),
|
|
54
|
-
ilike(posts.content, `%${validated.search}%`)
|
|
55
|
-
)!
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Fetch posts with filtering and pagination
|
|
60
|
-
const results = await db
|
|
61
|
-
.select()
|
|
62
|
-
.from(posts)
|
|
63
|
-
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
64
|
-
.orderBy(desc(posts.createdAt))
|
|
65
|
-
.limit(validated.limit)
|
|
66
|
-
.offset(offset);
|
|
67
|
-
|
|
68
|
-
return new Response(
|
|
69
|
-
JSON.stringify({
|
|
70
|
-
data: results,
|
|
71
|
-
meta: {
|
|
72
|
-
page: validated.page,
|
|
73
|
-
limit: validated.limit,
|
|
74
|
-
total: results.length,
|
|
75
|
-
},
|
|
76
|
-
}),
|
|
77
|
-
{
|
|
78
|
-
status: 200,
|
|
79
|
-
headers: {
|
|
80
|
-
'Content-Type': 'application/json',
|
|
81
|
-
},
|
|
82
|
-
}
|
|
83
|
-
);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
if (error instanceof z.ZodError) {
|
|
86
|
-
return new Response(
|
|
87
|
-
JSON.stringify({
|
|
88
|
-
error: 'Validation error',
|
|
89
|
-
details: error.errors,
|
|
90
|
-
}),
|
|
91
|
-
{
|
|
92
|
-
status: 400,
|
|
93
|
-
headers: {
|
|
94
|
-
'Content-Type': 'application/json',
|
|
95
|
-
},
|
|
96
|
-
}
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
console.error('GET /api/posts error:', error);
|
|
101
|
-
return new Response(
|
|
102
|
-
JSON.stringify({
|
|
103
|
-
error: 'Internal server error',
|
|
104
|
-
}),
|
|
105
|
-
{
|
|
106
|
-
status: 500,
|
|
107
|
-
headers: {
|
|
108
|
-
'Content-Type': 'application/json',
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// POST /api/posts - Create a new post
|
|
116
|
-
export const POST: APIRoute = async ({ request }) => {
|
|
117
|
-
try {
|
|
118
|
-
const body = await request.json();
|
|
119
|
-
|
|
120
|
-
// Validate request body with Zod
|
|
121
|
-
const validated: CreatePostInput = createPostSchema.parse(body);
|
|
122
|
-
|
|
123
|
-
// Insert post into database (NanoID is auto-generated)
|
|
124
|
-
const [newPost] = await db
|
|
125
|
-
.insert(posts)
|
|
126
|
-
.values({
|
|
127
|
-
...validated,
|
|
128
|
-
publishedAt: validated.published ? new Date() : null,
|
|
129
|
-
})
|
|
130
|
-
.returning();
|
|
131
|
-
|
|
132
|
-
return new Response(
|
|
133
|
-
JSON.stringify({
|
|
134
|
-
data: newPost,
|
|
135
|
-
message: 'Post created successfully',
|
|
136
|
-
}),
|
|
137
|
-
{
|
|
138
|
-
status: 201,
|
|
139
|
-
headers: {
|
|
140
|
-
'Content-Type': 'application/json',
|
|
141
|
-
},
|
|
142
|
-
}
|
|
143
|
-
);
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (error instanceof z.ZodError) {
|
|
146
|
-
return new Response(
|
|
147
|
-
JSON.stringify({
|
|
148
|
-
error: 'Validation error',
|
|
149
|
-
details: error.errors,
|
|
150
|
-
}),
|
|
151
|
-
{
|
|
152
|
-
status: 400,
|
|
153
|
-
headers: {
|
|
154
|
-
'Content-Type': 'application/json',
|
|
155
|
-
},
|
|
156
|
-
}
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
console.error('POST /api/posts error:', error);
|
|
161
|
-
return new Response(
|
|
162
|
-
JSON.stringify({
|
|
163
|
-
error: 'Internal server error',
|
|
164
|
-
}),
|
|
165
|
-
{
|
|
166
|
-
status: 500,
|
|
167
|
-
headers: {
|
|
168
|
-
'Content-Type': 'application/json',
|
|
169
|
-
},
|
|
170
|
-
}
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// PUT /api/posts - Update an existing post
|
|
176
|
-
export const PUT: APIRoute = async ({ request }) => {
|
|
177
|
-
try {
|
|
178
|
-
const body = await request.json();
|
|
179
|
-
|
|
180
|
-
// Validate request body with Zod
|
|
181
|
-
const validated: UpdatePostInput = updatePostSchema.parse(body);
|
|
182
|
-
const { id, ...updateData } = validated;
|
|
183
|
-
|
|
184
|
-
if (!id) {
|
|
185
|
-
return new Response(
|
|
186
|
-
JSON.stringify({
|
|
187
|
-
error: 'Post ID is required',
|
|
188
|
-
}),
|
|
189
|
-
{
|
|
190
|
-
status: 400,
|
|
191
|
-
headers: {
|
|
192
|
-
'Content-Type': 'application/json',
|
|
193
|
-
},
|
|
194
|
-
}
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Update post in database
|
|
199
|
-
const [updatedPost] = await db
|
|
200
|
-
.update(posts)
|
|
201
|
-
.set({
|
|
202
|
-
...updateData,
|
|
203
|
-
updatedAt: new Date(),
|
|
204
|
-
publishedAt:
|
|
205
|
-
updateData.published !== undefined
|
|
206
|
-
? updateData.published
|
|
207
|
-
? new Date()
|
|
208
|
-
: null
|
|
209
|
-
: undefined,
|
|
210
|
-
})
|
|
211
|
-
.where(eq(posts.id, id))
|
|
212
|
-
.returning();
|
|
213
|
-
|
|
214
|
-
if (!updatedPost) {
|
|
215
|
-
return new Response(
|
|
216
|
-
JSON.stringify({
|
|
217
|
-
error: 'Post not found',
|
|
218
|
-
}),
|
|
219
|
-
{
|
|
220
|
-
status: 404,
|
|
221
|
-
headers: {
|
|
222
|
-
'Content-Type': 'application/json',
|
|
223
|
-
},
|
|
224
|
-
}
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return new Response(
|
|
229
|
-
JSON.stringify({
|
|
230
|
-
data: updatedPost,
|
|
231
|
-
message: 'Post updated successfully',
|
|
232
|
-
}),
|
|
233
|
-
{
|
|
234
|
-
status: 200,
|
|
235
|
-
headers: {
|
|
236
|
-
'Content-Type': 'application/json',
|
|
237
|
-
},
|
|
238
|
-
}
|
|
239
|
-
);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
if (error instanceof z.ZodError) {
|
|
242
|
-
return new Response(
|
|
243
|
-
JSON.stringify({
|
|
244
|
-
error: 'Validation error',
|
|
245
|
-
details: error.errors,
|
|
246
|
-
}),
|
|
247
|
-
{
|
|
248
|
-
status: 400,
|
|
249
|
-
headers: {
|
|
250
|
-
'Content-Type': 'application/json',
|
|
251
|
-
},
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
console.error('PUT /api/posts error:', error);
|
|
257
|
-
return new Response(
|
|
258
|
-
JSON.stringify({
|
|
259
|
-
error: 'Internal server error',
|
|
260
|
-
}),
|
|
261
|
-
{
|
|
262
|
-
status: 500,
|
|
263
|
-
headers: {
|
|
264
|
-
'Content-Type': 'application/json',
|
|
265
|
-
},
|
|
266
|
-
}
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
// DELETE /api/posts - Delete a post
|
|
272
|
-
export const DELETE: APIRoute = async ({ request, url }) => {
|
|
273
|
-
try {
|
|
274
|
-
const id = url.searchParams.get('id');
|
|
275
|
-
|
|
276
|
-
if (!id) {
|
|
277
|
-
return new Response(
|
|
278
|
-
JSON.stringify({
|
|
279
|
-
error: 'Post ID is required',
|
|
280
|
-
}),
|
|
281
|
-
{
|
|
282
|
-
status: 400,
|
|
283
|
-
headers: {
|
|
284
|
-
'Content-Type': 'application/json',
|
|
285
|
-
},
|
|
286
|
-
}
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Validate ID format (NanoID is 21 characters)
|
|
291
|
-
if (id.length !== 21) {
|
|
292
|
-
return new Response(
|
|
293
|
-
JSON.stringify({
|
|
294
|
-
error: 'Invalid post ID format',
|
|
295
|
-
}),
|
|
296
|
-
{
|
|
297
|
-
status: 400,
|
|
298
|
-
headers: {
|
|
299
|
-
'Content-Type': 'application/json',
|
|
300
|
-
},
|
|
301
|
-
}
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Delete post from database
|
|
306
|
-
const [deletedPost] = await db
|
|
307
|
-
.delete(posts)
|
|
308
|
-
.where(eq(posts.id, id))
|
|
309
|
-
.returning();
|
|
310
|
-
|
|
311
|
-
if (!deletedPost) {
|
|
312
|
-
return new Response(
|
|
313
|
-
JSON.stringify({
|
|
314
|
-
error: 'Post not found',
|
|
315
|
-
}),
|
|
316
|
-
{
|
|
317
|
-
status: 404,
|
|
318
|
-
headers: {
|
|
319
|
-
'Content-Type': 'application/json',
|
|
320
|
-
},
|
|
321
|
-
}
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return new Response(
|
|
326
|
-
JSON.stringify({
|
|
327
|
-
message: 'Post deleted successfully',
|
|
328
|
-
}),
|
|
329
|
-
{
|
|
330
|
-
status: 200,
|
|
331
|
-
headers: {
|
|
332
|
-
'Content-Type': 'application/json',
|
|
333
|
-
},
|
|
334
|
-
}
|
|
335
|
-
);
|
|
336
|
-
} catch (error) {
|
|
337
|
-
console.error('DELETE /api/posts error:', error);
|
|
338
|
-
return new Response(
|
|
339
|
-
JSON.stringify({
|
|
340
|
-
error: 'Internal server error',
|
|
341
|
-
}),
|
|
342
|
-
{
|
|
343
|
-
status: 500,
|
|
344
|
-
headers: {
|
|
345
|
-
'Content-Type': 'application/json',
|
|
346
|
-
},
|
|
347
|
-
}
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
};
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
import { db } from '@/db/initialize';
|
|
3
|
+
import { posts } from '@/db/schema';
|
|
4
|
+
import {
|
|
5
|
+
createPostSchema,
|
|
6
|
+
updatePostSchema,
|
|
7
|
+
postQuerySchema,
|
|
8
|
+
type CreatePostInput,
|
|
9
|
+
type UpdatePostInput,
|
|
10
|
+
} from '@/db/validations';
|
|
11
|
+
import { eq, desc, and, ilike, or } from 'drizzle-orm';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Posts API Routes
|
|
16
|
+
* Demonstrates CRUD operations with Drizzle ORM, Zod validation, and NanoID
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// GET /api/posts - List posts with pagination and filtering
|
|
20
|
+
export const GET: APIRoute = async ({ request, url }) => {
|
|
21
|
+
try {
|
|
22
|
+
// Parse and validate query parameters
|
|
23
|
+
const queryParams = {
|
|
24
|
+
page: url.searchParams.get('page'),
|
|
25
|
+
limit: url.searchParams.get('limit'),
|
|
26
|
+
published: url.searchParams.get('published'),
|
|
27
|
+
featured: url.searchParams.get('featured'),
|
|
28
|
+
authorId: url.searchParams.get('authorId'),
|
|
29
|
+
search: url.searchParams.get('search'),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const validated = postQuerySchema.parse(queryParams);
|
|
33
|
+
const offset = (validated.page - 1) * validated.limit;
|
|
34
|
+
|
|
35
|
+
// Build query conditions
|
|
36
|
+
const conditions = [];
|
|
37
|
+
|
|
38
|
+
if (validated.published !== undefined) {
|
|
39
|
+
conditions.push(eq(posts.published, validated.published));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (validated.featured !== undefined) {
|
|
43
|
+
conditions.push(eq(posts.featured, validated.featured));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (validated.authorId) {
|
|
47
|
+
conditions.push(eq(posts.authorId, validated.authorId));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (validated.search) {
|
|
51
|
+
conditions.push(
|
|
52
|
+
or(
|
|
53
|
+
ilike(posts.title, `%${validated.search}%`),
|
|
54
|
+
ilike(posts.content, `%${validated.search}%`)
|
|
55
|
+
)!
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Fetch posts with filtering and pagination
|
|
60
|
+
const results = await db
|
|
61
|
+
.select()
|
|
62
|
+
.from(posts)
|
|
63
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
64
|
+
.orderBy(desc(posts.createdAt))
|
|
65
|
+
.limit(validated.limit)
|
|
66
|
+
.offset(offset);
|
|
67
|
+
|
|
68
|
+
return new Response(
|
|
69
|
+
JSON.stringify({
|
|
70
|
+
data: results,
|
|
71
|
+
meta: {
|
|
72
|
+
page: validated.page,
|
|
73
|
+
limit: validated.limit,
|
|
74
|
+
total: results.length,
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
{
|
|
78
|
+
status: 200,
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof z.ZodError) {
|
|
86
|
+
return new Response(
|
|
87
|
+
JSON.stringify({
|
|
88
|
+
error: 'Validation error',
|
|
89
|
+
details: error.errors,
|
|
90
|
+
}),
|
|
91
|
+
{
|
|
92
|
+
status: 400,
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.error('GET /api/posts error:', error);
|
|
101
|
+
return new Response(
|
|
102
|
+
JSON.stringify({
|
|
103
|
+
error: 'Internal server error',
|
|
104
|
+
}),
|
|
105
|
+
{
|
|
106
|
+
status: 500,
|
|
107
|
+
headers: {
|
|
108
|
+
'Content-Type': 'application/json',
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// POST /api/posts - Create a new post
|
|
116
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
117
|
+
try {
|
|
118
|
+
const body = await request.json();
|
|
119
|
+
|
|
120
|
+
// Validate request body with Zod
|
|
121
|
+
const validated: CreatePostInput = createPostSchema.parse(body);
|
|
122
|
+
|
|
123
|
+
// Insert post into database (NanoID is auto-generated)
|
|
124
|
+
const [newPost] = await db
|
|
125
|
+
.insert(posts)
|
|
126
|
+
.values({
|
|
127
|
+
...validated,
|
|
128
|
+
publishedAt: validated.published ? new Date() : null,
|
|
129
|
+
})
|
|
130
|
+
.returning();
|
|
131
|
+
|
|
132
|
+
return new Response(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
data: newPost,
|
|
135
|
+
message: 'Post created successfully',
|
|
136
|
+
}),
|
|
137
|
+
{
|
|
138
|
+
status: 201,
|
|
139
|
+
headers: {
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error instanceof z.ZodError) {
|
|
146
|
+
return new Response(
|
|
147
|
+
JSON.stringify({
|
|
148
|
+
error: 'Validation error',
|
|
149
|
+
details: error.errors,
|
|
150
|
+
}),
|
|
151
|
+
{
|
|
152
|
+
status: 400,
|
|
153
|
+
headers: {
|
|
154
|
+
'Content-Type': 'application/json',
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.error('POST /api/posts error:', error);
|
|
161
|
+
return new Response(
|
|
162
|
+
JSON.stringify({
|
|
163
|
+
error: 'Internal server error',
|
|
164
|
+
}),
|
|
165
|
+
{
|
|
166
|
+
status: 500,
|
|
167
|
+
headers: {
|
|
168
|
+
'Content-Type': 'application/json',
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// PUT /api/posts - Update an existing post
|
|
176
|
+
export const PUT: APIRoute = async ({ request }) => {
|
|
177
|
+
try {
|
|
178
|
+
const body = await request.json();
|
|
179
|
+
|
|
180
|
+
// Validate request body with Zod
|
|
181
|
+
const validated: UpdatePostInput = updatePostSchema.parse(body);
|
|
182
|
+
const { id, ...updateData } = validated;
|
|
183
|
+
|
|
184
|
+
if (!id) {
|
|
185
|
+
return new Response(
|
|
186
|
+
JSON.stringify({
|
|
187
|
+
error: 'Post ID is required',
|
|
188
|
+
}),
|
|
189
|
+
{
|
|
190
|
+
status: 400,
|
|
191
|
+
headers: {
|
|
192
|
+
'Content-Type': 'application/json',
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Update post in database
|
|
199
|
+
const [updatedPost] = await db
|
|
200
|
+
.update(posts)
|
|
201
|
+
.set({
|
|
202
|
+
...updateData,
|
|
203
|
+
updatedAt: new Date(),
|
|
204
|
+
publishedAt:
|
|
205
|
+
updateData.published !== undefined
|
|
206
|
+
? updateData.published
|
|
207
|
+
? new Date()
|
|
208
|
+
: null
|
|
209
|
+
: undefined,
|
|
210
|
+
})
|
|
211
|
+
.where(eq(posts.id, id))
|
|
212
|
+
.returning();
|
|
213
|
+
|
|
214
|
+
if (!updatedPost) {
|
|
215
|
+
return new Response(
|
|
216
|
+
JSON.stringify({
|
|
217
|
+
error: 'Post not found',
|
|
218
|
+
}),
|
|
219
|
+
{
|
|
220
|
+
status: 404,
|
|
221
|
+
headers: {
|
|
222
|
+
'Content-Type': 'application/json',
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return new Response(
|
|
229
|
+
JSON.stringify({
|
|
230
|
+
data: updatedPost,
|
|
231
|
+
message: 'Post updated successfully',
|
|
232
|
+
}),
|
|
233
|
+
{
|
|
234
|
+
status: 200,
|
|
235
|
+
headers: {
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error instanceof z.ZodError) {
|
|
242
|
+
return new Response(
|
|
243
|
+
JSON.stringify({
|
|
244
|
+
error: 'Validation error',
|
|
245
|
+
details: error.errors,
|
|
246
|
+
}),
|
|
247
|
+
{
|
|
248
|
+
status: 400,
|
|
249
|
+
headers: {
|
|
250
|
+
'Content-Type': 'application/json',
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.error('PUT /api/posts error:', error);
|
|
257
|
+
return new Response(
|
|
258
|
+
JSON.stringify({
|
|
259
|
+
error: 'Internal server error',
|
|
260
|
+
}),
|
|
261
|
+
{
|
|
262
|
+
status: 500,
|
|
263
|
+
headers: {
|
|
264
|
+
'Content-Type': 'application/json',
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// DELETE /api/posts - Delete a post
|
|
272
|
+
export const DELETE: APIRoute = async ({ request, url }) => {
|
|
273
|
+
try {
|
|
274
|
+
const id = url.searchParams.get('id');
|
|
275
|
+
|
|
276
|
+
if (!id) {
|
|
277
|
+
return new Response(
|
|
278
|
+
JSON.stringify({
|
|
279
|
+
error: 'Post ID is required',
|
|
280
|
+
}),
|
|
281
|
+
{
|
|
282
|
+
status: 400,
|
|
283
|
+
headers: {
|
|
284
|
+
'Content-Type': 'application/json',
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Validate ID format (NanoID is 21 characters)
|
|
291
|
+
if (id.length !== 21) {
|
|
292
|
+
return new Response(
|
|
293
|
+
JSON.stringify({
|
|
294
|
+
error: 'Invalid post ID format',
|
|
295
|
+
}),
|
|
296
|
+
{
|
|
297
|
+
status: 400,
|
|
298
|
+
headers: {
|
|
299
|
+
'Content-Type': 'application/json',
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Delete post from database
|
|
306
|
+
const [deletedPost] = await db
|
|
307
|
+
.delete(posts)
|
|
308
|
+
.where(eq(posts.id, id))
|
|
309
|
+
.returning();
|
|
310
|
+
|
|
311
|
+
if (!deletedPost) {
|
|
312
|
+
return new Response(
|
|
313
|
+
JSON.stringify({
|
|
314
|
+
error: 'Post not found',
|
|
315
|
+
}),
|
|
316
|
+
{
|
|
317
|
+
status: 404,
|
|
318
|
+
headers: {
|
|
319
|
+
'Content-Type': 'application/json',
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return new Response(
|
|
326
|
+
JSON.stringify({
|
|
327
|
+
message: 'Post deleted successfully',
|
|
328
|
+
}),
|
|
329
|
+
{
|
|
330
|
+
status: 200,
|
|
331
|
+
headers: {
|
|
332
|
+
'Content-Type': 'application/json',
|
|
333
|
+
},
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error('DELETE /api/posts error:', error);
|
|
338
|
+
return new Response(
|
|
339
|
+
JSON.stringify({
|
|
340
|
+
error: 'Internal server error',
|
|
341
|
+
}),
|
|
342
|
+
{
|
|
343
|
+
status: 500,
|
|
344
|
+
headers: {
|
|
345
|
+
'Content-Type': 'application/json',
|
|
346
|
+
},
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
};
|