Skip to content

Commit

Permalink
feat: deposit status endpoint and api refactor (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
melisaguevara authored Sep 30, 2024
1 parent b9cb3c5 commit 6351fd9
Show file tree
Hide file tree
Showing 21 changed files with 348 additions and 291 deletions.
30 changes: 30 additions & 0 deletions packages/indexer-api/src/controllers/balances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Request, Response, NextFunction } from "express";
import * as s from "superstruct";
import { DepositsService } from "../services/deposits";
import {
HubPoolBalanceParams,
SpokePoolBalanceParams,
} from "../dtos/balances.dto";

export class BalancesController {
constructor(private service: DepositsService) {}

public getHubPoolBalance = async (
req: Request,
res: Response,
next: NextFunction,
) => {
s.assert(req.query, HubPoolBalanceParams);
return 0;
};

public getSpokePoolBalance = async (
req: Request,
res: Response,
next: NextFunction,
) => {
s.assert(req.query, SpokePoolBalanceParams);

return 0;
};
}
36 changes: 36 additions & 0 deletions packages/indexer-api/src/controllers/deposits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Request, Response, NextFunction } from "express";
import * as s from "superstruct";
import { DepositsService } from "../services/deposits";
import { DepositsParams, DepositParams } from "../dtos/deposits.dto";

export class DepositsController {
constructor(private service: DepositsService) {}

public getDeposits = async (
req: Request,
res: Response,
next: NextFunction,
) => {
try {
const params = s.create(req.query, DepositsParams);
const result = await this.service.getDeposits(params);
return res.json(result);
} catch (err) {
next(err);
}
};

public getDepositStatus = async (
req: Request,
res: Response,
next: NextFunction,
) => {
try {
const params = s.create(req.query, DepositParams);
const result = await this.service.getDepositStatus(params);
return res.json(result);
} catch (err) {
next(err);
}
};
}
2 changes: 2 additions & 0 deletions packages/indexer-api/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./balances";
export * from "./deposits";
9 changes: 7 additions & 2 deletions packages/indexer-api/src/database/database.provider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import winston from "winston";
import { createDataSource, DatabaseConfig } from "@repo/indexer-database";

