UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,917 lines (1,638 loc) 55.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.tsedTemplate = void 0; exports.tsedTemplate = { id: 'tsed', name: 'Ts.ED', displayName: 'Ts.ED', description: 'TypeScript framework built on Express/Koa with decorators, DI, and enterprise features', language: 'typescript', framework: 'tsed', version: '7.0.0', tags: ['typescript', 'decorators', 'di', 'express', 'openapi'], features: [ 'TypeScript decorators', 'Dependency injection', 'OpenAPI/Swagger', 'GraphQL support', 'WebSocket (Socket.io)', 'Passport.js auth', 'TypeORM integration', 'Exception filters', 'Interceptors (AOP)', 'Model validation', 'Testing framework', 'Docker support' ], dependencies: { '@tsed/common': '^7.0.0', '@tsed/core': '^7.0.0', '@tsed/di': '^7.0.0', '@tsed/exceptions': '^7.0.0', '@tsed/json-mapper': '^7.0.0', '@tsed/platform-express': '^7.0.0', '@tsed/schema': '^7.0.0', '@tsed/swagger': '^7.0.0', '@tsed/typeorm': '^7.0.0', '@tsed/passport': '^7.0.0', '@tsed/socketio': '^7.0.0', '@tsed/graphql': '^7.0.0', '@tsed/apollo': '^7.0.0', '@tsed/ajv': '^7.0.0', '@tsed/testing': '^7.0.0', 'express': '^4.18.2', 'cors': '^2.8.5', 'helmet': '^7.0.0', 'compression': '^1.7.4', 'cookie-parser': '^1.4.6', 'express-session': '^1.17.3', 'typeorm': '^0.3.17', 'reflect-metadata': '^0.1.13', 'class-transformer': '^0.5.1', 'class-validator': '^0.14.0', 'passport': '^0.6.0', 'passport-local': '^1.0.0', 'passport-jwt': '^4.0.1', 'jsonwebtoken': '^9.0.2', 'bcrypt': '^5.1.1', 'socket.io': '^4.6.2', 'graphql': '^16.6.0', 'apollo-server-express': '^3.12.0', 'type-graphql': '^2.0.0-beta.1', 'dotenv': '^16.3.1', 'winston': '^3.10.0', 'sqlite3': '^5.1.6' }, devDependencies: { '@types/node': '^20.0.0', '@types/express': '^4.17.17', '@types/cors': '^2.8.13', '@types/compression': '^1.7.2', '@types/cookie-parser': '^1.4.3', '@types/express-session': '^1.17.7', '@types/passport': '^1.0.12', '@types/passport-local': '^1.0.35', '@types/passport-jwt': '^3.0.9', '@types/jsonwebtoken': '^9.0.2', '@types/bcrypt': '^5.0.0', '@types/jest': '^29.5.2', '@types/supertest': '^2.0.12', 'typescript': '^5.0.0', 'ts-node': '^10.9.1', 'ts-node-dev': '^2.0.0', 'jest': '^29.5.0', 'ts-jest': '^29.1.0', 'supertest': '^6.3.3', '@typescript-eslint/eslint-plugin': '^5.59.0', '@typescript-eslint/parser': '^5.59.0', 'eslint': '^8.38.0', 'prettier': '^2.8.7' }, scripts: { 'dev': 'ts-node-dev --respawn --transpile-only --exit-child src/index.ts', 'build': 'tsc', 'start': 'node dist/index.js', 'test': 'jest', 'test:watch': 'jest --watch', 'test:coverage': 'jest --coverage', 'lint': 'eslint "src/**/*.ts"', 'format': 'prettier --write "src/**/*.ts"', 'migration:run': 'typeorm migration:run', 'migration:revert': 'typeorm migration:revert', 'docker:build': 'docker build -t tsed-app .', 'docker:run': 'docker run -p 3000:3000 tsed-app' }, files: { 'src/index.ts': `import { $log } from '@tsed/common'; import { PlatformExpress } from '@tsed/platform-express'; import { Server } from './Server'; async function bootstrap() { try { const platform = await PlatformExpress.bootstrap(Server, { // Platform options }); await platform.listen(); $log.info('Server initialized'); } catch (error) { $log.error({ event: 'SERVER_BOOTSTRAP_ERROR', error }); process.exit(1); } } bootstrap(); `, 'src/Server.ts': `import { Configuration, Inject } from '@tsed/di'; import { PlatformApplication } from '@tsed/common'; import '@tsed/platform-express'; import '@tsed/ajv'; import '@tsed/swagger'; import '@tsed/typeorm'; import '@tsed/passport'; import '@tsed/socketio'; import '@tsed/graphql'; import '@tsed/apollo'; import cors from 'cors'; import helmet from 'helmet'; import compression from 'compression'; import cookieParser from 'cookie-parser'; import session from 'express-session'; import { config } from './config'; import * as rest from './controllers/rest'; import * as socket from './controllers/socket'; import * as pages from './controllers/pages'; @Configuration({ ...config, acceptMimes: ['application/json'], httpPort: process.env.PORT || 3000, httpsPort: false, componentsScan: [ \`\${__dirname}/protocols/*.ts\`, \`\${__dirname}/services/**/*.ts\`, \`\${__dirname}/middlewares/**/*.ts\`, \`\${__dirname}/filters/**/*.ts\`, \`\${__dirname}/decorators/**/*.ts\`, \`\${__dirname}/interceptors/**/*.ts\` ], mount: { '/api': [...Object.values(rest)], '/ws': [...Object.values(socket)], '/': [...Object.values(pages)] }, swagger: [ { path: '/api-docs', specVersion: '3.0.1', spec: { info: { title: 'Ts.ED API', version: '1.0.0', description: 'API documentation for Ts.ED application' } } } ], typeorm: [ { name: 'default', type: 'sqlite', database: './db.sqlite', synchronize: true, logging: false, entities: [ \`\${__dirname}/entities/**/*.ts\` ], migrations: [ \`\${__dirname}/migrations/**/*.ts\` ], subscribers: [ \`\${__dirname}/subscribers/**/*.ts\` ] } ], passport: { userInfoModel: 'UserInfo' }, socketIO: { cors: { origin: '*', credentials: true } }, graphql: { server1: { path: '/graphql', playground: true, schema: \`\${__dirname}/graphql/schema.graphql\`, resolvers: [ \`\${__dirname}/graphql/resolvers/**/*.ts\` ] } }, logger: { level: 'debug', logRequest: true, requestFields: ['reqId', 'method', 'url', 'headers', 'body', 'query', 'params', 'duration'] }, exclude: [ '**/*.spec.ts' ] }) export class Server { @Inject() protected app: PlatformApplication; @Configuration() protected settings: Configuration; $beforeRoutesInit() { this.app .use(helmet()) .use(cors()) .use(compression()) .use(cookieParser()) .use(session({ secret: process.env.SESSION_SECRET || 'default-secret', resave: false, saveUninitialized: true, cookie: { secure: false } })); } } `, 'src/config/index.ts': `import { readFileSync } from 'fs'; import { envs } from './envs'; import loggerConfig from './logger'; import { resolve } from 'path'; const pkg = JSON.parse(readFileSync('./package.json', { encoding: 'utf8' })); export const config: Partial<TsED.Configuration> = { version: pkg.version, envs, logger: loggerConfig, rootDir: resolve(__dirname, '..'), statics: { '/': [ { root: \`./public\`, maxAge: '1d' } ] } }; `, 'src/config/envs/index.ts': `export const envs = { production: process.env.NODE_ENV === 'production', development: process.env.NODE_ENV === 'development', test: process.env.NODE_ENV === 'test' }; `, 'src/config/logger/index.ts': `export default { disableRoutesSummary: false, format: process.env.NODE_ENV === 'production' ? 'json' : 'dev', jsonIndentation: process.env.NODE_ENV === 'production' ? 0 : 2, level: process.env.LOG_LEVEL || 'debug', logRequest: true, logStart: true }; `, 'src/controllers/rest/index.ts': `export * from './UserController'; export * from './AuthController'; export * from './ProductController'; `, 'src/controllers/rest/UserController.ts': `import { Controller, Get, Post, Put, Delete, PathParams, BodyParams } from '@tsed/common'; import { Inject } from '@tsed/di'; import { NotFound } from '@tsed/exceptions'; import { Summary, Returns, ReturnsArray, Groups, Required, Description } from '@tsed/schema'; import { Authorize } from '@tsed/passport'; import { UserService } from '../../services/UserService'; import { User } from '../../entities/User'; import { CreateUserDto, UpdateUserDto } from '../../dto/UserDto'; @Controller('/users') export class UserController { @Inject() private userService: UserService; @Get('/') @Summary('Get all users') @ReturnsArray(User) async getAll(): Promise<User[]> { return this.userService.findAll(); } @Get('/:id') @Summary('Get user by ID') @Returns(User) @Returns(404).Description('User not found') async getById(@PathParams('id') id: string): Promise<User> { const user = await this.userService.findById(id); if (!user) { throw new NotFound('User not found'); } return user; } @Post('/') @Summary('Create new user') @Returns(201, User) @Returns(400).Description('Invalid user data') async create(@Required() @BodyParams() @Groups('creation') dto: CreateUserDto): Promise<User> { return this.userService.create(dto); } @Put('/:id') @Summary('Update user') @Authorize('jwt') @Returns(User) @Returns(404).Description('User not found') async update( @PathParams('id') id: string, @Required() @BodyParams() @Groups('update') dto: UpdateUserDto ): Promise<User> { const user = await this.userService.update(id, dto); if (!user) { throw new NotFound('User not found'); } return user; } @Delete('/:id') @Summary('Delete user') @Authorize('jwt') @Returns(204) @Returns(404).Description('User not found') async delete(@PathParams('id') id: string): Promise<void> { const deleted = await this.userService.delete(id); if (!deleted) { throw new NotFound('User not found'); } } } `, 'src/controllers/rest/AuthController.ts': `import { Controller, Post, BodyParams, Req, Res, Get } from '@tsed/common'; import { Inject } from '@tsed/di'; import { Authenticate, Authorize } from '@tsed/passport'; import { Returns, Required, Security } from '@tsed/schema'; import { Unauthorized } from '@tsed/exceptions'; import { AuthService } from '../../services/AuthService'; import { LoginDto, RegisterDto, TokenResponse } from '../../dto/AuthDto'; import { User } from '../../entities/User'; @Controller('/auth') export class AuthController { @Inject() private authService: AuthService; @Post('/login') @Authenticate('local') @Returns(200, TokenResponse) @Returns(401).Description('Invalid credentials') async login(@Req() req: Req, @Required() @BodyParams() credentials: LoginDto): Promise<TokenResponse> { const user = req.user as User; if (!user) { throw new Unauthorized('Invalid credentials'); } return this.authService.generateToken(user); } @Post('/register') @Returns(201, User) @Returns(400).Description('Invalid registration data') async register(@Required() @BodyParams() dto: RegisterDto): Promise<User> { return this.authService.register(dto); } @Get('/profile') @Authorize('jwt') @Security('jwt') @Returns(User) @Returns(401).Description('Unauthorized') async getProfile(@Req() req: Req): Promise<User> { return req.user as User; } @Post('/logout') @Authorize('jwt') @Returns(204) async logout(@Req() req: Req, @Res() res: Res): Promise<void> { req.logout(() => { res.status(204).send(); }); } } `, 'src/controllers/rest/ProductController.ts': `import { Controller, Get, Post, Put, Delete, PathParams, BodyParams, QueryParams } from '@tsed/common'; import { Inject } from '@tsed/di'; import { NotFound } from '@tsed/exceptions'; import { Summary, Returns, ReturnsArray, Required, Min, Max } from '@tsed/schema'; import { UseCache } from '../../decorators/UseCache'; import { ValidateQuery } from '../../decorators/ValidateQuery'; import { ProductService } from '../../services/ProductService'; import { Product } from '../../entities/Product'; import { CreateProductDto, UpdateProductDto, ProductQueryDto } from '../../dto/ProductDto'; @Controller('/products') export class ProductController { @Inject() private productService: ProductService; @Get('/') @Summary('Get products with pagination and filtering') @ReturnsArray(Product) @UseCache({ ttl: 300 }) async getProducts(@QueryParams() @ValidateQuery() query: ProductQueryDto): Promise<Product[]> { return this.productService.findWithFilters(query); } @Get('/:id') @Summary('Get product by ID') @Returns(Product) @Returns(404).Description('Product not found') @UseCache({ ttl: 600 }) async getById(@PathParams('id') id: string): Promise<Product> { const product = await this.productService.findById(id); if (!product) { throw new NotFound('Product not found'); } return product; } @Post('/') @Summary('Create new product') @Returns(201, Product) @Returns(400).Description('Invalid product data') async create(@Required() @BodyParams() dto: CreateProductDto): Promise<Product> { return this.productService.create(dto); } @Put('/:id') @Summary('Update product') @Returns(Product) @Returns(404).Description('Product not found') async update( @PathParams('id') id: string, @Required() @BodyParams() dto: UpdateProductDto ): Promise<Product> { const product = await this.productService.update(id, dto); if (!product) { throw new NotFound('Product not found'); } return product; } @Delete('/:id') @Summary('Delete product') @Returns(204) @Returns(404).Description('Product not found') async delete(@PathParams('id') id: string): Promise<void> { const deleted = await this.productService.delete(id); if (!deleted) { throw new NotFound('Product not found'); } } } `, 'src/controllers/socket/index.ts': `export * from './ChatSocketController'; export * from './NotificationSocketController'; `, 'src/controllers/socket/ChatSocketController.ts': `import { SocketController, Input, Emit, Args, Socket, Nsp } from '@tsed/socketio'; import { Inject } from '@tsed/di'; import { Namespace, Socket as IOSocket } from 'socket.io'; import { ChatService } from '../../services/ChatService'; import { Message, JoinRoomDto, SendMessageDto } from '../../dto/ChatDto'; @SocketController('/chat') export class ChatSocketController { @Inject() private chatService: ChatService; @Input('join-room') @Emit('user-joined') async joinRoom(@Args(0) data: JoinRoomDto, @Socket() socket: IOSocket, @Nsp() nsp: Namespace) { await socket.join(data.room); const message = await this.chatService.createSystemMessage({ room: data.room, content: \`\${data.username} joined the room\` }); nsp.to(data.room).emit('message', message); return { room: data.room, username: data.username }; } @Input('send-message') @Emit('message') async sendMessage(@Args(0) data: SendMessageDto, @Socket() socket: IOSocket, @Nsp() nsp: Namespace): Promise<Message> { const message = await this.chatService.createMessage(data); nsp.to(data.room).emit('message', message); return message; } @Input('leave-room') @Emit('user-left') async leaveRoom(@Args(0) data: JoinRoomDto, @Socket() socket: IOSocket, @Nsp() nsp: Namespace) { await socket.leave(data.room); const message = await this.chatService.createSystemMessage({ room: data.room, content: \`\${data.username} left the room\` }); nsp.to(data.room).emit('message', message); return { room: data.room, username: data.username }; } @Input('typing') async userTyping(@Args(0) data: { room: string; username: string; }, @Socket() socket: IOSocket, @Nsp() nsp: Namespace) { socket.broadcast.to(data.room).emit('user-typing', { username: data.username, isTyping: true }); } @Input('stop-typing') async userStoppedTyping(@Args(0) data: { room: string; username: string; }, @Socket() socket: IOSocket, @Nsp() nsp: Namespace) { socket.broadcast.to(data.room).emit('user-typing', { username: data.username, isTyping: false }); } } `, 'src/controllers/socket/NotificationSocketController.ts': `import { SocketController, Input, Emit, Args, Socket, Nsp, SocketSession } from '@tsed/socketio'; import { Inject } from '@tsed/di'; import { Namespace, Socket as IOSocket } from 'socket.io'; import { NotificationService } from '../../services/NotificationService'; import { Notification, SubscribeDto } from '../../dto/NotificationDto'; @SocketController('/notifications') export class NotificationSocketController { @Inject() private notificationService: NotificationService; @Input('subscribe') async subscribe(@Args(0) data: SubscribeDto, @Socket() socket: IOSocket, @SocketSession() session: any) { session.userId = data.userId; await socket.join(\`user:\${data.userId}\`); const unreadNotifications = await this.notificationService.getUnreadForUser(data.userId); socket.emit('unread-notifications', unreadNotifications); } @Input('mark-as-read') @Emit('notification-read') async markAsRead(@Args(0) notificationId: string, @SocketSession() session: any): Promise<Notification> { return this.notificationService.markAsRead(notificationId, session.userId); } @Input('mark-all-as-read') @Emit('all-notifications-read') async markAllAsRead(@SocketSession() session: any): Promise<number> { return this.notificationService.markAllAsRead(session.userId); } // Server-side method to send notifications async sendNotificationToUser(userId: string, notification: Notification, @Nsp() nsp: Namespace) { nsp.to(\`user:\${userId}\`).emit('new-notification', notification); } @Input('unsubscribe') async unsubscribe(@Socket() socket: IOSocket, @SocketSession() session: any) { if (session.userId) { await socket.leave(\`user:\${session.userId}\`); delete session.userId; } } } `, 'src/controllers/pages/index.ts': `export * from './HomeController'; `, 'src/controllers/pages/HomeController.ts': `import { Controller, Get, View } from '@tsed/common'; @Controller('/') export class HomeController { @Get('/') @View('index.ejs') home() { return { title: 'Ts.ED Application', message: 'Welcome to Ts.ED!' }; } @Get('/health') health() { return { status: 'healthy', timestamp: new Date().toISOString() }; } } `, 'src/entities/User.ts': `import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, BeforeInsert, BeforeUpdate } from 'typeorm'; import { Property, Required, Email, MinLength, Groups, Ignore, Allow } from '@tsed/schema'; import * as bcrypt from 'bcrypt'; @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') @Property() id: string; @Column({ unique: true }) @Required() @Email() @Groups('!update') email: string; @Column() @Required() @MinLength(2) username: string; @Column() @Required() @MinLength(8) @Groups('creation') @Ignore() password: string; @Column({ default: 'user' }) @Allow('admin') role: string; @Column({ default: true }) @Property() isActive: boolean; @CreateDateColumn() @Property() createdAt: Date; @UpdateDateColumn() @Property() updatedAt: Date; @BeforeInsert() @BeforeUpdate() async hashPassword() { if (this.password) { this.password = await bcrypt.hash(this.password, 10); } } async validatePassword(password: string): Promise<boolean> { return bcrypt.compare(password, this.password); } } `, 'src/entities/Product.ts': `import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; import { Property, Required, Min, Max, Pattern, Default, Example, Description } from '@tsed/schema'; @Entity('products') export class Product { @PrimaryGeneratedColumn('uuid') @Property() id: string; @Column() @Required() @Property() @Description('Product name') @Example('Laptop') name: string; @Column('text') @Property() @Description('Product description') description: string; @Column('decimal', { precision: 10, scale: 2 }) @Required() @Min(0) @Property() @Description('Product price') @Example(999.99) price: number; @Column({ default: 0 }) @Min(0) @Default(0) @Property() @Description('Available quantity') stock: number; @Column() @Required() @Pattern(/^[A-Z]{3}-\d{4}$/) @Property() @Description('Product SKU') @Example('LAP-1234') sku: string; @Column({ default: true }) @Default(true) @Property() isAvailable: boolean; @CreateDateColumn() @Property() createdAt: Date; @UpdateDateColumn() @Property() updatedAt: Date; } `, 'src/dto/UserDto.ts': `import { Property, Required, Email, MinLength, Groups, Optional } from '@tsed/schema'; export class CreateUserDto { @Required() @Email() @Groups('creation') email: string; @Required() @MinLength(2) username: string; @Required() @MinLength(8) @Groups('creation') password: string; } export class UpdateUserDto { @Optional() @MinLength(2) username?: string; @Optional() @MinLength(8) password?: string; @Optional() isActive?: boolean; } `, 'src/dto/AuthDto.ts': `import { Property, Required, Email, MinLength } from '@tsed/schema'; export class LoginDto { @Required() @Email() email: string; @Required() password: string; } export class RegisterDto { @Required() @Email() email: string; @Required() @MinLength(2) username: string; @Required() @MinLength(8) password: string; } export class TokenResponse { @Property() accessToken: string; @Property() tokenType: string = 'Bearer'; @Property() expiresIn: number; @Property() user: { id: string; email: string; username: string; role: string; }; } `, 'src/dto/ProductDto.ts': `import { Property, Required, Min, Max, Pattern, Optional, Integer, Default } from '@tsed/schema'; export class CreateProductDto { @Required() name: string; @Optional() description?: string; @Required() @Min(0) price: number; @Optional() @Min(0) @Default(0) stock?: number; @Required() @Pattern(/^[A-Z]{3}-\d{4}$/) sku: string; } export class UpdateProductDto { @Optional() name?: string; @Optional() description?: string; @Optional() @Min(0) price?: number; @Optional() @Min(0) stock?: number; @Optional() isAvailable?: boolean; } export class ProductQueryDto { @Optional() @Min(1) @Integer() @Default(1) page?: number = 1; @Optional() @Min(1) @Max(100) @Integer() @Default(20) limit?: number = 20; @Optional() search?: string; @Optional() @Min(0) minPrice?: number; @Optional() @Min(0) maxPrice?: number; @Optional() isAvailable?: boolean; @Optional() @Pattern(/^(name|price|createdAt)$/) @Default('createdAt') sortBy?: string = 'createdAt'; @Optional() @Pattern(/^(asc|desc)$/) @Default('desc') sortOrder?: 'asc' | 'desc' = 'desc'; } `, 'src/dto/ChatDto.ts': `import { Property, Required } from '@tsed/schema'; export class JoinRoomDto { @Required() room: string; @Required() username: string; } export class SendMessageDto { @Required() room: string; @Required() username: string; @Required() content: string; } export class Message { @Property() id: string; @Property() room: string; @Property() username: string; @Property() content: string; @Property() timestamp: Date; @Property() type: 'user' | 'system'; } `, 'src/dto/NotificationDto.ts': `import { Property, Required } from '@tsed/schema'; export class SubscribeDto { @Required() userId: string; } export class Notification { @Property() id: string; @Property() userId: string; @Property() title: string; @Property() message: string; @Property() type: 'info' | 'warning' | 'error' | 'success'; @Property() isRead: boolean; @Property() createdAt: Date; @Property() readAt?: Date; } `, 'src/services/UserService.ts': `import { Injectable } from '@tsed/di'; import { InjectRepository } from '@tsed/typeorm'; import { Repository } from 'typeorm'; import { User } from '../entities/User'; import { CreateUserDto, UpdateUserDto } from '../dto/UserDto'; @Injectable() export class UserService { @InjectRepository(User) private repository: Repository<User>; async findAll(): Promise<User[]> { return this.repository.find({ select: ['id', 'email', 'username', 'role', 'isActive', 'createdAt', 'updatedAt'] }); } async findById(id: string): Promise<User | null> { return this.repository.findOne({ where: { id }, select: ['id', 'email', 'username', 'role', 'isActive', 'createdAt', 'updatedAt'] }); } async findByEmail(email: string): Promise<User | null> { return this.repository.findOne({ where: { email } }); } async create(dto: CreateUserDto): Promise<User> { const user = this.repository.create(dto); return this.repository.save(user); } async update(id: string, dto: UpdateUserDto): Promise<User | null> { const user = await this.repository.findOne({ where: { id } }); if (!user) { return null; } Object.assign(user, dto); return this.repository.save(user); } async delete(id: string): Promise<boolean> { const result = await this.repository.delete(id); return result.affected !== 0; } } `, 'src/services/AuthService.ts': `import { Injectable } from '@tsed/di'; import { Inject } from '@tsed/di'; import { Forbidden } from '@tsed/exceptions'; import * as jwt from 'jsonwebtoken'; import { UserService } from './UserService'; import { User } from '../entities/User'; import { RegisterDto, TokenResponse } from '../dto/AuthDto'; @Injectable() export class AuthService { @Inject() private userService: UserService; private readonly jwtSecret = process.env.JWT_SECRET || 'default-secret'; private readonly jwtExpiresIn = '24h'; async register(dto: RegisterDto): Promise<User> { const existingUser = await this.userService.findByEmail(dto.email); if (existingUser) { throw new Forbidden('Email already registered'); } return this.userService.create(dto); } async validateUser(email: string, password: string): Promise<User | null> { const user = await this.userService.findByEmail(email); if (user && await user.validatePassword(password)) { return user; } return null; } generateToken(user: User): TokenResponse { const payload = { sub: user.id, email: user.email, role: user.role }; const accessToken = jwt.sign(payload, this.jwtSecret, { expiresIn: this.jwtExpiresIn }); return { accessToken, tokenType: 'Bearer', expiresIn: 86400, // 24 hours in seconds user: { id: user.id, email: user.email, username: user.username, role: user.role } }; } async verifyToken(token: string): Promise<any> { try { return jwt.verify(token, this.jwtSecret); } catch (error) { return null; } } } `, 'src/services/ProductService.ts': `import { Injectable } from '@tsed/di'; import { InjectRepository } from '@tsed/typeorm'; import { Repository, Like, Between, FindManyOptions } from 'typeorm'; import { Product } from '../entities/Product'; import { CreateProductDto, UpdateProductDto, ProductQueryDto } from '../dto/ProductDto'; @Injectable() export class ProductService { @InjectRepository(Product) private repository: Repository<Product>; async findWithFilters(query: ProductQueryDto): Promise<Product[]> { const { page = 1, limit = 20, search, minPrice, maxPrice, isAvailable, sortBy = 'createdAt', sortOrder = 'desc' } = query; const where: any = {}; if (search) { where.name = Like(\`%\${search}%\`); } if (minPrice !== undefined && maxPrice !== undefined) { where.price = Between(minPrice, maxPrice); } else if (minPrice !== undefined) { where.price = Between(minPrice, Number.MAX_SAFE_INTEGER); } else if (maxPrice !== undefined) { where.price = Between(0, maxPrice); } if (isAvailable !== undefined) { where.isAvailable = isAvailable; } const options: FindManyOptions<Product> = { where, order: { [sortBy]: sortOrder.toUpperCase() }, skip: (page - 1) * limit, take: limit }; return this.repository.find(options); } async findById(id: string): Promise<Product | null> { return this.repository.findOne({ where: { id } }); } async create(dto: CreateProductDto): Promise<Product> { const product = this.repository.create(dto); return this.repository.save(product); } async update(id: string, dto: UpdateProductDto): Promise<Product | null> { const product = await this.repository.findOne({ where: { id } }); if (!product) { return null; } Object.assign(product, dto); return this.repository.save(product); } async delete(id: string): Promise<boolean> { const result = await this.repository.delete(id); return result.affected !== 0; } } `, 'src/services/ChatService.ts': `import { Injectable } from '@tsed/di'; import { v4 as uuidv4 } from 'uuid'; import { Message, SendMessageDto } from '../dto/ChatDto'; @Injectable() export class ChatService { private messages: Map<string, Message[]> = new Map(); async createMessage(dto: SendMessageDto): Promise<Message> { const message: Message = { id: uuidv4(), room: dto.room, username: dto.username, content: dto.content, timestamp: new Date(), type: 'user' }; this.addMessageToRoom(dto.room, message); return message; } async createSystemMessage(data: { room: string; content: string }): Promise<Message> { const message: Message = { id: uuidv4(), room: data.room, username: 'System', content: data.content, timestamp: new Date(), type: 'system' }; this.addMessageToRoom(data.room, message); return message; } async getMessagesForRoom(room: string, limit: number = 50): Promise<Message[]> { const messages = this.messages.get(room) || []; return messages.slice(-limit); } private addMessageToRoom(room: string, message: Message): void { if (!this.messages.has(room)) { this.messages.set(room, []); } const roomMessages = this.messages.get(room)!; roomMessages.push(message); // Keep only last 1000 messages per room if (roomMessages.length > 1000) { roomMessages.shift(); } } } `, 'src/services/NotificationService.ts': `import { Injectable } from '@tsed/di'; import { v4 as uuidv4 } from 'uuid'; import { Notification } from '../dto/NotificationDto'; @Injectable() export class NotificationService { private notifications: Map<string, Notification[]> = new Map(); async createNotification(userId: string, data: Partial<Notification>): Promise<Notification> { const notification: Notification = { id: uuidv4(), userId, title: data.title || 'New Notification', message: data.message || '', type: data.type || 'info', isRead: false, createdAt: new Date() }; if (!this.notifications.has(userId)) { this.notifications.set(userId, []); } this.notifications.get(userId)!.push(notification); return notification; } async getUnreadForUser(userId: string): Promise<Notification[]> { const userNotifications = this.notifications.get(userId) || []; return userNotifications.filter(n => !n.isRead); } async markAsRead(notificationId: string, userId: string): Promise<Notification> { const userNotifications = this.notifications.get(userId) || []; const notification = userNotifications.find(n => n.id === notificationId); if (notification) { notification.isRead = true; notification.readAt = new Date(); } return notification!; } async markAllAsRead(userId: string): Promise<number> { const userNotifications = this.notifications.get(userId) || []; let count = 0; userNotifications.forEach(notification => { if (!notification.isRead) { notification.isRead = true; notification.readAt = new Date(); count++; } }); return count; } } `, 'src/protocols/JwtProtocol.ts': `import { Inject } from '@tsed/di'; import { Req } from '@tsed/common'; import { Arg, OnInstall, OnVerify, Protocol } from '@tsed/passport'; import { Strategy, ExtractJwt } from 'passport-jwt'; import { UserService } from '../services/UserService'; @Protocol({ name: 'jwt', useStrategy: Strategy, settings: { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET || 'default-secret' } }) export class JwtProtocol implements OnVerify, OnInstall { @Inject() private userService: UserService; async $onVerify(@Req() req: Req, @Arg(0) payload: any) { const user = await this.userService.findById(payload.sub); if (!user || !user.isActive) { return false; } return user; } $onInstall(strategy: Strategy): void { // Additional strategy configuration if needed } } `, 'src/protocols/LocalProtocol.ts': `import { Inject } from '@tsed/di'; import { Req } from '@tsed/common'; import { BodyParams } from '@tsed/platform-params'; import { OnInstall, OnVerify, Protocol } from '@tsed/passport'; import { IStrategyOptions, Strategy } from 'passport-local'; import { AuthService } from '../services/AuthService'; @Protocol<IStrategyOptions>({ name: 'local', useStrategy: Strategy, settings: { usernameField: 'email', passwordField: 'password' } }) export class LocalProtocol implements OnVerify, OnInstall { @Inject() private authService: AuthService; async $onVerify(@Req() request: Req, @BodyParams() credentials: any) { const { email, password } = credentials; const user = await this.authService.validateUser(email, password); if (!user) { return false; } return user; } $onInstall(strategy: Strategy): void { // Additional strategy configuration if needed } } `, 'src/middlewares/ErrorHandlerMiddleware.ts': `import { Catch, ExceptionFilterMethods, PlatformContext } from '@tsed/common'; import { Exception } from '@tsed/exceptions'; @Catch(Error) export class ErrorHandlerMiddleware implements ExceptionFilterMethods { catch(exception: Exception, ctx: PlatformContext) { const { response, logger } = ctx; const error = this.mapError(exception); const status = exception.status || 500; logger.error({ error: exception, stack: exception.stack }); response .status(status) .json({ status, message: error.message, name: error.name, errors: error.errors, stack: process.env.NODE_ENV === 'development' ? exception.stack : undefined }); } private mapError(error: any) { return { name: error.name || 'InternalServerError', message: error.message || 'An error occurred', errors: error.errors || [] }; } } `, 'src/middlewares/RequestLoggerMiddleware.ts': `import { Middleware, Req, Res, Next } from '@tsed/common'; import { Logger } from '@tsed/logger'; @Middleware() export class RequestLoggerMiddleware { constructor(private logger: Logger) {} use(@Req() request: Req, @Res() response: Res, @Next() next: Next) { const start = Date.now(); const { method, url, headers } = request; this.logger.info({ event: 'REQUEST_STARTED', method, url, userAgent: headers['user-agent'] }); response.on('finish', () => { const duration = Date.now() - start; const { statusCode } = response; this.logger.info({ event: 'REQUEST_COMPLETED', method, url, statusCode, duration }); }); next(); } } `, 'src/decorators/UseCache.ts': `import { UseDecorator, StoreSet } from '@tsed/core'; import { useDecorators } from '@tsed/core'; import { Returns } from '@tsed/schema'; export interface CacheOptions { ttl?: number; key?: string; } export function UseCache(options: CacheOptions = {}): MethodDecorator { return useDecorators( StoreSet('cache', options), UseDecorator((target: any, propertyKey: string, descriptor: PropertyDescriptor) => { const originalMethod = descriptor.value; const cache = new Map<string, { value: any; expires: number }>(); descriptor.value = async function (...args: any[]) { const cacheKey = options.key || \`\${propertyKey}:\${JSON.stringify(args)}\`; const cached = cache.get(cacheKey); if (cached && cached.expires > Date.now()) { return cached.value; } const result = await originalMethod.apply(this, args); cache.set(cacheKey, { value: result, expires: Date.now() + (options.ttl || 300) * 1000 }); return result; }; return descriptor; }) ); } `, 'src/decorators/ValidateQuery.ts': `import { UseDecorator } from '@tsed/core'; import { BadRequest } from '@tsed/exceptions'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; export function ValidateQuery(): ParameterDecorator { return UseDecorator((target: any, propertyKey: string | symbol, parameterIndex: number) => { const types = Reflect.getMetadata('design:paramtypes', target, propertyKey); const type = types[parameterIndex]; const originalMethod = target[propertyKey]; target[propertyKey] = async function (...args: any[]) { const query = args[parameterIndex]; if (query && type) { const instance = plainToClass(type, query); const errors = await validate(instance); if (errors.length > 0) { throw new BadRequest('Invalid query parameters', errors); } args[parameterIndex] = instance; } return originalMethod.apply(this, args); }; }); } `, 'src/interceptors/PerformanceInterceptor.ts': `import { Interceptor, InterceptorMethods, InterceptorContext, InterceptorNext } from '@tsed/di'; import { Logger } from '@tsed/logger'; @Interceptor() export class PerformanceInterceptor implements InterceptorMethods { constructor(private logger: Logger) {} intercept(context: InterceptorContext<any>, next: InterceptorNext) { const start = Date.now(); const { target, propertyKey, args } = context; return next().then((result) => { const duration = Date.now() - start; if (duration > 1000) { this.logger.warn({ event: 'SLOW_METHOD', class: target.constructor.name, method: propertyKey, duration, threshold: 1000 }); } return result; }); } } `, 'src/filters/ValidationFilter.ts': `import { Catch, ExceptionFilterMethods, PlatformContext } from '@tsed/common'; import { ValidationError } from 'class-validator'; import { BadRequest } from '@tsed/exceptions'; @Catch(BadRequest) export class ValidationFilter implements ExceptionFilterMethods { catch(exception: BadRequest, ctx: PlatformContext) { const { response } = ctx; const errors = exception.origin as ValidationError[]; if (Array.isArray(errors) && errors[0] instanceof ValidationError) { const formattedErrors = this.formatValidationErrors(errors); response.status(400).json({ status: 400, message: 'Validation failed', errors: formattedErrors }); } else { response.status(400).json({ status: 400, message: exception.message }); } } private formatValidationErrors(errors: ValidationError[]): any[] { return errors.map(error => ({ property: error.property, value: error.value, constraints: error.constraints })); } } `, 'src/graphql/schema.graphql': `type Query { users: [User!]! user(id: ID!): User products(filter: ProductFilter): [Product!]! product(id: ID!): Product } type Mutation { createUser(input: CreateUserInput!): User! updateUser(id: ID!, input: UpdateUserInput!): User! deleteUser(id: ID!): Boolean! createProduct(input: CreateProductInput!): Product! updateProduct(id: ID!, input: UpdateProductInput!): Product! deleteProduct(id: ID!): Boolean! } type User { id: ID! email: String! username: String! role: String! isActive: Boolean! createdAt: String! updatedAt: String! } type Product { id: ID! name: String! description: String price: Float! stock: Int! sku: String! isAvailable: Boolean! createdAt: String! updatedAt: String! } input CreateUserInput { email: String! username: String! password: String! } input UpdateUserInput { username: String password: String isActive: Boolean } input CreateProductInput { name: String! description: String price: Float! stock: Int sku: String! } input UpdateProductInput { name: String description: String price: Float stock: Int isAvailable: Boolean } input ProductFilter { search: String minPrice: Float maxPrice: Float isAvailable: Boolean } `, 'src/graphql/resolvers/UserResolver.ts': `import { ResolverService } from '@tsed/graphql'; import { Inject } from '@tsed/di'; import { Query, Mutation, Arg, Args } from 'type-graphql'; import { UserService } from '../../services/UserService'; import { User } from '../../entities/User'; import { CreateUserDto, UpdateUserDto } from '../../dto/UserDto'; @ResolverService(User) export class UserResolver { @Inject() private userService: UserService; @Query(() => [User]) async users(): Promise<User[]> { return this.userService.findAll(); } @Query(() => User, { nullable: true }) async user(@Arg('id') id: string): Promise<User | null> { return this.userService.findById(id); } @Mutation(() => User) async createUser(@Arg('input') input: CreateUserDto): Promise<User> { return this.userService.create(input); } @Mutation(() => User) async updateUser( @Arg('id') id: string, @Arg('input') input: UpdateUserDto ): Promise<User> { const user = await this.userService.update(id, input); if (!user) { throw new Error('User not found'); } return user; } @Mutation(() => Boolean) async deleteUser(@Arg('id') id: string): Promise<boolean> { return this.userService.delete(id); } } `, 'src/graphql/resolvers/ProductResolver.ts': `import { ResolverService } from '@tsed/graphql'; import { Inject } from '@tsed/di'; import { Query, Mutation, Arg, Args } from 'type-graphql'; import { ProductService } from '../../services/ProductService'; import { Product } from '../../entities/Product'; import { CreateProductDto, UpdateProductDto, ProductQueryDto } from '../../dto/ProductDto'; @ResolverService(Product) export class ProductResolver { @Inject() private productService: ProductService; @Query(() => [Product]) async products(@Arg('filter', { nullable: true }) filter?: ProductQueryDto): Promise<Product[]> { return this.productService.findWithFilters(filter || {}); } @Query(() => Product, { nullable: true }) async product(@Arg('id') id: string): Promise<Product | null> { return this.productService.findById(id); } @Mutation(() => Product) async createProduct(@Arg('input') input: CreateProductDto): Promise<Product> { return this.productService.create(input); } @Mutation(() => Product) async updateProduct( @Arg('id') id: string, @Arg('input') input: UpdateProductDto ): Promise<Product> { const product = await this.productService.update(id, input); if (!product) { throw new Error('Product not found'); } return product; } @Mutation(() => Boolean) async deleteProduct(@Arg('id') id: string): Promise<boolean> { return this.productService.delete(id); } } `, 'tests/unit/services/UserService.spec.ts': `import { PlatformTest } from '@tsed/common'; import { UserService } from '../../../src/services/UserService'; import { User } from '../../../src/entities/User'; import { Repository } from 'typeorm'; describe('UserService', () => { let service: UserService; let repository: Repository<User>; beforeEach(async () => { const testModule = await PlatformTest.create({ providers: [ UserService, { provide: 'UserRepository', useValue: { find: jest.fn(), findOne: jest.fn(), create: jest.fn(), save: jest.fn(), delete: jest.fn() } } ] }); service = testModule.get<UserService>(UserService); repository = testModule.get<Repository<User>>('UserRepository'); }); afterEach(() => { PlatformTest.reset(); }); describe('findAll', () => { it('should return all users', async () => { const users = [ { id: '1', email: 'user1@test.com', username: 'user1' }, { id: '2', email: 'user2@test.com', username: 'user2' } ]; jest.spyOn(repository, 'find').mockResolvedValue(users as any); const result = await service.findAll(); expect(result).toEqual(users); expect(repository.find).toHaveBeenCalledWith({ select: ['id', 'email', 'username', 'role', 'isActive', 'createdAt', 'updatedAt'] }); }); }); describe('create', () => { it('should create a new user', async () => { const dto = { email: 'new@test.com', username: 'newuser', password: 'password123' }; const createdUser = { id: '1', ...dto }; jest.spyOn(repository, 'create').mockReturnValue(createdUser as any); jest.spyOn(repository, 'save').mockResolvedValue(createdUser as any); const result = await service.create(dto); expect(result).toEqual(createdUser); expect(repository.create).toHaveBeenCalledWith(dto); expect(repository.save).toHaveBeenCalledWith(createdUser); }); }); }); `, 'tests/integration/UserController.spec.ts': `import { PlatformTest } from '@tsed/common'; import SuperTest from 'supertest'; import { Server } from '../../src/Server'; describe('UserController', () => { let request: SuperTest.SuperTest<SuperTest.Test>; beforeAll(async () => { const testServer = await PlatformTest.bootstrap(Server, { logger: { level: 'off' } }); request = SuperTest(testServer.app.raw); }); afterAll(() => PlatformTest.reset()); describe('GET /api/users', () => { it('should return all users', async () => { const response = await request .get('/api/users') .expect(200); expect(response.body).toBeInstanceOf(Array); }); }); describe('POST /api/users', () => { it('should create a new user', async () => { const newUser = { email: 'test@example.com', username: 'testuser', password: 'password123' }; const response = await request .post('/api/users') .send(newUser) .expect(201); expect(response.body).toMatchObject({ email: newUser.email, username: newUser.username }); expect(response.body).toHaveProperty('id'); expect(response.body).not.toHaveProperty('password'); }); it('should return 400 for invalid user data', async () => { const invalidUser = { email: 'invalid-email', username: 'a', password: 'short' }; await request .post('/api/users') .send(invalidUser) .expect(400); }); }); }); `, 'jest.config.js': `module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['<rootDir>/src', '<rootDir>/tests'], testMatch: ['**/*.spec.ts'], collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts', '!src/index.ts', '!src/**/*.spec.ts' ], coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } }, moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' }, setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'] }; `, 'tests/setup.ts': `import 'reflect-metadata'; import { config } from 'dotenv'; // Load test environment variables config({ path: '.env.test' }); // Set test environment process.env.NODE_ENV = 'test'; // Mock console methods during tests global.console = { ...console, log: jest.fn(), debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() }; `, 'tsconfig.json': `{ "compilerOptions": { "target": "ES2021", "lib": ["ES2021"], "module": "commonjs", "moduleResolution": "node", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "removeComments": true, "preserveConstEnums": true, "sourceMap": true, "declaration": true, "declarationMap": true, "experimentalDecorat