Skip to content

Helper Functions, Config & DB - Deep Dive Documentation

Generated: 2026-03-30 Scope: saas-boilerplate/src/helper/, saas-boilerplate/src/config/, saas-boilerplate/src/db/ Files Analyzed: 33 (24 helper + 8 config + 1 db) Lines of Code: ~1,328 Workflow Mode: Exhaustive Deep-Dive Known Issues Found: 88 (20 Critical, 23 High, 26 Medium, 19 Low) Dead Code: ~350 LOC removable (5 dead files + 1 unused import)

Overview

The helper, config, and database connection layers form the foundational utility infrastructure for the entire backend. Every API module, middleware, and service depends on these files for configuration loading, authentication primitives, file operations, email dispatch, permission enforcement, and database access.

Purpose: Cross-cutting utility layer providing reusable functions for auth, file upload, email, encryption, pagination, config loading, and DB connection. Key Responsibilities: JWT management, password hashing, OTP generation, permission checking, file upload orchestration (Cloudinary/S3/local), email dispatch (Nodemailer/Resend), AES-256-GCM encryption, pagination parsing, boolean coercion, environment config loading, Stripe/LemonSqueezy/Cloudinary/Passport initialization, TypeORM DataSource management. Integration Points: 40+ consumers import config/index.ts; 24 services use checkPermissionAndThrow; 20 services use parsePaginationQuery; 13 use parseBoolean; 9 use hashedPassword.


Known Issues Summary

ID Severity File Description
C1 CRITICAL fileUploader.ts Hardcoded Cloudinary credentials (api_key, api_secret) in source code — LEAKED SECRETS
C2 CRITICAL deleteFile.ts Path traversal — no sanitization of relativeImagePath, allows ../../etc/passwd
C3 CRITICAL saveBufferToFile.ts Path traversal — unsanitized fileName/subFolder allows arbitrary file writes
C4 CRITICAL handleFileUpload.ts Path traversal in local storage branch — unsanitized subFolder
C5 CRITICAL handleFileUpload.ts Local storage branch never saves file — constructs path but no fs.writeFile (callers must independently call saveBufferToFile)
C6 CRITICAL Pipeline-wide Zero file type validation — no MIME, extension, or magic-byte checks anywhere
C7 CRITICAL encryption.ts MASTER_KEY not in requiredEnv — app starts without it, crashes at first import
C8 CRITICAL encryption.ts Key loaded at module scope with ! assertion — no length validation, no rotation support
C9 CRITICAL hasPermission.ts Normalization asymmetry — commented out in hasPermission() but active in hasPermissionForUser()
C10 CRITICAL config/AppConfig.ts Complete duplicate of config/index.ts (150 LOC dead code) — both load same 55+ env vars into same IConfig shape
C11 CRITICAL config/stripe.ts + getStripe.ts Dual Stripe initialization — sync (env-only) vs async (env+DB fallback), some modules import BOTH
C12 CRITICAL getLemonsqueezy.ts Hardcoded http://localhost:5500 redirect URL — production blocker
C13 CRITICAL config/index.ts Admin credentials on global configadmin.email/admin.password readable by any module
C14 CRITICAL dbConfig.ts synchronize: true controlled by typo key nod_env — if NODE_ENV=development leaks to prod, schema auto-modifies
C15 CRITICAL email/resend.ts Resend SDK instantiated at module load with potentially undefined API key
C16 CRITICAL email/resend.ts MAIL_USER env var shared between nodemailer SMTP auth and Resend from address
C17 CRITICAL paginationHelper.ts SQL injection via sortBy/sortOrder — no validation (mitigated: file is dead code)
C18 CRITICAL passport.ts Invite-only account takeover — Google OAuth can claim credential-based invite-only users without password
C19 CRITICAL config/cloudinary.ts Double-configures at module load then again in configureCloudinary() — credential validation bypassed on first call
C20 CRITICAL config/index.ts 49 of 55 env vars unvalidated — only 6 in requiredEnv, rest silently undefined
H1 HIGH checkPermissionAndThrow.ts Uses HTTP 400 instead of 403 for permission denial — misleads security auditing
H2 HIGH hasPermission.ts No isDeleted check in main path — soft-deleted user with valid JWT passes permission checks
H3 HIGH hasPermission.ts console.log({cachedResult}) debug log on line 68 — production leak
H4 HIGH hasPermission.ts Sequential permission checks in batch functions — for loop with await instead of Promise.all
H5 HIGH jwtHelpers.ts decodeToken hardcodes access token secret — cannot decode refresh tokens despite generic name
H6 HIGH jwtHelpers.ts refresh_token_secret as string — unsafe cast, undefined becomes "undefined" string
H7 HIGH fileUploader.ts 100% dead code (0 imports) with leaked Cloudinary credentials — delete and rotate credentials
H8 HIGH calculateDiscount.ts Dead code — 0 consumers anywhere in codebase
H9 HIGH paginationHelper.ts Dead code — 0 consumers (superseded by parsePaginationQuery.ts)
H10 HIGH httpLogger.ts req._startAt may be undefinedprocess.hrtime(undefined) crashes
H11 HIGH httpLogger.ts Full req.user object logged — password hashes, 2FA secrets leak to log files
H12 HIGH saveBufferToFile.ts Argument order mismatch with LocalSotrage.ts — folder name passed as fileName, fileName as subFolder
H13 HIGH email/resend.ts 0 consumers — dead code (Resend handled by shared/email/ResendService.ts)
H14 HIGH cloud/s3.ts DeleteObjectCommand imported but never used — no cloud file deletion exists, orphaned files accumulate
H15 HIGH getLemonsqueezy.ts custom.user_id: email instead of userIdwrong value in custom data
H16 HIGH getStripe.ts apiVersion: "2025-06-30.basil" + appInfo: { name: "SASS Template" } — typo in app name
H17 HIGH config/index.ts + AppConfig.ts 4 typos baked into type system: nod_env, sesseion_secret, SSL_VALIDATIOIN_API, "SASS Template"
H18 HIGH passport.ts withDeleted: true on user lookup — includes soft-deleted users in OAuth flow
H19 HIGH encryption.ts No key rotation support — changing MASTER_KEY invalidates all encrypted data with no migration path
H20 HIGH uploadToCloudinary.ts configureCloudinary imported but never called — credential validation bypassed
H21 HIGH getLemonsqueezy.ts Mixes config with business logic — checkout creation in a config file
H22 HIGH db/connection.ts Credential leak via console.error — full error object logged, may include DB URL with password
H23 HIGH config/passport.ts SRP violation — config file contains full OAuth user creation, DB queries, invite-only logic (same pattern as H21)
M1 MEDIUM email.ts Hardcoded "Sass Template" from address (typo, ignores config.mail.from)
M2 MEDIUM transporterMail.ts No TLS/SSL configuration; transport created at load time even when unused
M3 MEDIUM getTimeDifferenceLabel.ts Pluralization bug — returns "1 minutes" when diff rounds to 0
M4 MEDIUM stringToBoolean.ts Duplicate of parseBoolean.ts with inconsistent behavior (case-sensitive vs case-insensitive)
M5 MEDIUM parsePaginationQuery.ts No upper bound on limit?limit=999999 passes through (DoS risk)
M6 MEDIUM jwtHelpers.ts payload: any — no type safety on token payload content
M7 MEDIUM jwtHelpers.ts Redundant decodedecodeToken calls verify (returns payload) then calls decode again
M8 MEDIUM encryption.ts No AAD (Additional Authenticated Data) — encrypted values can be swapped between config keys undetected
M9 MEDIUM hashedPassword.ts Salt rounds 10 hardcoded — OWASP recommends 12+ for 2026
M10 MEDIUM transformImageUrl.ts baseUrl parameter is dead — accepted but never used; callers pass it, function ignores it
M11 MEDIUM transformImageUrl.ts Redundant null check!image and !Boolean(image) are logically identical
M12 MEDIUM uploadToCloudinary.ts Hardcoded image/jpeg MIME — all file types labeled as JPEG regardless of actual type
M13 MEDIUM cloud/s3.ts S3 client recreated per call — no caching, new connection per request
M14 MEDIUM fileUploader.ts fs.unlinkSync before error check — deletes local file even if upload failed (data loss)
M15 MEDIUM deleteFile.ts Uses CommonJS require in TypeScript — inconsistent with rest of codebase
M16 MEDIUM hasPermission.ts console.log and console.error throughout — no Winston logger usage
M17 MEDIUM cloud/s3.ts Manually constructs Location URL instead of reading from S3 response
M18 MEDIUM saveBufferToFile.ts JSDoc mismatch — doc says originalName but param is fileName
M19 MEDIUM dbConfig.ts Entity glob '../entity/**/*.{ts,js}' — may pick up compiled JS in production
M20 MEDIUM connection.ts Race condition — no mutex on concurrent .initialize() calls
M21 MEDIUM connection.ts Dual singleton desyncglobal.appDataSource vs dbConfig.ts module-level singleton
M22 MEDIUM email/resend.ts Synchronous readFileSync for attachments — blocks event loop
M23 MEDIUM email/resend.ts console.log/console.error instead of Winston logger
M24 MEDIUM db/connection.ts Uninitialized DataSource bypassgetDbRepository, getDataSource, getQueryBuilder call getAppDataSource() directly, skipping initialization guard
M25 MEDIUM deleteFile.ts Silent error swallowing — catches all fs.unlinkSync errors and logs to console, callers never know deletion failed
M26 MEDIUM parsePaginationQuery.ts No minimum boundpage=0, limit=-1 pass through (companion to M5 upper-bound issue)
L1 LOW hashedPassword.ts Misleading filename — hashedPassword (noun) used as function name, should be hashPassword (verb)
L2 LOW encryption.ts Buffer.slice deprecated in Node 17+ — use Buffer.subarray
L3 LOW jwtHelpers.ts Mixed function declaration styles — const arrow vs function declaration
L4 LOW jwtHelpers.ts No algorithm specified on generateRefreshToken
L5 LOW hasPermission.ts user.userRoles relation name confusing — typed as Role[] but name suggests junction table
L6 LOW generateOtp.ts No input validation needed, but OTP length not configurable
L7 LOW encryption.ts No empty string check — encrypting empty string probably a caller bug
L8 LOW hashedPassword.ts No input validation — null/undefined passed directly to bcrypt
L9 LOW hashedPassword.ts 72-byte bcrypt truncation not documented or mitigated
L10 LOW checkPermissionAndThrow.ts Error message leaks action description to caller
L11 LOW httpLogger.ts baseMsg set to empty string — commented-out format is dead code
L12 LOW httpLogger.ts Request/Response imports used in type annotations but msg callback params typed as any
L13 LOW paginationHelper.ts sortOrder accepts any string — no validation to 'asc'/'desc'
L14 LOW uploadImage.ts S3 case commented out (lines 19-20) — dead code branch
L15 LOW cloud/s3.ts Emoji in console warning logs — unprofessional for production
L16 LOW stripe.ts Warns to console if no key — doesn't throw
L17 LOW getStripe.ts Returns null gracefully if no key — callers must handle null
L18 LOW passport.ts serializeUser/deserializeUser use session (may conflict with JWT-only auth)
L19 LOW transporterMail.ts Dead CommonJS comment artifacts — // module.exports = transporter, // transporterMail.js

