Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #12 from ar-io/PE-6364-evaluate-admin-api
Browse files Browse the repository at this point in the history
feat(api): add admin endpoint for triggering evaluation
  • Loading branch information
dtfiedler authored Jun 28, 2024
2 parents 3b7ee23 + 3c52a1d commit c6cb5a9
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 5 deletions.
47 changes: 47 additions & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ info:
description: |
AR.IO ArNS Resolver
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: apiToken
description: ADMIN_API_KEY set in your .env file.
schemas:
ArweaveAddress:
type: string
Expand Down Expand Up @@ -67,6 +73,47 @@ paths:
application/json:
schema:
'$ref': '#/components/schemas/Info'
'/ar-io/resolver/admin/evaluate':
post:
security:
- bearerAuth: []
responses:
'200':
description: |-
200 response
content:
application/json:
schema:
type: object
properties:
message: { type: string }
'202':
description: |-
202 response
content:
application/json:
schema:
type: object
properties:
message: { type: string }
'401':
description: |-
401 response
content:
application/json:
schema:
type: object
properties:
message: { type: string }
'500':
description: |-
500 response
content:
application/json:
schema:
type: object
properties:
error: { type: string }
'/ar-io/resolver/records/{name}':
head:
parameters:
Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import * as env from './lib/env.js';

dotenv.config();

export const ADMIN_API_KEY = env.varOrRandom('ADMIN_API_KEY');

export const EVALUATION_INTERVAL_MS = +env.varOrDefault(
'EVALUATION_INTERVAL_MS',
`${1000 * 60 * 15}`, // 15 mins by default
Expand Down
13 changes: 13 additions & 0 deletions src/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
* 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 crypto from 'node:crypto';

import log from '../log.js';

export function varOrDefault(envVarName: string, defaultValue: string): string {
const value = process.env[envVarName];
Expand All @@ -25,3 +28,13 @@ export function varOrUndefined(envVarName: string): string | undefined {
const value = process.env[envVarName];
return value !== undefined && value.trim() !== '' ? value : undefined;
}

export function varOrRandom(envVarName: string): string {
const value = process.env[envVarName];
if (value === undefined) {
const value = crypto.randomBytes(32).toString('base64url');
log.info(`${envVarName} not provided, generated random value: ${value}`);
return value;
}
return value;
}
34 changes: 34 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* AR.IO ArNS Resolver
* Copyright (C) 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 { NextFunction, Request, Response } from 'express';

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

export const adminMiddleware = (
req: Request,
res: Response,
next: NextFunction,
) => {
if (req.headers['authorization'] !== `Bearer ${config.ADMIN_API_KEY}`) {
res.status(401).send({
message: 'Unauthorized',
});
return;
}
next();
};
27 changes: 25 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import YAML from 'yaml';

import * as config from './config.js';
import log from './log.js';
import { cache, getLastEvaluatedTimestamp } from './system.js';
import { adminMiddleware } from './middleware.js';
import {
cache,
evaluateArNSNames,
getLastEvaluatedTimestamp,
isEvaluationInProgress,
} from './system.js';
import { ArNSResolvedData } from './types.js';

// HTTP server
Expand All @@ -34,7 +40,7 @@ export const app = express();
app.use(
cors({
origin: '*',
methods: ['GET', 'HEAD'],
methods: ['GET', 'HEAD', 'POST'],
}),
);

Expand Down Expand Up @@ -86,6 +92,23 @@ app.get('/ar-io/resolver/info', (_req, res) => {
});
});

app.post('/ar-io/resolver/admin/evaluate', adminMiddleware, (_req, res) => {
// TODO: we could support post request to trigger evaluation for specific names rather than re-evaluate everything
if (isEvaluationInProgress()) {
res.status(202).send({
message: 'Evaluation in progress',
});
} else {
log.info('Evaluation triggered by request', {
processId: config.IO_PROCESS_ID,
});
evaluateArNSNames(); // don't await
res.status(200).send({
message: 'Evaluation triggered',
});
}
});

app.head('/ar-io/resolver/records/:name', async (req, res) => {
try {
log.debug('Checking cache for record', { name: req.params.name });
Expand Down
17 changes: 14 additions & 3 deletions src/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { ArNSResolvedData } from './types.js';
let lastEvaluationTimestamp: number | undefined;
let evaluationInProgress = false;
export const getLastEvaluatedTimestamp = () => lastEvaluationTimestamp;
export const isEvaluationInProgress = () => evaluationInProgress;
export const contract: AoIORead = IO.init({
processId: config.IO_PROCESS_ID,
});
Expand Down Expand Up @@ -75,7 +76,7 @@ export async function evaluateArNSNames() {
);

log.debug('Identified unique process ids assigned to records:', {
recordCount: Object.keys(apexRecords).length,
apexRecordCount: Object.keys(apexRecords).length,
processCount: processIds.size,
});

Expand Down Expand Up @@ -120,13 +121,16 @@ export async function evaluateArNSNames() {

log.info('Retrieved unique process ids assigned to records:', {
processCount: Object.keys(processRecordMap).length,
apexRecordCount: Object.keys(apexRecords).length,
});

// filter out any records associated with an invalid contract
const validArNSRecords = Object.entries(apexRecords).filter(
([_, record]) => record.processId in processRecordMap,
);

let successfulEvaluationCount = 0;

const insertPromises = [];

// now go through all the record names and assign them to the resolved tx ids
Expand Down Expand Up @@ -175,10 +179,17 @@ export async function evaluateArNSNames() {
}
// use pLimit to prevent overwhelming cache
await Promise.all(
insertPromises.map((promise) => parallelLimit(() => promise)),
insertPromises.map((promise) =>
parallelLimit(() => promise.then(() => successfulEvaluationCount++)),
),
);
log.info('Successfully evaluated arns names', {
log.info('Finished evaluating arns names', {
durationMs: Date.now() - startTime,
apexRecordCount: Object.keys(apexRecords).length,
evaluatedRecordCount: successfulEvaluationCount,
evaluatedProcessCount: Object.keys(processRecordMap).length,
failedProcessCount:
processIds.size - Object.keys(processRecordMap).length,
});
lastEvaluationTimestamp = Date.now();
} catch (err: any) {
Expand Down

0 comments on commit c6cb5a9

Please sign in to comment.