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 config — admin.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 undefined — process.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 userId — wrong 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 decode — decodeToken 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 desync — global.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 bypass — getDbRepository, 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 bound — page=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 vulnerability — relativeImagePath 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 vulnerabilities — subFolder 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.tshas 40+ consumers — any key rename is a multi-file changecheckPermissionAndThrowhas 24 consumers — changing error format/status affects entire apphasPermission.tsnormalization is commented out — enabling it without testing may break existing checksencryption.tscrashes at import ifMASTER_KEYmissing — cannot import in test environments without itfileUploader.tshas leaked Cloudinary credentials — rotate immediatelysaveBufferToFileargument order is swapped byLocalSotrage.ts— fixing either side requires fixing the other-
Dual Stripe instances (
stripe.tsvsgetStripe.ts) can point to different API keys -
Pre-change Verification Steps:
- Grep for all consumers before changing any export signature
- Verify
MASTER_KEYis set in test environment before importing encryption.ts - Check both
stripe.tsandgetStripe.tsconsumers 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 behaviorhasPermission.ts: super_admin bypass, cache hit/miss, deleted user, normalization consistencyjwtHelpers.ts: token round-trip, expired token, wrong secret, undefined secrethashedPassword.ts: hash/verify round-trip, wrong password, empty string, long passwordparseBoolean.ts: all input types, case variations, whitespaceparsePaginationQuery.ts: defaults, NaN handling, negative values, very large limitshandleFileUpload.ts: each storage branch, path traversal attempts, missing filesaveBufferToFile.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.tsall execute code at import time - Fail-safe Deny:
hasPermission.tsreturnsfalseon 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
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)
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 }
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 })
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
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
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 byencryption.ts(bypasses config)global.appDataSource— byconnection.ts(serverless singleton)config/index.tsdefault export — by 40+ files (module-level cache)stripeInstancemodule variable — bygetStripe.ts(lazy singleton)
Database Tables Accessed¶
User— byhasPermission.ts(with role/permission relations)Role,Permission— byhasPermission.ts(permission check)AppConfig— bygetStripe.tsviaStripeService.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.tshas no tests — wrong key length, missing key, tampered ciphertext behavior undocumented - CRITICAL:
hasPermission.tshas no tests — the most complex permission logic with 296 LOC, commented-out normalization - HIGH:
jwtHelpers.tshas no tests — token generation/verification core of auth - HIGH: File upload pipeline has no tests — path traversal vulnerabilities unverified
- HIGH:
config/index.tshas no tests — env var validation behavior undocumented
Related Code & Reuse Opportunities¶
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.tspipeline (4 consumers) vsshared/email/EmailFactory(newer, ~11% adoption). Different config keys (config.mail.typevsconfig.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) vsconfig/AppConfig.ts(dead, 0 consumers). - DUAL Stripe Init:
config/stripe.ts(sync) vsconfig/getStripe.ts(async+DB). - DUAL Pagination:
paginationHelper.ts(dead) vsparsePaginationQuery.ts(active).
Consolidation Candidates¶
- Delete
config/AppConfig.ts(150 LOC, complete duplicate) - Delete
fileUploader.ts(43 LOC, dead + leaked credentials) - Delete
calculateDiscount.ts(26 LOC, dead) - Delete
paginationHelper.ts(37 LOC, dead) - Replace
stringToBoolean.tswithparseBoolean.tsin its 1 consumer - Consolidate triple permission implementations into one
- Migrate remaining
helper/email.tsconsumers toshared/email/EmailFactory
Missing Abstractions¶
- File path sanitizer — needed by
deleteFile,saveBufferToFile,handleFileUploadto prevent path traversal - File type validator — needed across entire upload pipeline (MIME check, extension allowlist, magic bytes)
- Soft-delete query helper — multiple services duplicate
isDeletedfiltering - Config validator — only 6 of 55+ env vars validated; need comprehensive startup validation
- Pagination limit cap —
parsePaginationQueryneeds 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¶
hasPermission.tsbatch functions: Replace sequentialawaitwithPromise.allforhasMultiplePermissionscloud/s3.ts: Cache S3 client instance instead of recreating per uploaduploadToCloudinary.ts: Use actual MIME type instead of hardcodedimage/jpeghasPermission.ts: Removeconsole.log({cachedResult})from hot pathencryption.ts: AddMASTER_KEYtorequiredEnvinconfig/index.ts
Technical Debt¶
- Zero test coverage across all 33 files
- 3 competing permission implementations
- 2 competing email pipelines
- 2 competing Stripe initializations
- 4 typos in config type system (
nod_env,sesseion_secret, etc.) - Path traversal vulnerabilities in 3 file operation functions
- Module-level side effects in 4 files (transport, cloudinary, encryption, AppConfig)
- Leaked Cloudinary credentials in dead code
handleFileUploadlocal branch is architecturally broken (doesn't save)- 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¶
- Delete dead code files (AppConfig.ts, fileUploader.ts, calculateDiscount.ts, paginationHelper.ts): Safe — 0 consumers.
- Remove
stringToBoolean.ts: Replace 1 consumer withparseBoolean, then delete. - Remove
email/resend.ts: Verifyshared/email/ResendService.tscovers all use cases, updateemail.tsimport, then delete. - Consolidate Stripe: Pick one (
getStripe.tsrecommended — has DB fallback), migrate allstripe.tsconsumers, deletestripe.ts.
Testing Checklist for Changes¶
- All existing consumers still compile after signature changes
-
MASTER_KEYis 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)