diff --git a/packages/faucet/src/actions/start/start.ts b/packages/faucet/src/actions/start/start.ts index 0e96c9a2..408203f5 100644 --- a/packages/faucet/src/actions/start/start.ts +++ b/packages/faucet/src/actions/start/start.ts @@ -1,16 +1,11 @@ -import cors = require("@koa/cors"); import { createCosmWasmConnector } from "@cosmwasm/bcp"; -import Koa from "koa"; -import bodyParser from "koa-bodyparser"; -import { isValidAddress } from "../../addresses"; +import { Webserver } from "../../api/webserver"; import * as constants from "../../constants"; import { logAccountsState } from "../../debugging"; import { Faucet } from "../../faucet"; import { availableTokensFromHolder } from "../../multichainhelpers"; import { createUserProfile } from "../../profile"; -import { HttpError } from "./httperror"; -import { RequestParser } from "./requestparser"; export async function start(args: ReadonlyArray): Promise { if (args.length < 1) { @@ -57,71 +52,6 @@ export async function start(args: ReadonlyArray): Promise { setInterval(async () => faucet.refill(), 60_000); // ever 60 seconds console.info("Creating webserver ..."); - const api = new Koa(); - api.use(cors()); - api.use(bodyParser()); - - api.use(async context => { - switch (context.path) { - case "/": - case "/healthz": - context.response.body = - "Welcome to the faucet!\n" + - "\n" + - "Check the full status via the /status endpoint.\n" + - "You can get tokens from here by POSTing to /credit.\n" + - "See https://github.com/iov-one/iov-faucet for all further information.\n"; - break; - case "/status": { - const updatedAccounts = await faucet.loadAccounts(); - context.response.body = { - status: "ok", - nodeUrl: blockchainBaseUrl, - chainId: connection.chainId(), - chainTokens: chainTokens, - availableTokens: availableTokens, - holder: updatedAccounts[0], - distributors: updatedAccounts.slice(1), - }; - break; - } - case "/credit": { - if (context.request.method !== "POST") { - throw new HttpError(405, "This endpoint requires a POST request"); - } - - if (context.request.type !== "application/json") { - throw new HttpError(415, "Content-type application/json expected"); - } - - // context.request.body is set by the bodyParser() plugin - const requestBody = context.request.body; - const { address, ticker } = RequestParser.parseCreditBody(requestBody); - - if (!isValidAddress(address)) { - throw new HttpError(400, "Address is not in the expected format for this chain."); - } - - if (availableTokens.indexOf(ticker) === -1) { - const tokens = JSON.stringify(availableTokens); - throw new HttpError(422, `Token is not available. Available tokens are: ${tokens}`); - } - - try { - await faucet.credit(address, ticker); - } catch (e) { - console.error(e); - throw new HttpError(500, "Sending tokens failed"); - } - - context.response.body = "ok"; - break; - } - default: - // koa sends 404 by default - } - }); - const port = constants.port; - console.info(`Starting webserver on port ${port} ...`); - api.listen(port); + const server = new Webserver(faucet, { nodeUrl: blockchainBaseUrl, chainId: connection.chainId() }); + server.start(constants.port); } diff --git a/packages/faucet/src/actions/start/httperror.spec.ts b/packages/faucet/src/api/httperror.spec.ts similarity index 100% rename from packages/faucet/src/actions/start/httperror.spec.ts rename to packages/faucet/src/api/httperror.spec.ts diff --git a/packages/faucet/src/actions/start/httperror.ts b/packages/faucet/src/api/httperror.ts similarity index 100% rename from packages/faucet/src/actions/start/httperror.ts rename to packages/faucet/src/api/httperror.ts diff --git a/packages/faucet/src/actions/start/requestparser.spec.ts b/packages/faucet/src/api/requestparser.spec.ts similarity index 100% rename from packages/faucet/src/actions/start/requestparser.spec.ts rename to packages/faucet/src/api/requestparser.spec.ts diff --git a/packages/faucet/src/actions/start/requestparser.ts b/packages/faucet/src/api/requestparser.ts similarity index 100% rename from packages/faucet/src/actions/start/requestparser.ts rename to packages/faucet/src/api/requestparser.ts diff --git a/packages/faucet/src/api/webserver.ts b/packages/faucet/src/api/webserver.ts new file mode 100644 index 00000000..5669ada6 --- /dev/null +++ b/packages/faucet/src/api/webserver.ts @@ -0,0 +1,94 @@ +import Koa from "koa"; +import cors = require("@koa/cors"); +import { ChainId } from "@iov/bcp"; +import bodyParser from "koa-bodyparser"; + +import { isValidAddress } from "../addresses"; +import { Faucet } from "../faucet"; +import { availableTokensFromHolder } from "../multichainhelpers"; +import { HttpError } from "./httperror"; +import { RequestParser } from "./requestparser"; + +/** This will be passed 1:1 to the user */ +export interface ChainConstants { + readonly nodeUrl: string; + readonly chainId: ChainId; +} + +export class Webserver { + private readonly api = new Koa(); + + constructor(faucet: Faucet, chainChinstants: ChainConstants) { + this.api.use(cors()); + this.api.use(bodyParser()); + + this.api.use(async context => { + switch (context.path) { + case "/": + case "/healthz": + context.response.body = + "Welcome to the faucet!\n" + + "\n" + + "Check the full status via the /status endpoint.\n" + + "You can get tokens from here by POSTing to /credit.\n" + + "See https://github.com/iov-one/iov-faucet for all further information.\n"; + break; + case "/status": { + const [holder, ...distributors] = await faucet.loadAccounts(); + const availableTokens = availableTokensFromHolder(holder); + const chainTokens = await faucet.loadTokenTickers(); + context.response.body = { + status: "ok", + ...chainChinstants, + chainTokens: chainTokens, + availableTokens: availableTokens, + holder: holder, + distributors: distributors, + }; + break; + } + case "/credit": { + if (context.request.method !== "POST") { + throw new HttpError(405, "This endpoint requires a POST request"); + } + + if (context.request.type !== "application/json") { + throw new HttpError(415, "Content-type application/json expected"); + } + + // context.request.body is set by the bodyParser() plugin + const requestBody = context.request.body; + const { address, ticker } = RequestParser.parseCreditBody(requestBody); + + if (!isValidAddress(address)) { + throw new HttpError(400, "Address is not in the expected format for this chain."); + } + + const [holder] = await faucet.loadAccounts(); + const availableTokens = availableTokensFromHolder(holder); + if (availableTokens.indexOf(ticker) === -1) { + const tokens = JSON.stringify(availableTokens); + throw new HttpError(422, `Token is not available. Available tokens are: ${tokens}`); + } + + try { + await faucet.credit(address, ticker); + } catch (e) { + console.error(e); + throw new HttpError(500, "Sending tokens failed"); + } + + context.response.body = "ok"; + break; + } + default: + // koa sends 404 by default + } + }); + } + + public start(port: number): void { + console.info(`Starting webserver on port ${port} ...`); + this.api.listen(port); + } +}