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.
Files changed (48) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/CLAUDE.md +236 -0
  3. package/CONTRIBUTING.md +342 -342
  4. package/INSTALLATION.md +359 -359
  5. package/LICENSE +201 -201
  6. package/README.md +405 -405
  7. package/app/.env.example +17 -17
  8. package/app/.github/labeler.yml +61 -0
  9. package/app/.github/workflows/browser-tests.yml +101 -0
  10. package/app/.github/workflows/check.yml +24 -0
  11. package/app/.github/workflows/greetings.yml +16 -0
  12. package/app/.github/workflows/label.yml +22 -0
  13. package/app/.github/workflows/stale.yml +27 -0
  14. package/app/.github/workflows/summary.yml +34 -0
  15. package/app/.stylelintrc.json +8 -0
  16. package/app/README.md +251 -251
  17. package/app/astro.config.mjs +83 -83
  18. package/app/drizzle.config.ts +16 -16
  19. package/app/package.json +66 -52
  20. package/app/playwright.config.ts +27 -0
  21. package/app/public/manifest.webmanifest +36 -36
  22. package/app/pwa-assets.config.ts +8 -0
  23. package/app/src/components/Card.astro +36 -36
  24. package/app/src/db/initialize.ts +107 -107
  25. package/app/src/db/schema.ts +72 -72
  26. package/app/src/db/validations.ts +158 -158
  27. package/app/src/layouts/Layout.astro +63 -63
  28. package/app/src/lib/config.ts +36 -36
  29. package/app/src/lib/content-converter.ts +141 -141
  30. package/app/src/lib/dom-utils.ts +230 -230
  31. package/app/src/lib/exa-search.ts +269 -269
  32. package/app/src/pages/api/chat.ts +91 -91
  33. package/app/src/pages/api/posts.ts +350 -350
  34. package/app/src/pages/index.astro +87 -87
  35. package/app/src/styles/components/button.scss +152 -152
  36. package/app/src/styles/components/card.scss +180 -180
  37. package/app/src/styles/components/form.scss +240 -240
  38. package/app/src/styles/global.scss +141 -141
  39. package/app/src/styles/pages/index.scss +80 -80
  40. package/app/src/styles/reset.scss +83 -83
  41. package/app/src/styles/variables/globals.scss +96 -96
  42. package/app/src/styles/variables/mixins.scss +238 -238
  43. package/app/tests/browser.test.nopause.ts +10 -0
  44. package/app/tests/browser.test.ts +13 -0
  45. package/bin/cli.js +1151 -1138
  46. package/package.json +8 -6
  47. package/app/.astro/settings.json +0 -5
  48. 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
+ };