From 0d11e87ec804e326a596c2798bfd5a641a67cac1 Mon Sep 17 00:00:00 2001 From: Ludvig Date: Tue, 5 Mar 2024 00:46:30 +0100 Subject: [PATCH 1/4] Refactor door access query and fix overflow design --- src/lib/utils/member.ts | 210 ++++++++++-------- .../(app)/members/[studentId]/+page.server.ts | 21 +- .../(app)/members/[studentId]/+page.svelte | 27 +-- .../members/[studentId]/DoorAccess.svelte | 31 +++ 4 files changed, 164 insertions(+), 125 deletions(-) create mode 100644 src/routes/(app)/members/[studentId]/DoorAccess.svelte diff --git a/src/lib/utils/member.ts b/src/lib/utils/member.ts index ba5a16277..644fe27d6 100644 --- a/src/lib/utils/member.ts +++ b/src/lib/utils/member.ts @@ -1,6 +1,6 @@ import type { PrismaClient } from "@prisma/client"; -import { getDerivedRoles } from "./authorization"; import { error } from "@sveltejs/kit"; +import { getDerivedRoles } from "./authorization"; export const getCustomAuthorOptions = async ( prisma: PrismaClient, @@ -39,18 +39,30 @@ export const getCustomAuthorOptions = async ( }); }; +export type MemberDoorPolicies = { + name: string; + roles: string[]; + startDate: Date | null; + endDate: Date | null; +}[]; + export const getCurrentDoorPoliciesForMember = async ( prisma: PrismaClient, - memberId: string, + studentId: string, ) => { - const [memberResult] = await Promise.allSettled([ - prisma.member.findUnique({ - where: { - id: memberId, + const memberPositionIds = await prisma.position + .findMany({ + select: { + id: true, + name: true, + boardMember: true, }, - include: { + where: { mandates: { - where: { + some: { + member: { + studentId, + }, startDate: { lte: new Date(), }, @@ -58,98 +70,110 @@ export const getCurrentDoorPoliciesForMember = async ( gte: new Date(), }, }, - include: { - position: {}, - }, }, - doorAccessPolicies: {}, }, - }), - ]); - - if (memberResult.status === "rejected") { - throw error(500, "Could not fetch member"); - } - if (!memberResult.value) { - throw error(404, "Member not found"); - } - - const member = memberResult.value; - const allDoorPolicies = await prisma.doorAccessPolicy.findMany(); - - const roles = member.doorAccessPolicies.map((d) => d.role).filter(notEmpty); - const positions = ( - await prisma.position.findMany({ + }) + .catch(() => { + throw error(500, "Could not fetch member positions"); + }); + const userDoorPolicies = await prisma.doorAccessPolicy + .findMany({ where: { - id: { - in: roles, - }, + AND: [ + { + // is active, or indefinite + OR: [ + { + startDatetime: null, + }, + { + startDatetime: { + lte: new Date(), + }, + }, + ], + }, + { + // is active, or indefinite + OR: [ + { + endDatetime: null, + }, + { + endDatetime: { + gte: new Date(), + }, + }, + ], + }, + { + OR: [ + { + studentId, // is for this user + }, + { + role: { + in: getDerivedRoles( + memberPositionIds.map((pos) => pos.id), + true, + ).concat( + memberPositionIds.some((pos) => pos.boardMember) + ? ["dsek.styr"] + : [], + ), + }, + }, + ], + }, + ], }, }) - ).concat(member.mandates.map((m) => m.position)); - - // Map a doorname to roles, startDate and endDate - const allMemberDoors = new Map< - string, - { - roles: string[]; - startDate: Date | null; - endDate: Date | null; - } - >(); + .catch(() => { + throw error(500, "Could not fetch door access"); + }); - allDoorPolicies - .filter((doorPolicy) => - member.mandates.some( - (mandate) => - // A doorpolicy is associated with either a role or a specific member - (doorPolicy.role && mandate.positionId.startsWith(doorPolicy.role)) || - doorPolicy.studentId === member.studentId, - ), - ) - .forEach((doorPolicy) => { - // Get a nice name for a position instead of using the id - const positionNamesFromMandates = positions - .filter( - (position) => - doorPolicy.role && position.id.startsWith(doorPolicy.role), + const policiesByDoor: MemberDoorPolicies = userDoorPolicies.reduce( + (acc, policy) => { + const role = policy.role ?? "Du"; + const duplicate = acc.find( + (p) => + p.name === policy.doorName && + p.startDate === policy.startDatetime && + p.endDate === policy.endDatetime, + ); + if (duplicate) { + duplicate.roles.push(role); + return acc; + } + acc.push({ + name: policy.doorName, + roles: [role], + startDate: policy.startDatetime, + endDate: policy.endDatetime, + }); + return acc; + }, + [] as MemberDoorPolicies, + ); + const memberDoorPolicies: MemberDoorPolicies = policiesByDoor.map( + (policy) => { + const positionsMappedToThisDoor = memberPositionIds + .filter((pos) => + policy.roles.some( + (role) => + pos.id.startsWith(role) || + (pos.boardMember && role === "dsek.styr"), + ), ) - .map((position) => position.name); - const positionNames: string[] = - positionNamesFromMandates.length > 0 - ? positionNamesFromMandates - : ["Du"]; - - const oldData = allMemberDoors.get(doorPolicy.doorName); - const newData = oldData ?? { - roles: [...positionNames], - startDate: doorPolicy.startDatetime, - endDate: doorPolicy.endDatetime, + .map((pos) => pos.name); + positionsMappedToThisDoor.sort(); + return { + ...policy, + roles: positionsMappedToThisDoor || ["Du"], }; - if (oldData) { - // Remove duplicates - newData.roles = [...new Set(oldData.roles.concat(...positionNames))]; - - if (doorPolicy.startDatetime) { - newData.startDate = - oldData.startDate === null || - doorPolicy.startDatetime > oldData.startDate - ? oldData.startDate - : doorPolicy.startDatetime; - } - if (doorPolicy.endDatetime) { - newData.endDate = - oldData.endDate === null || doorPolicy.endDatetime < oldData.endDate - ? oldData.endDate - : doorPolicy.endDatetime; - } - } - allMemberDoors.set(doorPolicy.doorName, newData); - }); + }, + ); + memberDoorPolicies.sort((a, b) => a.name.localeCompare(b.name)); - return allMemberDoors; + return memberDoorPolicies; }; - -function notEmpty(value: TValue | null | undefined): value is TValue { - return value !== null && value !== undefined; -} diff --git a/src/routes/(app)/members/[studentId]/+page.server.ts b/src/routes/(app)/members/[studentId]/+page.server.ts index d4dc65c8d..c0152b20a 100644 --- a/src/routes/(app)/members/[studentId]/+page.server.ts +++ b/src/routes/(app)/members/[studentId]/+page.server.ts @@ -9,11 +9,12 @@ import { getCurrentDoorPoliciesForMember } from "$lib/utils/member"; import keycloak from "$lib/server/keycloak"; export const load: PageServerLoad = async ({ locals, params }) => { - const { user, prisma } = locals; + const { prisma, user } = locals; + const { studentId } = params; const [memberResult, publishedArticlesResult] = await Promise.allSettled([ prisma.member.findUnique({ where: { - studentId: params.studentId, + studentId: studentId, }, include: { mandates: { @@ -43,7 +44,7 @@ export const load: PageServerLoad = async ({ locals, params }) => { where: { author: { member: { - studentId: params.studentId, + studentId: studentId, }, }, removedAt: null, @@ -64,22 +65,26 @@ export const load: PageServerLoad = async ({ locals, params }) => { throw error(404, "Member not found"); } const member = memberResult.value; - const allMemberDoors = await getCurrentDoorPoliciesForMember( - prisma, - member.id, - ); + + const doorAccess = + member.id === user?.memberId + ? await getCurrentDoorPoliciesForMember(prisma, studentId) + : (new Map() as unknown as Awaited< + ReturnType + >); const email = member.studentId !== null ? await keycloak.getEmail(member.studentId) : undefined; + try { return { form: await superValidate(member, memberSchema), pingForm: await superValidate(emptySchema), viewedMember: member, // https://github.com/Dsek-LTH/web/issues/194 - allMemberDoors, + doorAccess, publishedArticles: publishedArticlesResult.value ?? [], email, ping: user diff --git a/src/routes/(app)/members/[studentId]/+page.svelte b/src/routes/(app)/members/[studentId]/+page.svelte index 033cdfbfa..7172b526f 100644 --- a/src/routes/(app)/members/[studentId]/+page.svelte +++ b/src/routes/(app)/members/[studentId]/+page.svelte @@ -1,4 +1,5 @@ + +
Dörraccess
+{#each doorAccess as doorPolicy (doorPolicy.name + doorPolicy.startDate + doorPolicy.endDate)} +
+
+ {doorPolicy.name} + {#if doorPolicy.startDate != null || doorPolicy.endDate != null} + + {doorPolicy.startDate?.toLocaleDateString("sv-SE") ?? ""} + - + {doorPolicy.endDate?.toLocaleDateString("sv-SE") ?? ""} + + {/if} +
+
+
+ {#each doorPolicy.roles as role (role)} + {role} + {/each} +
+
+
+{/each} From 5f60b0d33495d9796463816ed57d0dc4f4873a31 Mon Sep 17 00:00:00 2001 From: Ludvig Date: Wed, 6 Mar 2024 16:26:37 +0100 Subject: [PATCH 2/4] Fix invalid truth chaining --- src/lib/utils/member.ts | 5 ++++- src/routes/(app)/members/[studentId]/+page.server.ts | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/utils/member.ts b/src/lib/utils/member.ts index 644fe27d6..b757c8e55 100644 --- a/src/lib/utils/member.ts +++ b/src/lib/utils/member.ts @@ -169,7 +169,10 @@ export const getCurrentDoorPoliciesForMember = async ( positionsMappedToThisDoor.sort(); return { ...policy, - roles: positionsMappedToThisDoor || ["Du"], + roles: + positionsMappedToThisDoor.length > 0 + ? positionsMappedToThisDoor + : ["Du"], }; }, ); diff --git a/src/routes/(app)/members/[studentId]/+page.server.ts b/src/routes/(app)/members/[studentId]/+page.server.ts index c0152b20a..9987abe07 100644 --- a/src/routes/(app)/members/[studentId]/+page.server.ts +++ b/src/routes/(app)/members/[studentId]/+page.server.ts @@ -69,9 +69,7 @@ export const load: PageServerLoad = async ({ locals, params }) => { const doorAccess = member.id === user?.memberId ? await getCurrentDoorPoliciesForMember(prisma, studentId) - : (new Map() as unknown as Awaited< - ReturnType - >); + : []; const email = member.studentId !== null From 84fe388f70099c26bd945d160c741def1a150c07 Mon Sep 17 00:00:00 2001 From: IsakKallini Date: Tue, 16 Apr 2024 21:04:26 +0200 Subject: [PATCH 3/4] Fix lint error --- src/lib/utils/member.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/utils/member.ts b/src/lib/utils/member.ts index b757c8e55..348a0ac9a 100644 --- a/src/lib/utils/member.ts +++ b/src/lib/utils/member.ts @@ -39,12 +39,12 @@ export const getCustomAuthorOptions = async ( }); }; -export type MemberDoorPolicies = { +export type MemberDoorPolicies = Array<{ name: string; roles: string[]; startDate: Date | null; endDate: Date | null; -}[]; +}>; export const getCurrentDoorPoliciesForMember = async ( prisma: PrismaClient, From 2eb4b218870b759cc3aa17d19b08f1495ca926da Mon Sep 17 00:00:00 2001 From: Isak Kallini Date: Wed, 24 Apr 2024 11:43:36 +0200 Subject: [PATCH 4/4] Use verbose door name --- src/lib/utils/member.ts | 5 +++++ src/routes/(app)/members/[studentId]/+page.server.ts | 1 - src/routes/(app)/members/[studentId]/+page.svelte | 2 +- src/routes/(app)/members/[studentId]/DoorAccess.svelte | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/utils/member.ts b/src/lib/utils/member.ts index 348a0ac9a..4f515d76c 100644 --- a/src/lib/utils/member.ts +++ b/src/lib/utils/member.ts @@ -41,6 +41,7 @@ export const getCustomAuthorOptions = async ( export type MemberDoorPolicies = Array<{ name: string; + verboseName: string | undefined; roles: string[]; startDate: Date | null; endDate: Date | null; @@ -132,6 +133,8 @@ export const getCurrentDoorPoliciesForMember = async ( throw error(500, "Could not fetch door access"); }); + const doors = await prisma.door.findMany(); + const policiesByDoor: MemberDoorPolicies = userDoorPolicies.reduce( (acc, policy) => { const role = policy.role ?? "Du"; @@ -147,6 +150,8 @@ export const getCurrentDoorPoliciesForMember = async ( } acc.push({ name: policy.doorName, + verboseName: doors.find((door) => door.name == policy.doorName) + ?.verboseName, roles: [role], startDate: policy.startDatetime, endDate: policy.endDatetime, diff --git a/src/routes/(app)/members/[studentId]/+page.server.ts b/src/routes/(app)/members/[studentId]/+page.server.ts index 9987abe07..9858662a3 100644 --- a/src/routes/(app)/members/[studentId]/+page.server.ts +++ b/src/routes/(app)/members/[studentId]/+page.server.ts @@ -76,7 +76,6 @@ export const load: PageServerLoad = async ({ locals, params }) => { ? await keycloak.getEmail(member.studentId) : undefined; - try { return { form: await superValidate(member, memberSchema), diff --git a/src/routes/(app)/members/[studentId]/+page.svelte b/src/routes/(app)/members/[studentId]/+page.svelte index 7172b526f..2eedb44ed 100644 --- a/src/routes/(app)/members/[studentId]/+page.svelte +++ b/src/routes/(app)/members/[studentId]/+page.svelte @@ -136,7 +136,7 @@
{#if data.doorAccess.length > 0} diff --git a/src/routes/(app)/members/[studentId]/DoorAccess.svelte b/src/routes/(app)/members/[studentId]/DoorAccess.svelte index dd1eebb55..df9c3fd81 100644 --- a/src/routes/(app)/members/[studentId]/DoorAccess.svelte +++ b/src/routes/(app)/members/[studentId]/DoorAccess.svelte @@ -9,7 +9,7 @@ class="my-2 flex items-center justify-between gap-4 rounded-lg bg-base-200 p-3" >
- {doorPolicy.name} + {doorPolicy.verboseName} {#if doorPolicy.startDate != null || doorPolicy.endDate != null} {doorPolicy.startDate?.toLocaleDateString("sv-SE") ?? ""}