Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(routes): Move routes into a separate file #54

Merged
merged 1 commit into from
Oct 12, 2023
Merged
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
162 changes: 8 additions & 154 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,14 @@
*/
import { default as cors } from 'cors';
import express from 'express';
//import * as OpenApiValidator from 'express-openapi-validator';
import promMid from 'express-prometheus-middleware';
import fs from 'node:fs';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yaml';

import * as config from './config.js';
import log from './log.js';
import { createArnsMiddleware } from './middleware/arns.js';
import { createSandboxMiddleware } from './middleware/sandbox.js';
import {
DATA_PATH_REGEX,
RAW_DATA_PATH_REGEX,
createDataHandler,
createRawDataHandler,
} from './routes/data.js';
import { arIoRouter } from './routes/ar-io.js';
import { arnsRouter } from './routes/arns.js';
import { dataRouter } from './routes/data/index.js';
import { apolloServer } from './routes/graphql/index.js';
import { openApiRouter } from './routes/openapi.js';
import * as system from './system.js';

system.arweaveClient.refreshPeers();
Expand All @@ -48,136 +39,12 @@ if (config.START_WRITERS) {
// HTTP server
const app = express();

// TODO get path relative to source file instead of cwd
//app.use(
// OpenApiValidator.middleware({
// apiSpec: './docs/openapi.yaml',
// validateRequests: true, // (default)
// validateResponses: true, // false by default
// }),
//);

app.use(cors());

app.use(
promMid({
metricsPath: '/ar-io/__gateway_metrics',
extraMasks: [
// Mask all paths except for the ones below
/^(?!api-docs)(?!ar-io)(?!graphql)(?!openapi\.json)(?!raw).+$/,
// Mask Arweave TX IDs
/[a-zA-Z0-9_-]{43}/,
],
}),
);

const dataHandler = createDataHandler({
log,
dataIndex: system.contiguousDataIndex,
dataSource: system.contiguousDataSource,
blockListValidator: system.blockListValidator,
manifestPathResolver: system.manifestPathResolver,
});

if (config.ARNS_ROOT_HOST !== undefined) {
app.use(
createArnsMiddleware({
dataHandler,
nameResolver: system.nameResolver,
}),
);

app.use(
createSandboxMiddleware({
sandboxProtocol: config.SANDBOX_PROTOCOL,
}),
);
}

// OpenAPI Spec
const openapiDocument = YAML.parse(
fs.readFileSync('docs/openapi.yaml', 'utf8'),
);
app.get('/openapi.json', (_req, res) => {
res.json(openapiDocument);
});

// Swagger UI
const options = {
explorer: true,
};
app.use(
'/api-docs',
swaggerUi.serve,
swaggerUi.setup(openapiDocument, options),
);

// Healthcheck
app.get('/ar-io/healthcheck', (_req, res) => {
const data = {
uptime: process.uptime(),
message: 'Welcome to the Permaweb.',
date: new Date(),
};

res.status(200).send(data);
});

// ar.io network info
app.get('/ar-io/info', (_req, res) => {
res.status(200).send({
wallet: config.AR_IO_WALLET,
});
});

// Only allow access to admin routes if the bearer token matches the admin api key
app.use('/ar-io/admin', (req, res, next) => {
if (req.headers.authorization === `Bearer ${config.ADMIN_API_KEY}`) {
next();
} else {
res.status(401).send('Unauthorized');
}
});

// Debug info (for internal use)
app.get('/ar-io/admin/debug', async (_req, res) => {
res.json({
db: await system.db.getDebugInfo(),
});
});

// Block access to contiguous data by ID or hash
app.put('/ar-io/admin/block-data', express.json(), async (req, res) => {
// TODO improve validation
try {
const { id, hash, source, notes } = req.body;
if (id === undefined && hash === undefined) {
res.status(400).send("Must provide 'id' or 'hash'");
return;
}
system.db.blockData({ id, hash, source, notes });
// TODO check return value
res.json({ message: 'Content blocked' });
} catch (error: any) {
res.status(500).send(error?.message);
}
});

// Queue a TX ID for processing
app.post('/ar-io/admin/queue-tx', express.json(), async (req, res) => {
try {
const { id } = req.body;
if (id === undefined) {
res.status(400).send("Must provide 'id'");
return;
}
system.prioritizedTxIds.add(id);
system.txFetcher.queueTxId(id);
res.json({ message: 'TX queued' });
} catch (error: any) {
res.status(500).send(error?.message);
}
});
app.use(arnsRouter);
app.use(openApiRouter);
app.use(arIoRouter);
app.use(dataRouter);

// GraphQL
const apolloServerInstanceGql = apolloServer(system.db, {
Expand All @@ -193,16 +60,3 @@ apolloServerInstanceGql.start().then(() => {
log.info(`Listening on port ${config.PORT}`);
});
});

// Data routes
app.get(
RAW_DATA_PATH_REGEX,
createRawDataHandler({
log,
dataIndex: system.contiguousDataIndex,
dataSource: system.contiguousDataSource,
blockListValidator: system.blockListValidator,
}),
);

app.get(DATA_PATH_REGEX, dataHandler);
2 changes: 1 addition & 1 deletion src/middleware/arns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Handler } from 'express';
import { asyncMiddleware } from 'middleware-async';

