speedrun-cli 2.6.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 (159) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +21 -0
  3. package/README.md +620 -0
  4. package/bin/cli.js +224 -0
  5. package/index.js +12 -0
  6. package/package.json +74 -0
  7. package/src/constants.js +65 -0
  8. package/src/generator.js +271 -0
  9. package/src/index.js +13 -0
  10. package/src/moduleGenerator.js +586 -0
  11. package/src/postSetup.js +365 -0
  12. package/src/prompts.js +189 -0
  13. package/src/utils.js +112 -0
  14. package/templates/README.md +81 -0
  15. package/templates/base/eslint.config.mjs +34 -0
  16. package/templates/base/gitignore +78 -0
  17. package/templates/base/nest-cli.json +8 -0
  18. package/templates/base/src/common/constants/cookie.config.ts +20 -0
  19. package/templates/base/src/common/decorators/get-user.decorator.ts +13 -0
  20. package/templates/base/src/common/decorators/public.decorator.ts +4 -0
  21. package/templates/base/src/common/decorators/roles.decorator.ts +4 -0
  22. package/templates/base/src/common/dtos/pagination.dto.ts +29 -0
  23. package/templates/base/src/common/filters/http-exception.filter.ts +108 -0
  24. package/templates/base/src/common/guards/auth.guard.ts +51 -0
  25. package/templates/base/src/common/guards/refresh-token.guard.ts +39 -0
  26. package/templates/base/src/common/guards/roles.guard.ts +45 -0
  27. package/templates/base/src/common/interceptors/response.interceptor.ts +55 -0
  28. package/templates/base/src/common/interfaces/api-response.interface.ts +26 -0
  29. package/templates/base/src/common/middleware/correlation-id.middleware.ts +20 -0
  30. package/templates/base/src/common/validators/password.validator.ts +37 -0
  31. package/templates/base/src/config/config.module.ts +14 -0
  32. package/templates/base/src/config/logger.config.ts +109 -0
  33. package/templates/base/src/main.ts +84 -0
  34. package/templates/base/src/modules/auth/auth.controller.ts +133 -0
  35. package/templates/base/src/modules/auth/dtos/login.dto.ts +11 -0
  36. package/templates/base/src/modules/auth/dtos/signup.dto.ts +8 -0
  37. package/templates/base/src/modules/health/health.controller.ts +20 -0
  38. package/templates/base/src/modules/health/health.module.ts +7 -0
  39. package/templates/base/src/modules/users/dtos/update-profile.dto.ts +12 -0
  40. package/templates/base/src/modules/users/dtos/update-user.dto.ts +13 -0
  41. package/templates/base/src/modules/users/users.controller.ts +65 -0
  42. package/templates/base/test/app.e2e-spec.ts +24 -0
  43. package/templates/base/test/jest-e2e.json +9 -0
  44. package/templates/base/tsconfig.build.json +4 -0
  45. package/templates/base/tsconfig.json +24 -0
  46. package/templates/base-crud/CRUD_README.md +385 -0
  47. package/templates/base-crud/src/common/base/base.controller.ts +321 -0
  48. package/templates/base-crud/src/common/base/base.service.ts +192 -0
  49. package/templates/base-crud/src/common/base/index.ts +20 -0
  50. package/templates/base-crud/src/common/base/swagger/api-response.dto.ts +82 -0
  51. package/templates/base-crud/src/common/base/swagger/paginated.dto.ts +102 -0
  52. package/templates/base-crud/src/modules/products/dto/create-product.dto.ts +44 -0
  53. package/templates/base-crud/src/modules/products/dto/product.dto.ts +38 -0
  54. package/templates/base-crud/src/modules/products/dto/update-product.dto.ts +12 -0
  55. package/templates/base-crud/src/modules/products/products.controller.ts +94 -0
  56. package/templates/base-crud-drizzle/src/modules/products/products.controller.ts +79 -0
  57. package/templates/base-crud-drizzle/src/modules/products/products.module.ts +24 -0
  58. package/templates/base-crud-drizzle/src/modules/products/products.service.ts +140 -0
  59. package/templates/base-crud-drizzle/src/modules/products/schema/products.schema.ts +39 -0
  60. package/templates/base-crud-mongoose/src/modules/products/products.controller.ts +79 -0
  61. package/templates/base-crud-mongoose/src/modules/products/products.module.ts +26 -0
  62. package/templates/base-crud-mongoose/src/modules/products/products.service.ts +133 -0
  63. package/templates/base-crud-mongoose/src/modules/products/schemas/product.schema.ts +67 -0
  64. package/templates/base-crud-prisma/src/modules/products/products.module.ts +12 -0
  65. package/templates/base-crud-prisma/src/modules/products/products.service.ts +100 -0
  66. package/templates/base-crud-typeorm/src/modules/products/entities/product.entity.ts +58 -0
  67. package/templates/base-crud-typeorm/src/modules/products/products.controller.ts +79 -0
  68. package/templates/base-crud-typeorm/src/modules/products/products.module.ts +25 -0
  69. package/templates/base-crud-typeorm/src/modules/products/products.service.ts +102 -0
  70. package/templates/database/mongodb/.env.example +22 -0
  71. package/templates/database/mysql/.env.example +24 -0
  72. package/templates/database/mysql/drizzle.config.ts +13 -0
  73. package/templates/database/mysql/package.json +5 -0
  74. package/templates/database/mysql/prisma/schema.prisma +55 -0
  75. package/templates/database/mysql/src/database/drizzle.ts +13 -0
  76. package/templates/database/mysql/src/database/schema.ts +58 -0
  77. package/templates/database/postgres/.env.example +24 -0
  78. package/templates/database/postgres/drizzle.config.ts +13 -0
  79. package/templates/database/postgres/package.json +8 -0
  80. package/templates/database/postgres/prisma/schema.prisma +55 -0
  81. package/templates/database/sqlite/.env.example +24 -0
  82. package/templates/database/sqlite/drizzle.config.ts +13 -0
  83. package/templates/database/sqlite/package.json +8 -0
  84. package/templates/database/sqlite/prisma/schema.prisma +48 -0
  85. package/templates/database/sqlite/src/database/drizzle.ts +11 -0
  86. package/templates/database/sqlite/src/database/schema.ts +52 -0
  87. package/templates/orm/drizzle/drizzle.config.ts +13 -0
  88. package/templates/orm/drizzle/package.json +90 -0
  89. package/templates/orm/drizzle/src/app.module.ts +73 -0
  90. package/templates/orm/drizzle/src/config/env.validation.ts +55 -0
  91. package/templates/orm/drizzle/src/database/database.module.ts +25 -0
  92. package/templates/orm/drizzle/src/database/drizzle.ts +13 -0
  93. package/templates/orm/drizzle/src/database/schema.ts +60 -0
  94. package/templates/orm/drizzle/src/database/seed.ts +87 -0
  95. package/templates/orm/drizzle/src/modules/auth/auth.module.ts +12 -0
  96. package/templates/orm/drizzle/src/modules/auth/auth.service.ts +298 -0
  97. package/templates/orm/drizzle/src/modules/health/health.controller.ts +34 -0
  98. package/templates/orm/drizzle/src/modules/health/health.module.ts +7 -0
  99. package/templates/orm/drizzle/src/modules/users/users.module.ts +10 -0
  100. package/templates/orm/drizzle/src/modules/users/users.service.ts +152 -0
  101. package/templates/orm/mongoose/.env.example +22 -0
  102. package/templates/orm/mongoose/package.json +86 -0
  103. package/templates/orm/mongoose/src/app.module.ts +73 -0
  104. package/templates/orm/mongoose/src/config/env.validation.ts +55 -0
  105. package/templates/orm/mongoose/src/database/database.module.ts +19 -0
  106. package/templates/orm/mongoose/src/database/seed.ts +107 -0
  107. package/templates/orm/mongoose/src/modules/auth/auth.module.ts +21 -0
  108. package/templates/orm/mongoose/src/modules/auth/auth.service.ts +272 -0
  109. package/templates/orm/mongoose/src/modules/auth/dtos/login.dto.ts +10 -0
  110. package/templates/orm/mongoose/src/modules/auth/dtos/signup.dto.ts +8 -0
  111. package/templates/orm/mongoose/src/modules/health/health.controller.ts +26 -0
  112. package/templates/orm/mongoose/src/modules/health/health.module.ts +7 -0
  113. package/templates/orm/mongoose/src/modules/users/dtos/update-profile.dto.ts +24 -0
  114. package/templates/orm/mongoose/src/modules/users/dtos/update-user.dto.ts +13 -0
  115. package/templates/orm/mongoose/src/modules/users/users.module.ts +19 -0
  116. package/templates/orm/mongoose/src/modules/users/users.service.ts +179 -0
  117. package/templates/orm/mongoose/src/schemas/index.ts +2 -0
  118. package/templates/orm/mongoose/src/schemas/refresh-token.schema.ts +55 -0
  119. package/templates/orm/mongoose/src/schemas/user.schema.ts +53 -0
  120. package/templates/orm/prisma/package.json +102 -0
  121. package/templates/orm/prisma/prisma/schema.prisma +60 -0
  122. package/templates/orm/prisma/prisma/seed.ts +69 -0
  123. package/templates/orm/prisma/src/app.module.ts +55 -0
  124. package/templates/orm/prisma/src/config/env.validation.ts +56 -0
  125. package/templates/orm/prisma/src/modules/auth/auth.module.ts +17 -0
  126. package/templates/orm/prisma/src/modules/auth/auth.service.ts +308 -0
  127. package/templates/orm/prisma/src/modules/health/health.controller.ts +26 -0
  128. package/templates/orm/prisma/src/modules/health/health.module.ts +10 -0
  129. package/templates/orm/prisma/src/modules/users/users.module.ts +11 -0
  130. package/templates/orm/prisma/src/modules/users/users.service.ts +105 -0
  131. package/templates/orm/prisma/src/prisma/prisma.module.ts +9 -0
  132. package/templates/orm/prisma/src/prisma/prisma.service.ts +16 -0
  133. package/templates/orm/typeorm/package.json +100 -0
  134. package/templates/orm/typeorm/src/app.module.ts +55 -0
  135. package/templates/orm/typeorm/src/config/env.validation.ts +58 -0
  136. package/templates/orm/typeorm/src/database/data-source.ts +19 -0
  137. package/templates/orm/typeorm/src/database/database.module.ts +27 -0
  138. package/templates/orm/typeorm/src/database/seed.ts +76 -0
  139. package/templates/orm/typeorm/src/entities/index.ts +2 -0
  140. package/templates/orm/typeorm/src/entities/refresh-token.entity.ts +45 -0
  141. package/templates/orm/typeorm/src/entities/user.entity.ts +51 -0
  142. package/templates/orm/typeorm/src/modules/auth/auth.module.ts +19 -0
  143. package/templates/orm/typeorm/src/modules/auth/auth.service.ts +286 -0
  144. package/templates/orm/typeorm/src/modules/health/health.controller.ts +24 -0
  145. package/templates/orm/typeorm/src/modules/health/health.module.ts +9 -0
  146. package/templates/orm/typeorm/src/modules/users/users.module.ts +13 -0
  147. package/templates/orm/typeorm/src/modules/users/users.service.ts +108 -0
  148. package/templates/swagger/src/common/dtos/pagination.dto.ts +43 -0
  149. package/templates/swagger/src/main.ts +116 -0
  150. package/templates/swagger/src/modules/auth/auth.controller.ts +235 -0
  151. package/templates/swagger/src/modules/auth/dtos/login.dto.ts +20 -0
  152. package/templates/swagger/src/modules/auth/dtos/signup.dto.ts +14 -0
  153. package/templates/swagger/src/modules/users/dtos/update-profile.dto.ts +19 -0
  154. package/templates/swagger/src/modules/users/dtos/update-user.dto.ts +23 -0
  155. package/templates/swagger/src/modules/users/users.controller.ts +214 -0
  156. package/templates/swagger-mongoose/src/modules/auth/dtos/login.dto.ts +20 -0
  157. package/templates/swagger-mongoose/src/modules/auth/dtos/signup.dto.ts +14 -0
  158. package/templates/swagger-mongoose/src/modules/users/dtos/update-profile.dto.ts +40 -0
  159. package/templates/swagger-mongoose/src/modules/users/dtos/update-user.dto.ts +23 -0
