Files
Zumri-Backend/app/config/env.config.js
T
Sathira Sri Sathara abb760cc19
CI / test (push) Successful in 10m23s
feat: Implement loyalty and wholesale services with models, routes, and validation
- Added models for loyalty points allocation, redemption, rewards, tiers, tier history, and referrals.
- Created business credit ledger entries, settlements, and settlement items models.
- Developed services for loyalty operations including account management, point allocation, redemption, and referral handling.
- Implemented wholesale credit management services for transactions and settlements.
- Established routes for loyalty and wholesale admin and customer operations with appropriate middleware for authentication and permission checks.
- Introduced validation schemas for loyalty and wholesale operations.
- Set up cron jobs for loyalty reconciliation tasks such as birthday rewards and point expirations.
- Created migration scripts to set up the database schema for loyalty and wholesale features.
- Added unit tests for validation and policy enforcement in loyalty and wholesale services.
2026-09-09 13:56:37 +05:30

86 lines
4.8 KiB
JavaScript

const { z } = require("zod");
const booleanString = z.enum(["true", "false"]).default("false").transform((value) => value === "true");
const envSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
PORT: z.coerce.number().int().min(1).max(65535).default(3070),
DB_HOST: z.string().min(1),
DB_PORT: z.coerce.number().int().min(1).max(65535).default(3306),
DB_NAME: z.string().min(1),
DB_USER: z.string().min(1),
DB_PASSWORD: z.string(),
JWT_SECRET: z.string().min(32, "JWT_SECRET must contain at least 32 characters"),
JWT_ISSUER: z.string().min(1).default("zumri-api"),
JWT_AUDIENCE: z.string().min(1).default("zumri-clients"),
ACCESS_TOKEN_TTL: z.string().default("15m"),
REFRESH_TOKEN_TTL_DAYS: z.coerce.number().int().positive().default(7),
REMEMBER_ME_REFRESH_TOKEN_TTL_DAYS: z.coerce.number().int().positive().default(30),
LOGIN_OTP_TTL_SECONDS: z.coerce.number().int().positive().default(900),
LOGIN_OTP_MAX_ATTEMPTS: z.coerce.number().int().min(1).max(10).default(5),
REDIS_HOST: z.string().min(1),
REDIS_PORT: z.coerce.number().int().min(1).max(65535).default(6379),
REDIS_PASSWORD: z.string().optional(),
FRONTEND_URL: z.string().url(),
TRUST_PROXY: z.coerce.number().int().min(0).max(10).default(0),
JSON_BODY_LIMIT: z.string().default("1mb"),
API_RATE_LIMIT_WINDOW_MS: z.coerce.number().int().positive().default(900000),
API_RATE_LIMIT_MAX: z.coerce.number().int().positive().default(300),
SENSITIVE_RATE_LIMIT_WINDOW_MS: z.coerce.number().int().positive().default(900000),
SENSITIVE_RATE_LIMIT_MAX: z.coerce.number().int().positive().default(20),
RUN_CRON: booleanString,
ENABLE_MAIL: booleanString,
ENABLE_S3: booleanString,
SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().positive().default(10000),
CACHE: booleanString,
MAIL_HOST: z.string().min(1).optional(), MAIL_PORT: z.coerce.number().int().positive().optional(),
MAIL_SECURE: z.enum(["true", "false"]).optional(), MAIL_USER: z.string().optional(),
MAIL_PASS: z.string().optional(), MAIL_FROM: z.string().optional(),
AWS_REGION: z.string().optional(), AWS_ACCESS_KEY_ID: z.string().optional(),
AWS_SECRET_ACCESS_KEY: z.string().optional(), AWS_S3_BUCKET_NAME: z.string().optional(),
S3_ENDPOINT: z.string().url().optional(), S3_FORCE_PATH_STYLE: booleanString,
S3_SIGNED_URL_TTL_SECONDS: z.coerce.number().int().min(60).max(86400).default(900),
S3_MAX_UPLOAD_BYTES: z.coerce.number().int().positive().default(5242880),
EMAIL_QUEUE_CONCURRENCY: z.coerce.number().int().positive().default(5),
DOCUMENT_QUEUE_CONCURRENCY: z.coerce.number().int().positive().default(2),
NOTIFICATION_RETENTION_DAYS: z.coerce.number().int().positive().default(90),
INVENTORY_RESERVATION_TTL_MINUTES: z.coerce.number().int().positive().default(15),
CHECKOUT_TTL_MINUTES: z.coerce.number().int().positive().default(15),
CART_MAX_ITEM_QUANTITY: z.coerce.number().int().positive().default(100),
RETURN_WINDOW_DAYS: z.coerce.number().int().positive().default(30),
PAYHERE_MERCHANT_ID: z.string().optional(), PAYHERE_MERCHANT_SECRET: z.string().optional(),
PAYHERE_NOTIFY_URL: z.string().url().optional(), PAYHERE_RETURN_URL: z.string().url().optional(), PAYHERE_CANCEL_URL: z.string().url().optional(),
LOYALTY_RECONCILIATION_BATCH_SIZE: z.coerce.number().int().positive().max(1000).default(100),
LOG_RETENTION_DAYS: z.coerce.number().int().positive().default(30),
DOCS_USER: z.string().optional(), DOCS_PASS: z.string().optional(),
GOOGLE_CLIENT_ID: z.string().optional(), APPLE_CLIENT_ID: z.string().optional(),
}).superRefine((env, context) => {
const requireFeature = (enabled, names) => {
if (!enabled) return;
for (const name of names) {
if (!env[name]) context.addIssue({ code: "custom", path: [name], message: `${name} is required when enabled` });
}
};
requireFeature(env.ENABLE_MAIL, ["MAIL_HOST", "MAIL_PORT", "MAIL_USER", "MAIL_PASS", "MAIL_FROM"]);
requireFeature(env.ENABLE_S3, ["AWS_REGION", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_S3_BUCKET_NAME"]);
});
let validatedEnv;
const validateEnvironment = (source = process.env) => {
const result = envSchema.safeParse(source);
if (!result.success) {
const names = [...new Set(result.error.issues.map((issue) => issue.path.join(".") || "environment"))];
throw new Error(`Invalid environment configuration: ${names.join(", ")}`);
}
validatedEnv = result.data;
return validatedEnv;
};
const getEnvironment = () => validatedEnv || validateEnvironment();
const getOptionalFeatureStatus = (env = process.env) => ({
mail: Boolean(env.MAIL_HOST && env.MAIL_PORT && env.MAIL_USER && env.MAIL_PASS && env.MAIL_FROM),
s3: Boolean(env.AWS_REGION && env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY && env.AWS_S3_BUCKET_NAME),
docs: Boolean(env.DOCS_USER && env.DOCS_PASS),
});
module.exports = { validateEnvironment, getEnvironment, getOptionalFeatureStatus };