stackkit 0.2.3 → 0.2.7
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/dist/cli/add.js +2 -16
- package/dist/cli/create.js +2 -5
- package/dist/cli/doctor.js +0 -4
- package/dist/cli/list.js +73 -83
- package/dist/index.js +25 -44
- package/dist/lib/constants.d.ts +110 -0
- package/dist/lib/constants.js +112 -0
- package/dist/lib/conversion/js-conversion.js +27 -11
- package/dist/lib/discovery/installed-detection.js +3 -3
- package/dist/lib/discovery/module-discovery.d.ts +1 -1
- package/dist/lib/discovery/module-discovery.js +22 -6
- package/dist/lib/env/env-editor.js +41 -47
- package/dist/lib/fs/files.d.ts +0 -1
- package/dist/lib/fs/files.js +12 -40
- package/dist/lib/generation/code-generator.d.ts +4 -1
- package/dist/lib/generation/code-generator.js +39 -13
- package/dist/lib/pm/package-manager.d.ts +1 -1
- package/dist/lib/pm/package-manager.js +130 -14
- package/dist/lib/ui/logger.d.ts +8 -1
- package/dist/lib/ui/logger.js +60 -3
- package/dist/lib/utils/fs-helpers.d.ts +12 -0
- package/dist/lib/utils/fs-helpers.js +61 -0
- package/dist/lib/utils/json-loader.d.ts +6 -0
- package/dist/lib/utils/json-loader.js +34 -0
- package/dist/lib/utils/module-loader.d.ts +9 -0
- package/dist/lib/utils/module-loader.js +98 -0
- package/dist/lib/utils/package-root.d.ts +1 -0
- package/dist/lib/utils/package-root.js +75 -2
- package/dist/lib/utils/path-resolver.d.ts +9 -0
- package/dist/lib/utils/path-resolver.js +44 -0
- package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +3 -0
- package/modules/auth/authjs/files/nextjs/proxy.ts +1 -0
- package/modules/auth/authjs/files/shared/lib/auth.ts +119 -0
- package/modules/auth/authjs/files/{prisma → shared/prisma}/schema.prisma +11 -1
- package/modules/auth/authjs/generator.json +18 -8
- package/modules/auth/better-auth/files/express/middlewares/authorize.ts +54 -0
- package/modules/auth/better-auth/files/express/types/express.d.ts +16 -0
- package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +31 -0
- package/modules/auth/better-auth/files/nextjs/proxy.ts +34 -0
- package/modules/auth/better-auth/files/{lib → shared/lib}/auth-client.ts +1 -1
- package/modules/auth/better-auth/files/{lib → shared/lib}/auth.ts +46 -20
- package/modules/auth/better-auth/files/{prisma → shared/prisma}/schema.prisma +11 -2
- package/modules/auth/better-auth/generator.json +74 -19
- package/modules/database/mongoose/generator.json +16 -2
- package/modules/database/prisma/files/lib/prisma.ts +1 -1
- package/modules/database/prisma/files/prisma/schema.prisma +1 -2
- package/modules/database/prisma/generator.json +8 -1
- package/package.json +7 -7
- package/templates/express/env.example +2 -1
- package/templates/express/package.json +3 -4
- package/templates/express/src/app.ts +18 -25
- package/templates/express/src/config/cors.ts +12 -0
- package/templates/express/src/config/helmet.ts +5 -0
- package/templates/express/src/config/logger.ts +6 -0
- package/templates/express/src/config/rate-limit.ts +11 -0
- package/templates/express/src/{features → modules}/health/health.route.ts +1 -1
- package/templates/express/src/routes/index.ts +12 -0
- package/templates/express/src/shared/errors/api-error.ts +14 -0
- package/templates/express/src/shared/errors/error-codes.ts +9 -0
- package/templates/express/src/shared/logger/logger.ts +20 -0
- package/templates/express/src/{middlewares → shared/middlewares}/error.middleware.ts +1 -1
- package/templates/express/src/shared/middlewares/not-found.middleware.ts +9 -0
- package/templates/express/src/shared/utils/async-handler.ts +9 -0
- package/templates/express/src/shared/utils/pagination.ts +6 -0
- package/templates/express/src/shared/utils/response.ts +9 -0
- package/templates/express/tsconfig.json +9 -3
- package/templates/nextjs/next-env.d.ts +6 -0
- package/templates/react/dist/assets/index-D4AHT4dU.js +193 -0
- package/templates/react/dist/assets/index-rpwj5ZOX.css +1 -0
- package/templates/react/dist/index.html +14 -0
- package/templates/react/dist/vite.svg +1 -0
- package/templates/react/src/app/layouts/dashboard-layout.tsx +8 -0
- package/templates/react/src/app/layouts/public-layout.tsx +5 -0
- package/templates/react/src/app/providers.tsx +20 -0
- package/templates/react/src/app/router.tsx +21 -0
- package/templates/react/src/{pages/About.tsx → features/about/pages/about.tsx} +1 -1
- package/templates/react/src/{pages/Home.tsx → features/home/pages/home.tsx} +1 -1
- package/templates/react/src/main.tsx +2 -2
- package/templates/react/src/{api/client.ts → shared/api/http.ts} +1 -1
- package/templates/react/src/{pages/NotFound.tsx → shared/pages/not-found.tsx} +1 -1
- package/dist/lib/git-utils.d.ts +0 -1
- package/dist/lib/git-utils.js +0 -29
- package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +0 -2
- package/modules/auth/authjs/files/lib/auth.ts +0 -22
- package/templates/express/.env.example +0 -2
- package/templates/nextjs/.env.example +0 -1
- package/templates/react/.env.example +0 -1
- package/templates/react/.prettierignore +0 -4
- package/templates/react/.prettierrc +0 -9
- /package/modules/auth/better-auth/files/{api → nextjs/api}/auth/[...all]/route.ts +0 -0
- /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-service.ts +0 -0
- /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-templates.ts +0 -0
- /package/templates/express/src/{features → modules}/health/health.controller.ts +0 -0
- /package/templates/express/src/{features → modules}/health/health.service.ts +0 -0
- /package/templates/react/src/{components/ErrorBoundary.tsx → shared/components/error-boundary.tsx} +0 -0
- /package/templates/react/src/{components/Layout.tsx → shared/components/layout.tsx} +0 -0
- /package/templates/react/src/{components/Loading.tsx → shared/components/loading.tsx} +0 -0
- /package/templates/react/src/{components/SEO.tsx → shared/components/seo.tsx} +0 -0
- /package/templates/react/src/{lib/queryClient.ts → shared/lib/query-client.ts} +0 -0
|
@@ -1,35 +1,54 @@
|
|
|
1
1
|
import { betterAuth } from "better-auth";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
{
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
{{#if combo == "prisma:express"}}
|
|
3
|
+
import { sendEmail } from "../../shared/email/email-service";
|
|
4
|
+
import {
|
|
5
|
+
getPasswordResetEmailTemplate,
|
|
6
|
+
getVerificationEmailTemplate,
|
|
7
|
+
} from "../../shared/email/email-templates";
|
|
8
|
+
import { prisma } from "../../database/prisma";
|
|
7
9
|
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
8
|
-
{{/
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
{{/if}}
|
|
11
|
+
|
|
12
|
+
{{#if combo == "prisma:nextjs"}}
|
|
13
|
+
import { getPasswordResetEmailTemplate, getVerificationEmailTemplate } from "../service/email/email-templates";
|
|
14
|
+
import { sendEmail } from "../service/email/email-service";
|
|
15
|
+
import { prisma } from "../database/prisma";
|
|
16
|
+
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
17
|
+
{{/if}}
|
|
18
|
+
|
|
19
|
+
{{#if combo == "mongoose:express"}}
|
|
20
|
+
import { sendEmail } from "../../shared/email/email-service";
|
|
21
|
+
import {
|
|
22
|
+
getPasswordResetEmailTemplate,
|
|
23
|
+
getVerificationEmailTemplate,
|
|
24
|
+
} from "../../shared/email/email-templates";
|
|
25
|
+
import { mongoose } from "../../database/mongoose";
|
|
26
|
+
import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
|
27
|
+
{{/if}}
|
|
28
|
+
|
|
29
|
+
{{#if combo == "mongoose:nextjs"}}
|
|
30
|
+
import { getPasswordResetEmailTemplate, getVerificationEmailTemplate } from "../service/email/email-templates";
|
|
31
|
+
import { sendEmail } from "../service/email/email-service";
|
|
32
|
+
import { mongoose } from "../database/mongoose";
|
|
11
33
|
import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
|
12
|
-
{{/
|
|
13
|
-
{{/switch}}
|
|
34
|
+
{{/if}}
|
|
14
35
|
|
|
15
36
|
export async function initAuth() {
|
|
16
|
-
{{#if database ==
|
|
37
|
+
{{#if database == "mongoose"}}
|
|
17
38
|
const mongooseInstance = await mongoose();
|
|
18
39
|
const client = mongooseInstance.connection.getClient();
|
|
19
40
|
const db = client.db();
|
|
20
41
|
{{/if}}
|
|
21
42
|
|
|
22
43
|
return betterAuth({
|
|
23
|
-
{{#
|
|
24
|
-
{{#case prisma}}
|
|
44
|
+
{{#if database == "prisma"}}
|
|
25
45
|
database: prismaAdapter(prisma, {
|
|
26
46
|
provider: "{{prismaProvider}}",
|
|
27
47
|
}),
|
|
28
|
-
{{/
|
|
29
|
-
{{#
|
|
48
|
+
{{/if}}
|
|
49
|
+
{{#if database == "mongoose"}}
|
|
30
50
|
database: mongodbAdapter(db, { client }),
|
|
31
|
-
{{/
|
|
32
|
-
{{/switch}}
|
|
51
|
+
{{/if}}
|
|
33
52
|
baseURL: process.env.BETTER_AUTH_URL,
|
|
34
53
|
secret: process.env.BETTER_AUTH_SECRET,
|
|
35
54
|
trustedOrigins: [process.env.APP_URL!],
|
|
@@ -57,8 +76,10 @@ return betterAuth({
|
|
|
57
76
|
},
|
|
58
77
|
socialProviders: {
|
|
59
78
|
google: {
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
accessType: "offline",
|
|
80
|
+
prompt: "select_account consent",
|
|
81
|
+
clientId: process.env.GOOGLE_CLIENT_ID as string,
|
|
82
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
|
62
83
|
},
|
|
63
84
|
},
|
|
64
85
|
emailVerification: {
|
|
@@ -95,4 +116,9 @@ return betterAuth({
|
|
|
95
116
|
})
|
|
96
117
|
};
|
|
97
118
|
|
|
98
|
-
export
|
|
119
|
+
export let auth: any = undefined;
|
|
120
|
+
|
|
121
|
+
export async function setupAuth() {
|
|
122
|
+
auth = await initAuth();
|
|
123
|
+
return auth;
|
|
124
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
{{#
|
|
1
|
+
{{#if prismaProvider == "mongodb"}}
|
|
2
|
+
{{#var defaultId = @default(auto()) @map("_id") @db.ObjectId}}
|
|
3
|
+
{{else}}
|
|
4
|
+
{{#var defaultId = @default(cuid())}}
|
|
5
|
+
{{/if}}
|
|
2
6
|
model User {
|
|
3
7
|
id String @id {{defaultId}}
|
|
4
8
|
name String
|
|
@@ -9,7 +13,7 @@ model User {
|
|
|
9
13
|
updatedAt DateTime @updatedAt
|
|
10
14
|
sessions Session[]
|
|
11
15
|
accounts Account[]
|
|
12
|
-
role
|
|
16
|
+
role Role @default(USER)
|
|
13
17
|
|
|
14
18
|
@@unique([email])
|
|
15
19
|
@@map("user")
|
|
@@ -61,3 +65,8 @@ model Verification {
|
|
|
61
65
|
@@index([identifier])
|
|
62
66
|
@@map("verification")
|
|
63
67
|
}
|
|
68
|
+
|
|
69
|
+
enum Role {
|
|
70
|
+
USER
|
|
71
|
+
ADMIN
|
|
72
|
+
}
|
|
@@ -5,33 +5,81 @@
|
|
|
5
5
|
"operations": [
|
|
6
6
|
{
|
|
7
7
|
"type": "create-file",
|
|
8
|
-
"source": "lib/auth.ts",
|
|
9
|
-
"destination": "
|
|
10
|
-
"condition": { "framework": ["nextjs"
|
|
8
|
+
"source": "shared/lib/auth.ts",
|
|
9
|
+
"destination": "server/auth/auth.ts",
|
|
10
|
+
"condition": { "framework": ["nextjs"] }
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"type": "create-file",
|
|
14
|
-
"source": "lib/
|
|
15
|
-
"destination": "
|
|
16
|
-
"condition": { "framework": ["
|
|
14
|
+
"source": "shared/lib/auth.ts",
|
|
15
|
+
"destination": "src/modules/auth/auth.ts",
|
|
16
|
+
"condition": { "framework": ["express"] }
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"type": "create-file",
|
|
20
|
-
"source": "lib/email-
|
|
21
|
-
"destination": "
|
|
22
|
-
"condition": { "framework": ["nextjs"
|
|
20
|
+
"source": "shared/lib/email/email-service.ts",
|
|
21
|
+
"destination": "server/service/email/email-service.ts",
|
|
22
|
+
"condition": { "framework": ["nextjs"] }
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"type": "create-file",
|
|
26
|
-
"source": "
|
|
26
|
+
"source": "shared/lib/email/email-service.ts",
|
|
27
|
+
"destination": "src/shared/email/email-service.ts",
|
|
28
|
+
"condition": { "framework": ["express"] }
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "create-file",
|
|
32
|
+
"source": "shared/lib/email/email-templates.ts",
|
|
33
|
+
"destination": "server/service/email/email-templates.ts",
|
|
34
|
+
"condition": { "framework": ["nextjs"] }
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"type": "create-file",
|
|
38
|
+
"source": "shared/lib/email/email-templates.ts",
|
|
39
|
+
"destination": "src/shared/email/email-templates.ts",
|
|
40
|
+
"condition": { "framework": ["express"] }
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "create-file",
|
|
44
|
+
"source": "nextjs/api/auth/[...all]/route.ts",
|
|
27
45
|
"destination": "app/api/auth/[...all]/route.ts",
|
|
28
46
|
"condition": { "framework": "nextjs" }
|
|
29
47
|
},
|
|
30
48
|
{
|
|
31
49
|
"type": "create-file",
|
|
32
|
-
"source": "
|
|
33
|
-
"destination": "
|
|
34
|
-
"condition": { "framework":
|
|
50
|
+
"source": "nextjs/proxy.ts",
|
|
51
|
+
"destination": "proxy.ts",
|
|
52
|
+
"condition": { "framework": "nextjs" }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"type": "create-file",
|
|
56
|
+
"source": "nextjs/lib/auth/auth-guards.ts",
|
|
57
|
+
"destination": "server/auth/guards.ts",
|
|
58
|
+
"condition": { "framework": "nextjs" }
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "create-file",
|
|
62
|
+
"source": "express/middlewares/authorize.ts",
|
|
63
|
+
"destination": "src/shared/middlewares/authorize.middleware.ts",
|
|
64
|
+
"condition": { "framework": "express" }
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "create-file",
|
|
68
|
+
"source": "express/types/express.d.ts",
|
|
69
|
+
"destination": "src/types/express.d.ts",
|
|
70
|
+
"condition": { "framework": "express" }
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"type": "create-file",
|
|
74
|
+
"source": "shared/lib/auth-client.ts",
|
|
75
|
+
"destination": "lib/auth/auth-client.ts",
|
|
76
|
+
"condition": { "framework": ["nextjs"] }
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "create-file",
|
|
80
|
+
"source": "shared/lib/auth-client.ts",
|
|
81
|
+
"destination": "src/shared/lib/auth-client.ts",
|
|
82
|
+
"condition": { "framework": ["react"] }
|
|
35
83
|
},
|
|
36
84
|
{
|
|
37
85
|
"type": "patch-file",
|
|
@@ -40,7 +88,7 @@
|
|
|
40
88
|
"operations": [
|
|
41
89
|
{
|
|
42
90
|
"type": "add-to-bottom",
|
|
43
|
-
"source": "prisma/schema.prisma"
|
|
91
|
+
"source": "shared/prisma/schema.prisma"
|
|
44
92
|
}
|
|
45
93
|
]
|
|
46
94
|
},
|
|
@@ -51,12 +99,12 @@
|
|
|
51
99
|
"operations": [
|
|
52
100
|
{
|
|
53
101
|
"type": "add-import",
|
|
54
|
-
"imports": ["import {
|
|
102
|
+
"imports": ["import { setupAuth } from \"./modules/auth/auth\";"]
|
|
55
103
|
},
|
|
56
104
|
{
|
|
57
105
|
"type": "add-code",
|
|
58
106
|
"before": "const port = env.port;",
|
|
59
|
-
"code": "await
|
|
107
|
+
"code": "await setupAuth();"
|
|
60
108
|
}
|
|
61
109
|
]
|
|
62
110
|
},
|
|
@@ -67,13 +115,20 @@
|
|
|
67
115
|
"operations": [
|
|
68
116
|
{
|
|
69
117
|
"type": "add-import",
|
|
70
|
-
"imports": ["import { auth } from \"
|
|
118
|
+
"imports": ["import { auth } from \"./modules/auth/auth\";",
|
|
71
119
|
"import { toNodeHandler } from \"better-auth/node\";"]
|
|
72
120
|
},
|
|
73
121
|
{
|
|
74
122
|
"type": "add-code",
|
|
75
|
-
"after": "// routes",
|
|
76
|
-
"code":
|
|
123
|
+
"after": "// API routes",
|
|
124
|
+
"code": [
|
|
125
|
+
"app.all(\"/api/auth/*splat\", (req: Request, res: Response) => {",
|
|
126
|
+
" if (!auth) {",
|
|
127
|
+
" return res.status(503).json({ success: false, message: \"Auth not ready\" });",
|
|
128
|
+
" }",
|
|
129
|
+
" return toNodeHandler(auth)(req, res);",
|
|
130
|
+
"});"
|
|
131
|
+
]
|
|
77
132
|
}
|
|
78
133
|
]
|
|
79
134
|
},
|
|
@@ -6,12 +6,26 @@
|
|
|
6
6
|
{
|
|
7
7
|
"type": "create-file",
|
|
8
8
|
"source": "lib/mongoose.ts",
|
|
9
|
-
"destination": "
|
|
9
|
+
"destination": "server/database/mongoose.ts",
|
|
10
|
+
"condition": { "framework": ["nextjs"] }
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "create-file",
|
|
14
|
+
"source": "lib/mongoose.ts",
|
|
15
|
+
"destination": "src/database/mongoose.ts",
|
|
16
|
+
"condition": { "framework": ["express"] }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "create-file",
|
|
20
|
+
"source": "models/health.ts",
|
|
21
|
+
"destination": "src/modules/health/health.model.ts",
|
|
22
|
+
"condition": { "framework": ["express"] }
|
|
10
23
|
},
|
|
11
24
|
{
|
|
12
25
|
"type": "create-file",
|
|
13
26
|
"source": "models/health.ts",
|
|
14
|
-
"destination": "models/health.ts"
|
|
27
|
+
"destination": "server/database/models/health.model.ts",
|
|
28
|
+
"condition": { "framework": ["nextjs"] }
|
|
15
29
|
},
|
|
16
30
|
{
|
|
17
31
|
"type": "patch-file",
|
|
@@ -16,7 +16,14 @@
|
|
|
16
16
|
{
|
|
17
17
|
"type": "create-file",
|
|
18
18
|
"source": "lib/prisma.ts",
|
|
19
|
-
"destination": "
|
|
19
|
+
"destination": "server/database/prisma.ts",
|
|
20
|
+
"condition": { "framework": ["nextjs"] }
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"type": "create-file",
|
|
24
|
+
"source": "lib/prisma.ts",
|
|
25
|
+
"destination": "src/database/prisma.ts",
|
|
26
|
+
"condition": { "framework": ["express"] }
|
|
20
27
|
},
|
|
21
28
|
{
|
|
22
29
|
"type": "add-dependency",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stackkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
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": {
|
|
@@ -67,17 +67,17 @@
|
|
|
67
67
|
"@babel/core": "^7.28.5",
|
|
68
68
|
"@babel/preset-env": "^7.28.5",
|
|
69
69
|
"@babel/preset-typescript": "^7.28.5",
|
|
70
|
-
"@babel/preset-react": "^7.28.5"
|
|
70
|
+
"@babel/preset-react": "^7.28.5",
|
|
71
|
+
"recast": "^0.20.5",
|
|
72
|
+
"@babel/parser": "^7.28.5",
|
|
73
|
+
"@babel/plugin-transform-typescript": "^7.28.5",
|
|
74
|
+
"@babel/plugin-transform-react-jsx": "^7.27.1"
|
|
71
75
|
},
|
|
72
76
|
"devDependencies": {
|
|
73
77
|
"@types/fs-extra": "^11.0.4",
|
|
74
78
|
"@types/inquirer": "^9.0.7",
|
|
75
79
|
"@types/node": "^25.0.8",
|
|
76
80
|
"@types/validate-npm-package-name": "^4.0.2",
|
|
77
|
-
"typescript": "^5.3.3"
|
|
78
|
-
"recast": "^0.20.5",
|
|
79
|
-
"@babel/plugin-transform-typescript": "^7.28.5",
|
|
80
|
-
"@babel/parser": "^7.28.5",
|
|
81
|
-
"@babel/plugin-transform-react-jsx": "^7.27.1"
|
|
81
|
+
"typescript": "^5.3.3"
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"lint": "eslint src --ext .ts",
|
|
10
10
|
"lint:fix": "eslint src --ext .ts --fix",
|
|
11
|
-
"start": "node dist/server.js"
|
|
12
|
-
"start:prod": "cross-env NODE_ENV=production node dist/server.js"
|
|
11
|
+
"start": "node dist/server.js"
|
|
13
12
|
},
|
|
14
13
|
"dependencies": {
|
|
15
14
|
"cors": "^2.8.5",
|
|
16
15
|
"dotenv": "^17.2.3",
|
|
17
16
|
"express": "^5.2.1",
|
|
18
17
|
"helmet": "^8.1.0",
|
|
19
|
-
"morgan": "^1.10.1"
|
|
18
|
+
"morgan": "^1.10.1",
|
|
19
|
+
"express-rate-limit": "^6.10.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/cors": "^2.8.19",
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"@types/node": "^25.0.8",
|
|
26
26
|
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
27
27
|
"@typescript-eslint/parser": "^8.53.0",
|
|
28
|
-
"cross-env": "^10.1.0",
|
|
29
28
|
"eslint": "^9.39.2",
|
|
30
29
|
"tsx": "^4.21.0",
|
|
31
30
|
"typescript": "^5.9.3"
|
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import helmet from "helmet";
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { errorHandler } from "./middlewares/error.middleware";
|
|
1
|
+
import express, { Application, Request, Response } from "express";
|
|
2
|
+
import { cors } from "./config/cors";
|
|
3
|
+
import { helmet } from "./config/helmet";
|
|
4
|
+
import { logger } from "./config/logger";
|
|
5
|
+
import { limiter } from "./config/rate-limit";
|
|
6
|
+
import { apiRoutes } from "./routes";
|
|
7
|
+
import { errorHandler } from "./shared/middlewares/error.middleware";
|
|
8
|
+
import { notFound } from "./shared/middlewares/not-found.middleware";
|
|
8
9
|
|
|
9
10
|
// app initialization
|
|
10
11
|
const app: Application = express();
|
|
11
12
|
app.use(express.json());
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
app.use(
|
|
14
|
+
app.use(helmet);
|
|
15
|
+
app.use(logger);
|
|
16
|
+
app.use(cors);
|
|
17
|
+
app.use(limiter);
|
|
15
18
|
|
|
16
|
-
//
|
|
17
|
-
if (env.
|
|
18
|
-
app.
|
|
19
|
-
} else {
|
|
20
|
-
app.use(morgan("dev"));
|
|
19
|
+
// trust proxy when behind proxies (load balancers)
|
|
20
|
+
if (process.env.NODE_ENV === "production") {
|
|
21
|
+
app.set("trust proxy", 1);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
// cors configuration
|
|
24
|
-
app.use(cors());
|
|
25
|
-
|
|
26
24
|
// Home page route
|
|
27
25
|
app.get("/", (_req: Request, res: Response) => {
|
|
28
26
|
res.status(200).json({
|
|
@@ -34,16 +32,11 @@ app.get("/", (_req: Request, res: Response) => {
|
|
|
34
32
|
});
|
|
35
33
|
});
|
|
36
34
|
|
|
37
|
-
// routes
|
|
38
|
-
app.use("/api
|
|
35
|
+
// API routes
|
|
36
|
+
app.use("/api", apiRoutes);
|
|
39
37
|
|
|
40
38
|
// unhandled routes
|
|
41
|
-
app.use(
|
|
42
|
-
const error: any = new Error(`Can't find ${req.originalUrl} on this server!`);
|
|
43
|
-
error.status = 404;
|
|
44
|
-
|
|
45
|
-
next(error);
|
|
46
|
-
});
|
|
39
|
+
app.use(notFound);
|
|
47
40
|
|
|
48
41
|
// Global error handler
|
|
49
42
|
app.use(errorHandler);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import createCors from "cors";
|
|
2
|
+
import { env } from "./env";
|
|
3
|
+
|
|
4
|
+
const origin = env.isProduction
|
|
5
|
+
? process.env.CORS_ORIGIN
|
|
6
|
+
? process.env.CORS_ORIGIN.split(",").map((s) => s.trim())
|
|
7
|
+
: false
|
|
8
|
+
: true;
|
|
9
|
+
|
|
10
|
+
const cors = createCors({ origin });
|
|
11
|
+
|
|
12
|
+
export { cors };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import rateLimit from "express-rate-limit";
|
|
2
|
+
import { env } from "./env";
|
|
3
|
+
|
|
4
|
+
const limiter = rateLimit({
|
|
5
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
6
|
+
max: env.isProduction ? 100 : 1000, // limit each IP
|
|
7
|
+
standardHeaders: true,
|
|
8
|
+
legacyHeaders: false,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export { limiter };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { healthRoutes } from "../modules/health/health.route";
|
|
3
|
+
const router = Router();
|
|
4
|
+
|
|
5
|
+
// versioned API
|
|
6
|
+
const v1 = Router();
|
|
7
|
+
|
|
8
|
+
v1.use("/health", healthRoutes);
|
|
9
|
+
|
|
10
|
+
router.use("/v1", v1);
|
|
11
|
+
|
|
12
|
+
export const apiRoutes = router;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default class ApiError extends Error {
|
|
2
|
+
statusCode: number;
|
|
3
|
+
isOperational: boolean;
|
|
4
|
+
|
|
5
|
+
constructor(statusCode: number, message: string, isOperational = true) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.statusCode = statusCode;
|
|
8
|
+
this.isOperational = isOperational;
|
|
9
|
+
Object.setPrototypeOf(this, ApiError.prototype);
|
|
10
|
+
if (Error.captureStackTrace) {
|
|
11
|
+
Error.captureStackTrace(this, this.constructor);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function ts() {
|
|
2
|
+
return new Date().toISOString();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const info = (msg: string, meta?: any) => {
|
|
6
|
+
console.log(`${ts()} INFO: ${msg}` + (meta ? ` ${JSON.stringify(meta)}` : ""));
|
|
7
|
+
};
|
|
8
|
+
export const warn = (msg: string, meta?: any) => {
|
|
9
|
+
console.warn(`${ts()} WARN: ${msg}` + (meta ? ` ${JSON.stringify(meta)}` : ""));
|
|
10
|
+
};
|
|
11
|
+
export const error = (msg: string, meta?: any) => {
|
|
12
|
+
console.error(`${ts()} ERROR: ${msg}` + (meta ? ` ${JSON.stringify(meta)}` : ""));
|
|
13
|
+
};
|
|
14
|
+
export const debug = (msg: string, meta?: any) => {
|
|
15
|
+
if (process.env.NODE_ENV !== "production") {
|
|
16
|
+
console.debug(`${ts()} DEBUG: ${msg}` + (meta ? ` ${JSON.stringify(meta)}` : ""));
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default { info, warn, error, debug };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextFunction, Request, Response } from "express";
|
|
2
|
-
import { env } from "
|
|
2
|
+
import { env } from "../../config/env";
|
|
3
3
|
|
|
4
4
|
export const errorHandler = (err: any, _req: Request, res: Response, _: NextFunction) => {
|
|
5
5
|
const statusCode = err.status || 500;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from "express";
|
|
2
|
+
|
|
3
|
+
export default function asyncHandler(
|
|
4
|
+
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
|
|
5
|
+
) {
|
|
6
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
7
|
+
fn(req, res, next).catch(next);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function getPagination(query: any) {
|
|
2
|
+
const page = Math.max(1, parseInt(String(query.page || "1"), 10) || 1);
|
|
3
|
+
const limit = Math.min(100, parseInt(String(query.limit || "10"), 10) || 10);
|
|
4
|
+
const skip = (page - 1) * limit;
|
|
5
|
+
return { page, limit, skip, take: limit };
|
|
6
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Response } from "express";
|
|
2
|
+
|
|
3
|
+
export function success(res: Response, data: any, message = "Success", meta?: any) {
|
|
4
|
+
return res.json({ success: true, message, data, meta });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function fail(res: Response, status = 500, message = "Error", details?: any) {
|
|
8
|
+
return res.status(status).json({ success: false, message, details });
|
|
9
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
+
"sourceMap": true,
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"strict": true,
|
|
6
|
+
"target": "ES2020",
|
|
7
|
+
"lib": ["ES2020", "DOM"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
"esModuleInterop": true,
|
|
3
11
|
"module": "ESNext",
|
|
4
12
|
"moduleResolution": "node",
|
|
5
|
-
"
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true
|
|
13
|
+
"resolveJsonModule": true
|
|
8
14
|
}
|
|
9
15
|
}
|