create-wabck 1.0.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.
- package/README.md +79 -0
- package/package.json +48 -0
- package/src/generator.js +201 -0
- package/src/index.js +118 -0
- package/src/installer.js +62 -0
- package/src/prompts.js +113 -0
- package/src/templates/base.js +387 -0
- package/src/templates/config.js +271 -0
- package/src/templates/controllers.js +99 -0
- package/src/templates/docker.js +205 -0
- package/src/templates/graphql.js +157 -0
- package/src/templates/helpers.js +117 -0
- package/src/templates/index.js +78 -0
- package/src/templates/middlewares.js +294 -0
- package/src/templates/models.js +318 -0
- package/src/templates/routes.js +179 -0
- package/src/templates/services.js +252 -0
- package/src/templates/typescript.js +96 -0
- package/src/templates/utils.js +109 -0
- package/src/templates/validators.js +183 -0
- package/src/utils/fs.js +41 -0
- package/src/utils/logger.js +89 -0
- package/src/utils/pkg.js +158 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Controller Templates — user.controller.js
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/** Generate src/controllers/user.controller.js */
|
|
6
|
+
export function userController(options) {
|
|
7
|
+
const { auth, language } = options;
|
|
8
|
+
const isTS = language === 'typescript';
|
|
9
|
+
const hasJwt = auth === 'jwt';
|
|
10
|
+
|
|
11
|
+
const typeImports = isTS
|
|
12
|
+
? `import { Request, Response } from 'express';\n`
|
|
13
|
+
: '';
|
|
14
|
+
|
|
15
|
+
return `${typeImports}import { asyncHandler } from '../utils/asyncHandler.js';
|
|
16
|
+
import { ApiResponse } from '../utils/apiResponse.js';
|
|
17
|
+
import { UserService } from '../services/user.service.js';
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
// User CRUD Controllers
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** GET /api/v1/users — List all users */
|
|
23
|
+
export const getAllUsers = asyncHandler(async (_req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
24
|
+
const users = await UserService.getAll();
|
|
25
|
+
res.status(200).json(new ApiResponse(200, users, 'Users fetched successfully'));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/** GET /api/v1/users/:id — Get user by ID */
|
|
29
|
+
export const getUserById = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
30
|
+
const user = await UserService.getById(req.params.id);
|
|
31
|
+
res.status(200).json(new ApiResponse(200, user, 'User fetched successfully'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** POST /api/v1/users — Create a new user */
|
|
35
|
+
export const createUser = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
36
|
+
const user = await UserService.create(req.body);
|
|
37
|
+
res.status(201).json(new ApiResponse(201, user, 'User created successfully'));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/** PUT /api/v1/users/:id — Update a user */
|
|
41
|
+
export const updateUser = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
42
|
+
const user = await UserService.update(req.params.id, req.body);
|
|
43
|
+
res.status(200).json(new ApiResponse(200, user, 'User updated successfully'));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** DELETE /api/v1/users/:id — Delete a user */
|
|
47
|
+
export const deleteUser = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
48
|
+
const result = await UserService.remove(req.params.id);
|
|
49
|
+
res.status(200).json(new ApiResponse(200, result, 'User deleted successfully'));
|
|
50
|
+
});
|
|
51
|
+
${hasJwt ? `
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
// Auth Controllers
|
|
54
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/** POST /api/v1/auth/register */
|
|
57
|
+
export const register = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
58
|
+
const result = await UserService.register(req.body);
|
|
59
|
+
res
|
|
60
|
+
.status(201)
|
|
61
|
+
.cookie('refreshToken', result.refreshToken, {
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
secure: process.env.NODE_ENV === 'production',
|
|
64
|
+
sameSite: 'strict',
|
|
65
|
+
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
66
|
+
})
|
|
67
|
+
.json(new ApiResponse(201, result, 'Registration successful'));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/** POST /api/v1/auth/login */
|
|
71
|
+
export const login = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
72
|
+
const { email, password } = req.body;
|
|
73
|
+
const result = await UserService.login(email, password);
|
|
74
|
+
res
|
|
75
|
+
.status(200)
|
|
76
|
+
.cookie('refreshToken', result.refreshToken, {
|
|
77
|
+
httpOnly: true,
|
|
78
|
+
secure: process.env.NODE_ENV === 'production',
|
|
79
|
+
sameSite: 'strict',
|
|
80
|
+
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
81
|
+
})
|
|
82
|
+
.json(new ApiResponse(200, result, 'Login successful'));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/** POST /api/v1/auth/logout */
|
|
86
|
+
export const logout = asyncHandler(async (_req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
87
|
+
res
|
|
88
|
+
.clearCookie('refreshToken')
|
|
89
|
+
.status(200)
|
|
90
|
+
.json(new ApiResponse(200, null, 'Logged out successfully'));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/** GET /api/v1/auth/me — Get current authenticated user */
|
|
94
|
+
export const getMe = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
|
|
95
|
+
const user = await UserService.getById((req${isTS ? ' as any' : ''}).user.id);
|
|
96
|
+
res.status(200).json(new ApiResponse(200, user, 'Profile fetched'));
|
|
97
|
+
});
|
|
98
|
+
` : ''}`;
|
|
99
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Docker Templates — Dockerfile, docker-compose.yml, .dockerignore
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/** Generate Dockerfile */
|
|
6
|
+
export function dockerfile(options) {
|
|
7
|
+
const { language, database } = options;
|
|
8
|
+
const isTS = language === 'typescript';
|
|
9
|
+
const port = 5000;
|
|
10
|
+
|
|
11
|
+
if (isTS) {
|
|
12
|
+
return `# ─── Stage 1: Build ──────────────────────────────────────────────────────────
|
|
13
|
+
FROM node:20-alpine AS builder
|
|
14
|
+
|
|
15
|
+
WORKDIR /app
|
|
16
|
+
|
|
17
|
+
COPY package*.json ./
|
|
18
|
+
RUN npm ci
|
|
19
|
+
|
|
20
|
+
COPY . .
|
|
21
|
+
RUN npm run build
|
|
22
|
+
|
|
23
|
+
# ─── Stage 2: Production ─────────────────────────────────────────────────────
|
|
24
|
+
FROM node:20-alpine AS production
|
|
25
|
+
|
|
26
|
+
WORKDIR /app
|
|
27
|
+
|
|
28
|
+
# Create non-root user for security
|
|
29
|
+
RUN addgroup -g 1001 -S nodejs && \\
|
|
30
|
+
adduser -S nodejs -u 1001
|
|
31
|
+
|
|
32
|
+
COPY package*.json ./
|
|
33
|
+
RUN npm ci --omit=dev
|
|
34
|
+
|
|
35
|
+
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
|
|
36
|
+
|
|
37
|
+
USER nodejs
|
|
38
|
+
|
|
39
|
+
EXPOSE ${port}
|
|
40
|
+
|
|
41
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \\
|
|
42
|
+
CMD wget -qO- http://localhost:${port}/api/v1/health || exit 1
|
|
43
|
+
|
|
44
|
+
CMD ["node", "dist/server.js"]
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `# ─── Stage 1: Dependencies ───────────────────────────────────────────────────
|
|
49
|
+
FROM node:20-alpine AS deps
|
|
50
|
+
|
|
51
|
+
WORKDIR /app
|
|
52
|
+
COPY package*.json ./
|
|
53
|
+
RUN npm ci --omit=dev
|
|
54
|
+
|
|
55
|
+
# ─── Stage 2: Production ─────────────────────────────────────────────────────
|
|
56
|
+
FROM node:20-alpine AS production
|
|
57
|
+
|
|
58
|
+
WORKDIR /app
|
|
59
|
+
|
|
60
|
+
# Create non-root user
|
|
61
|
+
RUN addgroup -g 1001 -S nodejs && \\
|
|
62
|
+
adduser -S nodejs -u 1001
|
|
63
|
+
|
|
64
|
+
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
|
|
65
|
+
COPY --chown=nodejs:nodejs . .
|
|
66
|
+
|
|
67
|
+
USER nodejs
|
|
68
|
+
|
|
69
|
+
EXPOSE ${port}
|
|
70
|
+
|
|
71
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \\
|
|
72
|
+
CMD wget -qO- http://localhost:${port}/api/v1/health || exit 1
|
|
73
|
+
|
|
74
|
+
CMD ["node", "src/server.js"]
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Generate docker-compose.yml */
|
|
79
|
+
export function dockerCompose(options) {
|
|
80
|
+
const { database, projectName } = options;
|
|
81
|
+
const safe = projectName.toLowerCase().replace(/[^a-z0-9]/g, '_');
|
|
82
|
+
|
|
83
|
+
const dbService = {
|
|
84
|
+
mongodb: `
|
|
85
|
+
mongo:
|
|
86
|
+
image: mongo:7-jammy
|
|
87
|
+
container_name: ${safe}_mongo
|
|
88
|
+
restart: unless-stopped
|
|
89
|
+
ports:
|
|
90
|
+
- "27017:27017"
|
|
91
|
+
environment:
|
|
92
|
+
MONGO_INITDB_DATABASE: ${safe}
|
|
93
|
+
volumes:
|
|
94
|
+
- mongo_data:/data/db
|
|
95
|
+
networks:
|
|
96
|
+
- app_network
|
|
97
|
+
`,
|
|
98
|
+
postgresql: `
|
|
99
|
+
postgres:
|
|
100
|
+
image: postgres:16-alpine
|
|
101
|
+
container_name: ${safe}_postgres
|
|
102
|
+
restart: unless-stopped
|
|
103
|
+
ports:
|
|
104
|
+
- "5432:5432"
|
|
105
|
+
environment:
|
|
106
|
+
POSTGRES_USER: \${DB_USER:-postgres}
|
|
107
|
+
POSTGRES_PASSWORD: \${DB_PASSWORD:-password}
|
|
108
|
+
POSTGRES_DB: \${DB_NAME:-${safe}}
|
|
109
|
+
volumes:
|
|
110
|
+
- postgres_data:/var/lib/postgresql/data
|
|
111
|
+
networks:
|
|
112
|
+
- app_network
|
|
113
|
+
healthcheck:
|
|
114
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
115
|
+
interval: 10s
|
|
116
|
+
timeout: 5s
|
|
117
|
+
retries: 5
|
|
118
|
+
`,
|
|
119
|
+
mysql: `
|
|
120
|
+
mysql:
|
|
121
|
+
image: mysql:8.0
|
|
122
|
+
container_name: ${safe}_mysql
|
|
123
|
+
restart: unless-stopped
|
|
124
|
+
ports:
|
|
125
|
+
- "3306:3306"
|
|
126
|
+
environment:
|
|
127
|
+
MYSQL_ROOT_PASSWORD: \${DB_PASSWORD:-password}
|
|
128
|
+
MYSQL_DATABASE: \${DB_NAME:-${safe}}
|
|
129
|
+
MYSQL_USER: \${DB_USER:-appuser}
|
|
130
|
+
MYSQL_PASSWORD: \${DB_PASSWORD:-password}
|
|
131
|
+
volumes:
|
|
132
|
+
- mysql_data:/var/lib/mysql
|
|
133
|
+
networks:
|
|
134
|
+
- app_network
|
|
135
|
+
healthcheck:
|
|
136
|
+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
|
137
|
+
interval: 10s
|
|
138
|
+
timeout: 5s
|
|
139
|
+
retries: 5
|
|
140
|
+
`,
|
|
141
|
+
sqlite: '',
|
|
142
|
+
none: '',
|
|
143
|
+
}[database] || '';
|
|
144
|
+
|
|
145
|
+
const dbVolume = {
|
|
146
|
+
mongodb: ' mongo_data:\n',
|
|
147
|
+
postgresql: ' postgres_data:\n',
|
|
148
|
+
mysql: ' mysql_data:\n',
|
|
149
|
+
sqlite: '',
|
|
150
|
+
none: '',
|
|
151
|
+
}[database] || '';
|
|
152
|
+
|
|
153
|
+
const dbDepends = ['mongodb', 'postgresql', 'mysql'].includes(database)
|
|
154
|
+
? ` depends_on:\n - ${database === 'mongodb' ? 'mongo' : database}\n`
|
|
155
|
+
: '';
|
|
156
|
+
|
|
157
|
+
return `# docker-compose.yml — ${projectName}
|
|
158
|
+
# Usage: docker compose up --build
|
|
159
|
+
|
|
160
|
+
version: '3.9'
|
|
161
|
+
|
|
162
|
+
services:
|
|
163
|
+
app:
|
|
164
|
+
build:
|
|
165
|
+
context: .
|
|
166
|
+
target: production
|
|
167
|
+
container_name: ${safe}_app
|
|
168
|
+
restart: unless-stopped
|
|
169
|
+
ports:
|
|
170
|
+
- "\${PORT:-5000}:5000"
|
|
171
|
+
env_file:
|
|
172
|
+
- .env
|
|
173
|
+
networks:
|
|
174
|
+
- app_network
|
|
175
|
+
${dbDepends} healthcheck:
|
|
176
|
+
test: ["CMD", "wget", "-qO-", "http://localhost:5000/api/v1/health"]
|
|
177
|
+
interval: 30s
|
|
178
|
+
timeout: 10s
|
|
179
|
+
retries: 3
|
|
180
|
+
start_period: 30s
|
|
181
|
+
${dbService}
|
|
182
|
+
networks:
|
|
183
|
+
app_network:
|
|
184
|
+
driver: bridge
|
|
185
|
+
|
|
186
|
+
${dbVolume ? `volumes:\n${dbVolume}` : ''}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Generate .dockerignore */
|
|
190
|
+
export function dockerignore() {
|
|
191
|
+
return `node_modules/
|
|
192
|
+
npm-debug.log*
|
|
193
|
+
.git/
|
|
194
|
+
.gitignore
|
|
195
|
+
.env
|
|
196
|
+
.env.local
|
|
197
|
+
dist/
|
|
198
|
+
coverage/
|
|
199
|
+
*.md
|
|
200
|
+
.dockerignore
|
|
201
|
+
Dockerfile
|
|
202
|
+
docker-compose.yml
|
|
203
|
+
.DS_Store
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// GraphQL Templates — Apollo Server 4 integration
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/** Generate src/graphql/schema.js */
|
|
6
|
+
export function graphqlSchema(options) {
|
|
7
|
+
const { language } = options;
|
|
8
|
+
const isTS = language === 'typescript';
|
|
9
|
+
|
|
10
|
+
return `import { gql } from 'graphql-tag';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* GraphQL Type Definitions
|
|
14
|
+
*
|
|
15
|
+
* Defines the schema for the API including:
|
|
16
|
+
* - User type
|
|
17
|
+
* - Queries (getUsers, getUserById)
|
|
18
|
+
* - Mutations (createUser, updateUser, deleteUser)
|
|
19
|
+
*/
|
|
20
|
+
export const typeDefs = gql\`
|
|
21
|
+
type User {
|
|
22
|
+
id: ID!
|
|
23
|
+
name: String!
|
|
24
|
+
email: String!
|
|
25
|
+
role: String!
|
|
26
|
+
isActive: Boolean!
|
|
27
|
+
createdAt: String!
|
|
28
|
+
updatedAt: String!
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type AuthPayload {
|
|
32
|
+
user: User!
|
|
33
|
+
accessToken: String!
|
|
34
|
+
refreshToken: String!
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type DeleteResult {
|
|
38
|
+
message: String!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
input CreateUserInput {
|
|
42
|
+
name: String!
|
|
43
|
+
email: String!
|
|
44
|
+
password: String!
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
input UpdateUserInput {
|
|
48
|
+
name: String
|
|
49
|
+
email: String
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type Query {
|
|
53
|
+
"Get all active users"
|
|
54
|
+
users: [User!]!
|
|
55
|
+
|
|
56
|
+
"Get a single user by ID"
|
|
57
|
+
user(id: ID!): User
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type Mutation {
|
|
61
|
+
"Create a new user"
|
|
62
|
+
createUser(input: CreateUserInput!): User!
|
|
63
|
+
|
|
64
|
+
"Update an existing user"
|
|
65
|
+
updateUser(id: ID!, input: UpdateUserInput!): User!
|
|
66
|
+
|
|
67
|
+
"Delete a user"
|
|
68
|
+
deleteUser(id: ID!): DeleteResult!
|
|
69
|
+
}
|
|
70
|
+
\`;
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Generate src/graphql/resolvers.js */
|
|
75
|
+
export function graphqlResolvers(options) {
|
|
76
|
+
const { language } = options;
|
|
77
|
+
const isTS = language === 'typescript';
|
|
78
|
+
|
|
79
|
+
return `import { UserService } from '../services/user.service.js';
|
|
80
|
+
import { ApiError } from '../utils/apiError.js';
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* GraphQL Resolvers
|
|
84
|
+
*
|
|
85
|
+
* Maps GraphQL operations to service layer methods.
|
|
86
|
+
* Errors thrown by services are naturally propagated to Apollo's error handler.
|
|
87
|
+
*/
|
|
88
|
+
export const resolvers = {
|
|
89
|
+
Query: {
|
|
90
|
+
users: async (_parent${isTS ? ': unknown' : ''}, _args${isTS ? ': unknown' : ''}, _ctx${isTS ? ': unknown' : ''}) => {
|
|
91
|
+
return UserService.getAll();
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
user: async (_parent${isTS ? ': unknown' : ''}, args${isTS ? ': { id: string }' : ''}, _ctx${isTS ? ': unknown' : ''}) => {
|
|
95
|
+
return UserService.getById(args.id);
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
Mutation: {
|
|
100
|
+
createUser: async (
|
|
101
|
+
_parent${isTS ? ': unknown' : ''},
|
|
102
|
+
args${isTS ? ': { input: { name: string; email: string; password: string } }' : ''},
|
|
103
|
+
_ctx${isTS ? ': unknown' : ''}
|
|
104
|
+
) => {
|
|
105
|
+
return UserService.create(args.input);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
updateUser: async (
|
|
109
|
+
_parent${isTS ? ': unknown' : ''},
|
|
110
|
+
args${isTS ? ': { id: string; input: { name?: string; email?: string } }' : ''},
|
|
111
|
+
_ctx${isTS ? ': unknown' : ''}
|
|
112
|
+
) => {
|
|
113
|
+
return UserService.update(args.id, args.input);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
deleteUser: async (_parent${isTS ? ': unknown' : ''}, args${isTS ? ': { id: string }' : ''}, _ctx${isTS ? ': unknown' : ''}) => {
|
|
117
|
+
return UserService.remove(args.id);
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Generate src/graphql/server.js */
|
|
125
|
+
export function graphqlServer(options) {
|
|
126
|
+
const { language } = options;
|
|
127
|
+
const isTS = language === 'typescript';
|
|
128
|
+
|
|
129
|
+
return `import { ApolloServer } from '@apollo/server';
|
|
130
|
+
import { typeDefs } from './schema.js';
|
|
131
|
+
import { resolvers } from './resolvers.js';
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Creates and returns a configured Apollo Server instance.
|
|
135
|
+
* Mount via expressMiddleware in app.js.
|
|
136
|
+
*
|
|
137
|
+
* @returns {ApolloServer}
|
|
138
|
+
*/
|
|
139
|
+
export function createApolloServer()${isTS ? ': ApolloServer' : ''} {
|
|
140
|
+
return new ApolloServer({
|
|
141
|
+
typeDefs,
|
|
142
|
+
resolvers,
|
|
143
|
+
introspection: process.env.NODE_ENV !== 'production',
|
|
144
|
+
formatError: (formattedError${isTS ? ': any' : ''}, error${isTS ? ': unknown' : ''}) => {
|
|
145
|
+
// Mask internal server errors in production
|
|
146
|
+
if (
|
|
147
|
+
process.env.NODE_ENV === 'production' &&
|
|
148
|
+
formattedError.extensions?.code === 'INTERNAL_SERVER_ERROR'
|
|
149
|
+
) {
|
|
150
|
+
return { message: 'Internal server error' };
|
|
151
|
+
}
|
|
152
|
+
return formattedError;
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Helper Templates — token.helper.js, constants, etc.
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/** Generate src/helpers/token.helper.js (JWT only) */
|
|
6
|
+
export function tokenHelper(options) {
|
|
7
|
+
const { language } = options;
|
|
8
|
+
const isTS = language === 'typescript';
|
|
9
|
+
|
|
10
|
+
return `import jwt from 'jsonwebtoken';
|
|
11
|
+
import { ApiError } from '../utils/apiError.js';
|
|
12
|
+
|
|
13
|
+
${isTS ? `
|
|
14
|
+
interface TokenPayload {
|
|
15
|
+
id: string;
|
|
16
|
+
email: string;
|
|
17
|
+
role: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface TokenPair {
|
|
21
|
+
accessToken: string;
|
|
22
|
+
refreshToken: string;
|
|
23
|
+
}
|
|
24
|
+
` : ''}
|
|
25
|
+
const ACCESS_TOKEN_EXPIRY = process.env.JWT_EXPIRES_IN || '15m';
|
|
26
|
+
const REFRESH_TOKEN_EXPIRY = process.env.JWT_REFRESH_EXPIRES_IN || '30d';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generates an access token + refresh token pair for the given payload.
|
|
30
|
+
*
|
|
31
|
+
* Access tokens are short-lived (15 min by default).
|
|
32
|
+
* Refresh tokens are long-lived (30 days by default).
|
|
33
|
+
*
|
|
34
|
+
* @param payload - Data to embed in the token { id, email, role }
|
|
35
|
+
* @returns {{ accessToken, refreshToken }}
|
|
36
|
+
*/
|
|
37
|
+
export function generateTokens(payload${isTS ? ': TokenPayload' : ''})${isTS ? ': TokenPair' : ''} {
|
|
38
|
+
const secret = process.env.JWT_SECRET;
|
|
39
|
+
if (!secret) throw new ApiError(500, 'JWT_SECRET is not configured');
|
|
40
|
+
|
|
41
|
+
const accessToken = jwt.sign(payload, secret, {
|
|
42
|
+
expiresIn: ACCESS_TOKEN_EXPIRY,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const refreshToken = jwt.sign({ id: payload.id }, secret, {
|
|
46
|
+
expiresIn: REFRESH_TOKEN_EXPIRY,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return { accessToken, refreshToken };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Verifies a JWT token and returns the decoded payload.
|
|
54
|
+
* Throws an ApiError on invalid or expired tokens.
|
|
55
|
+
*
|
|
56
|
+
* @param token - The raw JWT string
|
|
57
|
+
* @returns Decoded payload
|
|
58
|
+
*/
|
|
59
|
+
export function verifyToken(token${isTS ? ': string' : ''})${isTS ? ': TokenPayload & jwt.JwtPayload' : ''} {
|
|
60
|
+
const secret = process.env.JWT_SECRET;
|
|
61
|
+
if (!secret) throw new ApiError(500, 'JWT_SECRET is not configured');
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
return jwt.verify(token, secret)${isTS ? ' as TokenPayload & jwt.JwtPayload' : ''};
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if ((error${isTS ? ' as Error' : ''}).name === 'TokenExpiredError') {
|
|
67
|
+
throw new ApiError(401, 'Token has expired. Please log in again.');
|
|
68
|
+
}
|
|
69
|
+
throw new ApiError(401, 'Invalid token. Please log in again.');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Generate src/constants/index.js */
|
|
76
|
+
export function constants(options) {
|
|
77
|
+
const { language } = options;
|
|
78
|
+
const isTS = language === 'typescript';
|
|
79
|
+
|
|
80
|
+
return `// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
+
// Application-Wide Constants
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/** HTTP Status Codes — avoids magic numbers in controllers/services */
|
|
85
|
+
export const HTTP_STATUS = {
|
|
86
|
+
OK: 200,
|
|
87
|
+
CREATED: 201,
|
|
88
|
+
NO_CONTENT: 204,
|
|
89
|
+
BAD_REQUEST: 400,
|
|
90
|
+
UNAUTHORIZED: 401,
|
|
91
|
+
FORBIDDEN: 403,
|
|
92
|
+
NOT_FOUND: 404,
|
|
93
|
+
CONFLICT: 409,
|
|
94
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
95
|
+
TOO_MANY_REQUESTS: 429,
|
|
96
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
97
|
+
}${isTS ? ' as const' : ''};
|
|
98
|
+
|
|
99
|
+
/** User roles */
|
|
100
|
+
export const ROLES = {
|
|
101
|
+
USER: 'user',
|
|
102
|
+
ADMIN: 'admin',
|
|
103
|
+
}${isTS ? ' as const' : ''};
|
|
104
|
+
|
|
105
|
+
/** Default pagination settings */
|
|
106
|
+
export const PAGINATION = {
|
|
107
|
+
DEFAULT_PAGE: 1,
|
|
108
|
+
DEFAULT_LIMIT: 10,
|
|
109
|
+
MAX_LIMIT: 100,
|
|
110
|
+
}${isTS ? ' as const' : ''};
|
|
111
|
+
|
|
112
|
+
/** Cookie names */
|
|
113
|
+
export const COOKIES = {
|
|
114
|
+
REFRESH_TOKEN: 'refreshToken',
|
|
115
|
+
}${isTS ? ' as const' : ''};
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Template Index — Re-exports all template functions
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
app,
|
|
7
|
+
server,
|
|
8
|
+
envFile,
|
|
9
|
+
envExample,
|
|
10
|
+
gitignore,
|
|
11
|
+
prettierrc,
|
|
12
|
+
eslintConfig,
|
|
13
|
+
readme,
|
|
14
|
+
} from './base.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
db,
|
|
18
|
+
envConfig,
|
|
19
|
+
} from './config.js';
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
routesIndex,
|
|
23
|
+
healthRoute,
|
|
24
|
+
userRoute,
|
|
25
|
+
authRoute,
|
|
26
|
+
} from './routes.js';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
userController,
|
|
30
|
+
} from './controllers.js';
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
userModel,
|
|
34
|
+
prismaSchema,
|
|
35
|
+
} from './models.js';
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
errorMiddleware,
|
|
39
|
+
notFoundMiddleware,
|
|
40
|
+
rateLimiterMiddleware,
|
|
41
|
+
authMiddleware,
|
|
42
|
+
} from './middlewares.js';
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
userService,
|
|
46
|
+
} from './services.js';
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
asyncHandler,
|
|
50
|
+
apiError,
|
|
51
|
+
apiResponse,
|
|
52
|
+
} from './utils.js';
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
userValidator,
|
|
56
|
+
} from './validators.js';
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
tokenHelper,
|
|
60
|
+
constants,
|
|
61
|
+
} from './helpers.js';
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
dockerfile,
|
|
65
|
+
dockerCompose,
|
|
66
|
+
dockerignore,
|
|
67
|
+
} from './docker.js';
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
tsconfig,
|
|
71
|
+
tsTypes,
|
|
72
|
+
} from './typescript.js';
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
graphqlSchema,
|
|
76
|
+
graphqlResolvers,
|
|
77
|
+
graphqlServer,
|
|
78
|
+
} from './graphql.js';
|