diff --git a/src/db-access/teams.ts b/src/db-access/teams.ts index 3ce46a8..a735f82 100644 --- a/src/db-access/teams.ts +++ b/src/db-access/teams.ts @@ -1,11 +1,19 @@ import { database } from "@/db/setup/query-postgres"; +import { departmentsTable } from "@/db/tables/departments"; import { teamsTable } from "@/db/tables/teams"; +import { teamUsersTable } from "@/db/tables/users"; import { type OrmResult, ormError } from "@/src/error/orm-error"; import type { Team, TeamKey } from "@/src/response-handling/teams"; -import { eq, inArray } from "drizzle-orm"; +import { count, eq, inArray } from "drizzle-orm"; import type { QueryParameters } from "../request-handling/common"; import { newDatabaseTransaction } from "./common"; +type TeamOverview = { + team: Team; + departmentCity: string; + memberCount: number; +}; + export async function selectTeamsById( teamIds: TeamKey[], ): Promise> { @@ -35,6 +43,7 @@ export const selectActiveTeams = async ( return teams; }); }; + export const selectInActiveTeams = async ( parameters: QueryParameters, ): Promise> => { @@ -49,3 +58,27 @@ export const selectInActiveTeams = async ( return teams; }); }; + +export const selectActiveTeamsOverview = async (): Promise< + OrmResult +> => { + return await newDatabaseTransaction(database, async (tx) => { + const teams = await tx + .select({ + team: teamsTable, + departmentCity: departmentsTable.city, + memberCount: count(teamUsersTable.id), + }) + .from(teamsTable) + .innerJoin( + departmentsTable, + eq(teamsTable.departmentId, departmentsTable.id), + ) + .leftJoin(teamUsersTable, eq(teamUsersTable.teamId, teamsTable.id)) + .where(eq(teamsTable.active, true)) + .groupBy(teamsTable.id, departmentsTable.id) + .orderBy(teamsTable.id); + + return teams; + }); +}; diff --git a/src/routers/teams.ts b/src/routers/teams.ts index 6f3127c..b1274f0 100644 --- a/src/routers/teams.ts +++ b/src/routers/teams.ts @@ -1,5 +1,6 @@ import { selectActiveTeams, + selectActiveTeamsOverview, selectInActiveTeams, selectTeamsById, } from "@/src/db-access/teams"; @@ -88,6 +89,38 @@ teamsRouter.get("/inactive", async (req, res, next) => { res.json(results.data); }); +/** + * @openapi + * /teams/active/overview/: + * get: + * tags: [teams] + * summary: Get all active teams with department name and number of members + * description: Return all active teams with extra info + * parameters: + * - $ref: "#/components/parameters/offset" + * - $ref: "#/components/parameters/limit" + * responses: + * 200: + * description: Successfull response + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/teams" + */ +teamsRouter.get("/active/overview", async (_req, res, next) => { + const results = await selectActiveTeamsOverview(); + if (!results.success) { + return next( + clientError( + 400, + "Failed to retrieve data from the database", + results.error, + ), + ); + } + res.json(results.data); +}); + /** * @openapi * /teams/{teamId}/: diff --git a/src/security.ts b/src/security.ts index 65cfb67..98196f4 100644 --- a/src/security.ts +++ b/src/security.ts @@ -8,11 +8,10 @@ import type { RequestHandler } from "express"; UPDATE THIS COMMENT IF YOU UPDATE THE SECURITY HEADERS */ const SECURITY_HEADERS = { - // "Content-Security-Policy": "default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests", "Cross-Origin-Opener-Policy": "same-origin", - "Cross-Origin-Resource-Policy": "same-origin", + "Cross-Origin-Resource-Policy": "cross-origin", "Origin-Agent-Cluster": "?1", "Referrer-Policy": "no-referrer", "Strict-Transport-Security": "max-age=15552000; includeSubDomains", @@ -30,7 +29,13 @@ export const customHelmetSecurity: RequestHandler = (_req, res, next) => { }; const corsOptions: CorsOptions = { - // origin: 'http://example.com', + origin: [ + "http://localhost:5173", + "https://vektorprogrammet.no", + "https://www.vektorprogrammet.no", + ], + methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], + allowedHeaders: ["Content-Type", "Authorization"], optionsSuccessStatus: 200, };