diff --git a/src/app.ts b/src/app.ts
index 6df5b94a..b6da322a 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -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();
@@ -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, {
@@ -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);
diff --git a/src/middleware/arns.ts b/src/middleware/arns.ts
index e51d4476..2c14c48c 100644
--- a/src/middleware/arns.ts
+++ b/src/middleware/arns.ts
@@ -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');
diff --git a/src/routes/ar-io.ts b/src/routes/ar-io.ts
new file mode 100644
index 00000000..c420f7df
--- /dev/null
+++ b/src/routes/ar-io.ts
@@ -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 .
+ */
+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);
+ }
+});
diff --git a/src/routes/arns.ts b/src/routes/arns.ts
new file mode 100644
index 00000000..9b4423e9
--- /dev/null
+++ b/src/routes/arns.ts
@@ -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 .
+ */
+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,
+ }),
+ );
+}
diff --git a/src/routes/data.ts b/src/routes/data/handlers.ts
similarity index 97%
rename from src/routes/data.ts
rename to src/routes/data/handlers.ts
index 79d5252e..f75a96cf 100644
--- a/src/routes/data.ts
+++ b/src/routes/data/handlers.ts
@@ -20,7 +20,7 @@ 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,
@@ -28,7 +28,7 @@ import {
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
@@ -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,
@@ -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,
diff --git a/src/routes/data/index.ts b/src/routes/data/index.ts
new file mode 100644
index 00000000..a694fabf
--- /dev/null
+++ b/src/routes/data/index.ts
@@ -0,0 +1,47 @@
+/**
+ * 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 .
+ */
+import { Router } from 'express';
+
+import log from '../../log.js';
+import * as system from '../../system.js';
+import { createDataHandler, createRawDataHandler } from './handlers.js';
+
+const DATA_PATH_REGEX =
+ /^\/?([a-zA-Z0-9-_]{43})\/?$|^\/?([a-zA-Z0-9-_]{43})\/(.*)$/i;
+const RAW_DATA_PATH_REGEX = /^\/raw\/([a-zA-Z0-9-_]{43})\/?$/i;
+
+// Used by ArNS Router
+export const dataHandler = createDataHandler({
+ log,
+ dataIndex: system.contiguousDataIndex,
+ dataSource: system.contiguousDataSource,
+ blockListValidator: system.blockListValidator,
+ manifestPathResolver: system.manifestPathResolver,
+});
+
+export const dataRouter = Router();
+dataRouter.get(DATA_PATH_REGEX, dataHandler);
+dataRouter.get(
+ RAW_DATA_PATH_REGEX,
+ createRawDataHandler({
+ log,
+ dataIndex: system.contiguousDataIndex,
+ dataSource: system.contiguousDataSource,
+ blockListValidator: system.blockListValidator,
+ }),
+);
diff --git a/src/routes/openapi.ts b/src/routes/openapi.ts
new file mode 100644
index 00000000..ffb06b03
--- /dev/null
+++ b/src/routes/openapi.ts
@@ -0,0 +1,55 @@
+/**
+ * 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 .
+ */
+//import * as OpenApiValidator from 'express-openapi-validator';
+import { Router } from 'express';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import swaggerUi from 'swagger-ui-express';
+import YAML from 'yaml';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+export const openApiRouter = Router();
+
+// 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
+// }),
+//);
+
+// OpenAPI Spec
+const openapiDocument = YAML.parse(
+ fs.readFileSync(__dirname + '/../../docs/openapi.yaml', 'utf8'),
+);
+openApiRouter.get('/openapi.json', (_req, res) => {
+ res.json(openapiDocument);
+});
+
+// Swagger UI
+const options = {
+ explorer: true,
+};
+openApiRouter.use(
+ '/api-docs',
+ swaggerUi.serve,
+ swaggerUi.setup(openapiDocument, options),
+);