Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit 301ed87

Browse files
committed
add: permission checks to application-round pages
1 parent 1467ca1 commit 301ed87

5 files changed

Lines changed: 93 additions & 42 deletions

File tree

apps/admin-ui/src/hooks/usePermission.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
UnitNode,
44
ReservationNode,
55
UserNode,
6+
ApplicationRoundNode,
67
} from "common/types/gql-types";
78
import {
89
hasPermission as baseHasPermission,
@@ -83,6 +84,24 @@ const usePermission = () => {
8384
return baseHasAnyPermission(user);
8485
};
8586

87+
// TODO restrict the Permission type to only those that are applicable to application rounds
88+
const hasApplicationRoundPermission = (
89+
applicationRound: ApplicationRoundNode,
90+
permission: Permission
91+
) => {
92+
if (!user) return false;
93+
const units = filterNonNullable(
94+
applicationRound.reservationUnits.flatMap((ru) => ru.unit)
95+
);
96+
for (const unit of units) {
97+
if (hasUnitPermission(user, permission, unit)) {
98+
return true;
99+
}
100+
}
101+
return false;
102+
};
103+
104+
// TODO this is becoming convoluted with the addition of a new function for each object type
86105
return {
87106
user,
88107
hasPermission: (
@@ -94,6 +113,7 @@ const usePermission = () => {
94113
hasAnyPermission,
95114
hasUnitPermission: (permission: Permission, unit: UnitNode) =>
96115
hasUnitPermission(user, permission, unit),
116+
hasApplicationRoundPermission,
97117
};
98118
};
99119

apps/admin-ui/src/modules/permissionHelper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export enum Permission {
2020
CAN_MANAGE_RESOURCES = GeneralPermissionChoices.CanManageResources,
2121
CAN_MANAGE_UNITS = GeneralPermissionChoices.CanManageUnits,
2222
CAN_VALIDATE_APPLICATIONS = GeneralPermissionChoices.CanValidateApplications,
23+
CAN_MANAGE_APPLICATIONS = GeneralPermissionChoices.CanHandleApplications,
2324
CAN_MANAGE_BANNER_NOTIFICATIONS = GeneralPermissionChoices.CanManageNotifications,
2425
}
2526
/* eslint-enable @typescript-eslint/prefer-literal-enum-member */

apps/admin-ui/src/spa/recurring-reservations/application-rounds/[id]/allocation/index.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ function AllocationWrapper({
589589

590590
const { t } = useTranslation();
591591
const { hasUnitPermission } = usePermission();
592+
const { hasApplicationRoundPermission } = usePermission();
592593

593594
// TODO don't use spinners, skeletons are better
594595
// also this blocks the sub component query (the initial with zero filters) which slows down the page load
@@ -603,8 +604,24 @@ function AllocationWrapper({
603604
return <p>{t("errors.errorFetchingData")}</p>;
604605
}
605606

606-
const appRound = data?.applicationRound;
607-
const reservationUnits = filterNonNullable(appRound?.reservationUnits);
607+
const { applicationRound } = data ?? {};
608+
609+
// should never be null but our codegen causes type problems
610+
const canManage =
611+
applicationRound != null
612+
? hasApplicationRoundPermission(
613+
applicationRound,
614+
Permission.CAN_MANAGE_APPLICATIONS
615+
)
616+
: false;
617+
618+
if (!canManage) {
619+
return <div>{t("errors.noPermission")}</div>;
620+
}
621+
622+
const reservationUnits = filterNonNullable(
623+
applicationRound?.reservationUnits
624+
);
608625
const unitData = reservationUnits.map((ru) => ru?.unit);
609626

610627
// TODO name sort fails with numbers because 11 < 2
@@ -614,7 +631,7 @@ function AllocationWrapper({
614631
)
615632
.sort((a, b) => a?.nameFi?.localeCompare(b?.nameFi ?? "") ?? 0);
616633

617-
const roundName = appRound?.nameFi ?? "-";
634+
const roundName = applicationRound?.nameFi ?? "-";
618635

619636
const resUnits = uniqBy(filterNonNullable(reservationUnits), "pk").sort(
620637
(a, b) => a?.nameFi?.localeCompare(b?.nameFi ?? "") ?? 0
@@ -624,13 +641,13 @@ function AllocationWrapper({
624641
<>
625642
<BreadcrumbWrapper backLink=".." />
626643
<ApplicationRoundAllocation
627-
applicationRound={appRound ?? undefined}
644+
applicationRound={applicationRound ?? undefined}
628645
applicationRoundPk={applicationRoundPk}
629646
units={units}
630647
reservationUnits={resUnits}
631648
roundName={roundName}
632649
applicationRoundStatus={
633-
appRound?.status ?? ApplicationRoundStatusChoice.Upcoming
650+
applicationRound?.status ?? ApplicationRoundStatusChoice.Upcoming
634651
}
635652
/>
636653
</>

apps/admin-ui/src/spa/recurring-reservations/application-rounds/[id]/index.tsx

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,38 @@ import React from "react";
22
import { useQuery } from "@apollo/client";
33
import { useParams } from "react-router-dom";
44
import { useTranslation } from "react-i18next";
5-
import { type Query } from "common/types/gql-types";
5+
import {
6+
type Query,
7+
type QueryApplicationRoundArgs,
8+
} from "common/types/gql-types";
69
import { useNotification } from "@/context/NotificationContext";
710
import BreadcrumbWrapper from "@/component/BreadcrumbWrapper";
811
import Loader from "@/component/Loader";
912
import { Review } from "./review/Review";
1013
import { APPLICATION_ROUND_QUERY } from "../queries";
14+
import usePermission from "@/hooks/usePermission";
15+
import { Permission } from "@/modules/permissionHelper";
16+
import { base64encode } from "common/src/helpers";
1117

12-
function ApplicationRound({
13-
applicationRoundId,
14-
}: {
15-
applicationRoundId: number;
16-
}): JSX.Element | null {
18+
function ApplicationRound({ pk }: { pk: number }): JSX.Element {
1719
const { notifyError } = useNotification();
1820
const { t } = useTranslation();
1921

20-
const { data, loading: isLoading } = useQuery<Query>(
21-
APPLICATION_ROUND_QUERY,
22-
{
23-
skip: !applicationRoundId,
24-
variables: {
25-
pk: [applicationRoundId],
26-
},
27-
onError: () => {
28-
notifyError(t("errors.errorFetchingData"));
29-
},
30-
}
31-
);
32-
const applicationRound = data?.applicationRounds?.edges?.[0]?.node;
22+
const id = base64encode(`ApplicationRoundNode:${pk}`);
23+
const { data, loading: isLoading } = useQuery<
24+
Query,
25+
QueryApplicationRoundArgs
26+
>(APPLICATION_ROUND_QUERY, {
27+
skip: !pk,
28+
variables: { id },
29+
onError: () => {
30+
notifyError(t("errors.errorFetchingData"));
31+
},
32+
});
33+
34+
const { applicationRound } = data ?? {};
35+
36+
const { hasApplicationRoundPermission } = usePermission();
3337

3438
if (isLoading) {
3539
return <Loader />;
@@ -39,6 +43,18 @@ function ApplicationRound({
3943
return <div>{t("errors.applicationRoundNotFound")}</div>;
4044
}
4145

46+
const canView = hasApplicationRoundPermission(
47+
applicationRound,
48+
Permission.CAN_VALIDATE_APPLICATIONS
49+
);
50+
const canManage = hasApplicationRoundPermission(
51+
applicationRound,
52+
Permission.CAN_MANAGE_APPLICATIONS
53+
);
54+
if (!canView && !canManage) {
55+
return <div>{t("errors.noPermission")}</div>;
56+
}
57+
4258
const route = [
4359
{
4460
alias: t("breadcrumb.recurring-reservations"),
@@ -70,10 +86,12 @@ function ApplicationRoundRouted(): JSX.Element | null {
7086
const { t } = useTranslation();
7187
const { applicationRoundId } = useParams<IParams>();
7288

73-
if (!applicationRoundId || Number.isNaN(Number(applicationRoundId))) {
74-
return <div>{t("errors.router.invalidApplicationRoundNumber")}</div>;
89+
const pk = Number(applicationRoundId);
90+
if (pk > 0) {
91+
return <ApplicationRound pk={pk} />;
7592
}
76-
return <ApplicationRound applicationRoundId={Number(applicationRoundId)} />;
93+
94+
return <div>{t("errors.router.invalidApplicationRoundNumber")}</div>;
7795
}
7896

7997
export default ApplicationRoundRouted;

apps/admin-ui/src/spa/recurring-reservations/application-rounds/queries.tsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,18 @@ export const APPLICATION_ROUNDS_QUERY = gql`
3232
}
3333
`;
3434

35-
// TODO replace with relay query
3635
export const APPLICATION_ROUND_QUERY = gql`
3736
${APPLICATION_ROUND_FRAGMENT}
38-
query ApplicationRound($pk: [Int]!) {
39-
applicationRounds(pk: $pk) {
40-
edges {
41-
node {
42-
...ApplicationRoundFragment
43-
applicationsCount
44-
reservationUnits {
45-
pk
46-
nameFi
47-
unit {
48-
pk
49-
nameFi
50-
}
51-
}
37+
query ApplicationRound($id: ID!) {
38+
applicationRound(id: $id) {
39+
...ApplicationRoundFragment
40+
applicationsCount
41+
reservationUnits {
42+
pk
43+
nameFi
44+
unit {
45+
pk
46+
nameFi
5247
}
5348
}
5449
}

0 commit comments

Comments
 (0)