diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25f4ae8..5092176 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: # Lint the server code - name: Lint Server Code - run: npm run eslint + run: npm run lint working-directory: ./server # Step: Formatting Frontend job diff --git a/server/.env.example b/server/.env.example index 341035b..70d70b1 100644 --- a/server/.env.example +++ b/server/.env.example @@ -20,16 +20,23 @@ DATE_FULL_FORMAT='yyyy-MM-dd HH:mm:ss.SSS' # Database # YOUR_MONGO_URI MONGO_URI=mongodb://localhost:27017/ + # YOUR_MONGO_DB_NAME MONGO_DB_DATABASE_NAME=tasks-db-v1 +#YOUR_MONGO_DB_USER_NAME +MONGO_DB_USERNAME=taskApplicationUser + +#YOUR_MONGO_DB_USER_PWD +MONGO_DB_PWD=123456789 + # Maintain up to x socket connections -DB_MIN_POOL_SIZE=2 -DB_MAX_POOL_SIZE=5 +MONGO_DB_MIN_POOL_SIZE=2 +MONGO_DB_MAX_POOL_SIZE=5 # Give up initial connection after 10 seconds -DB_CONNECT_TIMEOUT_MS=60000 +MONGO_DB_CONNECT_TIMEOUT_MS=60000 # Close sockets after 45 seconds of inactivity -DB_SOCKET_TIMEOUT_MS=45000 +MONGO_DB_SOCKET_TIMEOUT_MS=45000 #localhost or IP of the server # If using the docker installation then use 'mongo' for host name else localhost or ip or db server @@ -37,12 +44,6 @@ DB_SOCKET_TIMEOUT_MS=45000 MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 -#YOUR_MONGO_DB_USER_NAME -MONGO_DB_USERNAME=taskApplicationUser - -#YOUR_MONGO_DB_USER_PWD -MONGO_DB_PWD=123456789 - # Limiter # The time window for which login attempts are counted # 2 minutes = 120000 @@ -74,6 +75,9 @@ PASSWORD_RESET_TOKEN_VALIDITY_SEC=3600000 TOKEN_ISSUER=api.dev.saini.com TOKEN_AUDIENCE=dev.saini.com +# make the value production for prod env +MAILTRAP_EMAIL_ENV=testing + # Mailtrap(Email service) Info MAILTRAP_USERNAME= MAILTRAP_PASSWORD= diff --git a/server/dockerfile b/server/dockerfile index 97d396b..4b8660b 100644 --- a/server/dockerfile +++ b/server/dockerfile @@ -27,6 +27,7 @@ RUN rm -rf \ .eslintignore \ .prettierignore \ tsconfig.json \ + tsconfig.prod.json \ .prettierrc \ .eslintrc.json diff --git a/server/package.json b/server/package.json index e681351..787874c 100644 --- a/server/package.json +++ b/server/package.json @@ -10,9 +10,12 @@ "watch": "npx concurrently -k -p \"[{name}]\" -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold,green.bold,red.bold\" \"npm run watch-ts\" \"npm run watch-node\"", "watch-node": "nodemon -r dotenv/config build/server.js", "clean": "npx rimraf ./build", - "build-ts": "npx tsc", + "build-ts": "npx tsc --project tsconfig.prod.json", "watch-ts": "npx tsc -w", - "eslint": "npx eslint . --ext .ts", + "lint": "npx eslint . --ext ts --report-unused-disable-directives --max-warnings 0", + "lint:fix": "npm run lint --fix", + "prettier:write": "npx prettier . --write", + "prettier": "npx prettier . --check", "test": "npx jest --forceExit --detectOpenHandles --coverage --verbose", "install:packages": "npm i", "upgrade:packages": "npm update --save-dev && npm update --save", diff --git a/server/src/app.ts b/server/src/app.ts index ef477d0..dd230fc 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -1,6 +1,6 @@ import express, { NextFunction, Request, Response } from 'express'; import Logger from './middleware/Logger'; -import { API_VERSION, CORS_URL, ENVIRONMENT } from './config'; +import { API_VERSION, CORS_URL, ENVIRONMENT, LIMITER } from './config'; import cors from 'cors'; import helmet from 'helmet'; import './config/DatabaseConfig'; // initialize database @@ -12,6 +12,7 @@ import { NotFoundError, } from './middleware/ApiError'; import routes from './routes/v1'; +import LimiterHelper from './helpers/LimiterHelper'; process.on('uncaughtException', (e) => { Logger.error(e); @@ -19,6 +20,15 @@ process.on('uncaughtException', (e) => { const app = express(); +// Apply rate limiting to all requests +app.use( + LimiterHelper.createRateLimiter({ + windowMs: LIMITER.ipWS, // 15 minutes + max: LIMITER.ipMaxAttempt, // limit each IP to 100 requests per windowMs + message: 'Too many requests, please try again later.', + }) +); + // This middleware is responsible to enable cookie parsing // commonly used to parse cookies from the incoming HTTP request headers. app.use(cookieParser()); diff --git a/server/src/config/index.ts b/server/src/config/index.ts index 2fe7742..1339b3f 100644 --- a/server/src/config/index.ts +++ b/server/src/config/index.ts @@ -3,6 +3,7 @@ export const PORT = process.env.PORT; export const CORS_URL = process.env.CORS_URL?.split(',') || []; export const API_VERSION = process.env.API_VERSION; export const FRONTEND_RESET_URL = process.env.FRONTEND_RESET_URL; +export const MAILTRAP_EMAIL_ENV = process.env.MAILTRAP_EMAIL_ENV || 'testing'; export const DATE_FORMAT = process.env.DATE_FORMAT || 'yyyy-MM-dd'; export const DATE_FULL_FORMAT = @@ -15,14 +16,22 @@ export const MAILTRAP_EMAIL = { host: process.env.MAILTRAP_TESTING_HOST || '', port: parseInt(process.env.MAILTRAP_TESTING_PORT || ''), }, + prod: { + username: process.env.MAILTRAP_USERNAME || '', + password: process.env.MAILTRAP_PASSWORD || '', + host: process.env.MAILTRAP_HOST || '', + port: parseInt(process.env.MAILTRAP_PORT || ''), + }, }; export const LIMITER = { loginWS: parseInt(process.env.LIMITER_LOGIN_WS || '120000'), + ipWS: parseInt(process.env.LIMITER_IP_WS || '900000'), forgotPasswordWS: parseInt( process.env.LIMITER_FORGOT_PASSWORD_WS || '120000' ), loginMaxAttempt: parseInt(process.env.LIMITER_LOGIN_ATTEMPT || '5'), + ipMaxAttempt: parseInt(process.env.LIMITER_IP_ATTEMPT || '100'), forgotPasswordMaxAttempt: parseInt( process.env.LIMITER_FORGOT_PASSWORD_ATTEMPT || '2' ), @@ -40,10 +49,12 @@ export const DB = { username: process.env.MONGO_DB_USERNAME || '', pwd: process.env.MONGO_DB_PWD || '', port: process.env.MONGO_DB_PORT || '', - minPoolSize: parseInt(process.env.DB_MIN_POOL_SIZE || '5'), - maxPoolSize: parseInt(process.env.DB_MAX_POOL_SIZE || '10'), - connectTimeoutMS: parseInt(process.env.DB_CONNECT_TIMEOUT_MS || '60000'), - socketTimeoutMS: parseInt(process.env.DB_SOCKET_TIMEOUT_MS || '45000'), + minPoolSize: parseInt(process.env.MONGO_DB_MIN_POOL_SIZE || '5'), + maxPoolSize: parseInt(process.env.MONGO_DB_MAX_POOL_SIZE || '10'), + connectTimeoutMS: parseInt( + process.env.MONGO_DB_CONNECT_TIMEOUT_MS || '60000' + ), + socketTimeoutMS: parseInt(process.env.MONGO_DB_SOCKET_TIMEOUT_MS || '45000'), }; export const TOKEN_INFO = { diff --git a/server/src/controllers/AuthController.ts b/server/src/controllers/AuthController.ts index 5cb8af0..d642ab6 100644 --- a/server/src/controllers/AuthController.ts +++ b/server/src/controllers/AuthController.ts @@ -1,7 +1,6 @@ import asyncHandler from 'express-async-handler'; import Logger from '../middleware/Logger'; import { - ManyRequestResponse, SuccessMsgResponse, SuccessResponse, TokenRefreshResponse, @@ -16,23 +15,17 @@ import bcrypt from 'bcrypt'; import AuthHelper from '../helpers/AuthHelper'; import { COOKIE, LIMITER, TOKEN_INFO } from '../config'; import jwt, { JwtPayload } from 'jsonwebtoken'; -import rateLimit from 'express-rate-limit'; import { ProtectedRequest } from 'app-request'; import { UserModel } from '../models/UserModel'; import { RoleNameEnum, RoleStatusEnum } from '../models/RoleModel'; import RoleHelper from '../helpers/RoleHelper'; +import LimiterHelper from '../helpers/LimiterHelper'; class AuthController { - forgotPasswordLimiter = rateLimit({ + forgotPasswordLimiter = LimiterHelper.createRateLimiter({ windowMs: LIMITER.forgotPasswordWS, max: LIMITER.forgotPasswordMaxAttempt, - message: 'Too many reset passwords attempts, please try again later.', - handler: (req, res, _, options) => { - Logger.info(`${options.message}, Method: ${req.method}, Url: ${req.url}`); - new ManyRequestResponse(options.message).send(res); - }, - standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers - legacyHeaders: false, // Disable the `X-RateLimit-*` headers + message: 'Too many reset password attempts, please try again later.', }); forgotPassword = asyncHandler(async (req: ProtectedRequest, res, next) => { @@ -73,14 +66,25 @@ class AuthController { resetPassword = asyncHandler(async (req: ProtectedRequest, res, next) => { const { password, email } = req.body; + const { token } = req.params ?? ''; - const filter = { - passwordResetTokenRaw: req.params.token, - passwordResetToken: AuthHelper.generateHashTokenKey(req.params.token), + let filter = { + passwordResetTokenRaw: token, + passwordResetToken: token, email: email, passwordResetTokenExpires: { $gt: Date.now() }, }; + if (!token) { + Logger.info(`Attempted password reset, ${JSON.stringify(filter)}`); + throw new BadRequestError('Token is invalid or has been expired.'); + } + + filter = { + ...filter, + passwordResetToken: AuthHelper.generateHashTokenKey(token), + }; + const user = await UserModel.findOne(filter); if (!user) { @@ -104,20 +108,14 @@ class AuthController { next(); }); - loginLimiter = rateLimit({ + loginLimiter = LimiterHelper.createRateLimiter({ windowMs: LIMITER.loginWS, max: LIMITER.loginMaxAttempt, message: 'Too many login attempts, please try again later.', - handler: (req, res, _, options) => { - Logger.info(`${options.message}, Method: ${req.method}, Url: ${req.url}`); - new ManyRequestResponse(options.message).send(res); - }, - standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers - legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); isAuthorized = asyncHandler(async (req: ProtectedRequest, _, next) => { - const token = AuthHelper.getAccessToken(req.headers.authorization); + const token = AuthHelper.getAccessToken(req.headers.authorization) || ''; const accessTokenPayload = jwt.verify( token, TOKEN_INFO.accessTokenSecret @@ -177,10 +175,8 @@ class AuthController { }, ]); - if (!user || !user.password) { - throw new BadRequestError( - 'Your email address or your password is incorrect' - ); + if (!user?.password) { + throw new BadRequestError('Your email address or password is incorrect'); } const isMatched = await bcrypt.compare(req.body.password, user.password); diff --git a/server/src/controllers/EmailController.ts b/server/src/controllers/EmailController.ts index 9feb510..684b671 100644 --- a/server/src/controllers/EmailController.ts +++ b/server/src/controllers/EmailController.ts @@ -23,13 +23,13 @@ class EmailController { 'Your password has been successfully updated. If you did not initiate this change, please contact your administrator for assistance.'; const email: Email = { - to: user.email, + to: user.email || '', subject: 'Password update successfully', content: EmailHelper.emailFormatter(message, user.firstname), }; try { - await EmailHelper.testingEmailTransporter({ + await EmailHelper.emailTransporter({ to: email.to, subject: email.subject, html: email.content, @@ -72,14 +72,14 @@ class EmailController { If you didn't initiate this request or have any concerns, please ignore this message. Your account remains secure.`; const email: Email = { - to: user.email, + to: user.email || '', subject: 'Password change request received', content: EmailHelper.emailFormatter(message, user.firstname), url: resetUrl, }; try { - await EmailHelper.testingEmailTransporter({ + await EmailHelper.emailTransporter({ to: email.to, subject: email.subject, html: email.content, diff --git a/server/src/controllers/HealthCheckController.ts b/server/src/controllers/HealthCheckController.ts index 8398997..2717c6e 100644 --- a/server/src/controllers/HealthCheckController.ts +++ b/server/src/controllers/HealthCheckController.ts @@ -2,12 +2,12 @@ import asyncHandler from 'express-async-handler'; import { SuccessResponse } from '../middleware/ApiResponse'; class HealthCheckController { - checkHealth = asyncHandler(async (req, res) => { - new SuccessResponse('The API is up and running. Health check is passed.', {}).send(res); + new SuccessResponse( + 'The API is up and running. Health check is passed.', + {} + ).send(res); }); - } - -export default new HealthCheckController(); \ No newline at end of file +export default new HealthCheckController(); diff --git a/server/src/controllers/UserController.ts b/server/src/controllers/UserController.ts index ed8e6e5..a5bdbd0 100644 --- a/server/src/controllers/UserController.ts +++ b/server/src/controllers/UserController.ts @@ -49,7 +49,8 @@ class UserController { }); getUser = asyncHandler(async (req, res) => { - const user = await UserHelper.findById(req.params.id); + const { id } = req.params; + const user = (!!id && (await UserHelper.findById(id))) || null; if (!user) throw new NotFoundError('User not found'); new SuccessResponse('User fetched successfully', { @@ -59,24 +60,17 @@ class UserController { updateUser = asyncHandler(async (req, res) => { const { email, firstname, lastname } = req.body; - const updateFields: any = {}; + const updateFields: { [key: string]: any; } = { email, firstname, lastname }; - if (email) { - updateFields.email = email; - } - if (firstname) { - updateFields.firstname = firstname; - } - if (lastname) { - updateFields.lastname = lastname; - } - - const updatedUser = await UserModel.findOneAndUpdate( - { _id: req.params.id }, - { $set: updateFields }, - { new: true } + // Remove undefined fields + Object.keys(updateFields).forEach( + (key) => updateFields[key] === undefined && delete updateFields[key] ); + const { id } = req.params; + + const updatedUser = await UserHelper.findByIdAndUpdate(id, updateFields); + if (!updatedUser) { throw new NotFoundError('User not found'); } @@ -87,13 +81,16 @@ class UserController { }); deleteUser = asyncHandler(async (req, res) => { + const { id } = req.params; + const result: DeleteResult = await UserModel.deleteOne({ - _id: req.params.id, + _id: id, }); - if (!result.deletedCount) throw new NotFoundError('User not found'); + + if (result.deletedCount === 0) throw new NotFoundError('User not found'); new SuccessResponse('User deleted successfully', { - userId: req.params.id, + userId: id, }).send(res); }); } diff --git a/server/src/helpers/EmailHelper.ts b/server/src/helpers/EmailHelper.ts index d1b5508..115b5ad 100644 --- a/server/src/helpers/EmailHelper.ts +++ b/server/src/helpers/EmailHelper.ts @@ -1,5 +1,5 @@ import nodemailer from 'nodemailer'; -import { MAILTRAP_EMAIL } from '../config'; +import { MAILTRAP_EMAIL, MAILTRAP_EMAIL_ENV } from '../config'; import SMTPTransport from 'nodemailer/lib/smtp-transport'; import Mail from 'nodemailer/lib/mailer'; @@ -14,26 +14,33 @@ const emailFormatter = (content: string, firstname = '') => { ${content}

