create-atsdc-stack 1.1.0 → 1.2.1
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 -215
- 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 -1151
- package/package.json +8 -6
- package/app/.astro/settings.json +0 -5
- package/app/.astro/types.d.ts +0 -1
package/app/src/db/schema.ts
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
import { pgTable, text, timestamp, boolean, varchar } from 'drizzle-orm/pg-core';
|
|
2
|
-
import { nanoid } from 'nanoid';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Posts table schema
|
|
6
|
-
* Uses NanoID for primary keys for better URL-safe unique identifiers
|
|
7
|
-
*/
|
|
8
|
-
export const posts = pgTable('posts', {
|
|
9
|
-
// Primary key using NanoID (21 characters, URL-safe)
|
|
10
|
-
id: varchar('id', { length: 21 })
|
|
11
|
-
.primaryKey()
|
|
12
|
-
.$defaultFn(() => nanoid()),
|
|
13
|
-
|
|
14
|
-
// Post content fields
|
|
15
|
-
title: varchar('title', { length: 255 }).notNull(),
|
|
16
|
-
slug: varchar('slug', { length: 255 }).notNull().unique(),
|
|
17
|
-
content: text('content').notNull(),
|
|
18
|
-
excerpt: text('excerpt'),
|
|
19
|
-
|
|
20
|
-
// Author information (Clerk user ID)
|
|
21
|
-
authorId: varchar('author_id', { length: 255 }).notNull(),
|
|
22
|
-
authorName: varchar('author_name', { length: 255 }),
|
|
23
|
-
|
|
24
|
-
// Post metadata
|
|
25
|
-
published: boolean('published').default(false).notNull(),
|
|
26
|
-
featured: boolean('featured').default(false).notNull(),
|
|
27
|
-
|
|
28
|
-
// SEO fields
|
|
29
|
-
metaTitle: varchar('meta_title', { length: 255 }),
|
|
30
|
-
metaDescription: text('meta_description'),
|
|
31
|
-
|
|
32
|
-
// Timestamps
|
|
33
|
-
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
34
|
-
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
|
35
|
-
publishedAt: timestamp('published_at', { withTimezone: true }),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Comments table schema
|
|
40
|
-
* Demonstrates relationship with posts using NanoID
|
|
41
|
-
*/
|
|
42
|
-
export const comments = pgTable('comments', {
|
|
43
|
-
id: varchar('id', { length: 21 })
|
|
44
|
-
.primaryKey()
|
|
45
|
-
.$defaultFn(() => nanoid()),
|
|
46
|
-
|
|
47
|
-
// Foreign key to posts table
|
|
48
|
-
postId: varchar('post_id', { length: 21 })
|
|
49
|
-
.notNull()
|
|
50
|
-
.references(() => posts.id, { onDelete: 'cascade' }),
|
|
51
|
-
|
|
52
|
-
// Comment content
|
|
53
|
-
content: text('content').notNull(),
|
|
54
|
-
|
|
55
|
-
// Author information (Clerk user ID)
|
|
56
|
-
authorId: varchar('author_id', { length: 255 }).notNull(),
|
|
57
|
-
authorName: varchar('author_name', { length: 255 }),
|
|
58
|
-
|
|
59
|
-
// Moderation
|
|
60
|
-
approved: boolean('approved').default(false).notNull(),
|
|
61
|
-
flagged: boolean('flagged').default(false).notNull(),
|
|
62
|
-
|
|
63
|
-
// Timestamps
|
|
64
|
-
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
65
|
-
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Type exports for use in application
|
|
69
|
-
export type Post = typeof posts.$inferSelect;
|
|
70
|
-
export type NewPost = typeof posts.$inferInsert;
|
|
71
|
-
export type Comment = typeof comments.$inferSelect;
|
|
72
|
-
export type NewComment = typeof comments.$inferInsert;
|
|
1
|
+
import { pgTable, text, timestamp, boolean, varchar } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Posts table schema
|
|
6
|
+
* Uses NanoID for primary keys for better URL-safe unique identifiers
|
|
7
|
+
*/
|
|
8
|
+
export const posts = pgTable('posts', {
|
|
9
|
+
// Primary key using NanoID (21 characters, URL-safe)
|
|
10
|
+
id: varchar('id', { length: 21 })
|
|
11
|
+
.primaryKey()
|
|
12
|
+
.$defaultFn(() => nanoid()),
|
|
13
|
+
|
|
14
|
+
// Post content fields
|
|
15
|
+
title: varchar('title', { length: 255 }).notNull(),
|
|
16
|
+
slug: varchar('slug', { length: 255 }).notNull().unique(),
|
|
17
|
+
content: text('content').notNull(),
|
|
18
|
+
excerpt: text('excerpt'),
|
|
19
|
+
|
|
20
|
+
// Author information (Clerk user ID)
|
|
21
|
+
authorId: varchar('author_id', { length: 255 }).notNull(),
|
|
22
|
+
authorName: varchar('author_name', { length: 255 }),
|
|
23
|
+
|
|
24
|
+
// Post metadata
|
|
25
|
+
published: boolean('published').default(false).notNull(),
|
|
26
|
+
featured: boolean('featured').default(false).notNull(),
|
|
27
|
+
|
|
28
|
+
// SEO fields
|
|
29
|
+
metaTitle: varchar('meta_title', { length: 255 }),
|
|
30
|
+
metaDescription: text('meta_description'),
|
|
31
|
+
|
|
32
|
+
// Timestamps
|
|
33
|
+
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
34
|
+
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
|
35
|
+
publishedAt: timestamp('published_at', { withTimezone: true }),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Comments table schema
|
|
40
|
+
* Demonstrates relationship with posts using NanoID
|
|
41
|
+
*/
|
|
42
|
+
export const comments = pgTable('comments', {
|
|
43
|
+
id: varchar('id', { length: 21 })
|
|
44
|
+
.primaryKey()
|
|
45
|
+
.$defaultFn(() => nanoid()),
|
|
46
|
+
|
|
47
|
+
// Foreign key to posts table
|
|
48
|
+
postId: varchar('post_id', { length: 21 })
|
|
49
|
+
.notNull()
|
|
50
|
+
.references(() => posts.id, { onDelete: 'cascade' }),
|
|
51
|
+
|
|
52
|
+
// Comment content
|
|
53
|
+
content: text('content').notNull(),
|
|
54
|
+
|
|
55
|
+
// Author information (Clerk user ID)
|
|
56
|
+
authorId: varchar('author_id', { length: 255 }).notNull(),
|
|
57
|
+
authorName: varchar('author_name', { length: 255 }),
|
|
58
|
+
|
|
59
|
+
// Moderation
|
|
60
|
+
approved: boolean('approved').default(false).notNull(),
|
|
61
|
+
flagged: boolean('flagged').default(false).notNull(),
|
|
62
|
+
|
|
63
|
+
// Timestamps
|
|
64
|
+
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
65
|
+
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Type exports for use in application
|
|
69
|
+
export type Post = typeof posts.$inferSelect;
|
|
70
|
+
export type NewPost = typeof posts.$inferInsert;
|
|
71
|
+
export type Comment = typeof comments.$inferSelect;
|
|
72
|
+
export type NewComment = typeof comments.$inferInsert;
|
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Zod validation schemas for database models
|
|
5
|
-
* Provides runtime type safety and validation for user inputs
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Slug validation helper
|
|
9
|
-
const slugSchema = z
|
|
10
|
-
.string()
|
|
11
|
-
.min(1, 'Slug is required')
|
|
12
|
-
.max(255, 'Slug must be 255 characters or less')
|
|
13
|
-
.regex(
|
|
14
|
-
/^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
15
|
-
'Slug must be lowercase alphanumeric with hyphens only'
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Post validation schemas
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
// Schema for creating a new post
|
|
23
|
-
export const createPostSchema = z.object({
|
|
24
|
-
title: z
|
|
25
|
-
.string()
|
|
26
|
-
.min(1, 'Title is required')
|
|
27
|
-
.max(255, 'Title must be 255 characters or less')
|
|
28
|
-
.trim(),
|
|
29
|
-
|
|
30
|
-
slug: slugSchema,
|
|
31
|
-
|
|
32
|
-
content: z
|
|
33
|
-
.string()
|
|
34
|
-
.min(1, 'Content is required')
|
|
35
|
-
.max(50000, 'Content must be 50,000 characters or less'),
|
|
36
|
-
|
|
37
|
-
excerpt: z
|
|
38
|
-
.string()
|
|
39
|
-
.max(500, 'Excerpt must be 500 characters or less')
|
|
40
|
-
.optional()
|
|
41
|
-
.nullable(),
|
|
42
|
-
|
|
43
|
-
authorId: z.string().min(1, 'Author ID is required'),
|
|
44
|
-
|
|
45
|
-
authorName: z
|
|
46
|
-
.string()
|
|
47
|
-
.max(255, 'Author name must be 255 characters or less')
|
|
48
|
-
.optional()
|
|
49
|
-
.nullable(),
|
|
50
|
-
|
|
51
|
-
published: z.boolean().default(false),
|
|
52
|
-
|
|
53
|
-
featured: z.boolean().default(false),
|
|
54
|
-
|
|
55
|
-
metaTitle: z
|
|
56
|
-
.string()
|
|
57
|
-
.max(255, 'Meta title must be 255 characters or less')
|
|
58
|
-
.optional()
|
|
59
|
-
.nullable(),
|
|
60
|
-
|
|
61
|
-
metaDescription: z
|
|
62
|
-
.string()
|
|
63
|
-
.max(500, 'Meta description must be 500 characters or less')
|
|
64
|
-
.optional()
|
|
65
|
-
.nullable(),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Schema for updating an existing post
|
|
69
|
-
export const updatePostSchema = createPostSchema.partial().extend({
|
|
70
|
-
id: z.string().length(21, 'Invalid post ID'),
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Schema for publishing/unpublishing a post
|
|
74
|
-
export const publishPostSchema = z.object({
|
|
75
|
-
id: z.string().length(21, 'Invalid post ID'),
|
|
76
|
-
published: z.boolean(),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Schema for post query parameters
|
|
80
|
-
export const postQuerySchema = z.object({
|
|
81
|
-
page: z.coerce.number().int().positive().default(1),
|
|
82
|
-
limit: z.coerce.number().int().positive().max(100).default(10),
|
|
83
|
-
published: z
|
|
84
|
-
.string()
|
|
85
|
-
.transform((val) => val === 'true')
|
|
86
|
-
.optional(),
|
|
87
|
-
featured: z
|
|
88
|
-
.string()
|
|
89
|
-
.transform((val) => val === 'true')
|
|
90
|
-
.optional(),
|
|
91
|
-
authorId: z.string().optional(),
|
|
92
|
-
search: z.string().optional(),
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Comment validation schemas
|
|
97
|
-
*/
|
|
98
|
-
|
|
99
|
-
// Schema for creating a new comment
|
|
100
|
-
export const createCommentSchema = z.object({
|
|
101
|
-
postId: z.string().length(21, 'Invalid post ID'),
|
|
102
|
-
|
|
103
|
-
content: z
|
|
104
|
-
.string()
|
|
105
|
-
.min(1, 'Comment content is required')
|
|
106
|
-
.max(2000, 'Comment must be 2,000 characters or less')
|
|
107
|
-
.trim(),
|
|
108
|
-
|
|
109
|
-
authorId: z.string().min(1, 'Author ID is required'),
|
|
110
|
-
|
|
111
|
-
authorName: z
|
|
112
|
-
.string()
|
|
113
|
-
.max(255, 'Author name must be 255 characters or less')
|
|
114
|
-
.optional()
|
|
115
|
-
.nullable(),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Schema for updating a comment
|
|
119
|
-
export const updateCommentSchema = z.object({
|
|
120
|
-
id: z.string().length(21, 'Invalid comment ID'),
|
|
121
|
-
content: z
|
|
122
|
-
.string()
|
|
123
|
-
.min(1, 'Comment content is required')
|
|
124
|
-
.max(2000, 'Comment must be 2,000 characters or less')
|
|
125
|
-
.trim(),
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// Schema for moderating a comment
|
|
129
|
-
export const moderateCommentSchema = z.object({
|
|
130
|
-
id: z.string().length(21, 'Invalid comment ID'),
|
|
131
|
-
approved: z.boolean().optional(),
|
|
132
|
-
flagged: z.boolean().optional(),
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Schema for comment query parameters
|
|
136
|
-
export const commentQuerySchema = z.object({
|
|
137
|
-
postId: z.string().length(21, 'Invalid post ID').optional(),
|
|
138
|
-
page: z.coerce.number().int().positive().default(1),
|
|
139
|
-
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
140
|
-
approved: z
|
|
141
|
-
.string()
|
|
142
|
-
.transform((val) => val === 'true')
|
|
143
|
-
.optional(),
|
|
144
|
-
authorId: z.string().optional(),
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Type exports for use in application
|
|
149
|
-
*/
|
|
150
|
-
export type CreatePostInput = z.infer<typeof createPostSchema>;
|
|
151
|
-
export type UpdatePostInput = z.infer<typeof updatePostSchema>;
|
|
152
|
-
export type PublishPostInput = z.infer<typeof publishPostSchema>;
|
|
153
|
-
export type PostQueryInput = z.infer<typeof postQuerySchema>;
|
|
154
|
-
|
|
155
|
-
export type CreateCommentInput = z.infer<typeof createCommentSchema>;
|
|
156
|
-
export type UpdateCommentInput = z.infer<typeof updateCommentSchema>;
|
|
157
|
-
export type ModerateCommentInput = z.infer<typeof moderateCommentSchema>;
|
|
158
|
-
export type CommentQueryInput = z.infer<typeof commentQuerySchema>;
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod validation schemas for database models
|
|
5
|
+
* Provides runtime type safety and validation for user inputs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Slug validation helper
|
|
9
|
+
const slugSchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.min(1, 'Slug is required')
|
|
12
|
+
.max(255, 'Slug must be 255 characters or less')
|
|
13
|
+
.regex(
|
|
14
|
+
/^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
15
|
+
'Slug must be lowercase alphanumeric with hyphens only'
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Post validation schemas
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// Schema for creating a new post
|
|
23
|
+
export const createPostSchema = z.object({
|
|
24
|
+
title: z
|
|
25
|
+
.string()
|
|
26
|
+
.min(1, 'Title is required')
|
|
27
|
+
.max(255, 'Title must be 255 characters or less')
|
|
28
|
+
.trim(),
|
|
29
|
+
|
|
30
|
+
slug: slugSchema,
|
|
31
|
+
|
|
32
|
+
content: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1, 'Content is required')
|
|
35
|
+
.max(50000, 'Content must be 50,000 characters or less'),
|
|
36
|
+
|
|
37
|
+
excerpt: z
|
|
38
|
+
.string()
|
|
39
|
+
.max(500, 'Excerpt must be 500 characters or less')
|
|
40
|
+
.optional()
|
|
41
|
+
.nullable(),
|
|
42
|
+
|
|
43
|
+
authorId: z.string().min(1, 'Author ID is required'),
|
|
44
|
+
|
|
45
|
+
authorName: z
|
|
46
|
+
.string()
|
|
47
|
+
.max(255, 'Author name must be 255 characters or less')
|
|
48
|
+
.optional()
|
|
49
|
+
.nullable(),
|
|
50
|
+
|
|
51
|
+
published: z.boolean().default(false),
|
|
52
|
+
|
|
53
|
+
featured: z.boolean().default(false),
|
|
54
|
+
|
|
55
|
+
metaTitle: z
|
|
56
|
+
.string()
|
|
57
|
+
.max(255, 'Meta title must be 255 characters or less')
|
|
58
|
+
.optional()
|
|
59
|
+
.nullable(),
|
|
60
|
+
|
|
61
|
+
metaDescription: z
|
|
62
|
+
.string()
|
|
63
|
+
.max(500, 'Meta description must be 500 characters or less')
|
|
64
|
+
.optional()
|
|
65
|
+
.nullable(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Schema for updating an existing post
|
|
69
|
+
export const updatePostSchema = createPostSchema.partial().extend({
|
|
70
|
+
id: z.string().length(21, 'Invalid post ID'),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Schema for publishing/unpublishing a post
|
|
74
|
+
export const publishPostSchema = z.object({
|
|
75
|
+
id: z.string().length(21, 'Invalid post ID'),
|
|
76
|
+
published: z.boolean(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Schema for post query parameters
|
|
80
|
+
export const postQuerySchema = z.object({
|
|
81
|
+
page: z.coerce.number().int().positive().default(1),
|
|
82
|
+
limit: z.coerce.number().int().positive().max(100).default(10),
|
|
83
|
+
published: z
|
|
84
|
+
.string()
|
|
85
|
+
.transform((val) => val === 'true')
|
|
86
|
+
.optional(),
|
|
87
|
+
featured: z
|
|
88
|
+
.string()
|
|
89
|
+
.transform((val) => val === 'true')
|
|
90
|
+
.optional(),
|
|
91
|
+
authorId: z.string().optional(),
|
|
92
|
+
search: z.string().optional(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Comment validation schemas
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
// Schema for creating a new comment
|
|
100
|
+
export const createCommentSchema = z.object({
|
|
101
|
+
postId: z.string().length(21, 'Invalid post ID'),
|
|
102
|
+
|
|
103
|
+
content: z
|
|
104
|
+
.string()
|
|
105
|
+
.min(1, 'Comment content is required')
|
|
106
|
+
.max(2000, 'Comment must be 2,000 characters or less')
|
|
107
|
+
.trim(),
|
|
108
|
+
|
|
109
|
+
authorId: z.string().min(1, 'Author ID is required'),
|
|
110
|
+
|
|
111
|
+
authorName: z
|
|
112
|
+
.string()
|
|
113
|
+
.max(255, 'Author name must be 255 characters or less')
|
|
114
|
+
.optional()
|
|
115
|
+
.nullable(),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Schema for updating a comment
|
|
119
|
+
export const updateCommentSchema = z.object({
|
|
120
|
+
id: z.string().length(21, 'Invalid comment ID'),
|
|
121
|
+
content: z
|
|
122
|
+
.string()
|
|
123
|
+
.min(1, 'Comment content is required')
|
|
124
|
+
.max(2000, 'Comment must be 2,000 characters or less')
|
|
125
|
+
.trim(),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Schema for moderating a comment
|
|
129
|
+
export const moderateCommentSchema = z.object({
|
|
130
|
+
id: z.string().length(21, 'Invalid comment ID'),
|
|
131
|
+
approved: z.boolean().optional(),
|
|
132
|
+
flagged: z.boolean().optional(),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Schema for comment query parameters
|
|
136
|
+
export const commentQuerySchema = z.object({
|
|
137
|
+
postId: z.string().length(21, 'Invalid post ID').optional(),
|
|
138
|
+
page: z.coerce.number().int().positive().default(1),
|
|
139
|
+
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
140
|
+
approved: z
|
|
141
|
+
.string()
|
|
142
|
+
.transform((val) => val === 'true')
|
|
143
|
+
.optional(),
|
|
144
|
+
authorId: z.string().optional(),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Type exports for use in application
|
|
149
|
+
*/
|
|
150
|
+
export type CreatePostInput = z.infer<typeof createPostSchema>;
|
|
151
|
+
export type UpdatePostInput = z.infer<typeof updatePostSchema>;
|
|
152
|
+
export type PublishPostInput = z.infer<typeof publishPostSchema>;
|
|
153
|
+
export type PostQueryInput = z.infer<typeof postQuerySchema>;
|
|
154
|
+
|
|
155
|
+
export type CreateCommentInput = z.infer<typeof createCommentSchema>;
|
|
156
|
+
export type UpdateCommentInput = z.infer<typeof updateCommentSchema>;
|
|
157
|
+
export type ModerateCommentInput = z.infer<typeof moderateCommentSchema>;
|
|
158
|
+
export type CommentQueryInput = z.infer<typeof commentQuerySchema>;
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* Base Layout Component
|
|
4
|
-
* Demonstrates proper SCSS architecture:
|
|
5
|
-
* - No <style> tags in this file
|
|
6
|
-
* - All styles imported from external SCSS files
|
|
7
|
-
* - Global styles applied via import in the head
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { siteConfig } from '@/lib/config';
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
pageTitle?: string | string[];
|
|
14
|
-
description?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const {
|
|
18
|
-
pageTitle,
|
|
19
|
-
description = siteConfig.stackDescription,
|
|
20
|
-
} = Astro.props;
|
|
21
|
-
|
|
22
|
-
const pageArray = Array.isArray(pageTitle) ? pageTitle : [pageTitle];
|
|
23
|
-
|
|
24
|
-
const firstElement = pageArray?.[0];
|
|
25
|
-
const shouldSkipFirst =
|
|
26
|
-
firstElement === '' ||
|
|
27
|
-
firstElement === siteConfig.stackName ||
|
|
28
|
-
firstElement?.toLowerCase() === 'index' ||
|
|
29
|
-
firstElement?.toLowerCase() === 'home';
|
|
30
|
-
|
|
31
|
-
const newPageTitle = (pageArray && pageArray.length > 0 && firstElement)
|
|
32
|
-
? shouldSkipFirst
|
|
33
|
-
? pageArray.filter((a, i) => i !== 0)
|
|
34
|
-
: pageArray
|
|
35
|
-
: null;
|
|
36
|
-
|
|
37
|
-
const title = newPageTitle ? `${newPageTitle.join(' | ')} | ${siteConfig.stackName}` : siteConfig.stackName;
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
<!doctype html>
|
|
41
|
-
<html lang="en">
|
|
42
|
-
<head>
|
|
43
|
-
<meta charset="UTF-8" />
|
|
44
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
45
|
-
<meta name="description" content={description} />
|
|
46
|
-
<meta name="generator" content={Astro.generator} />
|
|
47
|
-
|
|
48
|
-
<!-- Favicons -->
|
|
49
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
50
|
-
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
51
|
-
|
|
52
|
-
<!-- PWA manifest -->
|
|
53
|
-
<link rel="manifest" href="/manifest.webmanifest" />
|
|
54
|
-
|
|
55
|
-
<title>{title}</title>
|
|
56
|
-
|
|
57
|
-
<!-- Global styles imported from external SCSS file -->
|
|
58
|
-
<link rel="stylesheet" href="/src/styles/global.scss" />
|
|
59
|
-
</head>
|
|
60
|
-
<body>
|
|
61
|
-
<slot />
|
|
62
|
-
</body>
|
|
63
|
-
</html>
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Base Layout Component
|
|
4
|
+
* Demonstrates proper SCSS architecture:
|
|
5
|
+
* - No <style> tags in this file
|
|
6
|
+
* - All styles imported from external SCSS files
|
|
7
|
+
* - Global styles applied via import in the head
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { siteConfig } from '@/lib/config';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
pageTitle?: string | string[];
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
pageTitle,
|
|
19
|
+
description = siteConfig.stackDescription,
|
|
20
|
+
} = Astro.props;
|
|
21
|
+
|
|
22
|
+
const pageArray = Array.isArray(pageTitle) ? pageTitle : [pageTitle];
|
|
23
|
+
|
|
24
|
+
const firstElement = pageArray?.[0];
|
|
25
|
+
const shouldSkipFirst =
|
|
26
|
+
firstElement === '' ||
|
|
27
|
+
firstElement === siteConfig.stackName ||
|
|
28
|
+
firstElement?.toLowerCase() === 'index' ||
|
|
29
|
+
firstElement?.toLowerCase() === 'home';
|
|
30
|
+
|
|
31
|
+
const newPageTitle = (pageArray && pageArray.length > 0 && firstElement)
|
|
32
|
+
? shouldSkipFirst
|
|
33
|
+
? pageArray.filter((a, i) => i !== 0)
|
|
34
|
+
: pageArray
|
|
35
|
+
: null;
|
|
36
|
+
|
|
37
|
+
const title = newPageTitle ? `${newPageTitle.join(' | ')} | ${siteConfig.stackName}` : siteConfig.stackName;
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
<!doctype html>
|
|
41
|
+
<html lang="en">
|
|
42
|
+
<head>
|
|
43
|
+
<meta charset="UTF-8" />
|
|
44
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
45
|
+
<meta name="description" content={description} />
|
|
46
|
+
<meta name="generator" content={Astro.generator} />
|
|
47
|
+
|
|
48
|
+
<!-- Favicons -->
|
|
49
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
50
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
51
|
+
|
|
52
|
+
<!-- PWA manifest -->
|
|
53
|
+
<link rel="manifest" href="/manifest.webmanifest" />
|
|
54
|
+
|
|
55
|
+
<title>{title}</title>
|
|
56
|
+
|
|
57
|
+
<!-- Global styles imported from external SCSS file -->
|
|
58
|
+
<link rel="stylesheet" href="/src/styles/global.scss" />
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<slot />
|
|
62
|
+
</body>
|
|
63
|
+
</html>
|
package/app/src/lib/config.ts
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Site Configuration
|
|
3
|
-
* Central configuration for the application that will be rendered on screen
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The core stack identifier - change this in ONE place to update everywhere
|
|
8
|
-
*/
|
|
9
|
-
const STACK_SHORT_NAME = 'ATSDC';
|
|
10
|
-
|
|
11
|
-
export const siteConfig = {
|
|
12
|
-
/**
|
|
13
|
-
* Short name for the stack (used in PWA and compact displays)
|
|
14
|
-
*/
|
|
15
|
-
stackShortName: STACK_SHORT_NAME,
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The full name of the stack/framework (derived from stackShortName)
|
|
19
|
-
*/
|
|
20
|
-
stackName: `${STACK_SHORT_NAME} Stack`,
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Full description of the stack
|
|
24
|
-
*/
|
|
25
|
-
stackDescription:
|
|
26
|
-
'Full-stack application built with Astro, TypeScript, Drizzle, Clerk, and SCSS',
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Docs URL
|
|
30
|
-
*/
|
|
31
|
-
docsUrl: 'https://github.com/adarshrkumar/The-ATSDC-Stack',
|
|
32
|
-
/**
|
|
33
|
-
* GitHub repository URL
|
|
34
|
-
*/
|
|
35
|
-
githubUrl: 'https://github.com/adarshrkumar/The-ATSDC-Stack',
|
|
36
|
-
} as const;
|
|
1
|
+
/**
|
|
2
|
+
* Site Configuration
|
|
3
|
+
* Central configuration for the application that will be rendered on screen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The core stack identifier - change this in ONE place to update everywhere
|
|
8
|
+
*/
|
|
9
|
+
const STACK_SHORT_NAME = 'ATSDC';
|
|
10
|
+
|
|
11
|
+
export const siteConfig = {
|
|
12
|
+
/**
|
|
13
|
+
* Short name for the stack (used in PWA and compact displays)
|
|
14
|
+
*/
|
|
15
|
+
stackShortName: STACK_SHORT_NAME,
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The full name of the stack/framework (derived from stackShortName)
|
|
19
|
+
*/
|
|
20
|
+
stackName: `${STACK_SHORT_NAME} Stack`,
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Full description of the stack
|
|
24
|
+
*/
|
|
25
|
+
stackDescription:
|
|
26
|
+
'Full-stack application built with Astro, TypeScript, Drizzle, Clerk, and SCSS',
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Docs URL
|
|
30
|
+
*/
|
|
31
|
+
docsUrl: 'https://github.com/adarshrkumar/The-ATSDC-Stack',
|
|
32
|
+
/**
|
|
33
|
+
* GitHub repository URL
|
|
34
|
+
*/
|
|
35
|
+
githubUrl: 'https://github.com/adarshrkumar/The-ATSDC-Stack',
|
|
36
|
+
} as const;
|