stackkit 0.2.9 → 0.3.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.
Files changed (35) hide show
  1. package/bin/stackkit.js +8 -5
  2. package/dist/cli/add.js +4 -9
  3. package/dist/cli/create.js +4 -9
  4. package/dist/cli/doctor.js +11 -32
  5. package/dist/lib/constants.js +3 -4
  6. package/dist/lib/conversion/js-conversion.js +20 -16
  7. package/dist/lib/discovery/installed-detection.js +28 -38
  8. package/dist/lib/discovery/module-discovery.d.ts +0 -15
  9. package/dist/lib/discovery/module-discovery.js +15 -50
  10. package/dist/lib/framework/framework-utils.d.ts +4 -5
  11. package/dist/lib/framework/framework-utils.js +38 -49
  12. package/dist/lib/generation/code-generator.d.ts +11 -12
  13. package/dist/lib/generation/code-generator.js +54 -134
  14. package/dist/lib/generation/generator-utils.js +3 -15
  15. package/dist/lib/project/detect.js +11 -19
  16. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +66 -8
  17. package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +7 -0
  18. package/modules/auth/better-auth/files/express/modules/auth/auth.route.ts +5 -0
  19. package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +145 -11
  20. package/modules/auth/better-auth/files/express/modules/auth/{auth.interface.ts → auth.type.ts} +16 -6
  21. package/modules/auth/better-auth/files/shared/config/env.ts +8 -4
  22. package/modules/auth/better-auth/files/shared/lib/auth.ts +24 -25
  23. package/modules/auth/better-auth/files/shared/mongoose/auth/constants.ts +11 -0
  24. package/modules/auth/better-auth/files/shared/mongoose/auth/helper.ts +51 -0
  25. package/modules/auth/better-auth/files/shared/utils/email.ts +0 -1
  26. package/modules/auth/better-auth/generator.json +24 -20
  27. package/modules/database/mongoose/files/lib/mongoose.ts +28 -3
  28. package/modules/database/mongoose/generator.json +1 -1
  29. package/package.json +1 -1
  30. package/templates/express/README.md +81 -0
  31. package/templates/express/env.example +1 -0
  32. package/templates/express/src/config/env.ts +4 -2
  33. package/templates/express/src/server.ts +50 -1
  34. package/templates/nextjs/README.md +96 -0
  35. package/templates/react/README.md +56 -0
@@ -1,49 +1,49 @@
1
- import { Role, UserStatus } from "@prisma/client";
2
1
  import { betterAuth } from "better-auth";
3
2
  import { bearer, emailOTP } from "better-auth/plugins";
