UNPKG

express-ignite-cli

Version:

Powerful Express.js CLI to generate boilerplate projects with auth, swagger, testing, and more!

464 lines (358 loc) 13.9 kB
// --- DYNAMIC AUTH GENERATION MODULE FOR CLI --- import fs from "fs"; import path from "path"; import chalk from "chalk"; import { execSync } from "child_process"; import { imp, exp } from './helper.js'; function toExt(isTS) { return isTS ? "ts" : "js"; } function write(filePath, content) { fs.writeFileSync(filePath, content.trim()); console.log(chalk.green("✔ Created:"), filePath); } function generateModels(projectPath, isTS, db, orm, isESM) { const ext = toExt(isTS); const modelsDir = path.join(projectPath, "models"); if (db === "MongoDB") { write(path.join(modelsDir, `userModel.${ext}`), ` ${imp("mongoose", "mongoose", isESM)} const userSchema = new mongoose.Schema({ name: String, role: String, }); const User = mongoose.model('User', userSchema); ${exp("User", isESM, true)} `); write(path.join(modelsDir, `authModel.${ext}`), ` ${imp("mongoose", "mongoose", isESM)} const authSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, password: String, user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } }); const Auth = mongoose.model('Auth', authSchema); ${exp("Auth", isESM, true)} `); } else if (orm === "Sequelize") { write(path.join(modelsDir, `userModel.${ext}`), ` // ${imp("Sequelize", "sequelize", isESM)} ${imp("{ DataTypes }", "sequelize", isESM)} ${imp("{ sequelize }", `../configs/connectDb.${ext}`, isESM)} const User = sequelize.define('User', { name: DataTypes.STRING, role: DataTypes.STRING, }); ${exp("User", isESM, true)} `); write(path.join(modelsDir, `authModel.${ext}`), ` // ${imp("Sequelize", "sequelize", isESM)} ${imp("{ DataTypes }", "sequelize", isESM)} ${imp("{ sequelize }", `../configs/connectDb.${ext}`, isESM)} const Auth = sequelize.define('Auth', { email: { type: DataTypes.STRING, unique: true }, password: DataTypes.STRING, }); ${exp("Auth", isESM, true)} ${imp("User", `./userModel.${ext}`, isESM)} User.hasOne(Auth); Auth.belongsTo(User);`); } else if (orm === "Prisma") { const prismaSchemaPath = path.join(projectPath, "prisma", "schema.prisma"); const schema = ` model User { id Int @id @default(autoincrement()) name String role String auth Auth? } model Auth { id Int @id @default(autoincrement()) email String @unique password String userId Int @unique user User @relation(fields: [userId], references: [id]) }`; fs.appendFileSync(prismaSchemaPath, "\n\n" + schema); console.log(chalk.green("✔ Updated Prisma schema with Auth/User models")); } else if (orm === "TypeORM") { write(path.join(modelsDir, `userModel.${ext}`), ` ${imp("{ Entity, PrimaryGeneratedColumn, Column, OneToOne }", "typeorm", isESM)} ${imp("Auth", `./authModel.${ext}`, isESM)} @Entity() class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() role: string; @OneToOne(() => Auth, (auth) => auth.user) auth: Auth; } ${exp("User", isESM)} `); write(path.join(modelsDir, `authModel.${ext}`), ` ${imp("{ Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn }", "typeorm", isESM)} ${imp("User", `./userModel.${ext}`, isESM)} @Entity() class Auth { @PrimaryGeneratedColumn() id: number; @Column({ unique: true }) email: string; @Column() password: string; @OneToOne(() => User) @JoinColumn() user: User; } ${exp("Auth", isESM)} `); } } function generateSwaggerDocs(projectPath, isTS, isESM) { const ext = toExt(isTS); const swaggerPath = path.join(projectPath, `configs/swagger.${ext}`); execSync("npm install swagger-ui-express swagger-jsdoc", { cwd: projectPath, stdio: "inherit", }); write(swaggerPath, ` ${imp("swaggerJsdoc", "swagger-jsdoc", isESM)} ${imp("swaggerUi", "swagger-ui-express", isESM)} const options = { definition: { openapi: '3.0.0', info: { title: 'Express API with Swagger', version: '1.0.0', }, }, apis: ['./routes/*.${ext}'], }; const specs = swaggerJsdoc(options); const setupSwagger = (app) => { app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); }; ${exp("setupSwagger", isESM, true)} `); } function generateAuthModule(projectPath, isTS, db, orm, isESM) { const ext = toExt(isTS); const modelsPath = path.join(projectPath, "models"); const routesPath = path.join(projectPath, "routes"); const controllersPath = path.join(projectPath, "controllers"); const validationsPath = path.join(projectPath, "validations"); const servicesPath = path.join(projectPath, "services"); const middlewaresPath = path.join(projectPath, "middlewares"); const utilsPath = path.join(projectPath, "utils"); const testsPath = path.join(projectPath, "__tests__"); fs.mkdirSync(testsPath); execSync("npm install express-validator", { cwd: projectPath, stdio: "inherit" }); execSync(`npm install --save-dev jest supertest${isTS ? " @types/jest ts-jest @types/supertest" : ""}`, { cwd: projectPath, stdio: "inherit" }); write( path.join(testsPath, `auth.test.${ext}`), // `${isTS ? `import request from 'supertest';\nimport app from '../app';` : "const request = require('supertest');\nconst app = require('../app');"} `${imp("request", "swagger-supertest", isESM)} ${imp("app", `../app.${ext}`, isESM)} describe('Auth API', () => { it('should return 201 for successful registration', async () => { const res = await request(app) .post('/api/auth/register') .send({ name: 'John', email: 'john@test.com', password: '123456' }); expect(res.statusCode).toBe(201); expect(res.body.token).toBeDefined(); }); it('should return 200 for successful login', async () => { const res = await request(app) .post('/api/auth/login') .send({ email: 'john@test.com', password: '123456' }); expect(res.statusCode).toBe(200); expect(res.body.token).toBeDefined(); }); });` ); write( path.join(routesPath, `authRoutes.${ext}`), `${imp("express", "express", isESM)} ${imp("register", `../controllers/authController.${ext}`, isESM, true)} ${imp("login", `../controllers/authController.${ext}`, isESM, true)} ${imp("validateRegister", `../validations/authValidation.${ext}`, isESM, true)} ${imp("validateLogin", `../validations/authValidation.${ext}`, isESM, true)} ${imp("validateRequest", `../middlewares/validateRequest.${ext}`, isESM, true)} const router = express.Router(); router.post('/register', validateRegister, validateRequest, register); router.post('/login', validateLogin, validateRequest, login); ${exp("router", isESM, true)}` ); write( path.join(middlewaresPath, `validateRequest.${ext}`), `${imp("validationResult", "express-validator", isESM)} ${imp("AppError", `../utils/AppError.${ext}`, isESM, true)} const validateRequest = (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return next(new AppError('Validation failed', 400)); } next(); }; ${exp("validateRequest", isESM)}` ); write( path.join(middlewaresPath, `errorHandler.${ext}`), `const errorHandler = (err, req, res, next) => { const statusCode = err.statusCode || 500; const message = err.message || 'Something went wrong'; res.status(statusCode).json({ message }); }; ${exp("errorHandler", isESM, true)}` ); write( path.join(utilsPath, `AppError.${ext}`), `class AppError extends Error { constructor(message, statusCode = 500) { super(message); this.statusCode = statusCode; } } ${exp("AppError", isESM)}` ); write( path.join(validationsPath, `authValidation.${ext}`), `${imp("check", "express-validator", isESM, true)} const validateRegister = [ check('name', 'Name is required').not().isEmpty(), check('email', 'Please include a valid email').isEmail(), check('password', 'Password must be 6+ chars').isLength({ min: 6 }) ]; const validateLogin = [ check('email', 'Email is required').isEmail(), check('password', 'Password is required').exists() ]; ${exp(["validateRegister", "validateLogin"], isESM)}` ); let authServiceContent = ` ${imp("hashPassword", `../utils/passwords.${ext}`, isESM, true)} ${imp("comparePasswords", `../utils/passwords.${ext}`, isESM, true)} ${imp("generateToken", `../utils/generateToken.${ext}`, isESM, true)} ${imp("AppError", `../utils/AppError.${ext}`, isESM, true)} `; if (db === "MongoDB") { authServiceContent += ` ${imp("Auth", `../models/authModel.${ext}`, isESM)} ${imp("User", `../models/userModel.${ext}`, isESM)} const registerService = async (name, email, password) => { const existing = await Auth.findOne({ email }); if (existing) throw new AppError('User already exists', 400); const hashedPassword = await hashPassword(password); const user = await User.create({ name, role: 'user' }); const auth = await Auth.create({ email, password: hashedPassword, user: user._id }); return generateToken(user._id); }; const loginService = async (email, password) => { const auth = await Auth.findOne({ email }).populate('user'); if (!auth) throw new AppError('Invalid credentials', 401); const isMatch = await comparePasswords(password, auth.password); if (!isMatch) throw new AppError('Invalid credentials', 401); return generateToken(auth.user._id); }; ${exp(["registerService", "loginService"], isESM)} `; } else if (orm === "Sequelize") { authServiceContent += ` ${imp("Auth", `../models/authModel.${ext}`, isESM)} ${imp("User", `../models/userModel.${ext}`, isESM)} const registerService = async (name, email, password) => { const existing = await Auth.findOne({ where: { email } }); if (existing) throw new AppError('User already exists', 400); const hashedPassword = await hashPassword(password); const user = await User.create({ name, role: 'user' }); const auth = await Auth.create({ email, password: hashedPassword, UserId: user.id }); return generateToken(user.id); }; const loginService = async (email, password) => { const auth = await Auth.findOne({ where: { email }, include: User }); if (!auth) throw new AppError('Invalid credentials', 401); const isMatch = await comparePasswords(password, auth.password); if (!isMatch) throw new AppError('Invalid credentials', 401); return generateToken(auth.User.id); }; ${exp(["registerService", "loginService"], isESM)} `; } else if (orm === "Prisma") { authServiceContent += ` ${imp("prisma", `../configs/connectDb.${ext}`, isESM)} const registerService = async (name, email, password) => { const existing = await prisma.auth.findUnique({ where: { email } }); if (existing) throw new AppError('User already exists', 400); const hashedPassword = await hashPassword(password); const user = await prisma.user.create({ data: { name, role: 'user' } }); await prisma.auth.create({ data: { email, password: hashedPassword, userId: user.id } }); return generateToken(user.id); }; const loginService = async (email, password) => { const auth = await prisma.auth.findUnique({ where: { email }, include: { user: true } }); if (!auth) throw new AppError('Invalid credentials', 401); const isMatch = await comparePasswords(password, auth.password); if (!isMatch) throw new AppError('Invalid credentials', 401); return generateToken(auth.user.id); }; ${exp(["registerService", "loginService"], isESM)} `; } else if (orm === "TypeORM") { authServiceContent += ` ${imp("AppDataSource", `../configs/connectDb.${ext}`, isESM)} ${imp("Auth", `../models/authModel.${ext}`, isESM)} ${imp("User", `../models/userModel.${ext}`, isESM)} const authRepo = AppDataSource.getRepository(Auth); const userRepo = AppDataSource.getRepository(User); const registerService = async (name, email, password) => { const existing = await authRepo.findOneBy({ email }); if (existing) throw new AppError('User already exists', 400); const hashedPassword = await hashPassword(password); const user = userRepo.create({ name, role: 'user' }); await userRepo.save(user); const auth = authRepo.create({ email, password: hashedPassword, user }); await authRepo.save(auth); return generateToken(user.id); }; const loginService = async (email, password) => { const auth = await authRepo.findOne({ where: { email }, relations: ['user'] }); if (!auth) throw new AppError('Invalid credentials', 401); const isMatch = await comparePasswords(password, auth.password); if (!isMatch) throw new AppError('Invalid credentials', 401); return generateToken(auth.user.id); }; ${exp(["registerService", "loginService"], isESM)} `; } write(path.join(servicesPath, `authService.${ext}`), authServiceContent); write( path.join(controllersPath, `authController.${ext}`), `${imp("registerService", `../services/authService.${ext}`, isESM, true)} ${imp("loginService", `../services/authService.${ext}`, isESM, true)} const register = async (req, res, next) => { const { name, email, password } = req.body; try { const token = await registerService(name, email, password); res.status(201).json({ token }); } catch (err) { next(err); } }; const login = async (req, res, next) => { const { email, password } = req.body; try { const token = await loginService(email, password); res.json({ token }); } catch (err) { next(err); } }; ${exp(["register", "login"], isESM)}` ); generateModels(projectPath, isTS, db, orm, isESM); } export { generateAuthModule };