Skip to content

Helper Functions, Config & DB — Deep Dive Documentation

Generated: 2026-03-29 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,460 Workflow Mode: Exhaustive Deep-Dive Issues Found: 113 (20 Critical, 28 High, 39 Medium, 26 Low) Dead Code: ~380 LOC across 5 files (+ 150 LOC duplicate AppConfig.ts)

Overview

The helper, config, and database connection layer forms the utility backbone of the SaaS boilerplate backend. Every API module, middleware, and service depends on these files for configuration values, authentication tokens, permission checks, file uploads, email sending, encryption, and database access.

Purpose: Cross-cutting utilities consumed by all 30 API modules Key Responsibilities: JWT management, RBAC permission checks, password hashing, file upload orchestration, email dispatch, AES-256-GCM encryption, environment configuration, database connection, pagination, boolean parsing Integration Points: 40+ consumers for config, 24+ for permission checking, 20+ for pagination, 9+ for password hashing, 7+ for Stripe, 5+ for file uploads

Issue Summary

Severity Count Key Examples
Critical 20 Hardcoded Cloudinary credentials, 3x path traversal, zero file type validation, MASTER_KEY not validated at startup, AppConfig.ts 150-LOC duplicate, dual Stripe initialization, hardcoded localhost in LemonSqueezy, synchronize:true via typo key, encryption crash on missing env var, saveBufferToFile arg swap with LocalSotrage.ts
High 28 checkPermissionAndThrow uses HTTP 400 not 403, hasPermission no isDeleted check, debug console.log in production, req.user leaked to logs, 3 dead code files, passport invite-only takeover, no key rotation for encryption, decodeToken hardcodes access secret, refresh_token_secret unsafe cast
Medium 39 Hardcoded "Sass Template" from address, no TLS in email transport, no pagination limit cap, boolean duplicate files, redundant JWT decode, no AAD in encryption, sequential batch permission checks, pluralization bug, S3 client recreated per call
Low 26 Filename typo hashedPassword, Buffer.slice deprecated, mixed export styles, 4 config typos propagated to type system, unused baseUrl param, CommonJS require in TS

Dead Code Inventory (~530 LOC removable)

File LOC Evidence Risk
helper/fileUploader.ts 43 0 imports anywhere CRITICAL: Contains hardcoded Cloudinary API key + secret. Delete and rotate credentials immediately.
helper/calculateDiscount.ts 26 0 imports anywhere Safe to delete
helper/paginationHelper.ts 37 0 imports, superseded by parsePaginationQuery.ts Safe to delete
config/AppConfig.ts 150 Character-for-character duplicate of config/index.ts wrapped in singleton class. 0 meaningful imports (only self-import pattern). Safe to delete — config/index.ts is the canonical config
helper/stringToBoolean.ts 4 1 consumer only, inferior to parseBoolean.ts (case-sensitive, no null-safety) Replace consumer with parseBoolean, then delete
helper/email/resend.ts ~40 0 imports (Resend handled by shared/email/ResendService.ts) Verify no runtime dynamic import, then delete
Subtotal ~300

Additionally, DeleteObjectCommand (imported but unused in cloud/s3.ts), configureCloudinary (imported but never called in uploadToCloudinary.ts), and baseUrl param (accepted but ignored in transformImageUrl.ts) are dead code within active files.


Complete File Inventory

helper/checkPermissionAndThrow.ts

Purpose: Thin wrapper around hasPermission() that throws a structured ApiError when permission is denied. The PRIMARY permission enforcement mechanism used by service-layer code. Lines of Code: 23 File Type: Permission utility

What Future Contributors Must Know: This file is imported by 24 service modules — every protected endpoint in the system. Changing the error format, status code, or function signature is an extremely high-blast-radius change that will affect frontend error handling (Axios interceptors parse status codes).

Exports: - export const checkPermissionAndThrow = async (path: string, actionDescription?: string): Promise<void> — Checks permission via hasPermission(path), throws ApiError(400, message) on denial

Dependencies: - http-status — HTTP status code constants - ./hasPermission — Default import; delegates actual permission check - ../app/errors/ApiError — Custom error class

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

Known Issues: 1. HIGH: Uses HTTP 400 (Bad Request) instead of 403 (Forbidden) — Semantically incorrect. Security audit tools scanning for 403s will miss all permission denials. Frontend Axios interceptors may rely on 400 vs 403 distinction. 2. LOW: No null/undefined check on path parameter — Passes raw input to hasPermission()


helper/hasPermission.ts

Purpose: Core RBAC permission-checking engine. Determines whether the current user has a given permission by checking cache first, then falling back to database queries. Supports both route format (/users/create) and dot notation (users.create). The most complex and critical file in the helper directory. Lines of Code: 296 File Type: RBAC engine