@@ -0,0 +1,109 @@
1
+ import { Params } from 'nestjs-pino';
2
+ import { IncomingMessage, ServerResponse } from 'http';
3
+
4
+ export const loggerConfig: Params = {
5
+ pinoHttp: {
6
+ transport:
7
+ process.env.NODE_ENV !== 'production'
8
+ ? {
9
+ target: 'pino-pretty',
10
+ options: {
11
+ colorize: true,
12
+ translateTime: 'SYS:standard',
13
+ ignore: 'pid,hostname',
14
+ singleLine: false,
15
+ messageFormat: '[{context}] {msg}',
16
+ },
17
+ }
18
+ : undefined,
19
+
20
+ customProps: (req: unknown) => {
21
+ const request = req as { correlationId?: string };
22
+ return {
23
+ correlationId: request.correlationId,
24
+ };
25
+ },
26
+
27
+ // Customize log levels for different status codes
28
+ customLogLevel: (
29
+ _req: IncomingMessage,
30
+ res: ServerResponse<IncomingMessage>,
31
+ err: Error | undefined,
32
+ ) => {
33
+ if (res.statusCode >= 500 || err) {
34
+ return 'error';
35
+ }
36
+ if (res.statusCode >= 400) {
37
+ return 'warn';
38
+ }
39
+ return 'info';
40
+ },
41
+
42
+ // Customize success message
43
+ customSuccessMessage: (
44
+ req: IncomingMessage,
45
+ res: ServerResponse<IncomingMessage>,
46
+ ) => {
47
+ return `${req.method} ${req.url} - ${res.statusCode}`;
48
+ },
49
+
50
+ // Customize error message
51
+ customErrorMessage: (
52
+ req: IncomingMessage,
53
+ res: ServerResponse<IncomingMessage>,
54
+ err: Error,
55
+ ) => {
56
+ return `${req.method} ${req.url} - ${res.statusCode} - ${err.message}`;
57
+ },
58
+
59
+ // Redact sensitive information
60
+ redact: {
61
+ paths: [
62
+ 'req.headers.authorization',
63
+ 'req.headers.cookie',
64
+ 'req.body.password',
65
+ 'res.headers["set-cookie"]',
66
+ ],
67
+ censor: '[REDACTED]',
68
+ },
69
+
70
+ serializers: {
71
+ req: (req: {
72
+ id: string;
73
+ method: string;
74
+ url: string;
75
+ query: unknown;
76
+ params: unknown;
77
+ headers: Record<string, unknown>;
78
+ remoteAddress: string;
79
+ remotePort: number;
80
+ }) => ({
81
+ id: req.id,
82
+ method: req.method,
83
+ url: req.url,
84
+ query: req.query,
85
+ params: req.params,
86
+ headers: {
87
+ 'user-agent': req.headers['user-agent'],
88
+ 'content-type': req.headers['content-type'],
89
+ },
90
+ remoteAddress: req.remoteAddress,
91
+ remotePort: req.remotePort,
92
+ }),
93
+ res: (res: { statusCode: number; headers: Record<string, unknown> }) => ({
94
+ statusCode: res.statusCode,
95
+ headers: {
96
+ 'content-type': res.headers['content-type'],
97
+ },
98
+ }),
99
+ },
100
+
101
+ level: process.env.LOG_LEVEL || 'info',
102
+
103
+ // Don't log health check endpoints
104
+ autoLogging: {
105
+ ignore: (req: IncomingMessage) =>
106
+ req.url === '/health' || req.url === '/api/v1/health',
107
+ },
108
+ },
109
+ };
@@ -0,0 +1,84 @@
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { AppModule } from './app.module';
3
+ import cookieParser from 'cookie-parser';
4
+ import helmet from 'helmet';
5
+ import { ValidationPipe } from '@nestjs/common';
6
+ import { ConfigService } from '@nestjs/config';
7
+ import { ResponseInterceptor } from './common/interceptors/response.interceptor';
8
+ import { HttpExceptionFilter } from './common/filters/http-exception.filter';
9
+ import { Logger } from 'nestjs-pino';
10
+
11
+ async function bootstrap() {
12
+ const app = await NestFactory.create(AppModule, { bufferLogs: true });
13
+
14
+ // Use Pino logger
15
+ app.useLogger(app.get(Logger));
16
+
17
+ app.setGlobalPrefix('api/v1');
18
+
19
+ const configService = app.get(ConfigService);
20
+
21
+ const port = configService.get<number>('PORT');
22
+ const corsOrigins = configService.get<string>('CORS_ORIGIN')?.split(',');
23
+
24
+ app.enableCors({
25
+ origins: corsOrigins,
26
+ credentials: true,
27
+ });
28
+
29
+ app.use(cookieParser());
30
+
31
+ // Configure Helmet with strict security headers
32
+ app.use(
33
+ helmet({
34
+ contentSecurityPolicy: {
35
+ directives: {
36
+ defaultSrc: ["'self'"],
37
+ styleSrc: ["'self'", "'unsafe-inline'"],
38
+ scriptSrc: ["'self'"],
39
+ imgSrc: ["'self'", 'data:', 'https:'],
40
+ },
41
+ },
42
+ crossOriginEmbedderPolicy: true,
43
+ crossOriginOpenerPolicy: { policy: 'same-origin' },
44
+ crossOriginResourcePolicy: { policy: 'same-origin' },
45
+ dnsPrefetchControl: { allow: false },
46
+ frameguard: { action: 'deny' },
47
+ hidePoweredBy: true,
48
+ hsts: {
49
+ maxAge: 31536000,
50
+ includeSubDomains: true,
51
+ preload: true,
52
+ },
53
+ ieNoOpen: true,
54
+ noSniff: true,
55
+ referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
56
+ xssFilter: true,
57
+ }),
58
+ );
59
+
60
+ app.useGlobalPipes(
61
+ new ValidationPipe({
62
+ whitelist: true,
63
+ forbidNonWhitelisted: true,
64
+ transform: true,
65
+ }),
66
+ );
67
+
68
+ // Get logger instance
69
+ const logger = app.get(Logger);
70
+
71
+ // Global response interceptor for standard API responses
72
+ app.useGlobalInterceptors(new ResponseInterceptor());
73
+
74
+ // Global exception filter for standard error responses
75
+ app.useGlobalFilters(new HttpExceptionFilter(logger));
76
+
77
+ await app.listen(port || 8080);
78
+
79
+ logger.log(
80
+ `🚀 Application is running on: http://localhost:${port || 8080}/api/v1`,
81
+ 'Bootstrap',
82
+ );
83
+ }
84
+ void bootstrap();
@@ -0,0 +1,133 @@
1
+ import {
2
+ Body,
3
+ Controller,
4
+ Get,
5
+ Post,
6
+ Req,
7
+ Res,
8
+ UseGuards,
9
+ } from '@nestjs/common';
10
+ import { AuthService } from './auth.service';
11
+ import { Request, Response } from 'express';
12
+ import { RefreshTokenGuard } from 'src/common/guards/refresh-token.guard';
13
+ import { GetUser } from 'src/common/decorators/get-user.decorator';
14
+ import { Public } from 'src/common/decorators/public.decorator';
15
+ import { SignupDto } from './dtos/signup.dto';
16
+ import { LoginDto } from './dtos/login.dto';
17
+ import { Throttle, SkipThrottle } from '@nestjs/throttler';
18
+ import { COOKIE_CONFIG } from 'src/common/constants/cookie.config';
19
+ import { CookieOptions } from 'express';
20
+
21
+ @Controller('auth')
22
+ export class AuthController {
23
+ constructor(private readonly authService: AuthService) {}
24
+
25
+ @Throttle({ strict: { ttl: 60000, limit: 3 } })
26
+ @Public()
27
+ @Post('/signup')
28
+ async signup(@Body() signupDto: SignupDto) {
29
+ return this.authService.signup(signupDto);
30
+ }
31
+
32
+ @Throttle({ strict: { ttl: 60000, limit: 5 } })
33
+ @Public()
34
+ @Post('/login')
35
+ async login(
36
+ @Body() loginDto: LoginDto,
37
+ @Req() req: Request,
38
+ @Res({ passthrough: true }) res: Response,
39
+ ) {
40
+ const deviceInfo = req.headers['user-agent'] || 'Unknown Device';
41
+ const ipAddress =
42
+ (req.headers['x-forwarded-for'] as string)?.split(',')[0] ||
43
+ req.ip ||
44
+ 'Unknown IP';
45
+
46
+ const data = await this.authService.login(loginDto, deviceInfo, ipAddress);
47
+
48
+ res.cookie(
49
+ COOKIE_CONFIG.ACCESS_TOKEN.name,
50
+ data.accessToken,
51
+ COOKIE_CONFIG.ACCESS_TOKEN.options as CookieOptions,
52
+ );
53
+ res.cookie(
54
+ COOKIE_CONFIG.REFRESH_TOKEN.name,
55
+ data.refreshToken,
56
+ COOKIE_CONFIG.REFRESH_TOKEN.options as CookieOptions,
57
+ );
58
+
59
+ return {
60
+ user: data.user,
61
+ };
62
+ }
63
+
64
+ @Throttle({ default: { ttl: 60000, limit: 10 } })
65
+ @Public()
66
+ @UseGuards(RefreshTokenGuard)
67
+ @Post('/refresh')
68
+ async refreshToken(
69
+ @GetUser('sub') userId: string,
70
+ @Req() req: Request,
71
+ @Res({ passthrough: true }) res: Response,
72
+ ) {
73
+ const rt = req.cookies[COOKIE_CONFIG.REFRESH_TOKEN.name] as string;
74
+ const deviceInfo = req.headers['user-agent'] || 'Unknown Device';
75
+ const ipAddress =
76
+ (req.headers['x-forwarded-for'] as string)?.split(',')[0] ||
77
+ req.ip ||
78
+ 'Unknown IP';
79
+
80
+ const { accessToken, refreshToken } = await this.authService.refreshToken(
81
+ userId,
82
+ rt,
83
+ deviceInfo,
84
+ ipAddress,
85
+ );
86
+
87
+ res.cookie(
88
+ COOKIE_CONFIG.ACCESS_TOKEN.name,
89
+ accessToken,
90
+ COOKIE_CONFIG.ACCESS_TOKEN.options as CookieOptions,
91
+ );
92
+ res.cookie(
93
+ COOKIE_CONFIG.REFRESH_TOKEN.name,
94
+ refreshToken,
95
+ COOKIE_CONFIG.REFRESH_TOKEN.options as CookieOptions,
96
+ );
97
+
98
+ return {
99
+ message: 'Tokens refreshed successfully',
100
+ };
101
+ }
102
+
103
+ @Public()
104
+ @UseGuards(RefreshTokenGuard)
105
+ @Post('/logout')
106
+ async logout(
107
+ @GetUser('sub') userId: string,
108
+ @Req() req: Request,
109
+ @Res({ passthrough: true }) res: Response,
110
+ ) {
111
+ const rt = req.cookies[COOKIE_CONFIG.REFRESH_TOKEN.name] as string | undefined;
112
+ await this.authService.logout(userId, rt);
113
+
114
+ res.clearCookie(
115
+ COOKIE_CONFIG.ACCESS_TOKEN.name,
116
+ COOKIE_CONFIG.ACCESS_TOKEN.options as CookieOptions | undefined,
117
+ );
118
+ res.clearCookie(
119
+ COOKIE_CONFIG.REFRESH_TOKEN.name,
120
+ COOKIE_CONFIG.REFRESH_TOKEN.options as CookieOptions | undefined,
121
+ );
122
+
123
+ return {
124
+ message: 'Logged out successfully',
125
+ };
126
+ }
127
+
128
+ @SkipThrottle()
129
+ @Get('/me')
130
+ async getMe(@GetUser('sub') userId: string) {
131
+ return this.authService.getMe(userId);
132
+ }
133
+ }
@@ -0,0 +1,11 @@
1
+ import { IsEmail, IsString } from 'class-validator';
2
+ import { IsStrongPassword } from 'src/common/validators/password.validator';
3
+
4
+ export class LoginDto {
5
+ @IsEmail()
6
+ email: string;
7
+
8
+ @IsString()
9
+ @IsStrongPassword()
10
+ password: string;
11
+ }
@@ -0,0 +1,8 @@
1
+ import { IsString, MinLength } from 'class-validator';
2
+ import { LoginDto } from './login.dto';
3
+
4
+ export class SignupDto extends LoginDto {
5
+ @IsString()
6
+ @MinLength(2)
7
+ fullName: string;
8
+ }
@@ -0,0 +1,20 @@
1
+ import { Controller, Get } from '@nestjs/common';
2
+ import { Public } from '../../common/decorators/public.decorator';
3
+
4
+ @Controller('health')
5
+ export class HealthController {
6
+ @Public()
7
+ @Get()
8
+ check() {
9
+ return {
10
+ status: 'ok',
11
+ timestamp: new Date().toISOString(),
12
+ services: {
13
+ application: {
14
+ status: 'healthy',
15
+ uptime: process.uptime(),
16
+ },
17
+ },
18
+ };
19
+ }
20
+ }
@@ -0,0 +1,7 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { HealthController } from './health.controller';
3
+
4
+ @Module({
5
+ controllers: [HealthController],
6
+ })
7
+ export class HealthModule {}
@@ -0,0 +1,12 @@
1
+ import { IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
2
+
3
+ export class UpdateProfileDto {
4
+ @IsOptional()
5
+ @IsString()
6
+ @MinLength(2)
7
+ @MaxLength(100)
8
+ fullName?: string;
9
+
10
+ // Email update removed - requires separate verification flow for security
11
+ // Implement email change in a dedicated endpoint with verification
12
+ }
@@ -0,0 +1,13 @@
1
+ import { IsBoolean, IsEnum, IsOptional } from 'class-validator';
2
+ import { UpdateProfileDto } from './update-profile.dto';
3
+ import { UserRole } from 'src/common/guards/roles.guard';
4
+
5
+ export class UpdateUserDto extends UpdateProfileDto {
6
+ @IsOptional()
7
+ @IsEnum(UserRole, { message: 'Role must be a valid UserRole' })
8
+ role?: UserRole;
9
+
10
+ @IsOptional()
11
+ @IsBoolean()
12
+ isActive?: boolean;
13
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ Body,
3
+ Controller,
4
+ Delete,
5
+ Get,
6
+ Param,
7
+ Patch,
8
+ Query,
9
+ } from '@nestjs/common';
10
+ import { UsersService } from './users.service';
11
+ import { GetUser } from 'src/common/decorators/get-user.decorator';
12
+ import { Roles } from 'src/common/decorators/roles.decorator';
13
+ import { UserRole } from 'src/common/guards/roles.guard';
14
+ import { UpdateUserDto } from './dtos/update-user.dto';
15
+ import { UpdateProfileDto } from './dtos/update-profile.dto';
16
+ import { PaginationDto } from 'src/common/dtos/pagination.dto';
17
+
18
+ @Controller('users')
19
+ export class UsersController {
20
+ constructor(private readonly usersService: UsersService) {}
21
+
22
+ // profile routes
23
+ @Get('/profile')
24
+ async getProfile(@GetUser('sub') userId: string) {
25
+ return this.usersService.getProfile(userId);
26
+ }
27
+
28
+ @Patch('/profile')
29
+ async updateProfile(
30
+ @GetUser('sub') userId: string,
31
+ @Body() updateProfileDto: UpdateProfileDto,
32
+ ) {
33
+ return this.usersService.updateProfile(userId, updateProfileDto);
34
+ }
35
+
36
+ // admin routes
37
+ @Roles(UserRole.ADMIN)
38
+ @Get('/')
39
+ async getAllUsers(@Query() paginationDto: PaginationDto) {
40
+ const { page = 1, limit = 10 } = paginationDto;
41
+ return this.usersService.getAllUsers(page, limit);
42
+ }
43
+
44
+ @Roles(UserRole.ADMIN)
45
+ @Get('/:id')
46
+ async getUserById(@Param('id') id: string) {
47
+ return this.usersService.getUserById(id);
48
+ }
49
+
50
+ @Roles(UserRole.ADMIN)
51
+ @Patch('/:id')
52
+ async updateUserById(
53
+ @Param('id') id: string,
54
+ @Body() updateUserDto: UpdateUserDto,
55
+ ) {
56
+ return this.usersService.updateUserById(id, updateUserDto);
57
+ }
58
+
59
+ //this soft deletes a user by setting isActive to false
60
+ @Roles(UserRole.ADMIN)
61
+ @Delete('/:id')
62
+ async deleteUserById(@Param('id') id: string) {
63
+ return this.usersService.deleteUserById(id);
64
+ }
65
+ }
@@ -0,0 +1,24 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { INestApplication } from '@nestjs/common';
3
+ import request from 'supertest';
4
+ import { AppModule } from './../src/app.module';
5
+
6
+ describe('AppController (e2e)', () => {
7
+ let app: INestApplication;
8
+
9
+ beforeEach(async () => {
10
+ const moduleFixture: TestingModule = await Test.createTestingModule({
11
+ imports: [AppModule],
12
+ }).compile();
13
+
14
+ app = moduleFixture.createNestApplication();
15
+ await app.init();
16
+ });
17
+
18
+ it('/ (GET)', async () => {
19
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
20
+ const server = app.getHttpServer();
21
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
22
+ await request(server).get('/').expect(404);
23
+ });
24
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "moduleFileExtensions": ["js", "json", "ts"],
3
+ "rootDir": ".",
4
+ "testEnvironment": "node",
5
+ "testRegex": ".e2e-spec.ts$",
6
+ "transform": {
7
+ "^.+\\.(t|j)s$": "ts-jest"
8
+ }
9
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "moduleResolution": "node",
5
+ "esModuleInterop": true,
6
+ "isolatedModules": true,
7
+ "declaration": true,
8
+ "removeComments": true,
9
+ "emitDecoratorMetadata": true,
10
+ "experimentalDecorators": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "target": "ES2022",
13
+ "sourceMap": true,
14
+ "outDir": "./dist",
15
+ "baseUrl": "./",
16
+ "incremental": true,
17
+ "skipLibCheck": true,
18
+ "strictNullChecks": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "noImplicitAny": false,
21
+ "strictBindCallApply": false,
22
+ "noFallthroughCasesInSwitch": false
23
+ }
24
+ }