Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,387 changes: 56 additions & 1,331 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"s3:setup": "ts-node src/utils/setup-s3-bucket.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.798.0",
"@aws-sdk/s3-request-presigner": "^3.798.0",
"@prisma/client": "^6.4.1",
"@stellar/stellar-sdk": "^13.3.0",
Expand All @@ -32,7 +31,6 @@
"@types/swagger-ui-express": "^4.1.7",
"@types/uuid": "^10.0.0",
"axios": "^1.7.9",
"backblaze-b2": "^1.7.0",
"bcryptjs": "^3.0.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
Expand All @@ -43,7 +41,6 @@
"express-rate-limit": "^7.5.0",
"express-validator": "^7.2.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.2",
"node-cron": "^3.0.3",
"nodemailer": "^6.10.0",
"pdf-lib": "^1.17.1",
Expand Down
13 changes: 0 additions & 13 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,19 +236,6 @@ npm run db:seed

---

## πŸ”Œ Supabase Integration

This project uses Supabase for external data access and future integrations.

Update your `.env` file with:

```bash
SUPABASE_URL=...
SUPABASE_ANON_KEY=...
```

---

## πŸ“ Module Overview

### Core Modules
Expand Down
120 changes: 120 additions & 0 deletions src/Server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import express, { Router, Application, ErrorRequestHandler } from "express";
import cors from "cors";
import { Logger } from "./utils";

interface ServerOptions {
port?: number;
environment?: string;
routes: Router;
middlewares?: Array<(app: Application) => void>;
errorHandlers?: ErrorRequestHandler[];
enableCors?: boolean;
}

export class Server {
public readonly app: Application = express();
private readonly port: number;
private readonly environment: string;
private readonly routes: Router;
private readonly middlewares: Array<(app: Application) => void>;
private readonly errorHandlers: ErrorRequestHandler[];
private readonly logger: Logger;
private readonly enableCors: boolean;

constructor(options: ServerOptions) {
const {
port = 3000,
environment = "development",
routes,
middlewares = [],
errorHandlers = [],
enableCors = true,
} = options;

this.port = port;
this.environment = environment;
this.routes = routes;
this.middlewares = middlewares;
this.errorHandlers = errorHandlers;
this.enableCors = enableCors;
this.logger = new Logger("VolunChain-Server");
}

private setupBaseMiddlewares(): void {
// Middleware for parsing JSON requests
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));

// CORS if enabled
if (this.enableCors) {
this.app.use(cors());
}
}

private setupCustomMiddlewares(): void {
// Apply custom middlewares in order
this.middlewares.forEach((middleware) => {
middleware(this.app);
});
}

private setupRoutes(): void {
// Setup routes
this.app.use(this.routes);
}

private setupHealthCheck(): void {
// Basic health check route
this.app.get("/", (req, res) => {
res.json({
message: "VolunChain API is running!",
environment: this.environment,
timestamp: new Date().toISOString(),
});
});
}

private setupErrorHandlers(): void {
// Apply error handlers (must be last)
this.errorHandlers.forEach((errorHandler) => {
this.app.use(errorHandler);
});
}

public async start(): Promise<void> {
try {
// Setup middlewares in order
this.setupBaseMiddlewares();
this.setupCustomMiddlewares();
this.setupHealthCheck();
this.setupRoutes();
this.setupErrorHandlers(); // Error handlers must be last

// Start server
return new Promise((resolve) => {
this.app.listen(this.port, () => {
this.logger.info(
`Server is running on http://localhost:${this.port}`,
{
port: this.port,
environment: this.environment,
nodeVersion: process.version,
}
);
resolve();
});
});
} catch (error: unknown) {
this.logger.error("Failed to start server", error ?? "error");
throw error;
}
}

public getApp(): Application {
return this.app;
}

public getPort(): number {
return this.port;
}
}
11 changes: 11 additions & 0 deletions src/config/BcryptAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { compareSync, hashSync } from "bcryptjs";

export class BcryptAdapter {
generateHash(password: string): string {
return hashSync(password);
}

compareHash(password: string, hashed: string): boolean {
return compareSync(password, hashed);
}
}
37 changes: 37 additions & 0 deletions src/config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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(),
LOG_LEVEL: get("LOG_LEVEL").default("debug").asString(),

// 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(),
};
Empty file added src/config/index.ts
Empty file.
36 changes: 36 additions & 0 deletions src/config/jwtAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import jwt from "jsonwebtoken";
import { env } from "./env";

const JWT_SEED = env.JWT_SECRET;

export class JwtAdapter {
static async generateToken(
payload: object,
durationInHours: number = 2
): Promise<string | null> {
return new Promise((resolve) => {
jwt.sign(
payload,
JWT_SEED,
{
expiresIn: `${durationInHours}h`,
},
(error, token) => {
if (error) return resolve(null);

return resolve(token!);
}
);
});
}

static validateToken<T>(token: string): Promise<T | null> {
return new Promise((resolve) => {
jwt.verify(token, JWT_SEED, (error: unknown, decoded: unknown) => {
if (error) return resolve(null);

resolve(decoded as T);
});
});
}
}
4 changes: 2 additions & 2 deletions src/config/swagger.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Application } from "express";
import swaggerUi from "swagger-ui-express";
import YAML from "yaml";
import { Express } from "express";
import fs from "fs";

export class SwaggerConfig {
private static swaggerDocument = YAML.parse(
fs.readFileSync("./openapi.yaml", "utf8")
);

static setup(app: Express): void {
static setup(app: Application): void {
if (process.env.NODE_ENV !== "development") {
console.log("⚠️ Swagger is disabled in production mode.");
return;
Expand Down
Loading