Complete File Inventory

helper/calculateDiscount.ts

Purpose: Computes discounted price for percent or fixed discount types. Clamps result to minimum 0. Lines of Code: 26 File Type: Pure utility (no I/O)

What Future Contributors Must Know: This file is 100% dead code with 0 consumers. It can be safely deleted.

Exports: - export default calculateDiscount({ originalPrice: number, discountType: string, discountValue: number }): { originalPrice, discountPrice, amountPaid } - Calculates discount

Dependencies: None

Used By: None (0 consumers)

Error Handling: Returns original price if discount type unrecognized.

Testing: No test file. 0% coverage.


helper/checkPermissionAndThrow.ts

Purpose: Thin wrapper around hasPermission() that throws ApiError(400) when permission is denied. The primary permission enforcement mechanism used by 24 service modules. Lines of Code: 23 File Type: Auth utility

What Future Contributors Must Know: This is the HIGHEST blast-radius file in the helper directory (24 direct consumers across virtually every service). Uses HTTP 400 instead of 403 — changing this would affect frontend error interceptors. The hasPermission import is the default export.

Exports: - export const checkPermissionAndThrow = async (path: string, actionDescription?: string): Promise<void> - Checks permission and throws on denial

Dependencies: - http-status - Status code constants - ./hasPermission - Core permission checking (default import) - ../app/errors/ApiError - Custom error class

Used By: 24 service files: Auth, User, Payment, Permission, Blog, BlogCategory, BlogTag, EmailTemplate, Package, PackageCategory, UserSetting, Subscription, Setting, Role, Product, ProductCategory, Public, MenuItem, GenericPage, InviteUser, Menu, AppConfig, Contact, Cupon

Error Handling: Throws ApiError(httpStatus.BAD_REQUEST, ...) on denial. Does NOT catch errors from hasPermission().

Testing: No test file. 0% coverage.


helper/deleteFile.ts

Purpose: Deletes a file from the local filesystem given a relative path from the project root. Lines of Code: 20 File Type: File I/O utility

What Future Contributors Must Know: Uses CommonJS require (not ES imports). Has a path traversal vulnerabilityrelativeImagePath is joined with process.cwd() without any sanitization. An input like ../../etc/passwd would escape the upload directory.

Exports: - export default deleteFileByPathName(relativeImagePath: string): void - Deletes file at given path

Dependencies: - fs (CommonJS require) - path (CommonJS require)

Used By: 4 services: blog.service, generic-page.service, user.service, LocalSotrage.ts

Side Effects: Filesystem deletion via fs.unlinkSync

