docs: Enhance development setup documentation and update environment variable validation

- Added a new section in CURRENT_STATUS.md detailing prerequisites and quick start instructions for development setup.
- Updated environment variable validation to include defaults for missing variables in env.ts.
- Improved error handling in errorHandler.ts for better validation feedback.
- Made various code adjustments across services to ensure robustness and clarity.
This commit is contained in:
defiQUG
2025-11-05 19:00:46 -08:00
parent c872168d23
commit 14dfd3c9bf
18 changed files with 311 additions and 27 deletions

View File

@@ -11,25 +11,26 @@
"migrate": "ts-node src/db/migrations/index.ts"
},
"dependencies": {
"express": "^4.18.2",
"uuid": "^9.0.1",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"zod": "^3.22.4",
"ioredis": "^5.8.2",
"pg": "^8.11.3",
"pino": "^8.16.2",
"pino-pretty": "^10.2.3",
"prom-client": "^15.1.0"
"prom-client": "^15.1.0",
"uuid": "^9.0.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.10.0",
"@types/uuid": "^9.0.6",
"@types/cors": "^2.8.17",
"@types/pg": "^8.10.9",
"typescript": "^5.3.3",
"ts-node": "^10.9.2"
"@types/uuid": "^9.0.6",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}

View File

@@ -1,6 +1,6 @@
import { Request, Response } from "express";
import { executionCoordinator } from "../services/execution";
import { asyncHandler } from "../services/errorHandler";
import { asyncHandler, AppError, ErrorType } from "../services/errorHandler";
import { auditLog } from "../middleware";
/**

View File

@@ -1,6 +1,7 @@
import { Request, Response } from "express";
import { executionCoordinator } from "../services/execution";
import { logger } from "../logging/logger";
import { asyncHandler, AppError, ErrorType } from "../services/errorHandler";
interface WebhookConfig {
url: string;

View File

@@ -41,7 +41,22 @@ export const env = envSchema.parse({
*/
export function validateEnv() {
try {
envSchema.parse(process.env);
// Use same defaults as env object
const envWithDefaults = {
NODE_ENV: process.env.NODE_ENV || "development",
PORT: process.env.PORT || "8080",
DATABASE_URL: process.env.DATABASE_URL,
API_KEYS: process.env.API_KEYS,
REDIS_URL: process.env.REDIS_URL,
LOG_LEVEL: process.env.LOG_LEVEL || "info",
ALLOWED_IPS: process.env.ALLOWED_IPS,
SESSION_SECRET: process.env.SESSION_SECRET || "dev-secret-change-in-production-min-32-chars",
JWT_SECRET: process.env.JWT_SECRET,
AZURE_KEY_VAULT_URL: process.env.AZURE_KEY_VAULT_URL,
AWS_SECRETS_MANAGER_REGION: process.env.AWS_SECRETS_MANAGER_REGION,
SENTRY_DSN: process.env.SENTRY_DSN,
};
envSchema.parse(envWithDefaults);
console.log("✅ Environment variables validated");
} catch (error) {
if (error instanceof z.ZodError) {

View File

@@ -34,7 +34,7 @@ export async function storePlan(plan: Plan): Promise<void> {
* Get plan by ID
*/
export async function getPlanById(planId: string): Promise<Plan | null> {
const result = await query<Plan>(
const result = await query<any>(
"SELECT * FROM plans WHERE plan_id = $1",
[planId]
);
@@ -52,7 +52,7 @@ export async function getPlanById(planId: string): Promise<Plan | null> {
maxLTV: row.max_ltv,
signature: row.signature,
plan_hash: row.plan_hash,
created_at: row.created_at?.toISOString(),
created_at: row.created_at ? (row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at)) : undefined,
status: row.status,
};
}

View File

@@ -57,7 +57,7 @@ export async function healthCheck(): Promise<HealthStatus> {
const allHealthy =
checks.database === "up" &&
checks.memory !== "critical" &&
checks.disk !== "critical" &&
(checks.disk === "ok" || checks.disk === "warning") &&
dependencies.every((d) => d.status === "healthy");
return {

View File

@@ -1,3 +1,4 @@
import "dotenv/config";
import express from "express";
import cors from "cors";
import { validateEnv } from "./config/env";

View File

@@ -33,7 +33,23 @@ export class ELKAggregator implements LogAggregator {
// });
// For now, just log normally
logger[level as keyof typeof logger](metadata || {}, message);
const meta = metadata || {};
switch (level) {
case "error":
logger.error(meta, message);
break;
case "warn":
logger.warn(meta, message);
break;
case "info":
logger.info(meta, message);
break;
case "debug":
logger.debug(meta, message);
break;
default:
logger.info(meta, message);
}
}
}
@@ -61,7 +77,24 @@ export class DatadogAggregator implements LogAggregator {
// }),
// });
logger[level as keyof typeof logger](metadata || {}, message);
// For now, just log normally
const meta = metadata || {};
switch (level) {
case "error":
logger.error(meta, message);
break;
case "warn":
logger.warn(meta, message);
break;
case "info":
logger.info(meta, message);
break;
case "debug":
logger.debug(meta, message);
break;
default:
logger.info(meta, message);
}
}
}

View File

@@ -10,6 +10,7 @@ export interface Alert {
title: string;
message: string;
metadata?: any;
timestamp?: string;
}
export class AlertingService {
@@ -90,7 +91,7 @@ export class AlertingService {
*/
private shouldThrottle(alert: Alert): boolean {
const recentAlerts = this.alertHistory.filter(
(a) => Date.now() - new Date(a.timestamp).getTime() < 5 * 60 * 1000 // 5 minutes
(a) => a.timestamp && Date.now() - new Date(a.timestamp).getTime() < 5 * 60 * 1000 // 5 minutes
);
// Throttle if more than 10 alerts in 5 minutes

View File

@@ -30,7 +30,7 @@ export async function addToDLQ(
* Get messages from DLQ for retry
*/
export async function getDLQMessages(queue: string, limit = 10): Promise<DeadLetterMessage[]> {
const result = await query<DeadLetterMessage>(
const result = await query<any>(
`SELECT * FROM dead_letter_queue
WHERE queue = $1 AND retry_count < 3
ORDER BY created_at ASC
@@ -38,13 +38,13 @@ export async function getDLQMessages(queue: string, limit = 10): Promise<DeadLet
[queue, limit]
);
return result.map((row) => ({
return result.map((row: any) => ({
messageId: row.message_id,
originalQueue: row.queue,
payload: typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload,
error: row.error,
retryCount: row.retry_count,
createdAt: row.created_at,
createdAt: row.created_at ? (row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at)) : new Date().toISOString(),
}));
}

View File

@@ -59,7 +59,8 @@ export function errorHandler(
}
// Handle validation errors
if (err.name === "ValidationError" || err.name === "ZodError" || err.issues) {
const isZodError = err.name === "ZodError" || (err as any).issues;
if (err.name === "ValidationError" || isZodError) {
logger.warn({
error: err,
requestId,
@@ -69,7 +70,7 @@ export function errorHandler(
return res.status(400).json({
error: ErrorType.VALIDATION_ERROR,
message: "Validation failed",
details: err.message || err.issues,
details: err.message || (isZodError ? (err as any).issues : undefined),
requestId,
});
}

View File

@@ -81,15 +81,15 @@ export async function generatePacs008(plan: Plan): Promise<string> {
},
CdtrAgt: {
FinInstnId: {
BICFI: payStep.beneficiary.BIC || "UNKNOWN",
BICFI: payStep.beneficiary?.BIC || "UNKNOWN",
},
},
Cdtr: {
Nm: payStep.beneficiary.name || "Unknown",
Nm: payStep.beneficiary?.name || "Unknown",
},
CdtrAcct: {
Id: {
IBAN: payStep.beneficiary.IBAN || "",
IBAN: payStep.beneficiary?.IBAN || "",
},
},
RmtInf: {

View File

@@ -115,9 +115,9 @@ export function checkStepDependencies(steps: PlanStep[]): ValidationResult {
function getStepOutput(step: PlanStep): { asset: string; amount: number } | null {
switch (step.type) {
case "borrow":
return { asset: step.asset, amount: step.amount };
return step.asset ? { asset: step.asset, amount: step.amount } : null;
case "swap":
return { asset: step.to, amount: step.amount };
return step.to ? { asset: step.to, amount: step.amount } : null;
default:
return null;
}

View File

@@ -29,6 +29,9 @@ export interface Receipt {
* Generate receipt for a plan execution
*/
export async function generateReceipt(plan: Plan): Promise<Receipt> {
if (!plan.plan_id) {
throw new Error("Plan ID is required");
}
const notaryProof = await getNotaryProof(plan.plan_id);
const dltStatus = await getDLTStatus(plan.plan_id);