create-pnpm-custom-app 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 +217 -0
- package/bin/cli.js +185 -0
- package/package.json +39 -0
- package/templates/.github/copilot-instructions.md +184 -0
- package/templates/.nvmrc +1 -0
- package/templates/.vscode/settings.json +51 -0
- package/templates/CONTRIBUTING.md +184 -0
- package/templates/LICENSE +21 -0
- package/templates/README.md +324 -0
- package/templates/apps/api/.env.example +36 -0
- package/templates/apps/api/.prettierrc.json +7 -0
- package/templates/apps/api/eslint.config.js +17 -0
- package/templates/apps/api/gitignore +45 -0
- package/templates/apps/api/jest.config.ts +22 -0
- package/templates/apps/api/package.json +49 -0
- package/templates/apps/api/src/app.ts +121 -0
- package/templates/apps/api/src/config/config.ts +38 -0
- package/templates/apps/api/src/config/logger.ts +57 -0
- package/templates/apps/api/src/db/mongo.ts +30 -0
- package/templates/apps/api/src/index.ts +40 -0
- package/templates/apps/api/src/middlewares/middleware.ts +75 -0
- package/templates/apps/api/src/models/example.model.ts +89 -0
- package/templates/apps/api/src/routes/routes.ts +54 -0
- package/templates/apps/api/src/schemas/swagger.schema.ts +58 -0
- package/templates/apps/api/src/services/example.service.ts +63 -0
- package/templates/apps/api/src/tests/health.test.ts +90 -0
- package/templates/apps/api/src/tests/helpers/test-helpers.ts +40 -0
- package/templates/apps/api/src/tests/mocks/mocks.ts +29 -0
- package/templates/apps/api/src/tests/setup.ts +11 -0
- package/templates/apps/api/src/types/fastify.d.ts +44 -0
- package/templates/apps/api/tsconfig.json +24 -0
- package/templates/apps/web/.env.example +25 -0
- package/templates/apps/web/.prettierignore +7 -0
- package/templates/apps/web/.prettierrc +9 -0
- package/templates/apps/web/app/ICONS.md +42 -0
- package/templates/apps/web/app/[locale]/(routes)/layout.tsx +13 -0
- package/templates/apps/web/app/[locale]/(routes)/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/[...not-found]/page.tsx +8 -0
- package/templates/apps/web/app/[locale]/layout.tsx +35 -0
- package/templates/apps/web/app/[locale]/not-found.tsx +12 -0
- package/templates/apps/web/app/components/layout/Footer.component.tsx +30 -0
- package/templates/apps/web/app/components/layout/Nav.component.tsx +34 -0
- package/templates/apps/web/app/components/ui/README.md +39 -0
- package/templates/apps/web/app/components/ui/atoms/README.md +55 -0
- package/templates/apps/web/app/components/ui/molecules/README.md +51 -0
- package/templates/apps/web/app/globals.css +104 -0
- package/templates/apps/web/app/icon.svg +5 -0
- package/templates/apps/web/app/layout.tsx +37 -0
- package/templates/apps/web/app/manifest.json +22 -0
- package/templates/apps/web/app/providers.tsx +25 -0
- package/templates/apps/web/app/robots.ts +12 -0
- package/templates/apps/web/app/sitemap.ts +18 -0
- package/templates/apps/web/eslint.config.mjs +16 -0
- package/templates/apps/web/gitignore +56 -0
- package/templates/apps/web/hooks/README.md +25 -0
- package/templates/apps/web/i18n/config.ts +9 -0
- package/templates/apps/web/i18n/request.ts +15 -0
- package/templates/apps/web/interfaces/README.md +5 -0
- package/templates/apps/web/lib/README.md +45 -0
- package/templates/apps/web/lib/utils.ts +18 -0
- package/templates/apps/web/messages/en.json +34 -0
- package/templates/apps/web/messages/es.json +34 -0
- package/templates/apps/web/next.config.ts +50 -0
- package/templates/apps/web/package.json +34 -0
- package/templates/apps/web/postcss.config.mjs +7 -0
- package/templates/apps/web/proxy.ts +17 -0
- package/templates/apps/web/public/README.md +7 -0
- package/templates/apps/web/tsconfig.json +27 -0
- package/templates/apps/web/types/README.md +3 -0
- package/templates/docs/README.md +13 -0
- package/templates/gitignore-root +51 -0
- package/templates/package.json +30 -0
- package/templates/packages/shared/eslint.config.js +26 -0
- package/templates/packages/shared/package.json +22 -0
- package/templates/packages/shared/src/index.ts +39 -0
- package/templates/packages/shared/tsconfig.json +19 -0
- package/templates/pnpm-workspace.yaml +3 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{PROJECT_NAME}}/api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx watch src/index.ts",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"test": "NODE_ENV=test node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
11
|
+
"test:watch": "NODE_ENV=test node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
|
12
|
+
"test:coverage": "NODE_ENV=test node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
13
|
+
"lint": "eslint src --ext .ts"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@fastify/cors": "^10.0.1",
|
|
17
|
+
"@fastify/jwt": "^9.0.1",
|
|
18
|
+
"@fastify/multipart": "^9.0.1",
|
|
19
|
+
"@fastify/rate-limit": "^10.1.1",
|
|
20
|
+
"@fastify/swagger": "^9.3.0",
|
|
21
|
+
"@fastify/swagger-ui": "^5.0.1",
|
|
22
|
+
"@{{PROJECT_NAME}}/shared": "workspace:*",
|
|
23
|
+
"bcrypt": "^5.1.1",
|
|
24
|
+
"dotenv": "^16.4.7",
|
|
25
|
+
"fastify": "^5.2.0",
|
|
26
|
+
"mongoose": "^8.9.3",
|
|
27
|
+
"pino": "^9.5.0",
|
|
28
|
+
"pino-pretty": "^13.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@eslint/js": "^9.18.0",
|
|
32
|
+
"@jest/globals": "^29.7.0",
|
|
33
|
+
"@types/bcrypt": "^5.0.2",
|
|
34
|
+
"@types/jest": "^29.5.14",
|
|
35
|
+
"@types/node": "^22.10.5",
|
|
36
|
+
"@types/supertest": "^6.0.2",
|
|
37
|
+
"eslint": "^9.18.0",
|
|
38
|
+
"jest": "^29.7.0",
|
|
39
|
+
"supertest": "^7.0.0",
|
|
40
|
+
"ts-jest": "^29.2.5",
|
|
41
|
+
"ts-node": "^10.9.2",
|
|
42
|
+
"tsx": "^4.19.2",
|
|
43
|
+
"typescript": "^5.7.3",
|
|
44
|
+
"typescript-eslint": "^8.21.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import Fastify, { FastifyInstance } from 'fastify';
|
|
2
|
+
import cors from '@fastify/cors';
|
|
3
|
+
import jwt from '@fastify/jwt';
|
|
4
|
+
import rateLimit from '@fastify/rate-limit';
|
|
5
|
+
import multipart from '@fastify/multipart';
|
|
6
|
+
import swagger from '@fastify/swagger';
|
|
7
|
+
import swaggerUi from '@fastify/swagger-ui';
|
|
8
|
+
import routes from './routes/routes.js';
|
|
9
|
+
import config from './config/config.js';
|
|
10
|
+
import { getLoggerConfig } from './config/logger.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Registers CORS middleware
|
|
14
|
+
*/
|
|
15
|
+
function registerCors(app: FastifyInstance) {
|
|
16
|
+
app.register(cors, {
|
|
17
|
+
origin: config.corsOrigin,
|
|
18
|
+
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
19
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
20
|
+
credentials: true,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Registers JWT authentication plugin
|
|
26
|
+
*/
|
|
27
|
+
function registerJWT(app: FastifyInstance) {
|
|
28
|
+
if (!config.jwtSecret) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'JWT_SECRET is not defined. Please copy apps/api/.env.example to apps/api/.env and set JWT_SECRET'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
app.register(jwt, {
|
|
35
|
+
secret: config.jwtSecret,
|
|
36
|
+
sign: {
|
|
37
|
+
expiresIn: '24h',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Registers rate limiting plugin
|
|
44
|
+
*/
|
|
45
|
+
function registerRateLimit(app: FastifyInstance) {
|
|
46
|
+
if (config.env === 'test') {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
app.register(rateLimit, {
|
|
51
|
+
global: false,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Registers Swagger documentation
|
|
57
|
+
*/
|
|
58
|
+
async function registerSwagger(app: FastifyInstance) {
|
|
59
|
+
await app.register(swagger, {
|
|
60
|
+
openapi: {
|
|
61
|
+
openapi: '3.0.0',
|
|
62
|
+
info: {
|
|
63
|
+
title: '{{PROJECT_NAME}} API',
|
|
64
|
+
description: 'API documentation for {{PROJECT_NAME}}',
|
|
65
|
+
version: '1.0.0',
|
|
66
|
+
},
|
|
67
|
+
servers: [
|
|
68
|
+
{
|
|
69
|
+
url: `http://localhost:${config.port}`,
|
|
70
|
+
description: 'Development server',
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
tags: [
|
|
74
|
+
{ name: 'Health', description: 'Health check endpoints' },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await app.register(swaggerUi, {
|
|
80
|
+
routePrefix: '/docs',
|
|
81
|
+
uiConfig: {
|
|
82
|
+
docExpansion: 'list',
|
|
83
|
+
deepLinking: true,
|
|
84
|
+
},
|
|
85
|
+
staticCSP: true,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Registers file upload support
|
|
91
|
+
*/
|
|
92
|
+
function registerMultipart(app: FastifyInstance) {
|
|
93
|
+
app.register(multipart, {
|
|
94
|
+
limits: {
|
|
95
|
+
fileSize: 10 * 1024 * 1024, // 10MB
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Builds and configures the Fastify application
|
|
102
|
+
*
|
|
103
|
+
* @returns Configured Fastify instance
|
|
104
|
+
*/
|
|
105
|
+
export async function buildApp(): Promise<FastifyInstance> {
|
|
106
|
+
const app = Fastify({
|
|
107
|
+
logger: getLoggerConfig(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Register plugins
|
|
111
|
+
registerCors(app);
|
|
112
|
+
registerJWT(app);
|
|
113
|
+
registerRateLimit(app);
|
|
114
|
+
registerMultipart(app);
|
|
115
|
+
await registerSwagger(app);
|
|
116
|
+
|
|
117
|
+
// Register routes
|
|
118
|
+
await app.register(routes);
|
|
119
|
+
|
|
120
|
+
return app;
|
|
121
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
|
|
3
|
+
dotenv.config();
|
|
4
|
+
|
|
5
|
+
interface Config {
|
|
6
|
+
env: string;
|
|
7
|
+
port: number;
|
|
8
|
+
mongoUri: string;
|
|
9
|
+
corsOrigin: string;
|
|
10
|
+
bcryptRounds: number;
|
|
11
|
+
jwtSecret?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const env = process.env.NODE_ENV ?? 'development';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Application configuration loaded from environment variables
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Required environment variables:
|
|
21
|
+
* - MONGODB_URI: MongoDB connection string
|
|
22
|
+
* - JWT_SECRET: Secret for JWT token signing
|
|
23
|
+
*
|
|
24
|
+
* Optional environment variables:
|
|
25
|
+
* - PORT: Server port (default: 3002)
|
|
26
|
+
* - CORS_ORIGIN: Allowed CORS origin (default: *)
|
|
27
|
+
* - BCRYPT_ROUNDS: Bcrypt hashing rounds (default: 10, 4 in test)
|
|
28
|
+
*/
|
|
29
|
+
const config: Config = {
|
|
30
|
+
env,
|
|
31
|
+
port: Number(process.env.PORT) || 3002,
|
|
32
|
+
mongoUri: process.env.MONGODB_URI || '',
|
|
33
|
+
corsOrigin: process.env.CORS_ORIGIN || '*',
|
|
34
|
+
bcryptRounds: Number(process.env.BCRYPT_ROUNDS || (env === 'test' ? 4 : 10)),
|
|
35
|
+
jwtSecret: process.env.JWT_SECRET,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default config;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import config from './config.js';
|
|
2
|
+
import pino from 'pino';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns logger configuration based on environment
|
|
6
|
+
*
|
|
7
|
+
* @returns Logger config or false to disable logging in test
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* - Test/E2E: Logging disabled
|
|
11
|
+
* - Production: Structured JSON logs at 'info' level
|
|
12
|
+
* - Development: Pretty-printed colored logs at 'debug' level
|
|
13
|
+
*/
|
|
14
|
+
export function getLoggerConfig(): false | object {
|
|
15
|
+
if (config.env === 'test' || process.env.E2E_MODE === 'true') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (config.env === 'production') {
|
|
20
|
+
return {
|
|
21
|
+
level: 'info',
|
|
22
|
+
serializers: {
|
|
23
|
+
req: (req: { method: string; url: string }) => ({
|
|
24
|
+
method: req.method,
|
|
25
|
+
url: req.url,
|
|
26
|
+
}),
|
|
27
|
+
res: (res: { statusCode: number }) => ({
|
|
28
|
+
statusCode: res.statusCode,
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
level: 'debug',
|
|
36
|
+
transport: {
|
|
37
|
+
target: 'pino-pretty',
|
|
38
|
+
options: {
|
|
39
|
+
colorize: true,
|
|
40
|
+
translateTime: 'HH:MM:ss',
|
|
41
|
+
ignore: 'pid,hostname,reqId',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Application-wide logger instance using Pino
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* logger.info({ userId: '123' }, 'User logged in');
|
|
53
|
+
* logger.error({ error }, 'Failed to process request');
|
|
54
|
+
* logger.debug({ data }, 'Processing data');
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export const logger = pino(getLoggerConfig() || { level: 'silent' });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Establishes connection to MongoDB
|
|
5
|
+
*
|
|
6
|
+
* @param uri - MongoDB connection URI
|
|
7
|
+
* @returns Mongoose connection object
|
|
8
|
+
* @throws Error if connection fails
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* await connectMongo('mongodb://localhost:27017/mydb');
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export async function connectMongo(uri: string) {
|
|
16
|
+
mongoose.set('strictQuery', true);
|
|
17
|
+
await mongoose.connect(uri);
|
|
18
|
+
return mongoose.connection;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Disconnects from MongoDB
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Call this when shutting down the application or when database
|
|
26
|
+
* connection is no longer needed.
|
|
27
|
+
*/
|
|
28
|
+
export async function disconnectMongo() {
|
|
29
|
+
await mongoose.disconnect();
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { buildApp } from './app.js';
|
|
2
|
+
import { connectMongo } from './db/mongo.js';
|
|
3
|
+
import config from './config/config.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Starts the API server
|
|
7
|
+
*
|
|
8
|
+
* - Connects to MongoDB
|
|
9
|
+
* - Starts the Fastify server on configured port
|
|
10
|
+
* - Logs server URL when ready
|
|
11
|
+
*/
|
|
12
|
+
async function start() {
|
|
13
|
+
const app = await buildApp();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
if (!config.mongoUri) {
|
|
17
|
+
app.log.error(
|
|
18
|
+
'MONGODB_URI is not defined. Please copy apps/api/.env.example to apps/api/.env and set MONGODB_URI'
|
|
19
|
+
);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await connectMongo(config.mongoUri);
|
|
24
|
+
app.log.info('MongoDB connected successfully');
|
|
25
|
+
|
|
26
|
+
await app.listen({ port: config.port, host: '0.0.0.0' });
|
|
27
|
+
app.log.info(`🚀 API server listening on http://localhost:${config.port}`);
|
|
28
|
+
app.log.info(`📚 API documentation available at http://localhost:${config.port}/docs`);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
app.log.error(error);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Only start server if not in test environment
|
|
36
|
+
if (config.env !== 'test') {
|
|
37
|
+
start();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { buildApp };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example Middleware
|
|
5
|
+
*
|
|
6
|
+
* Middlewares (preHandlers in Fastify) run before route handlers.
|
|
7
|
+
* They can modify requests, perform authentication, logging, etc.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Common middleware uses:
|
|
11
|
+
* - Authentication/Authorization
|
|
12
|
+
* - Request validation
|
|
13
|
+
* - Logging
|
|
14
|
+
* - Rate limiting
|
|
15
|
+
* - Request transformation
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // Authentication Middleware
|
|
20
|
+
* export async function authenticate(
|
|
21
|
+
* request: FastifyRequest,
|
|
22
|
+
* reply: FastifyReply
|
|
23
|
+
* ) {
|
|
24
|
+
* try {
|
|
25
|
+
* await request.jwtVerify();
|
|
26
|
+
* } catch (err) {
|
|
27
|
+
* reply.status(401).send({ error: 'Unauthorized' });
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* // Admin Check Middleware
|
|
32
|
+
* export async function requireAdmin(
|
|
33
|
+
* request: FastifyRequest,
|
|
34
|
+
* reply: FastifyReply
|
|
35
|
+
* ) {
|
|
36
|
+
* if (request.user?.role !== 'admin') {
|
|
37
|
+
* reply.status(403).send({ error: 'Forbidden: Admin access required' });
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* Usage in routes:
|
|
43
|
+
* ```typescript
|
|
44
|
+
* app.get('/admin/users', {
|
|
45
|
+
* preHandler: [authenticate, requireAdmin],
|
|
46
|
+
* handler: async (request, reply) => {
|
|
47
|
+
* // Only authenticated admins reach here
|
|
48
|
+
* },
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* Or register globally:
|
|
53
|
+
* ```typescript
|
|
54
|
+
* app.addHook('preHandler', authenticate);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
export async function exampleMiddleware(
|
|
59
|
+
request: FastifyRequest,
|
|
60
|
+
_reply: FastifyReply
|
|
61
|
+
) {
|
|
62
|
+
// Example: Log all requests
|
|
63
|
+
request.log.info(`${request.method} ${request.url}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function authenticate(
|
|
67
|
+
request: FastifyRequest,
|
|
68
|
+
reply: FastifyReply
|
|
69
|
+
) {
|
|
70
|
+
try {
|
|
71
|
+
await request.jwtVerify();
|
|
72
|
+
} catch {
|
|
73
|
+
reply.status(401).send({ error: 'Unauthorized' });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import mongoose, { Schema, Document } from 'mongoose';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example Mongoose Model
|
|
5
|
+
*
|
|
6
|
+
* Models define the structure of documents in MongoDB collections.
|
|
7
|
+
* They provide an interface for database operations.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Model best practices:
|
|
11
|
+
* - Define TypeScript interfaces for type safety
|
|
12
|
+
* - Use Mongoose Schema for MongoDB structure
|
|
13
|
+
* - Add indexes for frequently queried fields
|
|
14
|
+
* - Include timestamps for audit trail
|
|
15
|
+
* - Add schema validation
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // User Model Example
|
|
20
|
+
* import mongoose, { Schema, Document } from 'mongoose';
|
|
21
|
+
*
|
|
22
|
+
* export interface IUser extends Document {
|
|
23
|
+
* email: string;
|
|
24
|
+
* password: string;
|
|
25
|
+
* name: string;
|
|
26
|
+
* role: 'user' | 'admin';
|
|
27
|
+
* createdAt: Date;
|
|
28
|
+
* updatedAt: Date;
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* const UserSchema = new Schema<IUser>(
|
|
32
|
+
* {
|
|
33
|
+
* email: {
|
|
34
|
+
* type: String,
|
|
35
|
+
* required: true,
|
|
36
|
+
* unique: true,
|
|
37
|
+
* lowercase: true,
|
|
38
|
+
* trim: true,
|
|
39
|
+
* },
|
|
40
|
+
* password: {
|
|
41
|
+
* type: String,
|
|
42
|
+
* required: true,
|
|
43
|
+
* },
|
|
44
|
+
* name: {
|
|
45
|
+
* type: String,
|
|
46
|
+
* required: true,
|
|
47
|
+
* trim: true,
|
|
48
|
+
* },
|
|
49
|
+
* role: {
|
|
50
|
+
* type: String,
|
|
51
|
+
* enum: ['user', 'admin'],
|
|
52
|
+
* default: 'user',
|
|
53
|
+
* },
|
|
54
|
+
* },
|
|
55
|
+
* {
|
|
56
|
+
* timestamps: true, // Adds createdAt and updatedAt
|
|
57
|
+
* }
|
|
58
|
+
* );
|
|
59
|
+
*
|
|
60
|
+
* // Add indexes
|
|
61
|
+
* UserSchema.index({ email: 1 });
|
|
62
|
+
*
|
|
63
|
+
* export const User = mongoose.model<IUser>('User', UserSchema);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
export interface IExample extends Document {
|
|
68
|
+
name: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
createdAt: Date;
|
|
71
|
+
updatedAt: Date;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const ExampleSchema = new Schema<IExample>(
|
|
75
|
+
{
|
|
76
|
+
name: {
|
|
77
|
+
type: String,
|
|
78
|
+
required: true,
|
|
79
|
+
},
|
|
80
|
+
description: {
|
|
81
|
+
type: String,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
timestamps: true,
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export const Example = mongoose.model<IExample>('Example', ExampleSchema);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { FastifyInstance } from 'fastify';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Registers all API routes
|
|
5
|
+
*
|
|
6
|
+
* @param app - Fastify instance
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* This function registers all route modules.
|
|
10
|
+
* Add your route registrations here as you create new endpoints.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Register a new route module
|
|
15
|
+
* await app.register(userRoutes, { prefix: '/users' });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export default async function routes(app: FastifyInstance) {
|
|
19
|
+
// Health check endpoint
|
|
20
|
+
app.get('/health', {
|
|
21
|
+
schema: {
|
|
22
|
+
tags: ['Health'],
|
|
23
|
+
description: 'Health check endpoint',
|
|
24
|
+
response: {
|
|
25
|
+
200: {
|
|
26
|
+
description: 'Server is healthy',
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
status: { type: 'string' },
|
|
30
|
+
timestamp: { type: 'string' },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
handler: async () => {
|
|
36
|
+
return {
|
|
37
|
+
status: 'ok',
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Example protected route (uncomment when you have auth middleware)
|
|
44
|
+
// app.get('/protected', {
|
|
45
|
+
// preHandler: [app.authenticate], // JWT verification middleware
|
|
46
|
+
// handler: async (request) => {
|
|
47
|
+
// return { message: 'You are authenticated!', user: request.user };
|
|
48
|
+
// },
|
|
49
|
+
// });
|
|
50
|
+
|
|
51
|
+
// Register your route modules here:
|
|
52
|
+
// await app.register(authRoutes, { prefix: '/auth' });
|
|
53
|
+
// await app.register(userRoutes, { prefix: '/users' });
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swagger Schema Definitions
|
|
3
|
+
*
|
|
4
|
+
* This file contains reusable schema definitions for Swagger/OpenAPI documentation.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* - Define request/response schemas here
|
|
8
|
+
* - Use these schemas in your route definitions
|
|
9
|
+
* - Keep schemas organized by feature/entity
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* export const UserSchema = {
|
|
14
|
+
* type: 'object',
|
|
15
|
+
* properties: {
|
|
16
|
+
* id: { type: 'string' },
|
|
17
|
+
* email: { type: 'string' },
|
|
18
|
+
* name: { type: 'string' },
|
|
19
|
+
* },
|
|
20
|
+
* required: ['id', 'email', 'name'],
|
|
21
|
+
* };
|
|
22
|
+
*
|
|
23
|
+
* export const CreateUserSchema = {
|
|
24
|
+
* type: 'object',
|
|
25
|
+
* properties: {
|
|
26
|
+
* email: { type: 'string' },
|
|
27
|
+
* password: { type: 'string' },
|
|
28
|
+
* name: { type: 'string' },
|
|
29
|
+
* },
|
|
30
|
+
* required: ['email', 'password', 'name'],
|
|
31
|
+
* };
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Usage in routes:
|
|
35
|
+
* ```typescript
|
|
36
|
+
* app.post('/users', {
|
|
37
|
+
* schema: {
|
|
38
|
+
* body: CreateUserSchema,
|
|
39
|
+
* response: {
|
|
40
|
+
* 201: UserSchema,
|
|
41
|
+
* },
|
|
42
|
+
* },
|
|
43
|
+
* handler: async (request, reply) => {
|
|
44
|
+
* // ...
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
export const HealthCheckSchema = {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
status: { type: 'string' },
|
|
54
|
+
timestamp: { type: 'string' },
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Add your schemas here
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Service
|
|
3
|
+
*
|
|
4
|
+
* Services contain business logic and interact with models/database.
|
|
5
|
+
* They are called by route handlers and keep routes thin.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Service responsibilities:
|
|
9
|
+
* - Business logic implementation
|
|
10
|
+
* - Data validation
|
|
11
|
+
* - Database operations via models
|
|
12
|
+
* - Error handling
|
|
13
|
+
* - Data transformation
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // User Service Example
|
|
18
|
+
* import bcrypt from 'bcrypt';
|
|
19
|
+
* import { User } from '../models/user.model.js';
|
|
20
|
+
*
|
|
21
|
+
* export class UserService {
|
|
22
|
+
* async createUser(email: string, password: string, name: string) {
|
|
23
|
+
* const hashedPassword = await bcrypt.hash(password, 10);
|
|
24
|
+
* const user = await User.create({
|
|
25
|
+
* email,
|
|
26
|
+
* password: hashedPassword,
|
|
27
|
+
* name,
|
|
28
|
+
* });
|
|
29
|
+
* return user;
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* async findUserByEmail(email: string) {
|
|
33
|
+
* return User.findOne({ email });
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* async findUserById(id: string) {
|
|
37
|
+
* return User.findById(id);
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* export const userService = new UserService();
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Usage in routes:
|
|
45
|
+
* ```typescript
|
|
46
|
+
* import { userService } from '../services/user.service.js';
|
|
47
|
+
*
|
|
48
|
+
* app.post('/users', async (request, reply) => {
|
|
49
|
+
* const { email, password, name } = request.body;
|
|
50
|
+
* const user = await userService.createUser(email, password, name);
|
|
51
|
+
* reply.status(201).send(user);
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
export class ExampleService {
|
|
57
|
+
async doSomething() {
|
|
58
|
+
// Your business logic here
|
|
59
|
+
return { message: 'Example service method' };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const exampleService = new ExampleService();
|