Skip to content

Commit

Permalink
chore: format openapi validator errors + additional env vars
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnikter committed Feb 6, 2025
1 parent a90dd86 commit da89fbc
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 43 deletions.
38 changes: 33 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
PORT=
CLIENT_ID=

# If you want to use Redis cache instead of in-memory cache, you can set the REDIS_URL
REDIS_URL=
NODE_ENV=
SECRET_API_KEY=
DEMO=true
# If you want to use MongoDB instead of in-memory cache, you can set the MONGODB_URL
# It's cheaper than Redis, but not as fast
MONGODB_URL=

# REAL will require you to provide x-exchange-api-key and x-exchange-api-secret headers
# MOCK won't require to pass any exchange credentials
MODE=REAL

# If you want to build the SDK API locally, you need to have the access to the NPM package of the SDK
# You can find credentials in the tech integration guide
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

# Used for HMAC authentication, to verify the request signature
# You can leave it empty if you don't need an authentication
# SECRET_API_KEY=ccaf48b2-c932-4dac-91ce-8acb0c28b18c

# Your client id from the tech integration guide
CLIENT_ID=sdk-***

# The proxy URL to be used for exchanges requiring IP whitelisting
# /!\ Make sure to add a slash at the end of the URL
PROXY_URL=https://<your-proxy-url>/

# The port to run the server on
APP_PORT=3000

# The verbose mode
# - "v": Additional logs.
# - "vv": Prints all network requests/responses and logs.
VERBOSE=
19 changes: 7 additions & 12 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,12 @@ services:
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
ports:
- "3000:3000"
- "${APP_PORT}:${APP_PORT}"
environment:
- CLIENT_ID=${CEDE_CLIENT_ID}
- SECRET_API_KEY=${CEDE_SECRET_API_KEY}
- CLIENT_ID=${CLIENT_ID}
- SECRET_API_KEY=${SECRET_API_KEY}
- REDIS_URL=${REDIS_URL}
- PORT=3000
- NODE_ENV=production


secrets:
aws_access_key_id:
environment: "AWS_ACCESS_KEY_ID"
aws_secret_access_key:
environment: "AWS_SECRET_ACCESS_KEY"
- MONGODB_URL=${MONGODB_URL}
- APP_PORT=${APP_PORT}
- MODE=${MODE}
- PROXY_URL=${PROXY_URL}
18 changes: 15 additions & 3 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import bodyParser from "body-parser";
import express, { Express } from "express";
import * as OpenApiValidator from 'express-openapi-validator';
import path, { join } from "path";
import { globalErrorHandler } from './middleware/globalErrorHandler';
import { setupRoutes } from "./routes";
import { healthRoutes } from './routes/health.controller';
import { SdkApiConfiguration } from "./types";
Expand All @@ -12,7 +13,7 @@ const __dirname = path.resolve();
export async function sdkApi(configuration: SdkApiConfiguration) {
const sdk = await setupCedeSdk(configuration);
const app: Express = express();
const port = process.env.PORT || 3000;
const port = process.env.APP_PORT || 3000;

process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
Expand All @@ -36,6 +37,15 @@ export async function sdkApi(configuration: SdkApiConfiguration) {
app.use('/api/v1/health', healthRoutes());

app.use(bodyParser.json());

if (process.env.MODE === "MOCK") {
app.use((req, _res, next) => {
req.headers['x-exchange-api-key'] = req.headers['x-exchange-api-key'] || 'demo-api-key';
req.headers['x-exchange-api-secret'] = req.headers['x-exchange-api-secret'] || 'demo-secret-key';
next();
});
}

app.use(
OpenApiValidator.middleware({
apiSpec: join(__dirname, '/dist/swagger.json'),
Expand Down Expand Up @@ -66,15 +76,17 @@ export async function sdkApi(configuration: SdkApiConfiguration) {
} catch (error) {
res.status(500).json({
name: "AuthenticationError",
message: "Authentication check failed. Please check the authentication middleware implementation",
message: error instanceof Error ? error.message : "Authentication check failed",
code: 500,
});
}
}
});
}

app.use('/api/v1', setupRoutes(sdk));

app.use(globalErrorHandler);

const server = app.listen(port, () => {
console.log(`[server]: Server is running at http://localhost:${port}`);
});
Expand Down
38 changes: 30 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,49 @@ import { env } from "process";
import { sdkApi } from "./app";
import { hmacAuthStrategyMiddleware } from "./authentication/hmac.strategy";
import { MongoCacheStorage } from "./cache/mongo.storage";
import { RedisCacheStorage } from "./cache/redis.storage";

const getCache = () => {
if (process.env.NODE_ENV === "development" || !process.env.REDIS_URL) {
console.log("[server]: 🚧 You are using an in-memory cache. Please use Redis cache in production.");
return new InMemoryCacheStorage();
if (process.env.REDIS_URL) {
return RedisCacheStorage.create({
url: process.env.REDIS_URL,
});
} else if (process.env.MONGODB_URL) {
return MongoCacheStorage.create({
url: process.env.MONGODB_URL,
});
}
return MongoCacheStorage.create({
url: process.env.MONGODB_URL || "",
});

return new InMemoryCacheStorage();
}

const clientId = process.env.CLIENT_ID || "";
if (clientId === "" && env.MODE === "REAL") {
throw new Error("CLIENT_ID is required");
}
const secretApiKey = process.env.SECRET_API_KEY || "";
const proxyUrl = process.env.PROXY_URL || "";
const verbose = process.env.VERBOSE as "vv" | "v" | undefined;

console.log(`SDK-API config:
mode: ${env.MODE === "MOCK" ? "MOCK" : "REAL"},
clientId: ${clientId},
proxyUrl: ${proxyUrl},
sdk-api authentication enabled: ${!secretApiKey ? "NO" : "YES"}
cache: ${getCache().constructor.name}
verbose: ${verbose ? "YES" : "NO"}
${getCache().constructor.name === "InMemoryCacheStorage" ?
"🚧 You are using an in-memory cache. We recommend using Redis cache in production to avoid rate limiting issues." : ""}
`)

sdkApi({
mode: env.MODE === "MOCK" ? "MOCK" : "REAL",
clientId,
cache: getCache(),
authentication: env.DEMO === "true" || process.env.SECRET_API_KEY === undefined ? () => true : hmacAuthStrategyMiddleware({
secretKey: process.env.SECRET_API_KEY || "",
proxyUrl,
verbose,
authentication: !secretApiKey ? () => true : hmacAuthStrategyMiddleware({
secretKey: secretApiKey,
}),
});
20 changes: 20 additions & 0 deletions src/middleware/globalErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextFunction, Request, Response } from 'express';
import { ErrorResponse } from '../types';

export function globalErrorHandler(err: any, req: Request, res: Response, next: NextFunction) {
if (res.headersSent) {
return next(err);
}

// Default status is 500 if not provided
const status = err.status || 500;

const errorResponse: ErrorResponse = {
name: err.name || 'InternalServerError',
code: status,
message: err.message || 'An unexpected error occurred',
originalErrorMessage: err.errors ? JSON.stringify(err.errors) : undefined,
};

res.status(status).json(errorResponse);
}
1 change: 0 additions & 1 deletion src/routes/portfolio.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ export function portfolioRoutes(sdk: CedeSDK) {
auth.password,
auth.uid
);
console.log("balances", result);
res.json(result);
}));