Error Handling: Catches all errors and logs to console.error — silent failure.

Testing: No test file. 0% coverage.


helper/email.ts

Purpose: Email dispatch router — branches on config.mail.type to send via either nodemailer or Resend. Lines of Code: 29 File Type: Email utility

What Future Contributors Must Know: Hardcoded from address "Sass Template" <[email protected]> (typo — should be "SaaS"). Ignores config.mail.from. This is the LEGACY email pipeline — the newer shared/email/EmailFactory exists but has only ~11% adoption.

Exports: - export const sendEmail = async ({ to, subject, html }: IEmailSendPayload): Promise<void> - Sends email via configured provider

Dependencies: - http-status - Status codes - ../app/errors/ApiError - Error class - ../config - Mail configuration - ./email/resend - Resend provider - ./transporterMail - Nodemailer transport

Used By: 4 event handlers: registerEmailHandler, sendForgotPass.handler, sendLoginOtp.handler, inviteOnlyLoginSendEmail

Side Effects: Sends email via external service (SMTP or Resend API)

Error Handling: Throws ApiError for unknown mail type. Individual provider errors propagate.

Testing: No test file. 0% coverage.


helper/encryption.ts

Purpose: AES-256-GCM symmetric encryption/decryption for sensitive data at rest (AppConfig secrets, Stripe keys). Lines of Code: 31 File Type: Crypto utility

What Future Contributors Must Know: MASTER_KEY is loaded at MODULE SCOPE via process.env.MASTER_KEY! (non-null assertion). Crashes at import time if env var missing. Must be exactly 64 hex chars (32 bytes). NO key rotation mechanism — changing MASTER_KEY invalidates ALL encrypted data permanently. The ciphertext format is [IV 12 bytes][AuthTag 16 bytes][Ciphertext] encoded as base64.

Exports: - export function encrypt(plaintext: string): string - AES-256-GCM encrypt to base64 - export function decrypt(data: string): string - AES-256-GCM decrypt from base64

Dependencies: - crypto (Node.js built-in)

Used By: 2 files: config/getStripe.ts (decrypt Stripe keys), appConfig.service.ts (encrypt/decrypt config values)

Side Effects: Reads process.env.MASTER_KEY at module load (bypasses centralized config)

Error Handling: None. GCM auth tag verification throws on tampered ciphertext. Missing key crashes at import.

Testing: No test file. 0% coverage.


helper/fileUploader.ts

Purpose: Multer disk storage + Cloudinary upload utility. 100% DEAD CODE with LEAKED CREDENTIALS. Lines of Code: 43 File Type: DEAD CODE

What Future Contributors Must Know: This file has ZERO consumers and contains hardcoded Cloudinary credentials (cloud_name, api_key, api_secret at lines 8-12). These credentials should be rotated immediately and this file should be deleted. The fs.unlinkSync runs before error checking — data loss on upload failure.

Exports: - export const fileUploader = { upload, uploadToCloudinary } - Multer instance + Cloudinary upload

Dependencies: - multer, path, fs, cloudinary v2, ../app/interfaces/file

Used By: None (0 consumers)

Testing: No test file. 0% coverage.


helper/generateOtp.ts

Purpose: Generates a cryptographically secure 6-digit one-time password. Lines of Code: 5 File Type: Crypto utility

What Future Contributors Must Know: This is the cleanest file in the helper directory. Uses crypto.randomInt(100000, 1000000) which is CSPRNG. Range guarantees exactly 6 digits (no leading zero issue). Returns string.

Exports: - export function generateOTP(): string - Returns 6-digit numeric string

Dependencies: - crypto (Node.js built-in randomInt)

Used By: 1 file: OtpHandler.ts

Testing: No test file. 0% coverage.


helper/getTimeDifferenceLabel.ts

Purpose: Computes human-readable relative time label ("5 minutes ago", "2 hours ago", "3 days ago"). Lines of Code: 24 File Type: Pure utility

What Future Contributors Must Know: Has a pluralization bug — when difference rounds to 0 minutes, displays 1 but pluralization check uses original minutes variable (0), resulting in "1 minutes" instead of "1 minute".

Exports: - export function getTimeDifferenceLabel(dateString: string): string - Returns relative time label

Dependencies: None

Used By: 1 file: logger.service.ts

Testing: No test file. 0% coverage.


helper/handleFileUpload.ts

Purpose: Main file upload orchestrator — routes to local, Cloudinary, or S3 storage based on config. Lines of Code: 55 File Type: File I/O orchestrator

What Future Contributors Must Know: The local storage branch does NOT write the file to disk — it only constructs a URL path and returns it. Callers (blog, generic-page, user, setting controllers) must independently call saveBufferToFile(). All three branches have path traversal vulnerabilitiessubFolder is used unsanitized. No file type validation.

Exports: - export interface IReturnedFileResponse { imageUrl: string | string[] | undefined; fileName: string | null } - export async function handleFileUpload(payload: IPayload): Promise<IReturnedFileResponse> - Routes upload to storage backend

Dependencies: - http-status, ../app/errors/ApiError, ../config, ./cloud/s3, ./uploadImage

Used By: 5 files: Blog, GenericPage, User, Setting controllers + Image service

Side Effects: Uploads to Cloudinary or S3 (network calls); local branch: none (just constructs path)

Testing: No test file. 0% coverage.


helper/hasPermission.ts

Purpose: Core RBAC permission-checking engine. Checks cache first, then falls back to database queries. Supports super_admin bypass, route format, and dot notation. Lines of Code: 296 File Type: Auth/RBAC core

What Future Contributors Must Know: This is the most complex file in helper/. Has 6 exports + 2 internal functions. The normalization is COMMENTED OUT in the main hasPermission() function (lines 57-63) but ACTIVE in hasPermissionForUser() — same permission string can yield different results depending on which function is called. Has a debug console.log({cachedResult}) on line 68 that runs on every permission check in production. Does NOT check isDeleted on users (soft-deleted users pass permission checks). Batch functions use sequential await instead of Promise.all.

Exports: - export class PermissionError extends Error - Custom error - export const hasPermission = async (permissionName: string): Promise<boolean> - Check current user's permission (default export) - export const hasPermissionForUser = async (userId: number, permissionName: string): Promise<boolean> - Check specific user - export const getCurrentUserPermissions = async (): Promise<string[]> - List all permissions (['*'] for super_admin) - export const hasMultiplePermissions = async (permissions: string[]): Promise<Record<string, boolean>> - Batch check - export const hasAnyPermission = async (permissions: string[]): Promise<boolean> - OR logic - export const hasAllPermissions = async (permissions: string[]): Promise<boolean> - AND logic

Dependencies: - ../shared/requestContext - getCurrentUser, isCurrentUserSuperAdmin - ../shared/permissionCache - hasPermissionInCache, getCachedRoles - ../shared/permissionNormalizer - normalizePermissionString, getPermissionVariants - ../shared/getDbRepository - TypeORM repository factory - ../entity/Role, ../entity/Permission, ../entity/User