Thank you for using our service!

Best regards,
- Node.js Auth API + Task API `; }; /** - * used to send test emails during development and testing phases. + * used to send emails. * * for sending test emails using the Nodemailer library and a service like - * Mailtrap for testing email delivery + * Mailtrap for email delivery * @param options + * @returns */ -const testingEmailTransporter = async ( +const emailTransporter = async ( options: Mail.Options ): Promise => { + const isProdEnv = MAILTRAP_EMAIL_ENV === 'production'; + const transporter = nodemailer.createTransport({ - host: MAILTRAP_EMAIL.testing.host, - port: MAILTRAP_EMAIL.testing.port, + host: isProdEnv ? MAILTRAP_EMAIL.prod.host : MAILTRAP_EMAIL.testing.host, + port: isProdEnv ? MAILTRAP_EMAIL.prod.port : MAILTRAP_EMAIL.testing.port, auth: { - user: MAILTRAP_EMAIL.testing.username, - pass: MAILTRAP_EMAIL.testing.password, + user: isProdEnv + ? MAILTRAP_EMAIL.prod.username + : MAILTRAP_EMAIL.testing.username, + pass: isProdEnv + ? MAILTRAP_EMAIL.prod.password + : MAILTRAP_EMAIL.testing.password, }, }); @@ -46,6 +53,6 @@ const testingEmailTransporter = async ( }; export default { - testingEmailTransporter, emailFormatter, + emailTransporter, }; diff --git a/server/src/helpers/LimiterHelper.ts b/server/src/helpers/LimiterHelper.ts new file mode 100644 index 0000000..9614cd0 --- /dev/null +++ b/server/src/helpers/LimiterHelper.ts @@ -0,0 +1,28 @@ +import rateLimit from 'express-rate-limit'; +import Logger from '../middleware/Logger'; +import { ManyRequestResponse } from '../middleware/ApiResponse'; + +const createRateLimiter = ({ + windowMs, + max, + message, +}: { + windowMs: number; + max: number; + message: string; +}) => + rateLimit({ + windowMs, + max, + message, + handler: (req, res, _, options) => { + Logger.info(`${options.message}, Method: ${req.method}, Url: ${req.url}`); + new ManyRequestResponse(options.message).send(res); + }, + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + }); + +export default { + createRateLimiter, +}; diff --git a/server/src/helpers/UserHelper.ts b/server/src/helpers/UserHelper.ts index 25722b3..4c357ad 100644 --- a/server/src/helpers/UserHelper.ts +++ b/server/src/helpers/UserHelper.ts @@ -1,8 +1,15 @@ import User, { UserModel } from '../models/UserModel'; import { PopulateOptions, Types } from 'mongoose'; -import Role from '../models/RoleModel'; +import Role, { RoleStatusEnum } from '../models/RoleModel'; import RoleHelper from './RoleHelper'; +const defaultPopulates = [ + { + path: 'roles', + match: { status: RoleStatusEnum.ACTIVE }, + }, +]; + const fullName = (firstname?: string, lastname?: string) => { return `${firstname || ''}${lastname ? ' ' + lastname : ''}`; }; @@ -23,7 +30,7 @@ const sanitizedUser = (user: User, roles: Role[] = []): User => { const findById = async ( id: string, selectFields = '', - populates: PopulateOptions[] = [] + populates: PopulateOptions[] = defaultPopulates ): Promise => { return UserModel.findOne({ _id: new Types.ObjectId(id) }) .select(selectFields) @@ -34,7 +41,7 @@ const findById = async ( const findByEmail = async ( email: string, selectFields = '', - populates: PopulateOptions[] = [] + populates: PopulateOptions[] = defaultPopulates ): Promise => { return UserModel.findOne({ email: email }) .select(selectFields) @@ -45,9 +52,34 @@ const findByEmail = async ( const findAll = async ( filter: object = {}, selectFields = '', - populates: PopulateOptions[] = [] + populates: PopulateOptions[] = defaultPopulates ): Promise => { return UserModel.find(filter).select(selectFields).populate(populates).exec(); }; -export default { findByEmail, fullName, findById, sanitizedUser, findAll }; +const findByIdAndUpdate = async ( + id = '', + updateFields: { + [key: string]: any; + }, + populates: PopulateOptions[] = defaultPopulates +): Promise => { + return UserModel.findByIdAndUpdate( + { + _id: id, + }, + { $set: updateFields }, + { new: true } + ) + .populate(populates) + .exec(); +}; + +export default { + findByEmail, + fullName, + findById, + sanitizedUser, + findAll, + findByIdAndUpdate, +}; diff --git a/server/src/models/EmailModel.ts b/server/src/models/EmailModel.ts index d7dac42..6bfe765 100644 --- a/server/src/models/EmailModel.ts +++ b/server/src/models/EmailModel.ts @@ -11,10 +11,10 @@ export enum EmailStatusEnum { export default interface Email { _id?: Types.ObjectId; - subject?: string; - to?: string; + subject: string; + to: string; url?: string; - content?: string; + content: string; status?: EmailStatusEnum; error?: string; createdAt?: string; diff --git a/server/src/models/RoleModel.ts b/server/src/models/RoleModel.ts index cd613fb..7d2cc2e 100644 --- a/server/src/models/RoleModel.ts +++ b/server/src/models/RoleModel.ts @@ -26,11 +26,11 @@ export enum RolePermissionEnum { } export default interface Role { - _id?: Types.ObjectId; - name?: RoleNameEnum; + _id: Types.ObjectId; + name: RoleNameEnum; description?: string; status?: RoleStatusEnum; - permissions?: RolePermissionEnum[]; + permissions: RolePermissionEnum[]; createdAt?: string; updatedAt?: string; } @@ -59,14 +59,6 @@ const RoleSchema = new Schema( enum: Object.values(RolePermissionEnum), default: [RolePermissionEnum.VIEW], }, - createdAt: { - type: Schema.Types.Date, - select: false, - }, - updatedAt: { - type: Schema.Types.Date, - select: false, - }, }, { versionKey: false, @@ -76,8 +68,6 @@ const RoleSchema = new Schema( RoleSchema.index({ name: 1 }); RoleSchema.index({ status: 1 }); -RoleSchema.index({ createdAt: 1 }); -RoleSchema.index({ updatedAt: 1 }); export const RoleModel = model( DOCUMENT_NAME, diff --git a/server/src/models/UserModel.ts b/server/src/models/UserModel.ts index c743999..a2fcd40 100644 --- a/server/src/models/UserModel.ts +++ b/server/src/models/UserModel.ts @@ -56,7 +56,7 @@ const UserSchema = new Schema( terms: { type: Schema.Types.Boolean, select: false, - default: true, + default: false, }, passwordUpdatedAt: { type: Schema.Types.Date, @@ -82,7 +82,7 @@ const UserSchema = new Schema( }, ], required: true, - select: false, + select: true, }, }, { diff --git a/server/src/routes/v1/HealthRoutes.ts b/server/src/routes/v1/HealthRoutes.ts index a698231..b7d10ee 100644 --- a/server/src/routes/v1/HealthRoutes.ts +++ b/server/src/routes/v1/HealthRoutes.ts @@ -3,10 +3,6 @@ import HealthCheckController from '../../controllers/HealthCheckController'; const router = express.Router(); -router - .route('/') - .get( - HealthCheckController.checkHealth - ); +router.route('/').get(HealthCheckController.checkHealth); -export default router; \ No newline at end of file +export default router; diff --git a/server/tsconfig.json b/server/tsconfig.json index f9cbbfb..cd6845c 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,29 +1,30 @@ { "compilerOptions": { "module": "CommonJS", - // Import non-ES modules as default imports. - "esModuleInterop": true, - // Target latest version of ECMAScript. - "target": "ES2019", - // Process & infer types from .js files. + "esModuleInterop": true, // Import non-ES modules as default imports. + "target": "ES2021", // or higher if needed "allowJs": false, - // Enable strictNullChecks & noImplicitAny. "strictNullChecks": true, "noImplicitAny": true, "strict": true, - "strictFunctionTypes": false, - "noImplicitThis": false, - // Search under node_modules for non-relative imports. + "strictFunctionTypes": false, // Optional: Enforces stricter checks for function types + "noImplicitThis": true, // Optional: Enforces stricter checks on 'this' "moduleResolution": "node", - // Import .json files "resolveJsonModule": true, "sourceMap": true, "outDir": "build", "baseUrl": ".", "paths": { "*": ["node_modules/*", "src/types/*"] - } + }, + "types": ["node"], // Include Node.js types + "skipLibCheck": true, // Skips type checking of declaration files for faster builds + "forceConsistentCasingInFileNames": true, // Ensures file name casing consistency + "noFallthroughCasesInSwitch": true, // Prevents fall-through cases in switch statements + "noUncheckedIndexedAccess": true, // Ensures array access is checked for undefined + "declaration": true, // Generates .d.ts files alongside .js files + "incremental": true // Enables incremental builds for faster compilation }, "include": ["src/**/*"], - "exclude": [".templates"] + "exclude": [".templates", "node_modules"] } diff --git a/server/tsconfig.prod.json b/server/tsconfig.prod.json new file mode 100644 index 0000000..e145ccc --- /dev/null +++ b/server/tsconfig.prod.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false, // Disable source maps for production + "removeComments": true, // Remove comments to reduce bundle size + "declaration": false, // Disable declaration files if not needed in production + "noImplicitReturns": true // Ensure all code paths return a value + } +}