diff --git a/package.json b/package.json index 808384f..cbca416 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "backend", - "version": "0.16.4", + "version": "0.17.0", "description": "PoliNetwork backend server", "private": true, "keywords": [], diff --git a/package/package.json b/package/package.json index f5f39b1..7954cc0 100644 --- a/package/package.json +++ b/package/package.json @@ -1,6 +1,6 @@ { "name": "@polinetwork/backend", - "version": "0.16.4", + "version": "0.17.0", "description": "Utils to interact with the backend.", "repository": { "type": "git", diff --git a/src/azure/functions/groups.ts b/src/azure/functions/groups.ts new file mode 100644 index 0000000..d1f3518 --- /dev/null +++ b/src/azure/functions/groups.ts @@ -0,0 +1,52 @@ +import type { Group as TGroup, User as TUser } from "@microsoft/microsoft-graph-types" +import { logger } from "@/logger" +import { withRetry } from "@/utils/wait" +import { client } from "../client" +import type { ParsedGroup } from "../types" + +export type Group = Pick, "id" | "displayName" | "mailNickname" | "mailEnabled"> & { + members: Array, "id" | "displayName">> +} + +export async function getAllGroups(): Promise { + try { + const res: Group[] = await client + .api("/groups?$select=id,displayName,mailNickname,mailEnabled&$expand=members($select=id,displayName)") + .get() + .then((r) => r.value) + return res.map(({ mailNickname, mailEnabled, displayName, ...g }) => ({ + ...g, + displayName: displayName ?? "", + mailAddress: mailEnabled ? `${mailNickname}@polinetwork.org` : null, + members: g.members.map((m) => ({ ...m, displayName: m.displayName ?? "" })), + })) + } catch (error) { + logger.error({ error }, "[MS Graph API] Error in getAllGroups call") + return [] + } +} + +export async function addGroupMember(groupId: string, userId: string): Promise { + try { + const res = withRetry(() => + client.api(`/groups/${groupId}/members/$ref`).post({ + "@odata.id": `https://graph.microsoft.com/v1.0/directoryObjects/${userId}`, + }) + ) + logger.debug({ res, userId, groupId }, "[MS Graph API] OK addGroupMember call") + return true + } catch (error) { + logger.error({ error, userId, groupId }, "[MS Graph API] Error in addGroupMember call") + return false + } +} +export async function removeGroupMember(groupId: string, userId: string): Promise { + try { + withRetry(() => client.api(`/groups/${groupId}/members/${userId}/$ref`).delete()) + logger.debug({ userId, groupId }, "[MS Graph API] OK removeGroupMember call") + return true + } catch (error) { + logger.error({ error, userId, groupId }, "[MS Graph API] Error in removeGroupMember call") + return false + } +} diff --git a/src/azure/types.ts b/src/azure/types.ts index 9fed4c7..69eaaf5 100644 --- a/src/azure/types.ts +++ b/src/azure/types.ts @@ -32,3 +32,13 @@ export type ParsedUser = { isMember: boolean assignedLicensesIds: string[] } + +export type ParsedGroup = { + id: string + displayName: string + mailAddress: string | null + members: Array<{ + id: string + displayName: string + }> +} diff --git a/src/routers/azure/groups.ts b/src/routers/azure/groups.ts new file mode 100644 index 0000000..3f52b11 --- /dev/null +++ b/src/routers/azure/groups.ts @@ -0,0 +1,19 @@ +import { z } from "zod" +import { addGroupMember, getAllGroups, removeGroupMember } from "@/azure/functions/groups" +import { createTRPCRouter, publicProcedure } from "@/trpc" + +export default createTRPCRouter({ + getAll: publicProcedure.query(async () => { + return await getAllGroups() + }), + addMember: publicProcedure + .input(z.object({ groupId: z.string(), userId: z.string() })) + .mutation(async ({ input }) => { + return await addGroupMember(input.groupId, input.userId) + }), + removeMember: publicProcedure + .input(z.object({ groupId: z.string(), userId: z.string() })) + .mutation(async ({ input }) => { + return await removeGroupMember(input.groupId, input.userId) + }), +}) diff --git a/src/routers/azure/index.ts b/src/routers/azure/index.ts index 41f5d69..22568a8 100644 --- a/src/routers/azure/index.ts +++ b/src/routers/azure/index.ts @@ -1,6 +1,8 @@ import { createTRPCRouter } from "@/trpc" +import groups from "./groups" import members from "./members" export const azureRouter = createTRPCRouter({ members, + groups, }) diff --git a/src/routers/web/guides_matricole.ts b/src/routers/web/guides_matricole.ts index 37ffb39..675df60 100644 --- a/src/routers/web/guides_matricole.ts +++ b/src/routers/web/guides_matricole.ts @@ -30,9 +30,10 @@ export default createTRPCRouter({ }), getLatestGuide: publicProcedure.output(guideSchema.nullable()).query(async () => { - const [latestGuide] = await DB.select().from(GUIDES_MATRICOLE).orderBy(desc(GUIDES_MATRICOLE.date)).limit(1) + const res = await DB.select().from(GUIDES_MATRICOLE).orderBy(desc(GUIDES_MATRICOLE.date)).limit(1) + console.log(res, res[0]) - return latestGuide || null + return res[0] || null }), addGuide: publicProcedure diff --git a/src/server.ts b/src/server.ts index ede3118..a0a0214 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,6 +7,7 @@ import z from "zod" import { auth } from "./auth" import { getMembers } from "./azure/functions/members" import "./azure/blob" +import { addGroupMember, getAllGroups, removeGroupMember } from "./azure/functions/groups" import { AUTH_PATH, TRPC_PATH, WS_PATH } from "./constants" import { cron } from "./cron" import { DB, SCHEMA } from "./db" @@ -87,6 +88,25 @@ app.get("/test/members", async (c) => { return c.json({ users }) }) +app.get("/test/azure-groups", async (c) => { + if (env.NODE_ENV === "production") return c.status(500) + + const groups = await getAllGroups() + return c.json({ groups }) +}) + +app.post( + "/test/azure-group-member", + zValidator("json", z.object({ groupId: z.string(), userId: z.string(), mode: z.enum(["add", "remove"]) })), + async (c) => { + if (env.NODE_ENV === "production") return c.status(500) + const { userId, groupId, mode } = c.req.valid("json") + + const ok = mode === "add" ? await addGroupMember(groupId, userId) : await removeGroupMember(groupId, userId) + return c.json({ ok }) + } +) + app.all(`${WS_PATH}/`, (c) => { return wssEngine.handleRequest(c.req.raw, server) })