What Future Contributors Must Know: This is the most complex file in the helper directory with 6 exports and 2 internal functions. It has 3 direct importers + 24 indirect via checkPermissionAndThrow = affects every protected endpoint. The commented-out normalization (lines 57-63) was likely disabled during debugging and never re-enabled. The cache layer uses AsyncLocalStorage and is request-scoped. Both user.role (single FK) and user.userRoles (M2M) are loaded and deduplicated.

Exports: - export class PermissionError extends Error — Custom error for permission system failures - export const hasPermission = async (permissionName: string): Promise<boolean> — Main function, checks current user's permission via request context (also default export) - export const hasPermissionForUser = async (userId: number, permissionName: string): Promise<boolean> — Checks a specific user's permission (for background jobs) - export const getCurrentUserPermissions = async (): Promise<string[]> — Returns all permissions for current user (['*'] for super_admin) - export const hasMultiplePermissions = async (permissions: string[]): Promise<Record<string, boolean>> — Batch check (sequential) - export const hasAnyPermission = async (permissions: string[]): Promise<boolean> — OR logic - export const hasAllPermissions = async (permissions: string[]): Promise<boolean> — AND logic

Dependencies: - ../shared/requestContextgetCurrentUser(), isCurrentUserSuperAdmin() - ../shared/permissionCachehasPermissionInCache(), getCachedRoles() - ../shared/permissionNormalizernormalizePermissionString(), getPermissionVariants() - ../shared/getDbRepository — TypeORM repository factory - ../entity/Role, ../entity/Permission, ../entity/User — Entity types

Used By: checkPermissionAndThrow.ts (→ 24 services), hasPermission middleware (1), plus 2 direct service imports

Known Issues: 1. CRITICAL: Normalization asymmetryhasPermission() has normalization commented out (lines 57-63), but hasPermissionForUser() uses it (line 137). Same permission string yields different results depending on which function is called. 2. HIGH: Debug console.log({cachedResult}) on line 68 — Leaks permission check results to server logs on every permission check in production. 3. HIGH: No isDeleted check — Main hasPermission() path loads user via loadUserRoles() without filtering soft-deleted users. A deleted user with a valid JWT passes permission checks. 4. MEDIUM: Sequential permission checkshasMultiplePermissions, hasAnyPermission, hasAllPermissions use sequential await in for loops instead of Promise.all.


helper/hashedPassword.ts

Purpose: Bcrypt password hashing and verification utility for the auth system. Lines of Code: 12 File Type: Crypto utility

What Future Contributors Must Know: Used by 9 files across auth, registration, seeding, and settings. Changing salt rounds affects only new hashes (existing ones still verify). The function name hashedPassword is a noun (misleading — should be verb hashPassword).

Exports: - hashedPassword = async (password: string): Promise<string> — Hashes plaintext with bcrypt (10 rounds) - verifyPassword = async (password: string, hash: string): Promise<boolean> — Timing-safe comparison

Dependencies: - bcrypt — Password hashing library

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

Known Issues: 1. MEDIUM: Salt rounds 10 hardcoded — OWASP recommends 12+ for 2026. Not configurable. 2. LOW: 72-byte bcrypt truncation — Passwords longer than 72 bytes are silently truncated. No pre-hashing (SHA-256) mitigation. 3. LOW: Misleading filenamehashedPassword (noun) used as function name; should be hashPassword (verb). 4. LOW: No input validation — Empty string, null, undefined passed directly to bcrypt.


helper/jwtHelpers.ts

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

What Future Contributors Must Know: Used by 5 files in the auth pipeline. generateToken is generic (caller provides secret + expiry), while generateRefreshToken is hardcoded to config values. decodeToken verify-then-decodes (redundant but safe). Changing the algorithm from HS256 invalidates all existing tokens immediately.

Exports: - jwtHelpers.generateToken(payload: any, secret: Secret, expiresIn: string): string — Signs JWT with HS256 - jwtHelpers.verifyToken(token: string, secret: Secret): JwtPayload — Verifies and returns payload - jwtHelpers.decodeToken(token: string): JwtPayload — Verifies with access secret, then decodes (redundant) - jwtHelpers.generateRefreshToken(payload: object): string — Signs with refresh secret from config - jwtHelpers.verifyRefreshToken(token: string): JwtPayload — Verifies with refresh secret

Dependencies: - jsonwebtoken — JWT library - ../config — JWT secrets and expiration values

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

Known Issues: 1. HIGH: decodeToken hardcodes access token secret — Cannot be used for refresh tokens, but name doesn't indicate this. 2. HIGH: refresh_token_secret as string — Unsafe cast; if env var is undefined, becomes string "undefined" (valid but insecure secret). 3. MEDIUM: payload: any — No type safety on token payload contents. 4. MEDIUM: Redundant decodeverifyToken already returns the decoded payload, then decodeToken calls jwt.decode() again. 5. LOW: Mixed function declaration styles — Arrow functions vs function declarations in same object.


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: Cleanest file in the helper directory. Range [100000, 1000000) guarantees exactly 6 digits. crypto.randomInt is cryptographically secure (uses crypto.randomFillSync internally).

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

