Skip to content

Commit

Permalink
Sync report api (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
lcampos authored Jul 18, 2024
1 parent 50c28ad commit 3772cf6
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
127 changes: 127 additions & 0 deletions apps/api/src/routes/sync-report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import type { Multipart } from "@fastify/multipart";
import * as Sentry from "@sentry/node";
import { ApiError, Problems } from "@zuplo/errors";
import { randomUUID } from "crypto";
import { type FastifyPluginAsync } from "fastify";
import { assertValidFileExtension } from "../lib/types.js";
import validateOpenapi, {
checkFileIsJsonOrYaml,
} from "../lib/validate-openapi.js";
import { postSlackMessage } from "../services/slack.js";
import { getStorageBucketName, getStorageClient } from "../services/storage.js";
import { generateRatingFromStorage } from "../lib/rating.js";

const syncReportRoute: FastifyPluginAsync = async function (server) {
server.route({
method: "POST",
// @TODO - will add a schema after agreeing on final format
url: "/sync-report",
handler: async (request, reply) => {
const parts = request.parts();
let parseResult: ParseMultipartUploadResult;
try {
parseResult = await parseMultipartUpload(parts);
} catch (err) {
Sentry.captureException(err);
await postSlackMessage({
text: `Failed to parse uploaded file with error: ${
err.detail ?? err.message
}. Request ID: ${request.id}. ${
request.headers["api-user"]
? `API User: ${request.headers["api-user"]}`
: ""
}}`,
});
throw err;
}

// If not an API key user, error out
if (!request.headers["api-user"]) {
throw new ApiError({
...Problems.FORBIDDEN,
detail: "Please provide an api key.",
});
}

const reportId = randomUUID();
const { fileContentString: content, fileExtension } = parseResult;

try {
assertValidFileExtension(fileExtension);
} catch (err) {
throw new ApiError({
...Problems.BAD_REQUEST,
detail: err.message,
});
}

const fileName = `${reportId}.${fileExtension}`;

await getStorageClient()
.bucket(getStorageBucketName())
.file(fileName)
.save(content);

const results = await generateRatingFromStorage({
reportId,
fileExtension,
});

return reply.code(200).send({
results,
reportId,
reportUrl: `https://ratemyopenapi.com/rating/${reportId}`,
});
},
});
};

type ParseMultipartUploadResult = {
fileContentString: string;
fileExtension: string;
};

export async function parseMultipartUpload(
parts: AsyncIterableIterator<Multipart>,
): Promise<ParseMultipartUploadResult> {
let fileContent;
for await (const part of parts) {
if (part.type === "file") {
fileContent = await part.toBuffer();
}
}

if (!fileContent) {
throw new ApiError({
...Problems.BAD_REQUEST,
detail: `Invalid request body, no file content.`,
});
}
const fileContentString = fileContent.toString();

const fileIsJsonOrYamlResult = checkFileIsJsonOrYaml(fileContentString);
if (typeof fileIsJsonOrYamlResult !== "string") {
throw new ApiError({
...fileIsJsonOrYamlResult,
detail: `${fileIsJsonOrYamlResult.detail}.`,
});
}

const openApiValidationResult = validateOpenapi({
fileContent: fileContentString,
fileExtension: fileIsJsonOrYamlResult,
});
if (!openApiValidationResult.isValid) {
throw new ApiError({
...openApiValidationResult.error,
detail: `${openApiValidationResult.error.detail}. File extension: ${fileIsJsonOrYamlResult}.`,
});
}

return {
fileContentString,
fileExtension: fileIsJsonOrYamlResult,
};
}

export default syncReportRoute;
2 changes: 2 additions & 0 deletions apps/api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import healthRoute from "./routes/health.js";
import { reportRoute } from "./routes/report.js";
import uploadRoute from "./routes/upload.js";
import { aiFixRoute } from "./routes/ai-fix.js";
import syncReportRoute from "./routes/sync-report.js";

const fastify = Fastify({
logger: createNewLogger(),
Expand All @@ -47,6 +48,7 @@ async function build() {
await fastify.register(reportRoute);
await fastify.register(fileRoute);
await fastify.register(aiFixRoute);
await fastify.register(syncReportRoute);
}

const start = async () => {
Expand Down

0 comments on commit 3772cf6

Please sign in to comment.