@techstream/quark-create-app 1.5.3 → 1.6.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/package.json +4 -2
  2. package/src/index.js +52 -9
  3. package/templates/base-project/.github/dependabot.yml +12 -0
  4. package/templates/base-project/.github/workflows/ci.yml +97 -0
  5. package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
  6. package/templates/base-project/.github/workflows/release.yml +38 -0
  7. package/templates/base-project/apps/web/biome.json +7 -0
  8. package/templates/base-project/apps/web/jsconfig.json +5 -5
  9. package/templates/base-project/apps/web/next.config.js +86 -1
  10. package/templates/base-project/apps/web/package.json +4 -4
  11. package/templates/base-project/apps/web/src/app/api/auth/register/route.js +6 -7
  12. package/templates/base-project/apps/web/src/app/layout.js +3 -4
  13. package/templates/base-project/apps/web/src/app/manifest.js +12 -0
  14. package/templates/base-project/apps/web/src/app/robots.js +21 -0
  15. package/templates/base-project/apps/web/src/app/sitemap.js +20 -0
  16. package/templates/base-project/apps/web/src/lib/seo/indexing.js +23 -0
  17. package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +33 -0
  18. package/templates/base-project/apps/web/src/proxy.js +1 -2
  19. package/templates/base-project/apps/worker/package.json +4 -4
  20. package/templates/base-project/apps/worker/src/index.js +26 -12
  21. package/templates/base-project/apps/worker/src/index.test.js +296 -15
  22. package/templates/base-project/biome.json +44 -0
  23. package/templates/base-project/docker-compose.yml +7 -4
  24. package/templates/base-project/package.json +1 -1
  25. package/templates/base-project/packages/db/package.json +1 -1
  26. package/templates/base-project/packages/db/prisma/schema.prisma +1 -17
  27. package/templates/base-project/packages/db/prisma.config.ts +3 -2
  28. package/templates/base-project/packages/db/scripts/seed.js +117 -30
  29. package/templates/base-project/packages/db/src/queries.js +52 -118
  30. package/templates/base-project/packages/db/src/queries.test.js +0 -29
  31. package/templates/base-project/packages/db/src/schemas.js +0 -12
  32. package/templates/base-project/pnpm-workspace.yaml +4 -0
  33. package/templates/base-project/turbo.json +5 -3
  34. package/templates/config/package.json +2 -0
  35. package/templates/config/src/environment.js +270 -0
  36. package/templates/config/src/index.js +10 -18
  37. package/templates/config/src/load-config.js +135 -0
  38. package/templates/config/src/validate-env.js +60 -2
  39. package/templates/jobs/package.json +2 -2
  40. package/templates/jobs/src/definitions.test.js +34 -0
  41. package/templates/jobs/src/index.js +1 -1
  42. package/templates/ui/package.json +4 -4
  43. package/templates/ui/src/button.test.js +23 -0
  44. package/templates/ui/src/index.js +1 -3
  45. package/templates/base-project/apps/web/src/app/api/posts/[id]/route.js +0 -65
  46. package/templates/base-project/apps/web/src/app/api/posts/route.js +0 -95
  47. package/templates/ui/src/card.js +0 -14
  48. package/templates/ui/src/input.js +0 -11
@@ -1,40 +1,127 @@
1
+ import { faker } from "@faker-js/faker";
1
2
  import bcrypt from "bcryptjs";
2
- import { PrismaClient } from "../src/generated/prisma/client.js";
3
+ import { prisma } from "../src/index.js";
3
4
 
4
- const prisma = new PrismaClient();
5
+ // Deterministic output change this integer to get a different but consistent dataset
6
+ faker.seed(42);
5
7
 