Used By: 3 direct (checkPermissionAndThrow, hasPermission middleware, contextMiddleware) → 27 effective via checkPermissionAndThrow

Side Effects: Database queries on cache miss

Testing: No test file. 0% coverage.


helper/hashedPassword.ts

Purpose: bcrypt password hashing and verification. Lines of Code: 12 File Type: Crypto utility

What Future Contributors Must Know: Salt rounds hardcoded to 10 (OWASP recommends 12+ for 2026). Function name hashedPassword is a noun — misleading, should be hashPassword. bcrypt silently truncates passwords at 72 bytes. No input validation.

Exports: - hashedPassword = async (password: string): Promise<string> - Hash password - verifyPassword = async (password: string, hash: string): Promise<boolean> - Compare password to hash

Dependencies: - bcrypt

Used By: 9 files: auth.service, auth.utils, UserHandler, LoginAttemptHandler, OtpHandler, OtpPolicy, seed script, setting.service, user.service

Testing: No test file. 0% coverage.


helper/httpLogger.ts

Purpose: Express-winston HTTP request logger middleware. Logs request/response metadata including timing and user info. Lines of Code: 43 File Type: Logging middleware

What Future Contributors Must Know: Accesses req._startAt which may be undefined (crashes process.hrtime). Logs the entire req.user object including password hashes and 2FA secrets to log files. The baseMsg is empty string, making the format template dead code.

Exports: - export const httpLogger - express-winston logger middleware instance

Dependencies: - express-winston, ../app/middlewares/logger, express (types only)

Used By: 1 file: app.ts

Side Effects: Writes to Winston log transport on every HTTP request

Testing: No test file. 0% coverage.


helper/jwtHelpers.ts

Purpose: JWT token generation, verification, and decoding for access and refresh tokens. Lines of Code: 45 File Type: Auth utility

What Future Contributors Must Know: decodeToken hardcodes the access token secret — cannot be used for refresh tokens despite the generic name. refresh_token_secret as string is an unsafe cast — undefined becomes the string "undefined". generateToken accepts payload: any (no type safety). decodeToken does redundant work — verifies (returns payload) then decodes again.

Exports: - jwtHelpers.generateToken(payload: any, secret: Secret, expiresIn: string): string - jwtHelpers.verifyToken(token: string, secret: Secret): JwtPayload - jwtHelpers.decodeToken(token: string): JwtPayload - jwtHelpers.generateRefreshToken(payload: object): string - jwtHelpers.verifyRefreshToken(token: string): JwtPayload | string | object

Dependencies: - jsonwebtoken (jwt, JwtPayload, Secret) - ../config (JWT secrets and expiration config)

Used By: 5 files: auth.service, auth.utils, auth.controller, auth middleware, TokenHandler

Testing: No test file. 0% coverage.


helper/paginationHelper.ts

Purpose: Pagination parameter calculator with defaults. 100% DEAD CODE. Lines of Code: 37 File Type: DEAD CODE

What Future Contributors Must Know: Superseded by parsePaginationQuery.ts (20 consumers). Has 0 consumers. sortBy/sortOrder pass through without validation (SQL injection risk, though moot since file is unused). Can be safely deleted.

Exports: - export const paginationHelper = { calculatePagination } - Pagination calculator object

Dependencies: None

Used By: None (0 consumers)

Testing: No test file. 0% coverage.


helper/parseBoolean.ts

Purpose: Robust boolean parser — handles string, boolean, null, and undefined inputs with case-insensitive comparison. Lines of Code: 14 File Type: Pure utility

What Future Contributors Must Know: This is the best-designed helper — proper JSDoc, null-safe, case-insensitive, trims whitespace. The preferred boolean parser over stringToBoolean.ts (which is case-sensitive and handles only strings). Has 13 consumers.

Exports: - export const parseBoolean = (value: string | boolean | null | undefined): boolean - Parse any value to boolean

Dependencies: None

Used By: 13 files across blog, user, product, package, generic-page, and other services

Testing: No test file. 0% coverage.


helper/parsePaginationQuery.ts

Purpose: Parses page, limit, and searchTerm from Express query parameters with defaults. Lines of Code: 15 File Type: Pure utility

What Future Contributors Must Know: No upper bound on limit — a client can pass ?limit=999999 for DoS. No minimum bound either (0 or negative values pass through). Defaults: page=1, limit=10, searchTerm="".

Exports: - export const getPaginationParams = (query: Record<string, any>): PaginationParams - Parse pagination from query

Dependencies: None

Used By: 20 service files (virtually every paginated endpoint)

Testing: No test file. 0% coverage.


helper/saveBufferToFile.ts

Purpose: Writes a buffer to disk in the uploads directory, creating subdirectories as needed. Lines of Code: 37 File Type: File I/O utility

What Future Contributors Must Know: Path traversal vulnerability — neither fileName nor subFolder is sanitized. Argument order mismatch with LocalSotrage.ts (line 28) — caller passes folder name as second arg (fileName position) and filename as third arg (subFolder position), resulting in incorrect file paths. JSDoc says originalName but param is fileName.

Exports: - export async function saveBufferToFile(buffer: Buffer, fileName: string, subFolder?: string): Promise<string> - Write buffer to disk

Dependencies: - path, fs/promises

Used By: 6 files: Blog, GenericPage, User, Product, Setting controllers + LocalSotrage.ts

Side Effects: Filesystem writes, directory creation

Testing: No test file. 0% coverage.


helper/stringToBoolean.ts

Purpose: Converts string to boolean via strict equality check. Inferior duplicate of parseBoolean.ts. Lines of Code: 4 File Type: Pure utility (candidate for deletion)

What Future Contributors Must Know: Does value === 'true' (case-sensitive, no trim). "True", " true", "TRUE" all return false. Only accepts string (not boolean/null/undefined). Should be replaced with parseBoolean.ts in its 1 consumer.

Exports: - export function stringToBoolean(value: string): boolean - Strict boolean parse

Dependencies: None

Used By: 1 file

Testing: No test file. 0% coverage.


helper/transformImageUrl.ts

Purpose: Prepends API base URL to local upload paths; passes through external URLs unchanged. Lines of Code: 13 File Type: URL utility

What Future Contributors Must Know: The baseUrl parameter is dead code — declared but never used in the function body. Callers like product.service pass it, but it's silently ignored. Has redundant null checks (!image and !Boolean(image) are identical).

Exports: - export const transformImageUrl = (image: string | undefined | null, baseUrl?: string): string | null - Transform image URL

Dependencies: - ../config

Used By: 5 services: Blog, Product, Analytic, GenericPage, Public

Testing: No test file. 0% coverage.


helper/transporterMail.ts

Purpose: Creates and exports a nodemailer SMTP transport instance at module load time. Lines of Code: 14 File Type: Email transport