import * as config from '../config.js';
import { sendNotFound } from '../routes/data.js';
import { sendNotFound } from '../routes/data/handlers.js';
import { NameResolver } from '../types.js';

const EXCLUDED_SUBDOMAINS = new Set('www');
Expand Down
103 changes: 103 additions & 0 deletions src/routes/ar-io.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* AR.IO Gateway
* Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Router, default as express } from 'express';
import createPrometheusMiddleware from 'express-prometheus-middleware';

import * as config from '../config.js';
import * as system from '../system.js';

export const arIoRouter = Router();

arIoRouter.use(
createPrometheusMiddleware({
metricsPath: '/ar-io/__gateway_metrics',
extraMasks: [
// Mask all paths except for the ones below
/^(?!api-docs)(?!ar-io)(?!graphql)(?!openapi\.json)(?!raw).+$/,
// Mask Arweave TX IDs
/[a-zA-Z0-9_-]{43}/,
],
}),
);

// Healthcheck
arIoRouter.get('/ar-io/healthcheck', (_req, res) => {
const data = {
uptime: process.uptime(),
message: 'Welcome to the Permaweb.',
date: new Date(),
};

res.status(200).send(data);
});

// ar.io network info
arIoRouter.get('/ar-io/info', (_req, res) => {
res.status(200).send({
wallet: config.AR_IO_WALLET,
});
});

// Only allow access to admin routes if the bearer token matches the admin api key
arIoRouter.use('/ar-io/admin', (req, res, next) => {
if (req.headers.authorization === `Bearer ${config.ADMIN_API_KEY}`) {
next();
} else {
res.status(401).send('Unauthorized');
}
});

// Debug info (for internal use)
arIoRouter.get('/ar-io/admin/debug', async (_req, res) => {
res.json({
db: await system.db.getDebugInfo(),
});
});

// Block access to contiguous data by ID or hash
arIoRouter.put('/ar-io/admin/block-data', express.json(), async (req, res) => {
// TODO improve validation
try {
const { id, hash, source, notes } = req.body;
if (id === undefined && hash === undefined) {
res.status(400).send("Must provide 'id' or 'hash'");
return;
}
system.db.blockData({ id, hash, source, notes });
// TODO check return value
res.json({ message: 'Content blocked' });
} catch (error: any) {
res.status(500).send(error?.message);
}
});

// Queue a TX ID for processing
arIoRouter.post('/ar-io/admin/queue-tx', express.json(), async (req, res) => {
try {
const { id } = req.body;
if (id === undefined) {
res.status(400).send("Must provide 'id'");
return;
}
system.prioritizedTxIds.add(id);
system.txFetcher.queueTxId(id);
res.json({ message: 'TX queued' });
} catch (error: any) {
res.status(500).send(error?.message);
}
});
41 changes: 41 additions & 0 deletions src/routes/arns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* AR.IO Gateway
* Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Router } from 'express';

import * as config from '../config.js';
import { createArnsMiddleware } from '../middleware/arns.js';
import { createSandboxMiddleware } from '../middleware/sandbox.js';
import * as system from '../system.js';
import { dataHandler } from './data/index.js';

export const arnsRouter = Router();

if (config.ARNS_ROOT_HOST !== undefined) {
arnsRouter.use(
createArnsMiddleware({
dataHandler,
nameResolver: system.nameResolver,
}),
);

arnsRouter.use(
createSandboxMiddleware({
sandboxProtocol: config.SANDBOX_PROTOCOL,
}),
);
}
7 changes: 2 additions & 5 deletions src/routes/data.ts → src/routes/data/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ import { default as asyncHandler } from 'express-async-handler';
import url from 'node:url';
import { Logger } from 'winston';

import { MANIFEST_CONTENT_TYPE } from '../lib/encoding.js';
import { MANIFEST_CONTENT_TYPE } from '../../lib/encoding.js';
import {
BlockListValidator,
ContiguousData,
ContiguousDataAttributes,
ContiguousDataIndex,
ContiguousDataSource,
ManifestPathResolver,
} from '../types.js';
} from '../../types.js';

const STABLE_MAX_AGE = 60 * 60 * 24 * 30; // 30 days
const UNSTABLE_MAX_AGE = 60 * 60 * 2; // 2 hours
Expand Down Expand Up @@ -89,7 +89,6 @@ export const sendNotFound = (res: Response) => {
};

// Data routes
export const RAW_DATA_PATH_REGEX = /^\/raw\/([a-zA-Z0-9-_]{43})\/?$/i;
export const createRawDataHandler = ({
log,
dataIndex,
Expand Down Expand Up @@ -259,8 +258,6 @@ const sendManifestResponse = async ({
return false;
};

export const DATA_PATH_REGEX =
/^\/?([a-zA-Z0-9-_]{43})\/?$|^\/?([a-zA-Z0-9-_]{43})\/(.*)$/i;
export const createDataHandler = ({
log,
dataIndex,
Expand Down
Loading