6
- async function main() {
7
- console.log("Seeding database...");
8
-
9
- const email = "test@example.com";
10
- const password = await bcrypt.hash("Password1", 12);
11
-
12
- const user = await prisma.user.upsert({
13
- where: { email },
14
- update: {},
15
- create: {
16
- email,
17
- name: "Test User",
18
- password,
19
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=test",
20
- posts: {
21
- create: [
22
- {
23
- title: "Hello World",
24
- content: "This is a seeded post. Welcome to Quark!",
25
- published: true,
26
- },
27
- {
28
- title: "Draft Post",
29
- content: "This is a draft post. It is not published yet.",
30
- published: false,
31
- },
32
- ],
8
+ const PROFILE = process.env.SEED_PROFILE ?? "dev";
9
+ const VALID_PROFILES = ["minimal", "dev"];
10
+
11
+ if (!VALID_PROFILES.includes(PROFILE)) {
12
+ console.error(
13
+ `Unknown SEED_PROFILE "${PROFILE}". Valid options: ${VALID_PROFILES.join(", ")}`,
14
+ );
15
+ process.exit(1);
16
+ }
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Seeders
20
+ // ---------------------------------------------------------------------------
21
+
22
+ async function seedUsers() {
23
+ const users = [
24
+ {
25
+ email: "admin@example.com",
26
+ name: "Admin User",
27
+ role: "admin",
28
+ password: "Password1",
29
+ },
30
+ {
31
+ email: "viewer@example.com",
32
+ name: "Viewer User",
33
+ role: "viewer",
34
+ password: "Password1",
35
+ },
36
+ ];
37
+
38
+ const seeded = [];
39
+ for (const userData of users) {
40
+ // Skip hashing if the user already exists — upsert update:{} won't use it anyway.
41
+ const existing = await prisma.user.findUnique({
42
+ where: { email: userData.email },
43
+ });
44
+ const hashed =
45
+ existing?.password ?? (await bcrypt.hash(userData.password, 12));
46
+ const user = await prisma.user.upsert({
47
+ where: { email: userData.email },
48
+ // update:{} intentionally leaves existing users unchanged on re-seed.
49
+ // To reset a user's password or role, delete the row first or update the create block.
50
+ update: {},
51
+ create: {
52
+ email: userData.email,
53
+ name: userData.name,
54
+ role: userData.role,
55
+ password: hashed,
56
+ image: `https://api.dicebear.com/7.x/avataaars/svg?seed=${userData.email}`,
33
57
  },
58
+ });
59
+ seeded.push(user);
60
+ console.log(` ✓ User: ${user.email} (${user.role})`);
61
+ }
62
+ return seeded;
63
+ }
64
+
65
+ async function seedDevData(users) {
66
+ // Audit logs — representative actions per user
67
+ const actions = ["user.login", "user.update", "file.upload"];
68
+ const userIds = users.map((u) => u.id);
69
+ // Scoped to seeded user IDs so real audit entries in a shared dev DB are not wiped.
70
+ await prisma.auditLog.deleteMany({
71
+ where: { userId: { in: userIds }, action: { in: actions } },
72
+ });
73
+ for (const user of users) {
74
+ for (const action of actions) {
75
+ await prisma.auditLog.create({
76
+ data: {
77
+ userId: user.id,
78
+ action,
79
+ entity: "User",
80
+ entityId: user.id,
81
+ metadata: {
82
+ ip: faker.internet.ip(),
83
+ userAgent: faker.internet.userAgent(),
84
+ },
85
+ },
86
+ });
87
+ }
88
+ }
89
+ console.log(` ✓ AuditLogs: ${users.length * actions.length} rows`);
90
+
91
+ // A sample pending job so the worker dashboard has something to show
92
+ await prisma.job.deleteMany({ where: { name: "seed-example-job" } });
93
+ await prisma.job.create({
94
+ data: {
95
+ queue: "default",
96
+ name: "seed-example-job",
97
+ data: { message: "Hello from seed" },
98
+ status: "PENDING",
34
99
  },
35
100
  });
101
+ console.log(" ✓ Job: seed-example-job (PENDING)");
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Add your own model seeders below and call them from main()
106
+ // Example:
107
+ // async function seedTeams(users) {
108
+ // await prisma.team.upsert({ where: { slug: "acme" }, update: {}, create: { ... } });
109
+ // }
110
+ // ---------------------------------------------------------------------------
111
+
112
+ async function main() {
113
+ console.log(`\nSeeding database [profile: ${PROFILE}]...\n`);
114
+
115
+ const users = await seedUsers();
116
+
117
+ if (PROFILE === "dev") {
118
+ await seedDevData(users);
119
+ }
120
+
121
+ // Add your seeders here:
122
+ // await seedTeams(users);
36
123
 
37
- console.log("Seeded user:", user.email);
124
+ console.log("\nDone.\n");
38
125
  }
39
126
 
40
127
  main()
@@ -15,6 +15,8 @@ const USER_SAFE_SELECT = {
15
15
  updatedAt: true,
16
16
  };
17
17
 
18
+ export { USER_SAFE_SELECT };
19
+
18
20
  // User queries
19
21
  export const user = {
20
22
  findById: (id) => {
@@ -23,12 +25,7 @@ export const user = {
23
25
  select: USER_SAFE_SELECT,
24
26
  });
25
27
  },
26
- findByIdWithPosts: (id) => {
27
- return prisma.user.findUnique({
28
- where: { id },
29
- select: { ...USER_SAFE_SELECT, posts: true },
30
- });
31
- },
28
+
32
29
  /**
33
30
  * findByEmail returns ALL fields including password.
34
31
  * Only use for internal auth — never expose the result directly to clients.
@@ -67,69 +64,6 @@ export const user = {
67
64
  },
68
65
  };
69
66
 
70
- /**
71
- * Safe author include — returns author without sensitive fields.
72
- */
73
- const AUTHOR_SAFE_INCLUDE = { author: { select: USER_SAFE_SELECT } };
74
-
75
- // Post queries
76
- export const post = {
77
- findById: (id) => {
78
- return prisma.post.findUnique({
79
- where: { id },
80
- include: AUTHOR_SAFE_INCLUDE,
81
- });
82
- },
83
- findAll: (options = {}) => {
84
- const { skip = 0, take = 10, where, orderBy } = options;
85
- return prisma.post.findMany({
86
- where,
87
- skip,
88
- take,
89
- include: AUTHOR_SAFE_INCLUDE,
90
- orderBy: orderBy || { createdAt: "desc" },
91
- });
92
- },
93
- findPublished: (options = {}) => {
94
- const { skip = 0, take = 10 } = options;
95
- return prisma.post.findMany({
96
- where: { published: true },
97
- skip,
98
- take,
99
- include: AUTHOR_SAFE_INCLUDE,
100
- orderBy: { createdAt: "desc" },
101
- });
102
- },
103
- findByAuthor: (authorId, options = {}) => {
104
- const { skip = 0, take = 10 } = options;
105
- return prisma.post.findMany({
106
- where: { authorId },
107
- skip,
108
- take,
109
- include: AUTHOR_SAFE_INCLUDE,
110
- orderBy: { createdAt: "desc" },
111
- });
112
- },
113
- create: (data) => {
114
- return prisma.post.create({
115
- data,
116
- include: AUTHOR_SAFE_INCLUDE,
117
- });
118
- },
119
- update: (id, data) => {
120
- return prisma.post.update({
121
- where: { id },
122
- data,
123
- include: AUTHOR_SAFE_INCLUDE,
124
- });
125
- },
126
- delete: (id) => {
127
- return prisma.post.delete({
128
- where: { id },
129
- });
130
- },
131
- };
132
-
133
67
  // Note: Job tracking is handled by BullMQ's built-in Redis persistence.
134
68
  // The Prisma Job model is retained in the schema for optional audit/reporting
135
69
  // but these query helpers have been removed to avoid confusion with BullMQ.
@@ -231,6 +165,55 @@ export const verificationToken = {
231
165
  },
232
166
  };
233
167
 
168
+ // AuditLog queries
169
+ export const auditLog = {
170
+ findAll: (options = {}) => {
171
+ const { skip = 0, take = 50 } = options;
172
+ return prisma.auditLog.findMany({
173
+ skip,
174
+ take,
175
+ include: { user: { select: { id: true, email: true, name: true } } },
176
+ orderBy: { createdAt: "desc" },
177
+ });
178
+ },
179
+ findByUserId: (userId, options = {}) => {
180
+ const { skip = 0, take = 50 } = options;
181
+ return prisma.auditLog.findMany({
182
+ where: { userId },
183
+ skip,
184
+ take,
185
+ include: { user: { select: { id: true, email: true, name: true } } },
186
+ orderBy: { createdAt: "desc" },
187
+ });
188
+ },
189
+ findByEntity: (entity, options = {}) => {
190
+ const { skip = 0, take = 50 } = options;
191
+ return prisma.auditLog.findMany({
192
+ where: { entity },
193
+ skip,
194
+ take,
195
+ include: { user: { select: { id: true, email: true, name: true } } },
196
+ orderBy: { createdAt: "desc" },
197
+ });
198
+ },
199
+ findByAction: (action, options = {}) => {
200
+ const { skip = 0, take = 50 } = options;
201
+ return prisma.auditLog.findMany({
202
+ where: { action },
203
+ skip,
204
+ take,
205
+ include: { user: { select: { id: true, email: true, name: true } } },
206
+ orderBy: { createdAt: "desc" },
207
+ });
208
+ },
209
+ create: (data) => {
210
+ return prisma.auditLog.create({
211
+ data,
212
+ include: { user: { select: { id: true, email: true, name: true } } },
213
+ });
214
+ },
215
+ };
216
+
234
217
  // File queries
235
218
  export const file = {
236
219
  create: (data) => {
@@ -285,52 +268,3 @@ export const file = {
285
268
  return prisma.file.count({ where });
286
269
  },
287
270
  };
288
-
289
- // AuditLog queries
290
- export const auditLog = {
291
- findAll: (options = {}) => {
292
- const { skip = 0, take = 50 } = options;
293
- return prisma.auditLog.findMany({
294
- skip,
295
- take,
296
- include: { user: { select: { id: true, email: true, name: true } } },
297
- orderBy: { createdAt: "desc" },
298
- });
299
- },
300
- findByUserId: (userId, options = {}) => {
301
- const { skip = 0, take = 50 } = options;
302
- return prisma.auditLog.findMany({
303
- where: { userId },
304
- skip,
305
- take,
306
- include: { user: { select: { id: true, email: true, name: true } } },
307
- orderBy: { createdAt: "desc" },
308
- });
309
- },
310
- findByEntity: (entity, options = {}) => {
311
- const { skip = 0, take = 50 } = options;
312
- return prisma.auditLog.findMany({
313
- where: { entity },
314
- skip,
315
- take,
316
- include: { user: { select: { id: true, email: true, name: true } } },
317
- orderBy: { createdAt: "desc" },
318
- });
319
- },
320
- findByAction: (action, options = {}) => {
321
- const { skip = 0, take = 50 } = options;
322
- return prisma.auditLog.findMany({
323
- where: { action },
324
- skip,
325
- take,
326
- include: { user: { select: { id: true, email: true, name: true } } },
327
- orderBy: { createdAt: "desc" },
328
- });
329
- },
330
- create: (data) => {
331
- return prisma.auditLog.create({
332
- data,
333
- include: { user: { select: { id: true, email: true, name: true } } },
334
- });
335
- },
336
- };
@@ -7,10 +7,6 @@ const _mockPrisma = {
7
7
  findUnique: async (params) => params,
8
8
  create: async (params) => params,
9
9
  },
10
- post: {
11
- create: async (params) => params,
12
- findMany: async (params) => params,
13
- },
14
10
  };
15
11
 
16
12
  // Mock module for queries
@@ -31,15 +27,6 @@ const mockQueries = {
31
27
  ...data,
32
28
  }),
33
29
  },
34
- post: {
35
- create: async (data) => ({
36
- id: "1",
37
- ...data,
38
- }),
39
- findPublished: async () => [
40
- { id: "1", title: "Published", published: true },
41
- ],
42
- },
43
30
  };
44
31
 
45
32
  test("User Queries - findById calls prisma with correct params", async () => {
@@ -61,19 +48,3 @@ test("User Queries - create calls prisma with correct params", async () => {
61
48
  assert.strictEqual(result.email, "new@example.com");
62
49
  assert.strictEqual(result.name, "New User");
63
50
  });
64
-
65
- test("Post Queries - create calls prisma with correct params", async () => {
66
- const result = await mockQueries.post.create({
67
- title: "Test Post",
68
- authorId: "1",
69
- });
70
- assert.strictEqual(result.title, "Test Post");
71
- assert.strictEqual(result.authorId, "1");
72
- });
73
-
74
- test("Post Queries - findPublished returns only published posts", async () => {
75
- const result = await mockQueries.post.findPublished();
76
- assert.ok(Array.isArray(result));
77
- assert.strictEqual(result.length, 1);
78
- assert.strictEqual(result[0].published, true);
79
- });
@@ -23,18 +23,6 @@ export const userUpdateSchema = z.object({
23
23
  image: z.string().url().optional(),
24
24
  });
25
25
 
26
- export const postCreateSchema = z.object({
27
- title: z.string().min(1, "Title is required"),
28
- content: z.string().optional(),
29
- published: z.boolean().optional(),
30
- });
31
-
32
- export const postUpdateSchema = z.object({
33
- title: z.string().min(1, "Title is required").optional(),
34
- content: z.string().optional(),
35
- published: z.boolean().optional(),
36
- });
37
-
38
26
  export const fileUploadSchema = z.object({
39
27
  filename: z.string().min(1, "Filename is required"),
40
28
  mimeType: z.string().min(1, "MIME type is required"),
@@ -5,3 +5,7 @@ packages:
5
5
  onlyBuiltDependencies:
6
6
  - '@prisma/engines'
7
7
  - prisma
8
+ - esbuild
9
+ - msgpackr-extract
10
+ - sharp
11
+ - simple-git-hooks
@@ -26,9 +26,11 @@
26
26
  "db:seed": {
27
27
  "cache": false
28
28
  },
29
- "db:studio": {
30
- "cache": false,
31
- "persistent": true
29
+ "sync-templates": {
30
+ "cache": false
31
+ },
32
+ "sync-templates:check": {
33
+ "cache": false
32
34
  },
33
35
  "dev": {
34
36
  "cache": false,
@@ -5,6 +5,8 @@
5
5
  "exports": {
6
6
  ".": "./src/index.js",
7
7
  "./app-url": "./src/app-url.js",
8
+ "./environment": "./src/environment.js",
9
+ "./load-config": "./src/load-config.js",
8
10
  "./validate-env": "./src/validate-env.js"
9
11
  }
10
12
  }