4
3
  {{#if combo == "prisma:express"}}
4
+ import { Role, UserStatus } from "@prisma/client";
5
5
  import { envVars } from "../config/env";
6
6
  import { sendEmail } from "../shared/utils/email";
7
7
  import { prisma } from "../database/prisma";
8
8
  import { prismaAdapter } from "better-auth/adapters/prisma";
9
9
  {{/if}}
10
-
11
10
  {{#if combo == "prisma:nextjs"}}
11
+ import { Role, UserStatus } from "@prisma/client";
12
12
  import { sendEmail } from "../service/email/email-service";
13
13
  import { prisma } from "../database/prisma";
14
14
  import { envVars } from "@/lib/env";
15
15
  import { prismaAdapter } from "better-auth/adapters/prisma";
16
16
  {{/if}}
17
-
18
17
  {{#if combo == "mongoose:express"}}
19
18
  import { envVars } from "../config/env";
20
- import { sendEmail } from "../../shared/email/email-service";
21
- import { mongoose } from "../../database/mongoose";
19
+ import { Role, UserStatus } from "../modules/auth/auth.constants";
20
+ import { sendEmail } from "../shared/utils/email";
21
+ import { getMongoClient, getMongoDb, mongoose } from "../database/mongoose";
22
22
  import { mongodbAdapter } from "better-auth/adapters/mongodb";
23
23
  {{/if}}
24
-
25
24
  {{#if combo == "mongoose:nextjs"}}
26
25
  import { sendEmail } from "../service/email/email-service";
27
- import { mongoose } from "../database/mongoose";
26
+ import { getMongoClient, getMongoDb, mongoose } from "../database/mongoose";
27
+ import { Role, UserStatus } from "@/lib/auth/auth-constants";
28
28
  import { envVars } from "@/lib/env";
29
29
  import { mongodbAdapter } from "better-auth/adapters/mongodb";
30
30
  {{/if}}
31
31
 
32
32
  {{#if database == "mongoose"}}
33
- export async function initAuth() {
34
- const mongooseInstance = await mongoose();
35
- const client = mongooseInstance.connection.getClient();
36
- const db = client.db();
33
+ await mongoose();
34
+ const client = getMongoClient();
35
+ const db = getMongoDb();
36
+ const usersCollection = db.collection("user");
37
37
  {{/if}}
38
38
 
39
- {{#if database == "mongoose"}}return{{else}}export const auth = {{/if}} betterAuth({
39
+ export const auth = betterAuth({
40
40
  {{#if database == "prisma"}}
41
41
  database: prismaAdapter(prisma, {
42
42
  provider: "{{prismaProvider}}",
43
43
  }),
44
44
  {{/if}}
45
45
  {{#if database == "mongoose"}}
46
- database: mongodbAdapter(db, { client }),
46
+ database: mongodbAdapter(db, { client, transaction: false }),
47
47
  {{/if}}
48
48
  baseURL: envVars.BETTER_AUTH_URL,
49
49
  secret: envVars.BETTER_AUTH_SECRET,
@@ -112,11 +112,16 @@ const db = client.db();
112
112
  overrideDefaultEmailVerification: true,
113
113
  async sendVerificationOTP({ email, otp, type }) {
114
114
  if (type === "email-verification") {
115
+ {{#if database == "prisma"}}
115
116
  const user = await prisma.user.findUnique({
116
117
  where: {
117
118
  email,
118
119
  },
119
120
  });
121
+ {{/if}}
122
+ {{#if database == "mongoose"}}
123
+ const user = await usersCollection.findOne({ email });
124
+ {{/if}}
120
125
 
121
126
  if (!user) {
122
127
  console.error(
@@ -144,11 +149,16 @@ const db = client.db();
144
149
  });
145
150
  }
146
151
  } else if (type === "forget-password") {
152
+ {{#if database == "prisma"}}
147
153
  const user = await prisma.user.findUnique({
148
154
  where: {
149
155
  email,
150
156
  },
151
157
  });
158
+ {{/if}}
159
+ {{#if database == "mongoose"}}
160
+ const user = await usersCollection.findOne({ email });
161
+ {{/if}}
152
162
 
153
163
  if (user) {
154
164
  sendEmail({
@@ -179,7 +189,6 @@ const db = client.db();
179
189
  signIn: `${envVars.BETTER_AUTH_URL}/api/v1/auth/google/success`,
180
190
  },
181
191
  advanced: {
182
- // disableCSRFCheck: true,
183
192
  useSecureCookies: false,
184
193
  cookies: {
185
194
  state: {
@@ -200,14 +209,4 @@ const db = client.db();
200
209
  },
201
210
  },
202
211
  },
203
- })
204
- {{#if database == "mongoose"}}
205
- };
206
-
207
- export let auth: ReturnType<typeof betterAuth> | undefined = undefined;
208
-
209
- export async function setupAuth() {
210
- auth = await initAuth();
211
- return auth;
212
- }
213
- {{/if}}
212
+ });
@@ -0,0 +1,11 @@
1
+ export const Role = {
2
+ ADMIN: "ADMIN",
3
+ USER: "USER",
4
+ SUPER_ADMIN: "SUPER_ADMIN",
5
+ } as const;
6
+
7
+ export const UserStatus = {
8
+ ACTIVE: "ACTIVE",
9
+ BLOCKED: "BLOCKED",
10
+ DELETED: "DELETED",
11
+ } as const;
@@ -0,0 +1,51 @@
1
+ import status from "http-status";
2
+ import { getMongoDb, mongoose } from "../../database/mongoose";
3
+ import { AppError } from "../../shared/errors/app-error";
4
+
5
+ export type AuthUser = {
6
+ id: string;
7
+ role: string;
8
+ name: string;
9
+ email: string;
10
+ status?: string;
11
+ isDeleted?: boolean;
12
+ emailVerified?: boolean;
13
+ needPasswordChange?: boolean;
14
+ deletedAt?: Date | null;
15
+ };
16
+
17
+ export type AuthUserDocument = AuthUser & {
18
+ createdAt?: Date;
19
+ updatedAt?: Date;
20
+ };
21
+
22
+ export type AuthSessionDocument = {
23
+ token: string;
24
+ userId: string;
25
+ createdAt?: Date;
26
+ updatedAt?: Date;
27
+ expiresAt?: Date;
28
+ };
29
+
30
+ export const getAuthCollections = async () => {
31
+ await mongoose();
32
+
33
+ try {
34
+ const db = getMongoDb();
35
+
36
+ return {
37
+ users: db.collection<AuthUserDocument>("user"),
38
+ sessions: db.collection<AuthSessionDocument>("session"),
39
+ };
40
+ } catch {
41
+ throw new AppError(
42
+ status.INTERNAL_SERVER_ERROR,
43
+ "Auth database is not initialized",
44
+ );
45
+ }
46
+ };
47
+
48
+ export const deleteAuthUserById = async (id: string) => {
49
+ const { users } = await getAuthCollections();
50
+ await users.deleteOne({ id });
51
+ };
@@ -7,7 +7,6 @@ import { envVars } from "../../config/env";
7
7
  import { AppError } from "../errors/app-error";
8
8
  {{/if}}
9
9
  {{#if framework == "nextjs"}}
10
- import nodemailer from "nodemailer";
11
10
  import { renderEmailTemplate } from "../email/otp-template";
12
11
  import { envVars } from "../env";
13
12
  {{/if}}
@@ -27,6 +27,18 @@
27
27
  "destination": "src/modules/auth/*",
28
28
  "condition": { "framework": "express" }
29
29
  },
30
+ {
31
+ "type": "create-file",
32
+ "source": "shared/mongoose/auth/helper.ts",
33
+ "destination": "src/modules/auth/auth.helper.ts",
34
+ "condition": { "framework": "express", "database": "mongoose" }
35
+ },
36
+ {
37
+ "type": "create-file",
38
+ "source": "shared/mongoose/auth/constants.ts",
39
+ "destination": "src/modules/auth/auth.constants.ts",
40
+ "condition": { "framework": "express", "database": "mongoose" }
41
+ },
30
42
  {
31
43
  "type": "create-file",
32
44
  "source": "express/middlewares/authorize.ts",
@@ -67,7 +79,7 @@
67
79
  {
68
80
  "type": "add-code",
69
81
  "after": "// API routes",
70
- "code": ["app.use(\"/api/auth\", toNodeHandler(auth));"]
82
+ "code": ["app.use(\"/api/auth\", (req, res) => {", " return toNodeHandler(auth)(req, res);", "});"]
71
83
  },
72
84
  {
73
85
  "type": "add-code",
@@ -95,22 +107,6 @@
95
107
  }
96
108
  ]
97
109
  },
98
- {
99
- "type": "patch-file",
100
- "destination": "src/server.ts",
101
- "condition": { "framework": "express", "database": "mongoose" },
102
- "operations": [
103
- {
104
- "type": "add-import",
105
- "imports": ["import { setupAuth } from \"./modules/auth/auth\";"]
106
- },
107
- {
108
- "type": "add-code",
109
- "before": "app.listen(envVars.PORT, () => {",
110
- "code": "await setupAuth();"
111
- }
112
- ]
113
- },
114
110
  {
115
111
  "type": "add-dependency",
116
112
  "condition": { "framework": "express" },
@@ -127,7 +123,7 @@
127
123
  "type": "add-env",
128
124
  "condition": { "framework": "express" },
129
125
  "envVars": {
130
- "BETTER_AUTH_URL": "http://localhost:3000/api/auth",
126
+ "BETTER_AUTH_URL": "http://localhost:3000",
131
127
  "BETTER_AUTH_SECRET": "your_better_auth_secret",
132
128
  "BETTER_AUTH_SESSION_TOKEN_EXPIRES_IN": "1d",
133
129
  "BETTER_AUTH_SESSION_TOKEN_UPDATE_AGE": "1d",
@@ -135,6 +131,8 @@
135
131
  "ACCESS_TOKEN_EXPIRES_IN": "1d",
136
132
  "REFRESH_TOKEN_SECRET": "your_refresh_token_secret",
137
133
  "REFRESH_TOKEN_EXPIRES_IN": "7d",
134
+ "GOOGLE_CLIENT_ID": "your_google_client_id",
135
+ "GOOGLE_CLIENT_SECRET": "your_google_client_secret",
138
136
  "GOOGLE_CALLBACK_URL": "http://localhost:5000/api/auth/callback/google",
139
137
  "EMAIL_SENDER_SMTP_USER": "your_email@gmail.com",
140
138
  "EMAIL_SENDER_SMTP_PASS": "your_email_password",
@@ -173,7 +171,13 @@
173
171
  "type": "create-file",
174
172
  "source": "shared/lib/auth-client.ts",
175
173
  "destination": "lib/auth/auth-client.ts",
176
- "condition": { "framework": ["nextjs"] }
174
+ "condition": { "framework": "nextjs" }
175
+ },
176
+ {
177
+ "type": "create-file",
178
+ "source": "shared/mongoose/auth/constants.ts",
179
+ "destination": "lib/auth/auth-constants.ts",
180
+ "condition": { "framework": "nextjs" }
177
181
  },
178
182
  {
179
183
  "type": "create-file",
@@ -199,7 +203,7 @@
199
203
  "envVars": {
200
204
  "NEXT_PUBLIC_APP_URL": "http://localhost:3000",
201
205
  "NEXT_PUBLIC_FRONTEND_URL": "http://localhost:3000",
202
- "NEXT_PUBLIC_BETTER_AUTH_URL": "http://localhost:3000/api/auth",
206
+ "NEXT_PUBLIC_BETTER_AUTH_URL": "http://localhost:3000",
203
207
  "BETTER_AUTH_SECRET": "your_better_auth_secret",
204
208
  "GOOGLE_CLIENT_ID": "your_google_client_id",
205
209
  "GOOGLE_CLIENT_SECRET": "your_google_client_secret",
@@ -1,4 +1,5 @@
1
1
  import mongoose from "mongoose";
2
+ import { envVars } from "../config/env";
2
3
 
3
4
  type MongooseCache = {
4
5
  conn: typeof mongoose | null;
@@ -24,14 +25,13 @@ async function dbConnect(): Promise<typeof mongoose> {
24
25
  return cached.conn;
25
26
  }
26
27
 
27
- const uri = process.env.DATABASE_URL as string;
28
+ const uri = envVars.DATABASE_URL;
28
29
 
29
30
  if (!cached.promise) {
30
31
  const opts = {
31
32
  bufferCommands: false,
32
33
  connectTimeoutMS: 10000,
33
34
  serverSelectionTimeoutMS: 10000,
34
- // serverApi removed: not needed for mongoose-only connection
35
35
  };
36
36
 
37
37
  cached.promise = mongoose
@@ -60,4 +60,29 @@ async function dbConnect(): Promise<typeof mongoose> {
60
60
  return cached.conn;
61
61
  }
62
62
 
63
- export { dbConnect as mongoose, dbConnect as connectMongoose };
63
+ const getMongoClient = () => {
64
+ if (!mongoose.connection.readyState) {
65
+ throw new Error("MongoDB is not connected. Call mongoose() first.");
66
+ }
67
+
68
+ return mongoose.connection.getClient();
69
+ };
70
+
71
+ const getMongoDb = () => {
72
+ const db = mongoose.connection.db;
73
+
74
+ if (!db) {
75
+ throw new Error("MongoDB is not connected. Call mongoose() first.");
76
+ }
77
+
78
+ return db;
79
+ };
80
+
81
+ export {
82
+ dbConnect as connectMongoose,
83
+ getMongoClient,
84
+ getMongoDb,
85
+ dbConnect as mongoose,
86
+ dbConnect,
87
+ };
88
+
@@ -52,7 +52,7 @@
52
52
  {
53
53
  "type": "add-env",
54
54
  "envVars": {
55
- "DATABASE_URL": "mongodb://localhost:27017/database_name"
55
+ "DATABASE_URL": "mongodb://username:password@localhost:27017/database_name?authSource=admin"
56
56
  }
57
57
  }
58
58
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackkit",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "Production-ready CLI to create and extend JavaScript or TypeScript apps with modular stacks.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -31,6 +31,87 @@ npm run dev
31
31
 
32
32
  Use a `.env` file or environment variables for configuration. See `.env.example` for available keys.
33
33
 
34
+ ## Recommended Folder & File Structure
35
+
36
+ ```text
37
+ express-api/
38
+ ├── src/
39
+ │ ├── app.ts
40
+ │ ├── server.ts
41
+
42
+ │ ├── config/
43
+ │ │ ├── env.ts
44
+ │ │ ├── cors.ts
45
+ │ │ ├── rateLimit.ts
46
+ │ │ └── logger.ts # (optional)
47
+
48
+ │ ├── database/
49
+ │ │ └── prisma.ts # PrismaClient singleton
50
+
51
+ │ ├── lib/
52
+ │ └── auth.ts # Auth server config
53
+
54
+ │ ├── shared/
55
+ │ │ ├── middlewares/
56
+ │ │ │ ├── authorize.middleware.ts # reads session + attaches req.user
57
+ │ │ │ ├── error.middleware.ts
58
+ │ │ │ └── notFound.middleware.ts
59
+ │ │ ├── errors/
60
+ │ │ │ ├── ApiError.ts
61
+ │ │ │ └── errorCodes.ts
62
+ │ │ ├── utils/
63
+ │ │ │ ├── catchAsync.ts
64
+ │ │ │ ├── sendResponse.ts
65
+ │ │ │ └── pagination.ts
66
+ │ │ └── logger/
67
+ │ │ └── logger.ts
68
+
69
+ │ ├── modules/
70
+ │ │ ├── auth/
71
+ │ │ │ ├── auth.routes.ts
72
+ │ │ │ ├── auth.controller.ts
73
+ │ │ │ ├── auth.service.ts
74
+ │ │ │ ├── auth.validator.ts
75
+ │ │ │ └── auth.types.ts
76
+ │ │ ├── users/
77
+ │ │ │ ├── users.routes.ts
78
+ │ │ │ ├── users.controller.ts
79
+ │ │ │ ├── users.service.ts
80
+ │ │ │ ├── users.repository.ts
81
+ │ │ │ ├── users.validator.ts
82
+ │ │ │ └── users.types.ts
83
+ │ │ └── products/
84
+ │ │ ├── products.routes.ts
85
+ │ │ ├── products.controller.ts
86
+ │ │ ├── products.service.ts
87
+ │ │ ├── products.repository.ts
88
+ │ │ ├── products.validator.ts
89
+ │ │ └── products.types.ts
90
+
91
+ │ ├── routes/
92
+ │ │ └── index.ts # mounts all module routes
93
+ │ │
94
+ │ └── types/
95
+ │ └── express.d.ts # Request typing (req.user)
96
+
97
+ ├── prisma/
98
+ │ ├── schema.prisma
99
+ │ ├── migrations/
100
+ │ └── seed.ts
101
+
102
+ ├── tests/
103
+ │ ├── unit/
104
+ │ └── integration/
105
+
106
+ ├── Dockerfile
107
+ ├── docker-compose.yml
108
+ ├── package.json
109
+ ├── tsconfig.json
110
+ ├── eslint.config.js
111
+ ├── .env.example
112
+ └── README.md
113
+ ```
114
+
34
115
  ---
35
116
 
36
117
  ## Generated with StackKit
@@ -1,3 +1,4 @@
1
1
  PORT=5000
2
2
  NODE_ENV=development
3
+ APP_URL=http://localhost:5000
3
4
  FRONTEND_URL=http://localhost:3000
@@ -8,11 +8,12 @@ dotenv.config({ path: path.join(process.cwd(), ".env") });
8
8
  interface EnvConfig {
9
9
  NODE_ENV: string;
10
10
  PORT: string;
11
- FRONTEND_URL?: string;
11
+ APP_URL: string;
12
+ FRONTEND_URL: string;
12
13
  }
13
14
 
14
15
  const loadEnvVars = (): EnvConfig => {
15
- const requiredEnvVars = ["NODE_ENV", "PORT", "FRONTEND_URL"];
16
+ const requiredEnvVars = ["NODE_ENV", "PORT", "APP_URL", "FRONTEND_URL"];
16
17
 
17
18
  requiredEnvVars.forEach((varName) => {
18
19
  if (!process.env[varName]) {
@@ -26,6 +27,7 @@ const loadEnvVars = (): EnvConfig => {
26
27
  return {
27
28
  NODE_ENV: process.env.NODE_ENV as string,
28
29
  PORT: process.env.PORT as string,
30
+ APP_URL: process.env.APP_URL as string,
29
31
  FRONTEND_URL: process.env.FRONTEND_URL as string,
30
32
  };
31
33
  };
@@ -1,9 +1,12 @@
1
+ import { Server } from "http";
1
2
  import { app } from "./app";
2
3
  import { envVars } from "./config/env";
3
4
 
5
+ let server: Server | null = null;
6
+
4
7
  const bootstrap = async () => {
5
8
  try {
6
- app.listen(envVars.PORT, () => {
9
+ server = app.listen(envVars.PORT, () => {
7
10
  console.log(`Server is running on http://localhost:${envVars.PORT}`);
8
11
  });
9
12
  } catch (error) {
@@ -11,4 +14,50 @@ const bootstrap = async () => {
11
14
  }
12
15
  };
13
16
 
17
+ process.on("SIGTERM", () => {
18
+ console.log("SIGTERM signal received: closing HTTP server");
19
+ if (server) {
20
+ server.close(() => {
21
+ console.log("HTTP server closed");
22
+ });
23
+ }
24
+
25
+ process.exit(0);
26
+ });
27
+
28
+ process.on("SIGINT", () => {
29
+ console.log("SIGINT signal received: closing HTTP server");
30
+ if (server) {
31
+ server.close(() => {
32
+ console.log("HTTP server closed");
33
+ });
34
+ }
35
+
36
+ process.exit(0);
37
+ });
38
+
39
+ process.on("uncaughtException", (err) => {
40
+ console.error("Uncaught Exception:", err);
41
+
42
+ if (server) {
43
+ server.close(() => {
44
+ console.log("HTTP server closed due to uncaught exception");
45
+ });
46
+ }
47
+
48
+ process.exit(1);
49
+ });
50
+
51
+ process.on("unhandledRejection", (reason, promise) => {
52
+ console.error("Unhandled Rejection at:", promise, "reason:", reason);
53
+
54
+ if (server) {
55
+ server.close(() => {
56
+ console.log("HTTP server closed due to unhandled rejection");
57
+ });
58
+ }
59
+
60
+ process.exit(1);
61
+ });
62
+
14
63
  bootstrap();
@@ -50,6 +50,102 @@ lib/
50
50
  public/ # Static assets
51
51
  ```
52
52
 
53
+ ## Recommended Folder & File Structure
54
+
55
+ ```text
56
+ next-app/
57
+ ├── src/
58
+ │ ├── app/
59
+ │ │ ├── (public)/
60
+ │ │ │ ├── layout.tsx
61
+ │ │ │ ├── page.tsx
62
+ │ │ │ └── pricing/page.tsx
63
+ │ │ ├── (dashboard)/
64
+ │ │ │ ├── layout.tsx
65
+ │ │ │ ├── dashboard/page.tsx
66
+ │ │ │ ├── products/page.tsx
67
+ │ │ │ └── orders/page.tsx
68
+ │ │ ├── api/
69
+ │ │ │ ├── health/route.ts
70
+ │ │ │ └── auth/route.ts # (optional) if you expose auth endpoints
71
+ │ │ ├── layout.tsx
72
+ │ │ ├── globals.css
73
+ │ │ ├── error.tsx
74
+ │ │ └── not-found.tsx
75
+ │ │
76
+ │ ├── features/
77
+ │ │ ├── auth/
78
+ │ │ │ ├── components/
79
+ │ │ │ ├── actions/
80
+ │ │ │ ├── schemas/
81
+ │ │ │ ├── types/
82
+ │ │ │ └── index.ts
83
+ │ │ ├── products/
84
+ │ │ │ ├── components/
85
+ │ │ │ ├── actions/
86
+ │ │ │ ├── queries/
87
+ │ │ │ ├── schemas/
88
+ │ │ │ ├── types/
89
+ │ │ │ └── index.ts
90
+ │ │ └── orders/
91
+ │ │ ├── components/
92
+ │ │ ├── actions/
93
+ │ │ ├── queries/
94
+ │ │ ├── schemas/
95
+ │ │ ├── types/
96
+ │ │ └── index.ts
97
+ │ │
98
+ │ ├── components/
99
+ │ │ ├── ui/ # shadcn/ui only
100
+ │ │ └── shared/
101
+ │ │ ├── Header.tsx
102
+ │ │ ├── Footer.tsx
103
+ │ │ └── Sidebar.tsx
104
+ │ │
105
+ │ ├── server/ # server-only boundary
106
+ │ │ ├── auth/
107
+ │ │ │ ├── auth.ts # Auth (server config)
108
+ │ │ │ └── guards.ts
109
+ │ │ ├── db/
110
+ │ │ │ └── prisma.ts # PrismaClient singleton
111
+ │ │ ├── repositories/
112
+ │ │ │ ├── product.repo.ts
113
+ │ │ │ └── order.repo.ts
114
+ │ │ └── services/
115
+ │ │ ├── email.service.ts
116
+ │ │ └── storage.service.ts
117
+ │ │
118
+ │ ├── lib/
119
+ │ │ ├── env.ts
120
+ │ │ ├── utils.ts
121
+ │ │ ├── logger.ts
122
+ │ │ └── auth/
123
+ │ │ └── auth-client.ts # Auth (client helper)
124
+ │ │
125
+ │ ├── hooks/
126
+ │ │ └── useDebounce.ts
127
+ │ │
128
+ │ └── types/
129
+ │ └── global.d.ts
130
+
131
+ ├── prisma/
132
+ │ ├── schema.prisma
133
+ │ ├── migrations/
134
+ │ └── seed.ts
135
+
136
+ ├── public/
137
+ ├── tests/
138
+ │ ├── unit/
139
+ │ └── e2e/
140
+
141
+ ├── middleware.ts
142
+ ├── next.config.js
143
+ ├── package.json
144
+ ├── tsconfig.json
145
+ ├── .env.example
146
+ └── README.md
147
+ ```
148
+
53
149
  ## Environment Variables
54
150
 
55
151
  Create a `.env.local` (Next.js) file for local environment variables. Keep secrets out of the repository.