Expand Down
25 changes: 17 additions & 8 deletions src/routes/withdrawal.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ export class WithdrawalController extends Controller {
@Response<ErrorResponse>(503, 'Service Unavailable')
@Response<ErrorResponse>(402, 'Insufficient balance')
public async createWithdrawal(@Body() params: CreateWithdrawalParams): Promise<CreateWithdrawalResponse> {
return await this.sdk.api.createWithdrawal(params);
const createWithdrawalParams = {
...params,
fromExchangeInstanceId: params.auth.exchangeInstanceId,
};
return await this.sdk.api.createWithdrawal(createWithdrawalParams);
}

/**
Expand All @@ -119,7 +123,11 @@ export class WithdrawalController extends Controller {
@Response<ErrorResponse>(500, 'Internal Server Error')
@Response<ErrorResponse>(503, 'Service Unavailable')
public async prepareWithdrawal(@Body() params: PrepareWithdrawalParams): Promise<PrepareWithdrawalResponse> {
return await this.sdk.api.prepareWithdrawal(params);
const prepareWithdrawalParams = {
...params,
fromExchangeInstanceId: params.auth.exchangeInstanceId,
};
return await this.sdk.api.prepareWithdrawal(prepareWithdrawalParams);
}

/**
Expand Down Expand Up @@ -231,16 +239,17 @@ export function withdrawalRoutes(sdk: any) {
const controller = new WithdrawalController(sdk);

router.get('/:withdrawalId', errorHandler(async (req, res) => {
const auth = extractAuthFromHeaders(req);
const result = await controller.getWithdrawalById(
req.params.withdrawalId,
req.query.tokenSymbol as string,
Number(req.query.timestamp),
req.query.exchangeInstanceId as string,
req.query.exchangeId as string,
req.header('x-exchange-api-key') as string,
req.header('x-exchange-api-secret') as string,
req.header('x-exchange-api-password') as string,
req.header('x-exchange-api-uid') as string
auth.exchangeInstanceId,
auth.exchangeId,
auth.apiKey,
auth.secretKey,
auth.password,
auth.uid
);
res.json(result);
}));
Expand Down
9 changes: 8 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {
Any,
SdkCacheStorage,
DefiInfoTx,
EventEmitterDataTypes,
PureCefiInfoTx,
PureTransaction,
SdkCacheStorage,
} from "@cedelabs-private/sdk";
import { NextFunction, Request, Response } from "express";

Expand Down Expand Up @@ -45,6 +45,13 @@ export type SdkApiConfiguration = {
* Proxy URL to be used for making Exchange API calls for some exchanges that require it.
*/
proxyUrl?: string;
/**
* Verbose mode.
*
* - `"vv"`: Prints all network requests/responses and logs.
* - `"v"`: Additional logs.
*/
verbose?: "vv" | "v";
/**
* Plug in your own event listeners for the SDK by event name.
*/
Expand Down
4 changes: 0 additions & 4 deletions src/utils/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,5 @@ export function extractAuthFromHeaders(req: Request): AuthParams {
auth.uid = req.headers['x-exchange-api-uid'] as string;
}

if (!auth.apiKey || !auth.secretKey) {
throw new Error('Missing required authentication headers');
}

return auth;
}
3 changes: 2 additions & 1 deletion src/utils/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export async function setupCedeSdk(configuration: SdkApiConfiguration) {
const sdk = new CedeSDK(configuration.mode, {
clientId: configuration.clientId,
proxyUrl: configuration.proxyUrl,
cacheStorage: await configuration.cache
cacheStorage: await configuration.cache,
verbose: configuration.verbose
});

if (configuration.listenerByEvent) {
Expand Down

0 comments on commit da89fbc

Please sign in to comment.