Dependencies: - crypto — Node.js built-in randomInt

Used By: 1 file: OtpHandler.ts

Known Issues: None. This file is correct and well-implemented.


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 must be exactly 64 hex characters (32 bytes). Losing this key = all encrypted AppConfig values permanently unrecoverable. No key rotation mechanism exists. The module reads process.env.MASTER_KEY directly (bypasses config/index.ts). Output format: [IV 12 bytes | Auth Tag 16 bytes | Ciphertext] base64-encoded.

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 (decrypts Stripe keys), appConfig.service.ts (encrypts/decrypts stored secrets)

Known Issues: 1. CRITICAL: MASTER_KEY not in requiredEnv — App starts without it, crashes only when encryption.ts is first imported (module-scope Buffer.from(process.env.MASTER_KEY!, 'hex')). 2. CRITICAL: No key rotation support — Changing MASTER_KEY invalidates all encrypted data with no migration path. 3. HIGH: No key length validation — Malformed MASTER_KEY produces cryptic createCipheriv error. 4. MEDIUM: No AAD (Additional Authenticated Data) — Encrypted values not bound to context; values could be swapped between config keys undetected. 5. LOW: Buffer.slice deprecated — Should use Buffer.subarray (Node 17.5+).


helper/email.ts

Purpose: Email dispatch router that branches between nodemailer and Resend based on config. Lines of Code: 29 File Type: Email utility

