From 79cee87b8d2b9804beb0b46709790697d39a3328 Mon Sep 17 00:00:00 2001 From: Mat0to Date: Fri, 29 Aug 2025 23:53:24 -0600 Subject: [PATCH] fisrt_commit --- .env.example | 78 ++++++++++++++---------------------- package-lock.json | 10 +++++ package.json | 1 + readme.md | 19 +++++++++ src/config/data-source.ts | 24 ----------- src/config/env.ts | 34 ++++++++++++++++ src/config/horizon.config.ts | 5 ++- src/config/prisma.ts | 5 ++- src/config/redis.ts | 3 +- src/config/soroban.config.ts | 5 ++- src/config/supabase.ts | 5 ++- src/config/swagger.config.ts | 3 +- src/config/winston.config.ts | 5 ++- src/index.ts | 3 +- 14 files changed, 115 insertions(+), 85 deletions(-) delete mode 100644 src/config/data-source.ts create mode 100644 src/config/env.ts diff --git a/.env.example b/.env.example index 821a8c9..59f33e1 100644 --- a/.env.example +++ b/.env.example @@ -1,56 +1,38 @@ -# REDIS_URL=redis://redis:6379 -# REDIS_URL=redis://localhost:6379 -# DATABASE_URL=postgresql://volunchain:volunchain123@db:5432/volunchain -DATABASE_URL=postgresql://volunchain:volunchain123@localhost:5434/volunchain +# Core Environment Variables +NODE_ENV=development PORT=3000 -JWT_SECRET=your-jwt-secret -JWT_SECRET=Zq0gGppr1PIqJsYbCJptI+xmgVvd3BViQUEw2nvsiBs= -DB_TYPE=postgres -# DB_HOST=db -# DB_PORT=5432 -DB_HOST=localhost -DB_PORT=5434 - -# DIRECT_URL=DATABASE_URL=postgresql://user:password@localhost:5432/volunchain?schema=public&connection_limit=10&pool_timeout=30&idle_timeout=30 -DIRECT_URL=postgresql://volunchain:volunchain123@localhost:5434/volunchain -DATABASE_CONNECTION_LIMIT=10 -DATABASE_POOL_TIMEOUT=30 -DATABASE_IDLE_TIMEOUT=30 -DATABASE_CONNECTION_TIMEOUT=10 -ENABLE_QUERY_LOGGING=true -QUERY_TIMEOUT=30000 - - -DB_USER=volunchain -DB_PASSWORD=volunchain123 -DB_NAME=volunchain -NODE_ENV="development" -BACKBLAZE_APPLICATION_KEY_ID= -BACKBLAZE_APPLICATION_KEY= -BACKBLAZE_BUCKET_ID= -RATE_LIMIT_MAX_REQUESTS=100 -RATE_LIMIT_WINDOW_MINUTES=5 +# Database +DATABASE_URL=postgresql://user:password@localhost:5432/volunchain -RATE_LIMIT_AUTH_MAX=10 -RATE_LIMIT_AUTH_WINDOW=3 +# JWT / Security +JWT_SECRET=your_jwt_secret -RATE_LIMIT_WALLET_MAX=50 -RATE_LIMIT_WALLET_WINDOW=5 +# Supabase +SUPABASE_URL=https://your-supabase-url.supabase.co +SUPABASE_ANON_KEY=your_supabase_anon_key -RATE_LIMIT_EMAIL_MAX=20 -RATE_LIMIT_EMAIL_WINDOW=5 +# Redis +REDIS_URL=redis://localhost:6379 -RATE_LIMIT_DEFAULT_MAX=100 -RATE_LIMIT_DEFAULT_WINDOW=5 +# Stellar / Horizon +HORIZON_URL=https://horizon-testnet.stellar.org +STELLAR_NETWORK=testnet -AWS_ACCESS_KEY_ID=your-access-key-here -AWS_SECRET_ACCESS_KEY=your-secret-access-key -AWS_REGION=eu-north-1 -S3_BUCKET_NAME=volunchain-certificates-test -SOROBAN_RPC_URL=your_rpc_url -SOROBAN_SERVER_SECRET=your_server_secret +# Email +EMAIL_SERVICE=gmail +EMAIL_USER=your_email@gmail.com +EMAIL_PASSWORD=your_email_password +BASE_URL=http://localhost:3000 -# Supabase -SUPABASE_URL=https://your-project.supabase.co -SUPABASE_ANON_KEY=your-anon-key \ No newline at end of file +# Soroban +SOROBAN_RPC_URL=https://soroban-testnet.stellar.org +SOROBAN_SERVER_SECRET=your_soroban_server_secret + +# Rate Limiting +RATE_LIMIT_WINDOW_MS=900000 +RATE_LIMIT_MAX_REQUESTS=100 +RATE_LIMIT_MESSAGE=Too many requests, please try again later. + +# Email Verification +EMAIL_SECRET=your_email_verification_secret \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0253097..aa7fc57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.4.7", + "env-var": "^7.5.0", "express": "^4.21.2", "express-rate-limit": "^7.5.0", "express-validator": "^7.2.0", @@ -6466,6 +6467,15 @@ "node": ">=6" } }, + "node_modules/env-var": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/env-var/-/env-var-7.5.0.tgz", + "integrity": "sha512-mKZOzLRN0ETzau2W2QXefbFjo5EF4yWq28OyKb9ICdeNhHJlOE/pHHnz4hdYJ9cNZXcJHo5xN4OT4pzuSHSNvA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", diff --git a/package.json b/package.json index fe84f03..0aa2167 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.4.7", + "env-var": "^7.5.0", "express": "^4.21.2", "express-rate-limit": "^7.5.0", "express-validator": "^7.2.0", diff --git a/readme.md b/readme.md index ccdf2a7..b081bc0 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,4 @@ + # VolunChain **Innovating Volunteering with Blockchain ��** @@ -576,3 +577,21 @@ Run wallet verification tests: ```bash npm test -- --testPathPattern=wallet ``` + +## ⚠️ Environment Validation & Startup Errors + +VolunChain validates all required environment variables at startup. If any required variable is missing or invalid, the application will fail to start and display a clear error message indicating which variable is missing. + +**Common startup failure messages:** +- `DATABASE_URL environment variable is required` +- `JWT_SECRET environment variable is required` +- `SUPABASE_URL environment variable is required` +- `SUPABASE_ANON_KEY environment variable is required` +- `EMAIL_USER environment variable is required` +- `EMAIL_PASSWORD environment variable is required` +- `SOROBAN_SERVER_SECRET environment variable is required` + +**How to fix:** +Check your `.env` file and ensure all required variables are set. See `.env.example` for a complete list of required variables and example values. + +If you see a startup error, update your `.env` file and restart the application. diff --git a/src/config/data-source.ts b/src/config/data-source.ts deleted file mode 100644 index 6559535..0000000 --- a/src/config/data-source.ts +++ /dev/null @@ -1,24 +0,0 @@ -import "reflect-metadata"; -import { DataSource } from "typeorm"; -// Note: TypeORM entities are now managed through Prisma schema -// This file is kept for backward compatibility but not actively used - -export const AppDataSource = new DataSource({ - type: "postgres", - host: process.env.DB_HOST || "localhost", - port: Number(process.env.DB_PORT) || 5432, - username: process.env.DB_USER || "postgres", - password: process.env.DB_PASSWORD || "password", - database: process.env.DB_NAME || "mydatabase", - synchronize: false, // Disabled as we use Prisma - logging: false, - entities: [], // Empty as we use Prisma entities - migrations: [], - subscribers: [], -}); - -AppDataSource.initialize() - .then(() => { - console.log("Database connected successfully ✅"); - }) - .catch((error) => console.error("Database connection failed ❌", error)); diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000..15d4b30 --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,34 @@ +import { get } from "env-var"; + +// Use 'get' directly instead of 'env.get' +export const env = { + NODE_ENV: get("NODE_ENV").default("development").asString(), + PORT: get("PORT").default("3000").asPortNumber(), + + // Database + DATABASE_URL: get("DATABASE_URL").required().asUrlString(), + + // JWT / Security + JWT_SECRET: get("JWT_SECRET").required().asString(), + + // Supabase + SUPABASE_URL: get("SUPABASE_URL").required().asUrlString(), + SUPABASE_ANON_KEY: get("SUPABASE_ANON_KEY").required().asString(), + + // Redis + REDIS_URL: get("REDIS_URL").default("redis://localhost:6379").asString(), + + // Stellar / Horizon + HORIZON_URL: get("HORIZON_URL").default("https://horizon-testnet.stellar.org").asUrlString(), + STELLAR_NETWORK: get("STELLAR_NETWORK").default("testnet").asString(), + + // Email + EMAIL_SERVICE: get("EMAIL_SERVICE").default("gmail").asString(), + EMAIL_USER: get("EMAIL_USER").required().asString(), + EMAIL_PASSWORD: get("EMAIL_PASSWORD").required().asString(), + BASE_URL: get("BASE_URL").default("http://localhost:3000").asUrlString(), + + // Soroban + SOROBAN_RPC_URL: get("SOROBAN_RPC_URL").required().asUrlString(), + SOROBAN_SERVER_SECRET: get("SOROBAN_SERVER_SECRET").required().asString(), +}; \ No newline at end of file diff --git a/src/config/horizon.config.ts b/src/config/horizon.config.ts index 2820942..405a07a 100644 --- a/src/config/horizon.config.ts +++ b/src/config/horizon.config.ts @@ -1,10 +1,11 @@ import dotenv from "dotenv"; +import { env } from "./env"; dotenv.config(); export const horizonConfig = { - url: process.env.HORIZON_URL || "https://horizon-testnet.stellar.org", - network: process.env.STELLAR_NETWORK || "testnet", + url: env.HORIZON_URL || "https://horizon-testnet.stellar.org", + network: env.STELLAR_NETWORK || "testnet", timeout: 30000, // 30 seconds timeout for API calls }; diff --git a/src/config/prisma.ts b/src/config/prisma.ts index 90bc691..89a4194 100644 --- a/src/config/prisma.ts +++ b/src/config/prisma.ts @@ -1,5 +1,6 @@ import { PrismaClient } from "@prisma/client"; import { DatabaseMonitor } from "../utils/db-monitor"; +import { env } from "./env"; // Configure Prisma Client with connection pooling const prismaClientSingleton = () => { @@ -7,7 +8,7 @@ const prismaClientSingleton = () => { log: ["query", "info", "warn", "error"], datasources: { db: { - url: process.env.DATABASE_URL, + url: env.DATABASE_URL, }, }, }); @@ -23,7 +24,7 @@ const prisma = globalThis.prisma ?? prismaClientSingleton(); // Initialize database monitor export const dbMonitor = new DatabaseMonitor(prisma); -if (process.env.NODE_ENV !== "production") { +if (env.NODE_ENV !== "production") { globalThis.prisma = prisma; } diff --git a/src/config/redis.ts b/src/config/redis.ts index 1fbb150..428e8d7 100644 --- a/src/config/redis.ts +++ b/src/config/redis.ts @@ -1,7 +1,8 @@ import { createClient, RedisClientType } from "redis"; +import { env } from "./env"; const redisClient = createClient({ - url: process.env.REDIS_URL || "redis://localhost:6379", + url: env.REDIS_URL || "redis://localhost:6379", }) as RedisClientType & { incr: (key: string) => Promise; expire: (key: string, seconds: number) => Promise; diff --git a/src/config/soroban.config.ts b/src/config/soroban.config.ts index 20f3e4d..58f820d 100644 --- a/src/config/soroban.config.ts +++ b/src/config/soroban.config.ts @@ -1,10 +1,11 @@ import dotenv from "dotenv"; +import { env } from "./env"; dotenv.config(); export const sorobanConfig = { - rpcUrl: process.env.SOROBAN_RPC_URL || "https://soroban-testnet.stellar.org", - serverSecret: process.env.SOROBAN_SERVER_SECRET, + rpcUrl: env.SOROBAN_RPC_URL || "https://soroban-testnet.stellar.org", + serverSecret: env.SOROBAN_SERVER_SECRET, }; // Validate required environment variables diff --git a/src/config/supabase.ts b/src/config/supabase.ts index 12b629f..f5d4bc6 100644 --- a/src/config/supabase.ts +++ b/src/config/supabase.ts @@ -1,6 +1,7 @@ import { createClient } from '@supabase/supabase-js'; +import { env } from "./env"; -const supabaseUrl = process.env.SUPABASE_URL!; -const supabaseKey = process.env.SUPABASE_ANON_KEY!; +const supabaseUrl = env.SUPABASE_URL!; +const supabaseKey = env.SUPABASE_ANON_KEY!; export const supabase = createClient(supabaseUrl, supabaseKey); \ No newline at end of file diff --git a/src/config/swagger.config.ts b/src/config/swagger.config.ts index c49bcb4..af0f463 100644 --- a/src/config/swagger.config.ts +++ b/src/config/swagger.config.ts @@ -2,6 +2,7 @@ import swaggerUi from "swagger-ui-express"; import YAML from "yaml"; import { Express } from "express"; import fs from "fs"; +import { env } from "./env"; export class SwaggerConfig { private static swaggerDocument = YAML.parse( @@ -9,7 +10,7 @@ export class SwaggerConfig { ); static setup(app: Express): void { - if (process.env.NODE_ENV !== "development") { + if (env.NODE_ENV !== "development") { console.log("⚠️ Swagger is disabled in production mode."); return; } diff --git a/src/config/winston.config.ts b/src/config/winston.config.ts index 9dc0695..17b0a0f 100644 --- a/src/config/winston.config.ts +++ b/src/config/winston.config.ts @@ -1,5 +1,6 @@ import winston from "winston"; import path from "path"; +import { env } from "./env"; const { combine, timestamp, errors, json, printf } = winston.format; @@ -17,8 +18,8 @@ const consoleFormat = printf( const logsDir = path.join(process.cwd(), "logs"); const createLogger = () => { - const isProduction = process.env.NODE_ENV === "production"; - const isDevelopment = process.env.NODE_ENV === "development"; + const isProduction = env.NODE_ENV === "production"; + const isDevelopment = env.NODE_ENV === "development"; const transports: winston.transport[] = []; diff --git a/src/index.ts b/src/index.ts index e20d455..f02e92c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,13 +23,14 @@ import organizationRoutes from "./routes/OrganizationRoutes"; import messageRoutes from "./modules/messaging/routes/messaging.routes"; import testRoutes from "./routes/testRoutes"; import { Logger } from "./utils/logger"; +import { env } from "../src/config/env"; const globalLogger = new Logger("VolunChain"); import fs from "fs"; import path from "path"; const app = express(); -const PORT = process.env.PORT || 3000; +const PORT = env.PORT; const ENV = process.env.NODE_ENV || "development"; // Ensure logs directory exists