Skip to content

Commit

Permalink
Misc refactors / improvements (#100)
Browse files Browse the repository at this point in the history
- improved error handling / reporting
- moved initializers to separate files
- improved test case reliability
- improved request / response typing
  • Loading branch information
Miłosz Skaza authored Apr 8, 2023
1 parent f65f0ae commit 884f40c
Show file tree
Hide file tree
Showing 18 changed files with 182 additions and 235 deletions.
23 changes: 7 additions & 16 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
import Fastify, { FastifyServerOptions } from "fastify";
import Fastify, { FastifyInstance, FastifyServerOptions } from "fastify";
import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox";

import fastifySwagger from "@fastify/swagger";
import fastifySwaggerUI from "@fastify/swagger-ui";

import initSentry from "./sentry";
import { TouristConfig } from "./config";
import { createRouter } from "./router";
import { SwaggerConfig } from "./swagger";
import { getIssuerToken } from "./utils/auth";

import { initErrorHandler } from "./loaders/error-handling";
import { initSwagger } from "./loaders/swagger";

export const createApp = async (
options: FastifyServerOptions = { logger: true },
config: TouristConfig,
) => {
const app = Fastify(options).withTypeProvider<TypeBoxTypeProvider>();
const app: FastifyInstance = Fastify(options).withTypeProvider<TypeBoxTypeProvider>();
app.decorate("config", config);

if (app.config.SENTRY_DSN) {
app.log.info("Sentry Reporting Enabled");
initSentry(app);
}

app.register(fastifySwagger, SwaggerConfig);
app.register(fastifySwaggerUI, {
routePrefix: "/docs",
});
initErrorHandler(app);
initSwagger(app);

const router = createRouter();
app.register(router);
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import crypto from "crypto";
import _ from "lodash";
import { parseBool } from "./utils/config";

export declare type TouristConfig = {
export type TouristConfig = {
DEBUG: boolean;
CONCURRENCY: number;
SECRET: string;
Expand Down
5 changes: 2 additions & 3 deletions src/jobs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ import config from "../config";
import { JobBrowser, JobCookieType, JobOptions, JobStepType } from "../schemas/api";
import { PlaywrightRunner } from "../utils/runner";

export declare type VisitJobData = {
export type VisitJobData = {
browser: JobBrowser;
steps: JobStepType[];
cookies: JobCookieType[];
options: JobOptions[];
};

export const asyncVisitJob = async (job: Job<VisitJobData>) => {
const { data } = job;

const data = job.data;
const runner = new PlaywrightRunner(data, config.DEBUG);

try {
Expand Down
7 changes: 4 additions & 3 deletions src/jobs/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { Job } from "bull";

import { LegacyCookieType, LegacyStepType } from "../schemas/legacy";
import { LegacyPlaywrightRunner } from "../utils/legacy/runner";
import { JobResultType } from "../schemas/api";

export declare type LegacySimpleVisitJobData = {
export type LegacySimpleVisitJobData = {
steps: LegacyStepType[];
cookies: LegacyCookieType[];
};
Expand All @@ -27,7 +28,7 @@ export const legacySimpleVisitJob = async (job: Job<LegacySimpleVisitJobData>) =
}
};

export declare type LegacyReturningVisitJobData = {
export type LegacyReturningVisitJobData = {
steps: LegacyStepType[];
cookies: LegacyCookieType[];
record: boolean;
Expand All @@ -49,6 +50,6 @@ export const legacyReturningVisitJob = async (data: LegacyReturningVisitJobData)
throw e;
}

const result = await runner.finish();
const result: JobResultType = await runner.finish();
return { status: "success", result };
};
30 changes: 30 additions & 0 deletions src/loaders/error-handling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { FastifyError, FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import * as Sentry from "@sentry/node";

export const initErrorHandler = (app: FastifyInstance) => {
if (app.config.SENTRY_DSN) {
app.log.info("Sentry Reporting Enabled");
Sentry.init({
dsn: app.config.SENTRY_DSN,
tracesSampleRate: app.config.SENTRY_TRACES_SAMPLE,
});
}

app.setErrorHandler(
async (error: FastifyError, request: FastifyRequest, reply: FastifyReply) => {
if (!reply.statusCode || reply.statusCode === 200) {
if (error.statusCode && error.statusCode >= 400) {
return reply.code(error.statusCode).send(error);
}
}

// only log and capture 500s - not validation errors
request.log.error(error);
if (app.config.SENTRY_DSN) {
Sentry.captureException(error);
}

return reply.code(500).send(new Error("Internal Server Error"));
},
);
};
30 changes: 30 additions & 0 deletions src/loaders/swagger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import fs from "fs";
import path from "path";

import { FastifyInstance, FastifyRegisterOptions } from "fastify";
import fastifySwagger, { FastifyDynamicSwaggerOptions } from "@fastify/swagger";
import fastifySwaggerUI from "@fastify/swagger-ui";

const PkgPath: string = path.normalize(
path.join(__dirname, "..", "..", "package.json"),
);
const PKG: { name: string; description: string; version: string } = JSON.parse(
fs.readFileSync(PkgPath).toString(),
);

const SwaggerConfig: FastifyRegisterOptions<FastifyDynamicSwaggerOptions> = {
openapi: {
info: {
title: PKG.name,
description: PKG.description,
version: PKG.version,
},
},
};

export const initSwagger = (app: FastifyInstance) => {
app.register(fastifySwagger, SwaggerConfig);
app.register(fastifySwaggerUI, {
routePrefix: "/docs",
});
};
74 changes: 32 additions & 42 deletions src/routes/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,20 @@ import {
JobResultType,
AsyncJob200Reply,
AsyncJob200ReplyType,
AsyncJob400Reply,
AsyncJob400ReplyType,
AsyncJobStatusRequest,
AsyncJobStatusRequestType,
AsyncJobStatus200Reply,
AsyncJobStatus200ReplyType,
AsyncJobStatus404Reply,
AsyncJobStatus404ReplyType,
SyncJob200Reply,
SyncJob200ReplyType,
SyncJob400Reply,
SyncJob400ReplyType,
AsyncJob401Reply,
AsyncJob403Reply,
SyncJob401Reply,
SyncJob403Reply,
SyncJob401ReplyType,
SyncJob403ReplyType,
AsyncJob403ReplyType,
AsyncJob401ReplyType,
AsyncJobStatus401Reply,
AsyncJobStatus403Reply,
AsyncJobStatus401ReplyType,
AsyncJobStatus403ReplyType,
JobOperation400Reply,
JobOperation400ReplyType,
JobOperation401Reply,
JobOperation401ReplyType,
JobOperation403Reply,
JobOperation403ReplyType,
JobOperation404Reply,
JobOperation404ReplyType,
} from "../schemas/api";
import { AsyncVisitQueue } from "../queue";
import { syncVisitJob, VisitJobData } from "../jobs/api";
Expand All @@ -54,20 +44,15 @@ export default (
fastify.post<{
Headers: JobDispatchRequestHeadersType;
Body: JobDispatchRequestType;
Reply:
| AsyncJob200ReplyType
| AsyncJob400ReplyType
| AsyncJob401ReplyType
| AsyncJob403ReplyType;
Reply: AsyncJob200ReplyType | JobOperation401ReplyType | JobOperation403ReplyType;
}>("/async-job", {
schema: {
headers: JobDispatchRequestHeaders,
body: JobDispatchRequest,
response: {
200: AsyncJob200Reply,
400: AsyncJob400Reply,
401: AsyncJob401Reply,
403: AsyncJob403Reply,
401: JobOperation401Reply,
403: JobOperation403Reply,
},
},
handler: getAsyncJobHandler(fastify),
Expand All @@ -78,17 +63,17 @@ export default (
Body: JobDispatchRequestType;
Reply:
| SyncJob200ReplyType
| SyncJob400ReplyType
| SyncJob401ReplyType
| SyncJob403ReplyType;
| JobOperation400ReplyType
| JobOperation401ReplyType
| JobOperation403ReplyType;
}>("/sync-job", {
schema: {
body: JobDispatchRequest,
response: {
200: SyncJob200Reply,
400: SyncJob400Reply,
401: SyncJob401Reply,
403: SyncJob403Reply,
400: JobOperation400Reply,
401: JobOperation401Reply,
403: JobOperation403Reply,
},
},
handler: getSyncJobHandler(fastify),
Expand All @@ -98,17 +83,17 @@ export default (
Querystring: AsyncJobStatusRequestType;
Reply:
| AsyncJobStatus200ReplyType
| AsyncJobStatus401ReplyType
| AsyncJobStatus403ReplyType
| AsyncJobStatus404ReplyType;
| JobOperation401ReplyType
| JobOperation403ReplyType
| JobOperation404ReplyType;
}>("/job-status", {
schema: {
querystring: AsyncJobStatusRequest,
response: {
200: AsyncJobStatus200Reply,
401: AsyncJobStatus401Reply,
403: AsyncJobStatus403Reply,
404: AsyncJobStatus404Reply,
401: JobOperation401Reply,
403: JobOperation403Reply,
404: JobOperation404Reply,
},
},
handler: getAsyncJobStatusHandler(fastify),
Expand All @@ -117,7 +102,9 @@ export default (
done();
};

const authenticateDispatchRequest = (request: FastifyRequest) => {
const authenticateDispatchRequest = (
request: FastifyRequest,
): true | JobOperation401ReplyType | JobOperation403ReplyType => {
const data = <JobDispatchRequestType>request.body;
const { authorization } = <JobDispatchRequestHeadersType>request.headers;

Expand All @@ -129,7 +116,7 @@ const authenticateDispatchRequest = (request: FastifyRequest) => {
};
}

const visitURLs = _.map(data.steps, "url");
const visitURLs: string[] = _.map(data.steps, "url");
if (!authenticateVisitToken(authorization, visitURLs)) {
return {
statusCode: 403,
Expand All @@ -142,7 +129,10 @@ const authenticateDispatchRequest = (request: FastifyRequest) => {
return true;
};

const authenticateStatusRequest = (request: FastifyRequest, data: VisitJobData) => {
const authenticateStatusRequest = (
request: FastifyRequest,
data: VisitJobData,
): true | JobOperation401ReplyType | JobOperation403ReplyType => {
const { authorization } = <JobDispatchRequestHeadersType>request.headers;

if (!authorization) {
Expand All @@ -153,7 +143,7 @@ const authenticateStatusRequest = (request: FastifyRequest, data: VisitJobData)
};
}

const visitURLs = _.map(data.steps, "url");
const visitURLs: string[] = _.map(data.steps, "url");
if (!authenticateVisitToken(authorization, visitURLs)) {
return {
statusCode: 403,
Expand Down
Loading

0 comments on commit 884f40c

Please sign in to comment.