What Future Contributors Must Know: Transport is created at import time regardless of whether nodemailer is the configured provider. No TLS/SSL configuration — emails sent in cleartext. Contains dead comments (// module.exports = transporter and // transporterMail.js).

Exports: - export default transporter - nodemailer Transporter instance

Dependencies: - nodemailer, ../config

Used By: 1 file: helper/email.ts

Side Effects: Creates SMTP connection at module load

Testing: No test file. 0% coverage.


helper/uploadImage.ts

Purpose: Image upload switch — currently only Cloudinary is implemented, S3 case is commented out. Lines of Code: 25 File Type: Upload router

What Future Contributors Must Know: Only "cloudinary" case works. S3 case is commented out (lines 19-20). Handles both single file and array. Default case throws generic Error.

Exports: - export const uploadImage = async (storage: string, file: Express.Multer.File | Express.Multer.File[]): Promise<string[] | string> - Upload image(s) to storage

Dependencies: - ./uploadToCloudinary

Used By: 3 files: handleFileUpload.ts, Image service, Product controller

Testing: No test file. 0% coverage.


helper/uploadToCloudinary.ts

Purpose: Uploads a buffer to Cloudinary as base64 with auto-conversion to WebP format. Lines of Code: 25 File Type: Cloud upload

What Future Contributors Must Know: Hardcodes image/jpeg MIME type in data URI regardless of actual file type. Imports configureCloudinary but never calls it — credential validation is bypassed. Relies on module-level cloudinary.config() in config/cloudinary.ts.

Exports: - export default uploadToCloudinary(buffer: Buffer, filename: string): Promise<string> - Upload buffer, return secure URL

Dependencies: - ../config/cloudinary (default import: v2 instance; named import: configureCloudinary — unused)

Used By: 1 file: uploadImage.ts

Side Effects: HTTP upload to Cloudinary API

Testing: No test file. 0% coverage.


helper/cloud/s3.ts

Purpose: AWS S3 file upload using the v3 SDK (@aws-sdk/client-s3). Lines of Code: 79 File Type: Cloud upload

What Future Contributors Must Know: S3 client is recreated on every upload call (not cached). DeleteObjectCommand is imported but never used — no file deletion capability exists. Location URL is manually constructed from bucket/region instead of read from S3 response. Returns null from createS3Client() if env vars missing (graceful degradation with console warning).

Exports: - export { uploadToS3 } - async ({ buffer, fileName, mimeType, folder? }): Promise<{ Location, Key }> - Upload to S3

Dependencies: - @aws-sdk/client-s3 (S3Client, PutObjectCommand, DeleteObjectCommand) - ../../config

Used By: 1 file: handleFileUpload.ts

Side Effects: HTTP upload to AWS S3

Testing: No test file. 0% coverage.


helper/email/resend.ts

Purpose: Sends email via the Resend SDK with attachment support. Dead code — 0 direct consumers. Lines of Code: 53 File Type: DEAD CODE (superseded by shared/email/ResendService.ts)

What Future Contributors Must Know: Imported only by helper/email.ts, but helper/email.ts itself is the legacy pipeline. The shared/email/ResendService.ts is the newer implementation. Resend client instantiated at module load with potentially undefined API key. Uses synchronous fs.readFileSync for attachments. MAIL_USER env var is shared with nodemailer SMTP auth.

Exports: - export async function resendEmail({ to, subject, html, attachments? }): Promise<...> - Send via Resend

Dependencies: - resend, fs, path, ../../config, ../../app/errors/ApiError

Used By: 1 file: helper/email.ts (which itself has 4 consumers)

Side Effects: HTTP POST to Resend API; synchronous file reads for attachments

Testing: No test file. 0% coverage.


config/AppConfig.ts

Purpose: Singleton class wrapper around environment configuration loading. 100% DUPLICATE of config/index.ts — 150 LOC dead code. Lines of Code: 150 File Type: DEAD CODE (duplicate)

What Future Contributors Must Know: The loadConfig() method (lines 40-128) is character-for-character identical to config/index.ts lines 23-109. Both read the same 55+ env vars into the same IConfig shape. Both call dotenv.config(). Both validate the same 6 required vars. AppConfig wraps it in a singleton class; index.ts exports a plain object. Nobody imports AppConfig.ts — all 40+ consumers use config/index.ts.

Exports: - export class AppConfig - Singleton with .value: IConfig, .isProduction, .isDevelopment - export default AppConfig.getInstance().value - Same IConfig object

Dependencies: - dotenv, path, ../app/interfaces/config.interface

Used By: 0 consumers

Testing: No test file. 0% coverage.


config/cloudinary.ts

Purpose: Configures the Cloudinary v2 SDK instance with credentials from config. Lines of Code: 25 File Type: Service config

What Future Contributors Must Know: Double-configures — module-level cloudinary.config() runs at import (no validation), then configureCloudinary() function does the same call again but WITH validation. uploadToCloudinary.ts imports configureCloudinary but never calls it — validation is bypassed.

Exports: - export const configureCloudinary = (): cloudinary.ConfigOptions - Configure with validation - export default cloudinary - Pre-configured v2 instance

Dependencies: - cloudinary, ../config (index.ts), ../app/errors/ApiError, http-status

Used By: 1 file: helper/uploadToCloudinary.ts

Testing: No test file. 0% coverage.


config/dbConfig.ts

Purpose: TypeORM DataSource configuration with lazy singleton pattern. Lines of Code: 21 File Type: Database config

What Future Contributors Must Know: synchronize: config.nod_env === 'development' uses the typo key nod_env — functionally maps to NODE_ENV in config/index.ts, so it works, but the key name is wrong. If NODE_ENV somehow becomes 'development' in production, TypeORM will auto-modify the database schema.

Exports: - export const getAppDataSource = (): DataSource - Lazy singleton DataSource factory

Dependencies: - typeorm, ../config (index.ts), path

Used By: 4 files: db/connection.ts, shared/getDbRepository.ts, shared/getDataSource.ts, builder/getQueryBuilder.ts

Testing: No test file. 0% coverage.


config/getLemonsqueezy.ts

Purpose: LemonSqueezy SDK initialization AND checkout session creation. Mixes config with business logic. Lines of Code: 117 File Type: Config + Business logic (violation of SRP)

What Future Contributors Must Know: Contains a hardcoded http://localhost:5500 redirect URL (line 107) — production blocker. custom.user_id is set to email instead of userId (line 98) — wrong value. The createSubscriptionCheckout() function is business logic that belongs in a service file, not config.

Exports: - export const getLemonsqueezy = (): void - Initialize SDK - export async function createSubscriptionCheckout({ variantId, email, userId, planId }): Promise<string> - Create checkout session

Dependencies: - @lemonsqueezy/lemonsqueezy.js, ../config (index.ts), ../app/errors/ApiError, http-status

Used By: 2 files: payment.service.ts, payment.route.ts

Side Effects: HTTP calls to LemonSqueezy API

Testing: No test file. 0% coverage.


config/getStripe.ts

Purpose: Async lazy Stripe instance factory with env var + database fallback and decryption. Lines of Code: 37 File Type: Service config

What Future Contributors Must Know: Falls back to DB lookup via StripeService.loadStripeKeyFromDb() then decrypts with helper/encryption.ts. Caches in module-level stripeInstance variable. Returns null if no key found (callers must handle). App name has typo: "SASS Template" (should be SaaS).

Exports: - export const getStripe = async (): Promise<Stripe | null> - Get or create Stripe instance

Dependencies: - stripe, ../config (index.ts), ../app/modules/v1/stripe.service, ../helper/encryption

Used By: 7 files: payment.service, payment.controller, subscription service, cupon.service, webhook.controller, requireStripe middleware, stripe.service

Side Effects: Database query on first call (to load Stripe key from AppConfig)

Testing: No test file. 0% coverage.


config/index.ts

Purpose: Central configuration loader — reads 55+ environment variables into a typed IConfig object. Lines of Code: 112 File Type: Core config (hub)

What Future Contributors Must Know: This is the highest-consumer file in the entire helper/config/db scope (40+ consumers). Only 6 of 55+ env vars are validated as required. Contains 4 typos baked into the type system: nod_env, sesseion_secret, SSL_VALIDATIOIN_API. Admin credentials (email/password) are exposed on the global config object. Changing any config key name is a multi-file change.

Exports: - export default config: IConfig - The configuration object

Dependencies: - dotenv, path, ../app/interfaces/config.interface

Used By: 40+ files (virtually every module, helper, and config file)

Testing: No test file. 0% coverage.


config/passport.ts

Purpose: Configures Passport.js with Google OAuth 2.0 strategy including user creation/lookup. Lines of Code: 102 File Type: Auth config

What Future Contributors Must Know: Has an invite-only account takeover vulnerability — Google OAuth can claim credential-based invite-only users without password verification (lines 33-36 only block non-invite credential users). Uses withDeleted: true on user lookup — includes soft-deleted users. Mixes config with auth business logic.

Exports: - export default passport - Configured passport instance

Dependencies: - passport, passport-google-oauth20, ../config (index.ts), ../entity/User, ../shared/getDbRepository, ../app/modules/v1/Role/role.service

Used By: 2 files: app.ts, google-oauth.controller.ts

Side Effects: Database queries and writes during OAuth flow

Testing: No test file. 0% coverage.


config/stripe.ts

Purpose: Synchronous Stripe instance creation from environment variable only (no DB fallback). Lines of Code: 15 File Type: Service config

What Future Contributors Must Know: Creates Stripe instance at module load (or null if no key). Unlike getStripe.ts, has no DB fallback and no decryption. Some modules import BOTH stripe.ts and getStripe.ts — they can produce different Stripe instances pointing to different API keys. Same "SASS Template" typo.

Exports: - export default stripe: Stripe | null - Stripe instance or null

Dependencies: - stripe, ../config (index.ts)

Used By: 6 files: payment.service, cupon.service, webhook.controller, requireStripe, stripe.service, subscription

Testing: No test file. 0% coverage.


db/connection.ts

Purpose: Singleton database connection manager using global.appDataSource for serverless/hot-reload survival. Lines of Code: 27 File Type: Database connection

What Future Contributors Must Know: Has a race condition — no mutex on concurrent .initialize() calls during startup. Uses global.appDataSource as singleton, but dbConfig.ts has its own module-level singleton. Three files (getDbRepository, getDataSource, getQueryBuilder) bypass connectToDB() and call getAppDataSource() directly — risking access to uninitialized DataSource. console.error dumps full error (may include DB URL with credentials).

Exports: - export const connectToDB = async (): Promise<DataSource> - Initialize and return DataSource

Dependencies: - typeorm (DataSource type), ../config/dbConfig (getAppDataSource)

Used By: 2 files: server.ts, seed/seed.ts

Side Effects: PostgreSQL connection establishment

Testing: No test file. 0% coverage.


Contributor Checklist

  • Risks & Gotchas:
  • config/index.ts has 40+ consumers — any key rename is a multi-file change
  • checkPermissionAndThrow has 24 consumers — changing error format/status affects entire app
  • hasPermission.ts normalization is commented out — enabling it without testing may break existing checks
  • encryption.ts crashes at import if MASTER_KEY missing — cannot import in test environments without it
  • fileUploader.ts has leaked Cloudinary credentials — rotate immediately
  • saveBufferToFile argument order is swapped by LocalSotrage.ts — fixing either side requires fixing the other
  • Dual Stripe instances (stripe.ts vs getStripe.ts) can point to different API keys

  • Pre-change Verification Steps:

  • Grep for all consumers before changing any export signature
  • Verify MASTER_KEY is set in test environment before importing encryption.ts
  • Check both stripe.ts and getStripe.ts consumers when modifying Stripe config
  • Verify frontend error interceptors if changing HTTP status codes in checkPermissionAndThrow

  • Suggested Tests Before PR:

  • encryption.ts: encrypt/decrypt round-trip, tampered ciphertext rejection, missing key behavior
  • hasPermission.ts: super_admin bypass, cache hit/miss, deleted user, normalization consistency
  • jwtHelpers.ts: token round-trip, expired token, wrong secret, undefined secret
  • hashedPassword.ts: hash/verify round-trip, wrong password, empty string, long password
  • parseBoolean.ts: all input types, case variations, whitespace
  • parsePaginationQuery.ts: defaults, NaN handling, negative values, very large limits
  • handleFileUpload.ts: each storage branch, path traversal attempts, missing file
  • saveBufferToFile.ts: directory creation, path traversal rejection, argument order

Architecture & Design Patterns

Code Organization

Files are organized by function type (helper/, config/, db/) rather than by feature domain. This creates a flat utility layer where auth helpers, file utilities, and email functions coexist in the same directory. No subdirectory organization within helper/ except cloud/ and email/ (1 file each).

Design Patterns

  • Singleton Pattern: config/AppConfig.ts (class-based, unused), config/getStripe.ts (module-level cache), db/connection.ts (global variable)
  • Factory Pattern: handleFileUpload.ts (routes to storage backend), email.ts (routes to email provider)
  • Strategy Pattern: uploadImage.ts (switch on storage type — only Cloudinary implemented)
  • Module-level Side Effects: transporterMail.ts, config/cloudinary.ts, encryption.ts all execute code at import time
  • Fail-safe Deny: hasPermission.ts returns false on any error (except PermissionError)

State Management Strategy

No shared state. Config is loaded once at import time via config/index.ts (module-level). hasPermission uses request-scoped AsyncLocalStorage via requestContext.ts and permissionCache.ts. Stripe instance is cached in module-level variable in getStripe.ts.

Error Handling Philosophy

Inconsistent across files: - checkPermissionAndThrow: Throws ApiError (structured, HTTP-aware) - hasPermission: Catches errors, returns false (fail-safe deny) - encryption.ts: No error handling (crashes propagate) - deleteFile.ts: Catches and logs (silent failure) - jwtHelpers.ts: No error handling (JWT errors propagate) - handleFileUpload.ts: Throws ApiError for invalid storage type - connection.ts: Catches, logs full error, re-throws

Testing Strategy

Zero test coverage across all 33 files. No test files exist. No testing framework configured for the backend (no jest, mocha, or vitest in dependencies).


Data Flow

1. Auth Token Flow

Caller → jwtHelpers.generateToken(payload, config.jwt.jwt_secret, expiresIn)
       → jwt.sign(payload, secret, {algorithm: 'HS256', expiresIn})
       → JWT string returned

Caller → jwtHelpers.verifyToken(token, secret)
       → jwt.verify(token, secret) → JwtPayload or throws
- Pure crypto, no I/O - Errors propagate uncaught (JsonWebTokenError, TokenExpiredError)

2. Permission Check Flow

Service → checkPermissionAndThrow(path)
       → hasPermission(path)
         → isCurrentUserSuperAdmin() → true → return true
         → hasPermissionInCache(path) → hit → return cached
         → loadUserRoles(userId) → DB query → deduplicate roles
         → checkPermissionInRoles(roles, path) → getPermissionVariants → match
       → false → throw ApiError(400)
- DB query on cache miss - console.log({cachedResult}) on every check - Normalization SKIPPED in main path

3. File Upload Flow

Controller → handleFileUpload({ file, storage, subFolder })
  "local"     → construct URL path (NO write) → caller calls saveBufferToFile separately
  "cloudinary" → uploadImage("cloudinary", file)
               → uploadToCloudinary(buffer, filename)
               → cloudinary.uploader.upload(base64, {format: 'webp'})
               → return secure_url
  "s3"        → uploadToS3({ buffer, fileName, mimeType, folder })
               → new S3Client → PutObjectCommand → return { Location, Key }
- No file type validation at any step - Path traversal risk in local/cloudinary/s3 paths

4. Email Sending Flow

Event handler → sendEmail({ to, subject, html })
  "nodemailer" → transporter.sendMail({ from: "Sass Template", to, subject, html })
  "resend"     → resendEmail({ to, subject, html })
               → resend.emails.send({ from: MAIL_USER, to, subject, html })
- Hardcoded from address in nodemailer path - MAIL_USER shared between providers

5. Encryption Flow

Caller → encrypt(plaintext)
       → randomBytes(12) → IV
       → createCipheriv('aes-256-gcm', KEY, iv) → cipher
       → cipher.update + cipher.final → ciphertext
       → getAuthTag → 16 bytes
       → concat [iv | authTag | ciphertext] → base64 → return

Caller → decrypt(data)
       → base64 decode → slice IV, authTag, ciphertext
       → createDecipheriv → setAuthTag → decipher → plaintext
- KEY loaded from process.env.MASTER_KEY at module scope - No AAD, no key rotation

6. DB Connection Flow

server.ts → connectToDB()
          → global.appDataSource ??= getAppDataSource()
          → if !initialized → .initialize() → PostgreSQL connection
          → return DataSource
- Race condition on concurrent calls - console.error may leak DB credentials


Integration Points

External Services Consumed

  • Cloudinary API: Image upload via uploadToCloudinary.ts (buffer → base64 → upload)
  • AWS S3: File upload via cloud/s3.ts (buffer → PutObjectCommand)
  • Resend API: Email via email/resend.ts (HTTP POST)
  • SMTP Server: Email via transporterMail.ts (nodemailer transport)
  • Google OAuth: User auth via passport.ts (OAuth 2.0 flow)
  • LemonSqueezy API: Checkout via getLemonsqueezy.ts (SDK call)
  • Stripe API: Payment via stripe.ts/getStripe.ts (SDK call)
  • PostgreSQL: DB via connection.ts → TypeORM DataSource

Shared State Accessed

  • process.env.MASTER_KEY — directly by encryption.ts (bypasses config)
  • global.appDataSource — by connection.ts (serverless singleton)
  • config/index.ts default export — by 40+ files (module-level cache)
  • stripeInstance module variable — by getStripe.ts (lazy singleton)

Database Tables Accessed

  • User — by hasPermission.ts (with role/permission relations)
  • Role, Permission — by hasPermission.ts (permission check)
  • AppConfig — by getStripe.ts via StripeService.loadStripeKeyFromDb()

Dependency Graph

config/index.ts (HUB — 40+ consumers)
  ├── config/cloudinary.ts
  ├── config/dbConfig.ts
  │     └── db/connection.ts
  ├── config/stripe.ts
  ├── config/getStripe.ts ──→ helper/encryption.ts
  ├── config/getLemonsqueezy.ts
  ├── config/passport.ts
  ├── helper/jwtHelpers.ts
  ├── helper/transformImageUrl.ts
  ├── helper/email.ts
  │     ├── helper/transporterMail.ts
  │     └── helper/email/resend.ts
  ├── helper/handleFileUpload.ts
  │     ├── helper/uploadImage.ts
  │     │     └── helper/uploadToCloudinary.ts ──→ config/cloudinary.ts
  │     └── helper/cloud/s3.ts
  └── helper/checkPermissionAndThrow.ts
        └── helper/hasPermission.ts (→ shared modules, entities)

ISOLATED (no in-scope imports):
  helper/calculateDiscount.ts (DEAD)
  helper/parseBoolean.ts
  helper/parsePaginationQuery.ts
  helper/paginationHelper.ts (DEAD)
  helper/hashedPassword.ts
  helper/generateOtp.ts
  helper/getTimeDifferenceLabel.ts
  helper/stringToBoolean.ts
  helper/deleteFile.ts
  helper/saveBufferToFile.ts
  helper/httpLogger.ts
  helper/fileUploader.ts (DEAD)
  config/AppConfig.ts (DEAD)

Entry Points (Not Imported by Others in Scope)

  • checkPermissionAndThrow.ts (24 external consumers)
  • parsePaginationQuery.ts (20 external consumers)
  • parseBoolean.ts (13 external consumers)
  • hashedPassword.ts (9 external consumers)
  • All isolated leaf files listed above

Leaf Nodes (Don't Import Others in Scope)

  • config/index.ts (pure env loading)
  • encryption.ts (pure crypto)
  • generateOtp.ts (pure crypto)
  • All isolated files listed above

Circular Dependencies

No circular dependencies detected. The graph is a clean DAG.


Testing Analysis

Test Coverage Summary

  • Statements: 0%
  • Branches: 0%
  • Functions: 0%
  • Lines: 0%

Test Files

None. No test files exist for any of the 33 files. No testing framework is configured in the backend package.json.

Testing Gaps

  • CRITICAL: encryption.ts has no tests — wrong key length, missing key, tampered ciphertext behavior undocumented
  • CRITICAL: hasPermission.ts has no tests — the most complex permission logic with 296 LOC, commented-out normalization
  • HIGH: jwtHelpers.ts has no tests — token generation/verification core of auth
  • HIGH: File upload pipeline has no tests — path traversal vulnerabilities unverified
  • HIGH: config/index.ts has no tests — env var validation behavior undocumented

Similar Features Elsewhere

  • TRIPLE Permission Implementation: helper/hasPermission.ts (service-layer, cache+normalizer), middlewares/hasPermission.ts (legacy, hits DB every request, single role), middlewares/permissionMiddleware.ts (uses pre-loaded Set, no normalization). Three different logic paths, three different results.
  • DUAL Email Infrastructure: Legacy helper/email.ts pipeline (4 consumers) vs shared/email/EmailFactory (newer, ~11% adoption). Different config keys (config.mail.type vs config.mail.service).
  • TRIPLE Boolean Parsing: parseBoolean() (robust), stringToBoolean() (strict), sanitizeQuery.tryCast() (inline). Plus 9 inline === "true" comparisons in service files.
  • DUAL Config Loading: config/index.ts (active, 40+ consumers) vs config/AppConfig.ts (dead, 0 consumers).
  • DUAL Stripe Init: config/stripe.ts (sync) vs config/getStripe.ts (async+DB).
  • DUAL Pagination: paginationHelper.ts (dead) vs parsePaginationQuery.ts (active).

Consolidation Candidates

  1. Delete config/AppConfig.ts (150 LOC, complete duplicate)
  2. Delete fileUploader.ts (43 LOC, dead + leaked credentials)
  3. Delete calculateDiscount.ts (26 LOC, dead)
  4. Delete paginationHelper.ts (37 LOC, dead)
  5. Replace stringToBoolean.ts with parseBoolean.ts in its 1 consumer
  6. Consolidate triple permission implementations into one
  7. Migrate remaining helper/email.ts consumers to shared/email/EmailFactory

Missing Abstractions

  1. File path sanitizer — needed by deleteFile, saveBufferToFile, handleFileUpload to prevent path traversal
  2. File type validator — needed across entire upload pipeline (MIME check, extension allowlist, magic bytes)
  3. Soft-delete query helper — multiple services duplicate isDeleted filtering
  4. Config validator — only 6 of 55+ env vars validated; need comprehensive startup validation
  5. Pagination limit capparsePaginationQuery needs a max limit (e.g., 100)

Implementation Notes

Dead Code Summary (~350 LOC removable across 5 dead files)

File LOC Risk Action
config/AppConfig.ts 150 None (0 consumers) Delete
helper/fileUploader.ts 43 Rotate Cloudinary credentials first Delete + rotate
helper/paginationHelper.ts 37 None (0 consumers) Delete
helper/calculateDiscount.ts 26 None (0 consumers) Delete
helper/email/resend.ts 53 1 consumer (email.ts) — verify factory covers all cases Replace import

Additionally, helper/cloud/s3.ts has an unused DeleteObjectCommand import (~1 line) but the file itself is active (1 consumer).

Optimization Opportunities

  1. hasPermission.ts batch functions: Replace sequential await with Promise.all for hasMultiplePermissions
  2. cloud/s3.ts: Cache S3 client instance instead of recreating per upload
  3. uploadToCloudinary.ts: Use actual MIME type instead of hardcoded image/jpeg
  4. hasPermission.ts: Remove console.log({cachedResult}) from hot path
  5. encryption.ts: Add MASTER_KEY to requiredEnv in config/index.ts

Technical Debt

  1. Zero test coverage across all 33 files
  2. 3 competing permission implementations
  3. 2 competing email pipelines
  4. 2 competing Stripe initializations
  5. 4 typos in config type system (nod_env, sesseion_secret, etc.)
  6. Path traversal vulnerabilities in 3 file operation functions
  7. Module-level side effects in 4 files (transport, cloudinary, encryption, AppConfig)
  8. Leaked Cloudinary credentials in dead code
  9. handleFileUpload local branch is architecturally broken (doesn't save)
  10. Inconsistent error handling (ApiError vs throw Error vs silent catch vs propagate)

Modification Guidance

To Add New Functionality

Adding a new helper function: 1. Create file in src/helper/ with descriptive name 2. Use named exports (not default) for consistency with newer files 3. Add JSDoc with parameter descriptions (follow parseBoolean.ts as reference) 4. Import config from ../config (never read process.env directly) 5. Add error handling appropriate to the function type 6. Consider adding to the test suite (when one exists)

Adding a new config provider: 1. Create file in src/config/ (e.g., getNewProvider.ts) 2. Follow getStripe.ts pattern: async lazy singleton with DB fallback 3. Add env vars to config/index.ts with IConfig interface update 4. Add env var to requiredEnv array if mandatory 5. Do NOT mix business logic into config files

Adding a new storage backend: 1. Implement upload function in src/helper/cloud/ or src/shared/storage/ 2. Add case to handleFileUpload.ts switch 3. Add case to uploadImage.ts switch 4. Add file type validation (currently missing everywhere) 5. Add path sanitization (currently missing everywhere)

To Modify Existing Functionality

Changing checkPermissionAndThrow behavior: - Impact: 24 service files. Grep for all consumers first. - If changing HTTP status from 400 to 403: update frontend Axios interceptors too. - If changing error message format: check all catch blocks in consumers.

Changing config key names (fixing typos): - Impact: 40+ consumers for config/index.ts keys. - Must update: IConfig interface, config/index.ts, all consumers, dbConfig.ts checks. - Recommend: Create migration script or find-and-replace across all files.

Changing encryption algorithm or format: - Impact: ALL existing encrypted data in database becomes unreadable. - Must: Write data migration to decrypt with old format, re-encrypt with new. - Must: Keep old decrypt function available during migration period.

To Remove/Deprecate

  1. Delete dead code files (AppConfig.ts, fileUploader.ts, calculateDiscount.ts, paginationHelper.ts): Safe — 0 consumers.
  2. Remove stringToBoolean.ts: Replace 1 consumer with parseBoolean, then delete.
  3. Remove email/resend.ts: Verify shared/email/ResendService.ts covers all use cases, update email.ts import, then delete.
  4. Consolidate Stripe: Pick one (getStripe.ts recommended — has DB fallback), migrate all stripe.ts consumers, delete stripe.ts.

Testing Checklist for Changes

  • All existing consumers still compile after signature changes
  • MASTER_KEY is set in test environment before importing encryption.ts
  • Path traversal attempts are rejected (if adding validation)
  • File type validation rejects non-allowed types (if adding validation)
  • Permission checks return consistent results regardless of format (route vs dot)
  • JWT round-trip: generate → verify → decode produces original payload
  • Password hash round-trip: hash → verify returns true
  • Config loads correctly with minimal env vars (only required 6)
  • Stripe instance is not null when key is configured
  • Email sends successfully via configured provider
  • Database connects and initializes on first call

Generated by document-project workflow (deep-dive mode) Base Documentation: docs/index.md Scan Date: 2026-03-30 Analysis Mode: Exhaustive Agents Used: 12 (5 scan + 5 re-verify + 1 dependency graph + 1 related code)