What Future Contributors Must Know: This is the legacy email pipeline (Pipeline #1). A separate modern pipeline exists in shared/email/ with factory pattern. Both coexist — this one uses config.mail.type while the factory uses config.mail.service. 4 consumers (event handlers + user settings).

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

Dependencies: - http-status, ../app/errors/ApiError — Error handling - ../config — Provider selection (config.mail.type) - ./email/resend — Resend provider - ./transporterMail — Nodemailer transport

Used By: 4 files: event handlers (registerEmailHandler, sendForgotPass, sendLoginOtp, inviteOnlyLoginSendEmail) + userSetting.service

Known Issues: 1. MEDIUM: Hardcoded "Sass Template" <[email protected]> from address — Typo ("Sass" vs "SaaS") and ignores config.mail.from. 2. MEDIUM: Dual email infrastructure — This pipeline coexists with shared/email/EmailFactory using different config keys.


helper/transporterMail.ts

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

What Future Contributors Must Know: Transport is created on import regardless of whether nodemailer is the configured provider. No TLS/SSL options configured.

Exports: - export default transporternodemailer.Transporter instance

Dependencies: - nodemailer — Email library - ../config — SMTP credentials

Used By: 1 file: helper/email.ts

Known Issues: 1. MEDIUM: No TLS/SSL configuration — SMTP transport created without secure, tls, or requireTLS options. 2. LOW: Created at module load time — Even when Resend is configured as the provider.


helper/email/resend.ts

Purpose: Sends email via Resend SDK with attachment support. Lines of Code: 53 File Type: Email provider

What Future Contributors Must Know: This file has 0 imports — it's the legacy Resend implementation. The active one is shared/email/ResendService.ts. The Resend client is instantiated at module load with potentially undefined API key.

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

Dependencies: - resend — Resend SDK - fs, path — File system (synchronous attachment reading) - ../../config — API key, mail user

Used By: 0 files (dead code — superseded by shared/email/ResendService.ts)

Known Issues: 1. CRITICAL: Likely dead code — 0 imports found. Verify no dynamic imports before deletion. 2. HIGH: Resend SDK instantiated at module loadnew Resend(config.resend.api_key) at line 7, before runtime validation at line 29. 3. MEDIUM: Synchronous fs.readFileSync for attachments — blocks event loop.


helper/calculateDiscount.ts

Purpose: Calculates price after applying percentage or fixed discount. Lines of Code: 26 File Type: Business logic utility

What Future Contributors Must Know: Dead code — 0 imports anywhere. Safe to delete.

Exports: - export default calculateDiscount({ originalPrice, discountType, discountValue }): { originalPrice, discountPrice, amountPaid } — Discount calculation

Dependencies: None

Used By: 0 files

Known Issues: 1. HIGH: Entirely dead code — 0 consumers. Safe to delete.


helper/getTimeDifferenceLabel.ts

Purpose: Returns human-readable time difference string ("5 minutes ago", "2 hours ago", "3 days ago"). Lines of Code: 24 File Type: Formatting utility

Exports: - export function getTimeDifferenceLabel(dateString: string): string — Time difference formatter

Dependencies: None

Used By: Analytic service (dashboard display)

Known Issues: 1. MEDIUM: Pluralization bug — When diff rounds to 0 minutes, displays "1 minutes" instead of "1 minute". The display value is overridden to 1 but pluralization check uses the original minutes variable (which is 0).


helper/httpLogger.ts

Purpose: Express-winston HTTP request logger middleware with response time tracking. Lines of Code: 43 File Type: Logging middleware

What Future Contributors Must Know: Logs the ENTIRE req.user object (including password hashes, 2FA secrets) to the log transport. The _startAt property must be set by upstream middleware or process.hrtime() crashes.

Exports: - export const httpLogger — Express middleware instance (express-winston)

Dependencies: - express-winston — Express logging integration - ../app/middlewares/logger — Winston logger instance

Used By: app.ts (mounted globally)

Known Issues: 1. HIGH: Full req.user object logged — Password hashes, 2FA secrets, and other sensitive fields leak to log files/transport. 2. HIGH: req._startAt may be undefinedprocess.hrtime(undefined) throws TypeError, crashing the response. 3. MEDIUM: baseMsg set to empty string — Log messages contain no meaningful text.


helper/paginationHelper.ts

Purpose: Pagination parameter calculator (page/limit/skip/sort). Lines of Code: 37 File Type: Query utility

What Future Contributors Must Know: Dead code — 0 imports. Superseded by parsePaginationQuery.ts.

Exports: - export const paginationHelper = { calculatePagination(options): IOptionsResult } — Pagination calculator

Dependencies: None

Used By: 0 files

Known Issues: 1. HIGH: Entirely dead code — Superseded by parsePaginationQuery.ts. Safe to delete.


helper/parseBoolean.ts

Purpose: Robust boolean parser with null-safety, case-insensitivity, and trimming. Lines of Code: 14 File Type: Parsing utility

What Future Contributors Must Know: The BEST-DESIGNED helper in the folder. Has JSDoc, handles null/undefined, trims whitespace, case-insensitive comparison. 13 consumers. Use this instead of stringToBoolean.ts.

Exports: - export const parseBoolean = (value: string | boolean | null | undefined): boolean — Robust boolean parser

Dependencies: None

Used By: 13 service files (soft-delete/filter logic across many modules)

Known Issues: None — this file is well-implemented.


helper/parsePaginationQuery.ts

Purpose: Extracts pagination parameters (page, limit, searchTerm) from Express query object. Lines of Code: 15 File Type: Query utility

What Future Contributors Must Know: Used by 20 service files — second-highest consumer count in helper/. No upper bound on limit parameter.

Exports: - export const getPaginationParams = (query: Record<string, any>): PaginationParams — Extracts page, limit, searchTerm

Dependencies: None

Used By: 20 service files (every paginated endpoint)

Known Issues: 1. MEDIUM: No upper bound on limit — Client can pass ?limit=999999 (DoS risk). No minimum bound either.


helper/stringToBoolean.ts

Purpose: Strict boolean string parser (case-sensitive, no trimming). Lines of Code: 4 File Type: Parsing utility

What Future Contributors Must Know: Inferior duplicate of parseBoolean.ts. Only 1 consumer. Should be replaced.

Exports: - export function stringToBoolean(value: string): boolean — Strict === 'true' check

Dependencies: None

Used By: 1 file

Known Issues: 1. MEDIUM: Duplicate of parseBoolean.ts — Case-sensitive, no null-safety, no trimming. Inconsistent behavior with the 13-consumer version.


helper/deleteFile.ts

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

What Future Contributors Must Know: Path traversal vulnerability — relativeImagePath is joined with process.cwd() without sanitization. A ../../etc/passwd value would escape the project directory.

Exports: - export default deleteFileByPathName(relativeImagePath: string): void — Deletes file synchronously

Dependencies: - fs, path — Node.js built-ins (CommonJS require)

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

Known Issues: 1. CRITICAL: Path traversal — No sanitization of relativeImagePath. Can delete arbitrary files. 2. LOW: Uses CommonJS require — Inconsistent with ES imports used elsewhere.


helper/fileUploader.ts

Purpose: Multer disk storage + Cloudinary upload utility. Lines of Code: 43 File Type: File upload utility

What Future Contributors Must Know: DEAD CODE with LEAKED CREDENTIALS. Contains hardcoded Cloudinary API key and secret in plaintext. 0 imports anywhere. Delete immediately and rotate the Cloudinary credentials.

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

Dependencies: - multer, path, fs, cloudinary — File handling

Used By: 0 files

Known Issues: 1. CRITICAL: Hardcoded Cloudinary credentialsapi_key: '173484379744282', api_secret: 'eHKsVTxIOLl5oaO_BHxBQWAK3GA' at lines 8-12. 2. CRITICAL: Dead code — 0 consumers. Delete and rotate credentials. 3. HIGH: fs.unlinkSync before error check — Original file deleted even on upload failure (data loss).


helper/handleFileUpload.ts

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

What Future Contributors Must Know: The local branch constructs a path but does NOT write the file — callers must separately call saveBufferToFile. This is mitigated architecturally (4 controllers do call it), but the function itself is incomplete. 5 consumers.

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

Dependencies: - http-status, ../app/errors/ApiError — Error handling - ../config — Storage config - ./cloud/s3 — S3 upload - ./uploadImage — Cloudinary upload

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

Known Issues: 1. CRITICAL: Path traversal in local branchsubFolder used unsanitized in path construction. 2. HIGH: Local branch doesn't save file — Constructs path but no fs.writeFile. Callers must handle separately. 3. MEDIUM: Zero file type validation — Any file type accepted.


helper/saveBufferToFile.ts

Purpose: Writes a buffer to the filesystem with directory creation. Lines of Code: 37 File Type: File I/O utility

What Future Contributors Must Know: LocalSotrage.ts calls this with swapped arguments — folder name as fileName and filename as subFolder, resulting in incorrect file paths. 6 consumers.

Exports: - export async function saveBufferToFile(buffer: Buffer, fileName: string, subFolder?: string): Promise<string> — Writes buffer, returns path

Dependencies: - path, fs/promises — File system

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

Known Issues: 1. CRITICAL: Path traversal — Neither fileName nor subFolder sanitized. 2. HIGH: Argument order mismatch with LocalSotrage.ts — Caller passes (buffer, folderName, fileName) but signature is (buffer, fileName, subFolder). 3. MEDIUM: JSDoc mismatch — Doc says originalName but param is fileName.


helper/transformImageUrl.ts

Purpose: Prepends API base URL to local upload paths. Lines of Code: 13 File Type: URL utility

Exports: - export const transformImageUrl = (image: string | undefined | null, baseUrl?: string): string | null — Transforms upload paths to full URLs

Dependencies: - ../config — API base URL

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

Known Issues: 1. LOW: baseUrl parameter is dead code — Accepted but never used in function body. Callers pass it, function ignores it. 2. LOW: Redundant null check!image and !Boolean(image) are logically identical.


helper/uploadImage.ts

Purpose: Storage router for image uploads (currently only Cloudinary implemented). Lines of Code: 25 File Type: File upload router

Exports: - export const uploadImage = async (storage: string, file: File | File[]): Promise<string[] | string> — Routes to storage backend

Dependencies: - ./uploadToCloudinary — Cloudinary upload

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

Known Issues: 1. MEDIUM: S3 case commented out — Only Cloudinary works; S3 branch is dead code within this file.


helper/uploadToCloudinary.ts

Purpose: Uploads buffer to Cloudinary with WebP conversion. Lines of Code: 25 File Type: Cloud upload utility

Exports: - export default uploadToCloudinary(buffer: Buffer, filename: string): Promise<string> — Returns secure_url

Dependencies: - ../config/cloudinary — Configured Cloudinary instance - ../config/cloudinaryconfigureCloudinary (imported but never called)

Used By: 1 file: uploadImage.ts

Known Issues: 1. MEDIUM: Hardcoded image/jpeg MIME type — Data URI always uses image/jpeg regardless of actual file type. 2. LOW: configureCloudinary imported but never called — Credential validation bypassed.


helper/cloud/s3.ts

Purpose: AWS S3 upload utility using @aws-sdk/client-s3. Lines of Code: 79 File Type: Cloud upload utility

Exports: - export { uploadToS3 }async (options: UploadToS3Options): Promise<UploadToS3Result> — Uploads buffer to S3

Dependencies: - @aws-sdk/client-s3 — S3 SDK (PutObjectCommand, DeleteObjectCommand) - ../../config — AWS credentials

Used By: 1 file: handleFileUpload.ts

Known Issues: 1. MEDIUM: S3 client recreated on every uploadcreateS3Client() called per invocation, no caching. 2. LOW: DeleteObjectCommand imported but never used — Dead import. 3. LOW: Location URL manually constructed — Could break with non-standard S3 endpoints.


config/index.ts

Purpose: Central configuration loader. Reads 55+ environment variables and exports a typed IConfig object. The hub of the entire application — 40+ direct consumers. Lines of Code: 112 File Type: Configuration

What Future Contributors Must Know: This is the most-imported file in the entire codebase (40+ consumers). Only 6 of 55+ env vars are validated as required (JWT_SECRET, REFRESH_TOKEN_SECRET, DB_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET). All others silently undefined if missing. Contains 4 typos baked into the type system.

Exports: - export default configIConfig object with all application configuration

Dependencies: - dotenv — Environment variable loading - path — Path resolution

Used By: 40+ files across the entire codebase

Known Issues: 1. CRITICAL: Only 6 of 55+ env vars validated — 49 env vars silently undefined if missing. 2. HIGH: Admin credentials on config objectconfig.admin.email and config.admin.password accessible from any module. 3. MEDIUM: 4 typos in type systemnod_env (for NODE_ENV), sesseion_secret (for SESSION_SECRET), SSL_VALIDATIOIN_API, "SASS Template" (for "SaaS Template").


config/AppConfig.ts

Purpose: Singleton class wrapper around the exact same config loading logic as index.ts. Lines of Code: 150 File Type: Configuration (DEAD CODE)

What Future Contributors Must Know: Character-for-character duplicate of config/index.ts wrapped in a singleton class. The loadConfig() method (lines 40-128) is identical to index.ts lines 23-109. 0 meaningful consumers. Safe to delete.

Exports: - export class AppConfig — Singleton with getInstance(), value, isProduction, isDevelopment - export default AppConfig.getInstance().value — Same config object as config/index.ts

Dependencies: - dotenv, path, IConfig — Same as index.ts

Used By: 0 files (dead code)

Known Issues: 1. CRITICAL: 150 LOC duplicate — Identical to config/index.ts. Delete.


config/cloudinary.ts

Purpose: Configures Cloudinary SDK with credentials from environment. Lines of Code: 25 File Type: Cloud service config

Exports: - export const configureCloudinary = () => cloudinary.ConfigOptions — Validates and configures - export default cloudinary — Pre-configured v2 instance

Dependencies: - cloudinary — Cloudinary SDK - ./ (config/index.ts) — Credentials - ../app/errors/ApiError, http-status — Error handling

Used By: 1 file: helper/uploadToCloudinary.ts

Known Issues: 1. MEDIUM: Double configuration — Module-level cloudinary.config() (lines 6-10) then configureCloudinary() does the same call again (lines 16-20). The named export adds validation but the default export already configures without validation.


config/dbConfig.ts

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

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

Dependencies: - typeormDataSource - ./ (config/index.ts) — DB URL, nod_env - path — Entity glob resolution

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

Known Issues: 1. CRITICAL: synchronize: true controlled by typo key nod_envconfig.nod_env === 'development' works because nod_env maps to NODE_ENV, but if the typo is ever "fixed" without updating dbConfig, production databases get synchronize: true (schema auto-modification).


config/getLemonsqueezy.ts

Purpose: LemonSqueezy SDK initialization AND checkout session creation (config + business logic mixed). Lines of Code: 117 File Type: Payment config + business logic

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

Dependencies: - @lemonsqueezy/lemonsqueezy.js — LemonSqueezy SDK - ./ (config/index.ts) — API key, store ID - ../app/errors/ApiError, http-status — Error handling

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

Known Issues: 1. CRITICAL: Hardcoded http://localhost:5500 redirect URL — Line 107. Production blocker. 2. HIGH: custom.user_id set to email instead of userId — Line 98. Webhook data mismatch. 3. MEDIUM: Business logic in config filecreateSubscriptionCheckout() belongs in a service.


config/getStripe.ts

Purpose: Async lazy Stripe instance with env-only + DB fallback initialization. Lines of Code: 37 File Type: Payment config

What Future Contributors Must Know: This is the async alternative to stripe.ts. Falls back to loading the Stripe key from the database (encrypted via helper/encryption.ts). 7 consumers import this. Some modules import BOTH stripe.ts and getStripe.ts.

Exports: - export const getStripe = async (): Promise<Stripe | null> — Async lazy singleton with DB fallback

Dependencies: - stripe — Stripe SDK - ./ (config/index.ts) — Stripe secret from env - ../app/modules/v1/stripe.service — DB lookup for Stripe key - ../helper/encryption — Decrypts DB-stored Stripe key

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

Known Issues: 1. CRITICAL: Dual Stripe initialization — Coexists with stripe.ts (sync). Some modules import both, potentially creating 2 different Stripe instances with different API keys. 2. MEDIUM: "SASS Template" typoappInfo.name should be "SaaS Template".


config/stripe.ts

Purpose: Synchronous Stripe instance from environment variable only. Lines of Code: 15 File Type: Payment config

Exports: - export default stripeStripe | null — Synchronous instance

Dependencies: - stripe — Stripe SDK - ./ (config/index.ts) — Stripe secret

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

Known Issues: 1. CRITICAL: Part of dual Stripe initialization — See getStripe.ts above. 2. MEDIUM: "SASS Template" typo — Same as getStripe.ts.


config/passport.ts

Purpose: Passport.js Google OAuth strategy configuration with user creation/linking. Lines of Code: 102 File Type: Auth config

What Future Contributors Must Know: Contains the invite-only account takeover vulnerability. Google OAuth can claim credential-based invite-only users without password verification. Includes withDeleted: true in user lookup (finds soft-deleted users).

Exports: - export default passport — Configured passport instance

Dependencies: - passport, passport-google-oauth20 — Auth libraries - ./ (config/index.ts) — OAuth credentials - ../entity/User — User entity - ../shared/getDbRepository — DB access - ../app/modules/v1/Role/role.service — Role assignment

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

Known Issues: 1. HIGH: Invite-only account takeover — Google OAuth can claim credential-based invite-only users' accounts. The condition provider === CREDENTIAL && inviteOnly === false blocks only non-invited credential users; invited credential users fall through. 2. MEDIUM: withDeleted: true in user lookup — Soft-deleted users found and get error message "This account is removed" instead of generic "not found".


db/connection.ts

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

Exports: - export const connectToDB = async (): Promise<DataSource> — Lazy init + connect

Dependencies: - typeormDataSource - ../config/dbConfiggetAppDataSource() factory

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

Known Issues: 1. HIGH: Race condition — No mutex; concurrent .initialize() calls possible during startup. 2. MEDIUM: Dual singletonglobal.appDataSource vs dbConfig.ts module-level singleton. Other consumers (getDbRepository, getDataSource, getQueryBuilder) bypass connectToDB() entirely. 3. MEDIUM: Credential leak riskconsole.error(error) may dump DB URL with password.


Architecture & Design Patterns

Code Organization

The 33 files are organized into 3 directories by concern: - helper/ (24 files): Pure utilities and service-layer helpers. No consistent internal organization — auth, file upload, email, formatting, and parsing utilities are all at the same level. - config/ (8 files): Environment configuration loading + external service initialization. Mixes pure config (index.ts, dbConfig.ts) with service initialization (passport.ts, getLemonsqueezy.ts). - db/ (1 file): Database connection lifecycle.

Design Patterns

  • Hub-and-spoke config: config/index.ts is the central hub (40+ consumers). All other config files and most helpers import from it.
  • Factory routing: handleFileUpload and email.ts both use switch/branch to route to different storage/email backends. Not formalized as a factory pattern.
  • Lazy singleton: getStripe.ts, dbConfig.ts, db/connection.ts all use lazy singleton patterns (but implemented differently — module-level variable, global variable, or class singleton).
  • Wrapper pattern: checkPermissionAndThrow wraps hasPermission adding error throwing.

Error Handling Philosophy

Inconsistent. Three approaches coexist: 1. Throw ApiError: Used by checkPermissionAndThrow, handleFileUpload, email.ts, cloudinary.ts 2. Silent return false/null: Used by hasPermission (fail-safe deny), getStripe (graceful degradation) 3. No error handling: Used by hashedPassword, jwtHelpers, encryption (errors propagate to callers)

Testing Strategy

0% coverage. No test files exist for any of the 33 files. No test framework configured.

Data Flow

Entry Points

  • Permission check: Controller → checkPermissionAndThrow(path)hasPermission → cache/DB → boolean
  • File upload: Controller → handleFileUpload(payload)uploadImage/uploadToS3 → cloud service → URL string
  • Email send: Event handler → sendEmail(payload)transporter.sendMail/resendEmail → SMTP/API
  • Token create: Auth service → jwtHelpers.generateToken(payload, secret, expiry) → JWT string
  • Encrypt: AppConfig service → encrypt(plaintext) → base64 ciphertext
  • DB connect: server.tsconnectToDB()getAppDataSource()DataSource.initialize() → PostgreSQL

Data Exit Points

  • Permission result: boolean returned to service layer
  • Upload URL: string (Cloudinary secure_url or S3 location) returned to controller
  • Email: Side effect (SMTP/API call), no return value
  • JWT: string token returned to auth flow → sent to client via cookie
  • Ciphertext: string stored in AppConfig DB table
  • DB connection: DataSource instance used globally for all queries

Integration Points

External Services

  • Cloudinary: Image upload + WebP conversion (uploadToCloudinary.ts)
  • AWS S3: Object storage (cloud/s3.ts)
  • Stripe: Payment processing (stripe.ts, getStripe.ts)
  • LemonSqueezy: Alternative payment processing (getLemonsqueezy.ts)
  • SMTP server: Email via nodemailer (transporterMail.ts)
  • Resend API: Email via Resend SDK (email/resend.ts)
  • Google OAuth: Authentication via passport (passport.ts)
  • PostgreSQL: Database via TypeORM (dbConfig.ts, connection.ts)

Shared State

  • global.appDataSource — TypeORM DataSource singleton (survives hot reloads)
  • stripeInstance (module-level in getStripe.ts) — Cached Stripe instance
  • Module-level KEY in encryption.ts — AES key loaded once at import
  • Module-level transporter in transporterMail.ts — SMTP transport created at import
  • Module-level resend in email/resend.ts — Resend client created at import

Dependency Graph

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

STANDALONE (no in-scope dependencies):
  helper/checkPermissionAndThrow.ts → helper/hasPermission.ts (→ shared/ modules, not in scope)
  helper/hashedPassword.ts
  helper/generateOtp.ts
  helper/encryption.ts
  helper/parseBoolean.ts
  helper/parsePaginationQuery.ts
  helper/deleteFile.ts
  helper/saveBufferToFile.ts
  helper/stringToBoolean.ts
  helper/getTimeDifferenceLabel.ts
  helper/httpLogger.ts
  helper/calculateDiscount.ts (DEAD)
  helper/paginationHelper.ts (DEAD)
  helper/fileUploader.ts (DEAD)
  config/AppConfig.ts (DEAD)

Circular Dependencies

None detected. The dependency graph is a clean DAG.

TRIPLE Permission Implementation (Critical Consolidation Target)

Three separate permission-checking systems exist with incompatible logic:

System Location Logic Consumers
helper/hasPermission.ts Service layer Cache → normalizer → DB, checks all userRoles 24 (via wrapper)
middlewares/hasPermission.ts Legacy middleware Direct DB query, checks single role only 1 (payment.route)
middlewares/permissionMiddleware.ts New middleware Pre-loaded Set from contextMiddleware, no normalization 1 (user.routes)

Recommendation: Consolidate to helper/hasPermission.ts as the single source of truth. The middleware versions should call the helper.

DUAL Email Infrastructure

Two completely separate email pipelines coexist:

Pipeline Config Key Providers Used By
helper/email.ts config.mail.type nodemailer, Resend 4 event handlers
shared/email/EmailFactory config.mail.service nodemailer, Resend, Brevo 1 file (newer code)

Recommendation: Migrate all consumers to shared/email/EmailFactory and delete helper/email.ts, helper/transporterMail.ts, helper/email/resend.ts.

Missing Abstractions

  1. File upload controller pattern: 4 controllers duplicate 30-line upload blocks. Extract to shared middleware.
  2. Soft-delete query helper: Multiple services manually add isDeleted: false filters.
  3. Sort alias prefixer: Multiple services inline entity.${sortBy} prefixing.
  4. Super admin bypass: 4 places check user.role?.name === 'super_admin' inline instead of using isCurrentUserSuperAdmin().

Contributor Checklist

  • Risks & Gotchas:
  • config/index.ts has 40+ consumers — any key rename breaks the entire app
  • checkPermissionAndThrow has 24 consumers — changing error format breaks all frontend error handling
  • encryption.ts crash at import time if MASTER_KEY is unset — will break test environments
  • fileUploader.ts has LEAKED Cloudinary credentials that need rotation regardless of code changes
  • Permission normalization is silently disabled — re-enabling without testing could break existing checks
  • saveBufferToFile arg swap with LocalSotrage.ts creates incorrect file paths silently
  • Dual Stripe instances may use different API keys depending on import path

  • Pre-change Verification Steps:

  • Check consumer count before modifying any export signature
  • Verify env vars are set in .env.example before adding new config dependencies
  • Test permission checks with both route-format and dot-notation strings
  • Verify file upload works for local, Cloudinary, and S3 storage backends
  • Test email sending with both nodemailer and Resend providers

  • Suggested Tests Before PR:

  • Permission check round-trip (grant → check → deny → check)
  • JWT generate → verify → decode round-trip for both access and refresh tokens
  • Encrypt → decrypt round-trip with various string lengths
  • File upload to each storage backend with various file types
  • Email dispatch via each configured provider
  • Pagination with edge cases (limit=0, limit=MAX, negative page)
  • Boolean parsing with all input types (string, boolean, null, undefined)
  • Path traversal prevention (../../../etc/passwd in file paths)
  • Config loading with missing required env vars (should throw at startup)
  • DB connection race condition (concurrent connectToDB calls)

Modification Guidance

To Add New Functionality

  1. New helper: Create in helper/ at root level. Use named exports (not default). Add JSDoc. Follow parseBoolean.ts as the reference implementation (clean, typed, documented).
  2. New config value: Add to config/index.ts IConfig shape AND AppConfig.ts (if not deleted) AND IConfig interface. Add to requiredEnv array if critical.
  3. New storage backend: Add case to handleFileUpload.ts switch AND uploadImage.ts switch. Follow cloud/s3.ts pattern.

To Modify Existing Functionality

  1. Changing permission logic: Test with ALL 24 consumer services. Verify both route-format and dot-notation inputs. Check cache invalidation.
  2. Changing error status codes: Verify frontend Axios interceptors handle the new code. checkPermissionAndThrow uses 400 — changing to 403 affects 24 modules.
  3. Changing encryption: All existing encrypted data in DB becomes unreadable if algorithm/format changes. Migration required.

To Remove/Deprecate

  1. Delete fileUploader.ts immediately (dead + leaked credentials)
  2. Delete calculateDiscount.ts (dead, 0 consumers)
  3. Delete paginationHelper.ts (dead, superseded)
  4. Delete config/AppConfig.ts (150 LOC duplicate)
  5. Delete stringToBoolean.ts after migrating 1 consumer to parseBoolean.ts
  6. Delete helper/email/resend.ts after verifying no dynamic imports

Testing Checklist for Changes

  • All 24 checkPermissionAndThrow consumers still work
  • JWT tokens from before the change still verify (backward compat)
  • Encrypted config values from before the change still decrypt
  • File uploads succeed for all 3 storage backends
  • Emails send via both nodemailer and Resend
  • Pagination works with edge case inputs
  • No new console.log statements in production paths
  • No path traversal possible in file operations
  • All 6 required env vars still validated at startup
  • Stripe instance is consistent regardless of import path

Generated by document-project workflow (deep-dive mode) Base Documentation: docs/index.md Scan Date: 2026-03-29 Analysis Mode: Exhaustive — 33 files, ~1,460 LOC, 113 issues (20C, 28H, 39M, 26L) 5 parallel subagents + re-verification pass after git pull