From ca36c277dbf61bfd2203565a9af2b291a4597487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Berg=C3=A9?= Date: Fri, 23 Aug 2024 09:41:39 +0200 Subject: [PATCH] feat: add auth project endpoint --- .../api/handlers/getAuthProject.e2e.test.ts | 55 +++++++++++++++++++ .../src/api/handlers/getAuthProject.ts | 19 +++++++ apps/backend/src/api/index.ts | 2 + apps/backend/src/api/schema.ts | 22 ++++++++ apps/backend/src/api/util.ts | 18 +++--- 5 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 apps/backend/src/api/handlers/getAuthProject.e2e.test.ts create mode 100644 apps/backend/src/api/handlers/getAuthProject.ts diff --git a/apps/backend/src/api/handlers/getAuthProject.e2e.test.ts b/apps/backend/src/api/handlers/getAuthProject.e2e.test.ts new file mode 100644 index 000000000..42375a6e3 --- /dev/null +++ b/apps/backend/src/api/handlers/getAuthProject.e2e.test.ts @@ -0,0 +1,55 @@ +import request from "supertest"; +import { beforeEach, describe, expect, it } from "vitest"; + +import type { Build, Project } from "@/database/models/index.js"; +import { factory, setupDatabase } from "@/database/testing/index.js"; + +import { createTestHandlerApp } from "../test-util"; +import { getAuthProject } from "./getAuthProject"; + +const app = createTestHandlerApp(getAuthProject); + +describe("getAuthProject", () => { + let project: Project; + let builds: Build[]; + + beforeEach(async () => { + await setupDatabase(); + project = await factory.Project.create({ + token: "the-awesome-token", + }); + builds = await factory.Build.createMany(3, { + projectId: project.id, + name: "default", + }); + // Sort builds by id desc + builds.sort((a: Build, b: Build) => b.id.localeCompare(a.id)); + }); + + describe("without a valid token", () => { + it("returns 401 status code", async () => { + await request(app) + .get("/project") + .set("Authorization", "Bearer invalid-token") + .expect((res) => { + expect(res.text).toBe( + `Project not found in Argos. If the issue persists, verify your token. (token: "invalid-token").`, + ); + }) + .expect(401); + }); + }); + + it("returns a project", async () => { + await request(app) + .get("/project") + .set("Authorization", "Bearer the-awesome-token") + .expect(200) + .expect((res) => { + expect(res.body).toEqual({ + id: project.id, + defaultBaseBranch: "main", + }); + }); + }); +}); diff --git a/apps/backend/src/api/handlers/getAuthProject.ts b/apps/backend/src/api/handlers/getAuthProject.ts new file mode 100644 index 000000000..d976b183d --- /dev/null +++ b/apps/backend/src/api/handlers/getAuthProject.ts @@ -0,0 +1,19 @@ +import { repoAuth } from "@/web/middlewares/repoAuth.js"; +import { boom } from "@/web/util.js"; + +import { CreateAPIHandler } from "../util.js"; + +export const getAuthProject: CreateAPIHandler = ({ get }) => { + return get("/project", repoAuth, async (req, res) => { + if (!req.authProject) { + throw boom(401, "Unauthorized"); + } + + const referenceBranch = await req.authProject.$getReferenceBranch(); + + res.send({ + id: req.authProject.id, + defaultBaseBranch: referenceBranch, + }); + }); +}; diff --git a/apps/backend/src/api/index.ts b/apps/backend/src/api/index.ts index 4b9eb1d5e..16771a12a 100644 --- a/apps/backend/src/api/index.ts +++ b/apps/backend/src/api/index.ts @@ -2,6 +2,7 @@ import cors from "cors"; import { Router } from "express"; import { stringify } from "yaml"; +import { getAuthProject } from "./handlers/getAuthProject.js"; import { getAuthProjectBuilds } from "./handlers/getAuthProjectBuilds.js"; import { schema } from "./schema.js"; import { errorHandler, registerHandler } from "./util.js"; @@ -24,6 +25,7 @@ router.get("/openapi.yaml", (_req, res) => { }); // Register the handlers. +registerHandler(router, getAuthProject); registerHandler(router, getAuthProjectBuilds); // Error handlers diff --git a/apps/backend/src/api/schema.ts b/apps/backend/src/api/schema.ts index e8516bcc4..295922be6 100644 --- a/apps/backend/src/api/schema.ts +++ b/apps/backend/src/api/schema.ts @@ -37,6 +37,11 @@ const invalidParameters = createErrorResponse("Invalid parameters"); const unauthorized = createErrorResponse("Unauthorized"); const serverError = createErrorResponse("Server error"); +const Project = z.object({ + id: z.string(), + defaultBaseBranch: z.string(), +}); + const Build = z .object({ id: z.string(), @@ -113,6 +118,23 @@ export const zodSchema = { }, security: [{ tokenAuth: [] }], paths: { + "/project": { + get: { + operationId: "getAuthProject", + responses: { + "200": { + description: "Project", + content: { + "application/json": { + schema: Project, + }, + }, + }, + "401": unauthorized, + "500": serverError, + }, + }, + }, "/project/builds": { get: { operationId: "getAuthProjectBuilds", diff --git a/apps/backend/src/api/util.ts b/apps/backend/src/api/util.ts index ba1f7cdf5..ca8e15606 100644 --- a/apps/backend/src/api/util.ts +++ b/apps/backend/src/api/util.ts @@ -87,16 +87,18 @@ export const errorHandler: ErrorRequestHandler = ( type HandlerParams = (T | T[])[]; -type GetAPIHandler = ( - path: TPath, - ...handlers: HandlerParams> -) => void; +type Context = { + get( + path: TPath, + ...handlers: HandlerParams> + ): void; +}; /** * Register a GET handler. */ -function get(router: Router) { - const apiHandler: GetAPIHandler = (path, ...handlers) => { +function get(router: Router) { + const apiHandler: Context["get"] = (path, ...handlers) => { const operation: ZodOpenApiOperationObject = zodSchema.paths[path].get; const wrappedHandlers = handlers.map((handler) => typeof handler === "function" @@ -144,10 +146,6 @@ function get(router: Router) { return apiHandler; } -type Context = { - get: GetAPIHandler; -}; - export type CreateAPIHandler = (context: Context) => void; /**