Skip to content
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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"@nestjs/swagger": "12.0.1",
"date-fns": "4.4.0",
"helmet": "8.3.0",
"http-status-codes": "2.3.0",
"logdown": "3.3.1",
"package-json": "10.0.1",
"reflect-metadata": "0.2.2",
Expand Down
8 changes: 3 additions & 5 deletions src/Server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {NestFactory} from '@nestjs/core';
import {HttpStatus} from '@nestjs/common';
import {NestExpressApplication} from '@nestjs/platform-express';
import {DocumentBuilder, SwaggerModule} from '@nestjs/swagger';
import {NextFunction, Request, Response} from 'express';
import helmet from 'helmet';
import {StatusCodes as HTTP_STATUS} from 'http-status-codes';

import {AppModule} from './app.module.js';
import {ServerConfig} from './config.js';
Expand Down Expand Up @@ -89,8 +89,8 @@ function createRateLimitMiddleware(config: ServerConfig) {
if (current.count >= config.RATE_LIMIT_MAX_REQUESTS) {
const retryAfter = Math.ceil((current.resetAt - now) / rateLimitCleanupThreshold);
response.setHeader('Retry-After', String(retryAfter));
response.status(HTTP_STATUS.TOO_MANY_REQUESTS).json({
code: HTTP_STATUS.TOO_MANY_REQUESTS,
response.status(HttpStatus.TOO_MANY_REQUESTS).json({
code: HttpStatus.TOO_MANY_REQUESTS,
message: 'Too many requests',
});
return;
Expand All @@ -100,5 +100,3 @@ function createRateLimitMiddleware(config: ServerConfig) {
next();
};
}

export {HTTP_STATUS};
9 changes: 4 additions & 5 deletions src/controllers/info.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {Controller, Get} from '@nestjs/common';
import {Controller, Get, HttpStatus} from '@nestjs/common';
import {ApiOperation, ApiResponse, ApiTags} from '@nestjs/swagger';
import {StatusCodes as HTTP_STATUS} from 'http-status-codes';

import {config} from '../config.js';
import {InfoResult} from '../swagger.js';

interface InfoRouteResponseBody {
code: HTTP_STATUS;
code: HttpStatus;
commit?: string;
version?: string;
}
Expand All @@ -15,11 +14,11 @@ interface InfoRouteResponseBody {
@Controller()
export class InfoController {
@ApiOperation({description: 'Get information about the server', operationId: 'getServerInformation'})
@ApiResponse({description: 'That worked', status: HTTP_STATUS.OK, type: InfoResult})
@ApiResponse({description: 'That worked', status: HttpStatus.OK, type: InfoResult})
@Get('_info')
info(): InfoRouteResponseBody {
return {
code: HTTP_STATUS.OK,
code: HttpStatus.OK,
commit: config.COMMIT,
version: config.VERSION,
};
Expand Down
19 changes: 9 additions & 10 deletions src/controllers/main.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {Controller, Get, Query, Res} from '@nestjs/common';
import {Controller, Get, HttpStatus, Query, Res} from '@nestjs/common';
import {ApiExcludeEndpoint, ApiOperation, ApiQuery, ApiResponse, ApiTags} from '@nestjs/swagger';
import {Response} from 'express';
import {StatusCodes as HTTP_STATUS} from 'http-status-codes';

import {RawResult} from '../swagger.js';
import {getLogger} from '../utils.js';
import {unpkgBase} from './packages.controller.js';

interface MainRouteResponseBody {
code: HTTP_STATUS;
code: HttpStatus;
message?: string;
url?: string;
}
Expand All @@ -22,17 +21,17 @@ export class MainController {
@ApiExcludeEndpoint()
@Get('favicon.ico')
favicon(@Res() res: Response): void {
res.status(HTTP_STATUS.NOT_FOUND).json({
code: HTTP_STATUS.NOT_FOUND,
res.status(HttpStatus.NOT_FOUND).json({
code: HttpStatus.NOT_FOUND,
message: 'Not found',
} satisfies MainRouteResponseBody);
}

@ApiOperation({description: "Get the server's repository URL", operationId: 'getServerRepositoryUrl'})
@ApiQuery({description: 'Get the result as JSON', name: 'raw', required: false, type: Boolean})
@ApiQuery({description: 'Get a link to unpkg.com', name: 'unpkg', required: false, type: Boolean})
@ApiResponse({description: 'That worked', status: HTTP_STATUS.OK, type: RawResult})
@ApiResponse({description: 'Redirect to repository URL', status: HTTP_STATUS.MOVED_TEMPORARILY})
@ApiResponse({description: 'That worked', status: HttpStatus.OK, type: RawResult})
@ApiResponse({description: 'Redirect to repository URL', status: HttpStatus.FOUND})
@Get()
main(@Query('raw') raw: string, @Query('unpkg') unpkg: string, @Res() res: Response): void {
logger.info('Got request for main page');
Expand All @@ -42,20 +41,20 @@ export class MainController {
if (raw !== undefined && raw !== 'false') {
logger.info(`Returning raw unpkg info for main page: "${redirectUrl}" ...`);
res.json({
code: HTTP_STATUS.OK,
code: HttpStatus.OK,
url: redirectUrl,
} satisfies MainRouteResponseBody);
return;
}
logger.info(`Redirecting main page to unpkg: "${redirectUrl}" ...`);
res.redirect(HTTP_STATUS.MOVED_TEMPORARILY, redirectUrl);
res.redirect(HttpStatus.FOUND, redirectUrl);
return;
}

if (raw !== undefined && raw !== 'false') {
logger.info(`Returning raw info for main page: "${repositoryUrl}" ...`);
res.json({
code: HTTP_STATUS.OK,
code: HttpStatus.OK,
url: repositoryUrl,
} satisfies MainRouteResponseBody);
return;
Expand Down
45 changes: 22 additions & 23 deletions src/controllers/packages.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {Controller, Get, Param, Query, Res} from '@nestjs/common';
import {Controller, Get, HttpStatus, Param, Query, Res} from '@nestjs/common';
import {ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags} from '@nestjs/swagger';
import {Response} from 'express';
import {StatusCodes as HTTP_STATUS} from 'http-status-codes';
import {URL} from 'node:url';
import validatePackageName from 'validate-npm-package-name';

Expand All @@ -10,7 +9,7 @@ import {RawError, RawResult} from '../swagger.js';
import {getLogger, validateUrl} from '../utils.js';

interface PackagesRouteResponseBody {
code: HTTP_STATUS;
code: HttpStatus;
message?: string;
url?: string;
}
Expand All @@ -25,11 +24,11 @@ export class PackagesController {
@ApiParam({name: 'packageName', required: true, type: String})
@ApiQuery({description: 'Get the result as JSON', name: 'raw', required: false, type: Boolean})
@ApiQuery({description: 'Get a link to unpkg.com', name: 'unpkg', required: false, type: Boolean})
@ApiResponse({description: 'That worked', status: HTTP_STATUS.OK, type: RawResult})
@ApiResponse({description: 'Redirect to repository URL', status: HTTP_STATUS.MOVED_TEMPORARILY})
@ApiResponse({description: 'Version or package not found', status: HTTP_STATUS.NOT_FOUND, type: RawError})
@ApiResponse({description: 'Invalid package name', status: HTTP_STATUS.UNPROCESSABLE_ENTITY, type: RawError})
@ApiResponse({description: 'Internal server error', status: HTTP_STATUS.INTERNAL_SERVER_ERROR, type: RawError})
@ApiResponse({description: 'That worked', status: HttpStatus.OK, type: RawResult})
@ApiResponse({description: 'Redirect to repository URL', status: HttpStatus.FOUND})
@ApiResponse({description: 'Version or package not found', status: HttpStatus.NOT_FOUND, type: RawError})
@ApiResponse({description: 'Invalid package name', status: HttpStatus.UNPROCESSABLE_ENTITY, type: RawError})
@ApiResponse({description: 'Internal server error', status: HttpStatus.INTERNAL_SERVER_ERROR, type: RawError})
@Get(':packageName')
async getPackage(
@Param('packageName') rawPackageName: string,
Expand All @@ -48,7 +47,7 @@ export class PackagesController {
@Res() res: Response
): Promise<void> {
if (!scope.trim().startsWith('@')) {
res.status(HTTP_STATUS.NOT_FOUND).json({code: HTTP_STATUS.NOT_FOUND, message: 'Not found'});
res.status(HttpStatus.NOT_FOUND).json({code: HttpStatus.NOT_FOUND, message: 'Not found'});
return;
}
const {name: pkgPart, version} = parsePackageAndVersion(rawPackageName.trim());
Expand All @@ -66,8 +65,8 @@ async function handlePackageRequest(
logger.info(`Got request for package "${packageName}" (version "${version}").`);

if (!validatePackageName(packageName).validForNewPackages) {
response.status(HTTP_STATUS.UNPROCESSABLE_ENTITY).json({
code: HTTP_STATUS.UNPROCESSABLE_ENTITY,
response.status(HttpStatus.UNPROCESSABLE_ENTITY).json({
code: HttpStatus.UNPROCESSABLE_ENTITY,
message: 'Invalid package name',
} satisfies PackagesRouteResponseBody);
return;
Expand All @@ -77,45 +76,45 @@ async function handlePackageRequest(
const redirectUrl = `${unpkgBase}/${packageName}@${version}/`;

if (!validateUrl(redirectUrl)) {
response.status(HTTP_STATUS.BAD_REQUEST).json({
code: HTTP_STATUS.BAD_REQUEST,
response.status(HttpStatus.BAD_REQUEST).json({
code: HttpStatus.BAD_REQUEST,
message: `Invalid URL: ${redirectUrl}`,
} satisfies PackagesRouteResponseBody);
return;
}

if (queryParamExists(query, 'raw')) {
logger.info(`Returning raw unpkg info for "${packageName}": "${redirectUrl}" ...`);
response.json({code: HTTP_STATUS.OK, url: redirectUrl} satisfies PackagesRouteResponseBody);
response.json({code: HttpStatus.OK, url: redirectUrl} satisfies PackagesRouteResponseBody);
return;
}

logger.info(`Redirecting package "${packageName}" to unpkg: "${redirectUrl}" ...`);
response.redirect(HTTP_STATUS.MOVED_TEMPORARILY, redirectUrl);
response.redirect(HttpStatus.FOUND, redirectUrl);
return;
}

const parseResult = await getPackageUrl(packageName, version);

let errorCode: HTTP_STATUS;
let errorCode: HttpStatus;
let errorMessage: string;

switch (parseResult.status) {
case ParseStatus.INVALID_PACKAGE_NAME: {
errorCode = HTTP_STATUS.UNPROCESSABLE_ENTITY;
errorCode = HttpStatus.UNPROCESSABLE_ENTITY;
errorMessage = 'Invalid package name';
break;
}

case ParseStatus.INVALID_URL:
case ParseStatus.NO_URL_FOUND: {
errorCode = HTTP_STATUS.NOT_FOUND;
errorCode = HttpStatus.NOT_FOUND;
errorMessage = `No source URL found. Please visit https://www.npmjs.com/package/${packageName}.`;
break;
}

case ParseStatus.PACKAGE_NOT_FOUND: {
errorCode = HTTP_STATUS.NOT_FOUND;
errorCode = HttpStatus.NOT_FOUND;
errorMessage = 'Package not found';
break;
}
Expand All @@ -124,23 +123,23 @@ async function handlePackageRequest(
const redirectSite = parseResult.url;
if (queryParamExists(query, 'raw')) {
logger.info(`Returning raw info for "${packageName}": "${redirectSite}" ...`);
response.json({code: HTTP_STATUS.OK, url: redirectSite} satisfies PackagesRouteResponseBody);
response.json({code: HttpStatus.OK, url: redirectSite} satisfies PackagesRouteResponseBody);
return;
}
logger.info(`Redirecting package "${packageName}" to "${redirectSite}" ...`);
response.redirect(HTTP_STATUS.MOVED_TEMPORARILY, redirectSite);
response.redirect(HttpStatus.FOUND, redirectSite);
return;
}

case ParseStatus.VERSION_NOT_FOUND: {
errorCode = HTTP_STATUS.NOT_FOUND;
errorCode = HttpStatus.NOT_FOUND;
errorMessage = 'Version not found';
break;
}

case ParseStatus.SERVER_ERROR:
default: {
errorCode = HTTP_STATUS.INTERNAL_SERVER_ERROR;
errorCode = HttpStatus.INTERNAL_SERVER_ERROR;
errorMessage = 'Internal server error';
break;
}
Expand Down
Loading