export async function connectToDatabase(databaseConfig: DatabaseConfig) {
export async function connectToDatabase(
databaseConfig: DatabaseConfig,
logger: winston.Logger,
) {
try {
const database = await createDataSource(databaseConfig).initialize();
logger.info("Postgres connection established");
return database;
} catch (error) {
console.log("Unable to connect to database", error);
logger.error("Unable to connect to database", error);
throw error;
}
}
16 changes: 16 additions & 0 deletions packages/indexer-api/src/dtos/balances.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as s from "superstruct";

// query hub pools by chainId? default to 1 if not specified. will leave option in case of testnets?
export const HubPoolBalanceParams = s.object({
chainId: s.defaulted(s.number(), 1),
l1Token: s.string(),
});

// query spokepools by chainId, must specify
export const SpokePoolBalanceParams = s.object({
chainId: s.number(),
// unsure why we have timestamp, implies we are storign history of balances? this is in the spec.
timestamp: s.number(),
// unsure why specified as l2Token in spec, don't we have spoke pool on L1?
l2Token: s.optional(s.number()),
});
34 changes: 34 additions & 0 deletions packages/indexer-api/src/dtos/deposits.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as s from "superstruct";

const stringToInt = s.coerce(s.number(), s.string(), (value) =>
parseInt(value),
);

export const DepositsParams = s.object({
depositor: s.optional(s.string()),
recipient: s.optional(s.string()),
inputToken: s.optional(s.string()),
outputToken: s.optional(s.string()),
integrator: s.optional(s.string()),
status: s.optional(s.string()),
// some kind of pagination options, skip could be the start point
skip: s.optional(stringToInt),
// pagination limit, how many to return after the start, note we convert string to number
limit: s.optional(stringToInt),
});

export type DepositsParams = s.Infer<typeof DepositsParams>;

export const DepositParams = s.object({
depositId: s.optional(stringToInt),
originChainId: s.optional(stringToInt),
depositTxHash: s.optional(s.string()),
relayDataHash: s.optional(s.string()),
index: s.refine(
s.defaulted(stringToInt, 0),
"positiveIndex",
(value) => value >= 0,
),
});

export type DepositParams = s.Infer<typeof DepositParams>;
19 changes: 7 additions & 12 deletions packages/indexer-api/src/express-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import bodyParser from "body-parser";
import type { Request, Response, NextFunction, Express, Router } from "express";
import express from "express";

export class ExtendedError extends Error {
export class HttpError extends Error {
status?: number;
}
export function isExtendedError(error: any): error is ExtendedError {
export function isHttpError(error: any): error is HttpError {
return error.status !== undefined;
}

Expand All @@ -24,23 +24,18 @@ export function ExpressApp(routers: RouterConfigs): Express {

app.options("*", cors());

[...Object.entries(routers)].forEach(([key, router]) => {
app.use(`/${key}`, router);
});

// return callable routers
app.get("/", (req: Request, res: Response) => {
res.json([...Object.keys(routers)]);
[...Object.values(routers)].forEach((router) => {
app.use("/", router);
});

app.use(function (_: Request, __: Response, next: NextFunction) {
const error = new ExtendedError("Not Found");
const error = new HttpError("Not Found");
error["status"] = 404;
next(error);
});

app.use(function (
err: ExtendedError | Error,
err: HttpError | Error,
req: Request,
res: Response,
// this needs to be included even if unused, since 4 param call triggers error handler
Expand All @@ -52,7 +47,7 @@ export function ExpressApp(routers: RouterConfigs): Express {
body: req.body,
};
let status = 500;
if (isExtendedError(err)) {
if (isHttpError(err)) {
status = err.status ?? status;
}
res.status(status).json({
Expand Down
7 changes: 7 additions & 0 deletions packages/indexer-api/src/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { expect } from "chai";

describe("main", () => {
it("should return true", async () => {
expect(true).to.be.true;
});
});
47 changes: 20 additions & 27 deletions packages/indexer-api/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { object, string, assert } from "superstruct";
import assert from "assert";
import { ExpressApp } from "./express-app";
import { createDataSource, DatabaseConfig } from "@repo/indexer-database";
import * as services from "./services";
import * as routers from "./routers";
import winston from "winston";
import { type Router } from "express";

Expand All @@ -18,22 +18,22 @@ export async function connectToDatabase(
throw error;
}
}

function getPostgresConfig(
env: Record<string, string | undefined>,
): DatabaseConfig | undefined {
return env.DATABASE_HOST &&
env.DATABASE_PORT &&
env.DATABASE_USER &&
env.DATABASE_PASSWORD &&
env.DATABASE_NAME
? {
host: env.DATABASE_HOST,
port: env.DATABASE_PORT,
user: env.DATABASE_USER,
password: env.DATABASE_PASSWORD,
dbName: env.DATABASE_NAME,
}
: undefined;
): DatabaseConfig {
assert(env.DATABASE_HOST, "requires DATABASE_HOST");
assert(env.DATABASE_PORT, "requires DATABASE_PORT");
assert(env.DATABASE_USER, "requires DATABASE_USER");
assert(env.DATABASE_PASSWORD, "requires DATABASE_PASSWORD");
assert(env.DATABASE_NAME, "requires DATABASE_NAME");
return {
host: env.DATABASE_HOST,
port: env.DATABASE_PORT,
user: env.DATABASE_USER,
password: env.DATABASE_PASSWORD,
dbName: env.DATABASE_NAME,
};
}

export async function Main(
Expand All @@ -44,19 +44,12 @@ export async function Main(
const port = Number(PORT);

const postgresConfig = getPostgresConfig(env);
const postgres = postgresConfig
? await connectToDatabase(postgresConfig, logger)
: undefined;
const postgres = await connectToDatabase(postgresConfig, logger);

const exampleRouter = services.example.getRouter();
const allServices: Record<string, Router> = {
example: exampleRouter,
const allRouters: Record<string, Router> = {
deposits: routers.deposits.getRouter(postgres),
};
if (postgres) {
const indexerRouter = services.indexer.getRouter(postgres);
allServices.indexer = indexerRouter;
}
const app = ExpressApp(allServices);
const app = ExpressApp(allRouters);

logger.info({
message: `Starting indexer api on port ${port}`,
Expand Down
5 changes: 5 additions & 0 deletions packages/indexer-api/src/model/httpStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum HttpStatus {
OK = 200,
BAD_REQUEST = 400,
NOT_FOUND = 404,
}
13 changes: 13 additions & 0 deletions packages/indexer-api/src/routers/deposits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Router } from "express";
import { DataSource } from "@repo/indexer-database";
import { DepositsController } from "../controllers/deposits";
import { DepositsService } from "../services/deposits";

export function getRouter(db: DataSource): Router {
const router = Router();
const service = new DepositsService(db);
const controller = new DepositsController(service);
router.get("/deposits", controller.getDeposits);
router.get("/deposit/status", controller.getDepositStatus);
return router;
}
1 change: 1 addition & 0 deletions packages/indexer-api/src/routers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as deposits from "./deposits";
Loading

0 comments on commit 6351fd9

Please sign in to comment.