Skip to content

Commit

Permalink
Merge pull request #259 from utmgdsc/develop
Browse files Browse the repository at this point in the history
Room settings
  • Loading branch information
ggggg authored Nov 9, 2024
2 parents de81b65 + 9734c84 commit 167b493
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "Room" ADD COLUMN "description" TEXT,
ADD COLUMN "needAccess" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "roomRules" TEXT;
19 changes: 11 additions & 8 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ model User {
discordWebhook String?
slackWebhook String?
theme Theme @default(system)
roomApprover Room[] @relation(name: "RoomApprover")
roomApprover Room[] @relation(name: "RoomApprover")
}

enum Theme {
Expand Down Expand Up @@ -83,11 +83,14 @@ model Group {
}

model Room {
roomName String @id // the room number, eg. DH2014
friendlyName String // friendly name, eg. Hacklab
capacity Int? // (optional) occupancy limit
requestLimit Int @default(10) // limit on the number of pending requests per user/group
requests Request[] // all requests made for this room
userAccess User[] @relation("RoomAccess") // all users with access to this room
approvers User[] @relation("RoomApprover") // all users with approver access to this room
roomName String @id // the room number, eg. DH2014
friendlyName String // friendly name, eg. Hacklab
capacity Int? // (optional) occupancy limit
requestLimit Int @default(10) // limit on the number of pending requests per user/group
requests Request[] // all requests made for this room
userAccess User[] @relation("RoomAccess") // all users with access to this room
approvers User[] @relation("RoomApprover") // all users with approver access to this room
needTCardAccess Boolean @default(false) // whether the room requires access to be granted
description String?
roomRules String?
}
7 changes: 5 additions & 2 deletions backend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ app.get('/joan6/:room', async (req, res) => {

if (req.params.room.endsWith('.ical')) {
const cal = ical({
name: 'Events for ' + room.data?.friendlyName,
name: `Events for ${room.data?.friendlyName}`,
});

cal.prodId({
Expand Down Expand Up @@ -83,7 +83,10 @@ app.use(async (req, res, next) => {
const data = await accountsModel.upsertUser({
utorid: req.headers.utorid as string,
email: req.headers.http_mail as string,
name: (req.headers.http_mail as string).split('@')[0],
name:
req.headers.sn && req.headers.givenname
? `${req.headers.givenname} ${req.headers.sn}`
: (req.headers.http_mail as string).split('@')[0],
webhooks: defaultWebhooksSetttings,
slackWebhook: null,
discordWebhook: null,
Expand Down
30 changes: 30 additions & 0 deletions backend/src/api/routes/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,34 @@ router.put(
},
);

router.put('/:rooms/update', permissionMiddleware(PermissionLevel.admin), async (req, res) => {
const { friendlyName, capacity, needTCardAccess, description, roomRules, requestLimit } = req.body;
if (
(capacity !== undefined && isNaN(parseInt(capacity))) ||
capacity === null ||
capacity < 0 ||
(needTCardAccess !== undefined && typeof needTCardAccess !== 'boolean') ||
(description !== undefined && typeof description !== 'string') ||
(roomRules !== undefined && typeof roomRules !== 'string') ||
(requestLimit !== undefined && isNaN(parseInt(requestLimit)))
) {
sendResponse(res, {
status: 400,
message: 'Invalid request.',
});
return;
}
sendResponse(
res,
await roomsModel.setSettings(req.params.rooms, {
friendlyName,
capacity,
needTCardAccess,
description,
roomRules,
requestLimit,
}),
);
});

export default router;
18 changes: 16 additions & 2 deletions backend/src/models/requestsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,21 @@ const requestCounter = () => {
};
};
const validateRequest = async (request: CreateRequest): Promise<ModelResponseError | undefined> => {
if (request.title.trim() === '' || request.description.trim() === '') {
if (
[request.title, request.description, request.roomName].some(
(x) => x === undefined || typeof x !== 'string' || x.trim() === '',
)
) {
return { status: 400, message: 'Missing required fields.' };
}

if (
request.approvers &&
(!Array.isArray(request.approvers) || request.approvers.some((x) => typeof x !== 'string'))
) {
return { status: 400, message: 'Approvers must be a string array.' };
}

const startDate = new Date(request.startDate);
const endDate = new Date(request.endDate);
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
Expand Down Expand Up @@ -98,7 +109,10 @@ const approveRequest = async (id: string, approver: User, reason?: string) => {
return { status: 400, message: 'Request is not pending.' };
}
let status: RequestStatus = RequestStatus.needTCard;
if (request.room.userAccess.some((user: { utorid: string }) => user.utorid === request.author.utorid)) {
if (
!request.room.needTCardAccess ||
request.room.userAccess.some((user: { utorid: string }) => user.utorid === request.author.utorid)
) {
status = RequestStatus.completed;
}
const requestFetched = await db.request.update({
Expand Down
7 changes: 7 additions & 0 deletions backend/src/models/roomsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,11 @@ export default {
throw e;
}
},
setSettings: async (roomName: string, settings: { [key in keyof Omit<Room, 'roomName'>]: Room[key] }) => {
await db.room.update({
where: { roomName },
data: Object.fromEntries(Object.entries(settings).filter(([, value]) => value !== undefined)) as typeof settings,
});
return { status: 200, data: {} };
},
} satisfies Model;
13 changes: 9 additions & 4 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ services:

backend:
restart: always
image: node
image: node:18-alpine
command: ["npm","run", "dev", "--prefix", "/app"]
environment:
- DATABASE_URL=postgres://postgres:example@db:5432/postgres
Expand All @@ -22,11 +22,16 @@ services:
- ./backend:/app

frontend:
image: node
image: node:18-alpine
restart: always
command: ["npm","run", "dev", "--prefix", "/app"]
# command: ["npm","run", "dev", "--prefix", "/app"]
build:
context: ./frontend
dockerfile: dev.dockerfile
ports:
- "3001:3001"
volumes:
- ./frontend:/app
- ./frontend/src:/app/src

db:
image: postgres
Expand Down
14 changes: 14 additions & 0 deletions frontend/dev.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM node:18-alpine

WORKDIR /app

COPY package.json package-lock.json ./

RUN npm i

COPY . ./tmp
RUN rm -rf ./tmp/src
RUN cp -rf ./tmp/* ./
RUN rm -rf ./tmp

CMD ["npm","run", "dev"]
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/components/DateTimePicker/DateTimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface DateTimePickerProps {
/** a react hook that is a function that takes a list of dates, and will set the scheduleDates state */
setScheduleDates: (dates: Date[]) => void;
/** the room that the user is booking */
room: string;
roomName: string;
}

/**
Expand All @@ -23,7 +23,12 @@ interface DateTimePickerProps {
* @property {function} setScheduleDates a react hook that is a function that takes a list of dates, and will set the scheduleDates state
* @returns
*/
export const DateTimePicker = ({ handleScheduleDate, scheduleDates, setScheduleDates, room }: DateTimePickerProps) => {
export const DateTimePicker = ({
handleScheduleDate,
scheduleDates,
setScheduleDates,
roomName,
}: DateTimePickerProps) => {
const [calendarDate, setDate] = useState(dayjs(new Date()));
const [blockedDates, setBlockedDates] = useState<Date[]>([]);
const [pendingDates, setPendingDates] = useState<Date[]>([]);
Expand All @@ -45,7 +50,7 @@ export const DateTimePicker = ({ handleScheduleDate, scheduleDates, setScheduleD

await axios
.get(
`/rooms/${room}/blockedDates?start_date=${startMonday.toISOString()}&end_date=${endDate.toISOString()}`,
`/rooms/${roomName}/blockedDates?start_date=${startMonday.toISOString()}&end_date=${endDate.toISOString()}`,
)
.then(({ data }) => {
setBlockedDates(data as Date[]);
Expand All @@ -72,7 +77,7 @@ export const DateTimePicker = ({ handleScheduleDate, scheduleDates, setScheduleD
useEffect(() => {
handleBlockedDates(calendarDate.toDate());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [calendarDate, room]);
}, [calendarDate, roomName]);

return (
<>
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/RoomPicker/RoomPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { useState, useEffect } from 'react';

interface RoomPickerProps {
/** the name of the room */
roomName: string;
room?: Room;
/** a react useState hook that sets the room name */
setRoomName: (roomName: string) => void;
setRoom: (room?: Room) => void;
}

/**
* A component that allows the user to pick a room from a list of rooms
*/
export const RoomPicker = ({ roomName, setRoomName }: RoomPickerProps) => {
export const RoomPicker = ({ room, setRoom }: RoomPickerProps) => {
/** list of rooms */
const [rooms, setRooms] = useState<Room[]>([]);

Expand All @@ -24,26 +24,26 @@ export const RoomPicker = ({ roomName, setRoomName }: RoomPickerProps) => {
.then((res) => {
setRooms(res.data);
if (res.data.length === 1) {
setRoomName(res.data[0].roomName);
setRoom(res.data[0]);
}
})
.catch((err) => {
console.error(err);
});
})();
}, [setRoomName]);
}, [setRoom]);

return (
<FormControl fullWidth sx={{ marginTop: '1em' }}>
<InputLabel id="room-label">Room</InputLabel>
<Select
labelId="room-label"
id="room-select"
value={roomName}
value={room?.roomName ?? ''}
fullWidth
label="Room"
onChange={(e) => {
setRoomName(e.target.value);
setRoom(rooms.find((x) => x.roomName == e.target.value));
}}
>
{rooms.map((room) => {
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ interface Room {
capacity: number | null;
friendlyName: string;
roomName: string;
needTCardAccess: boolean;
description?: string;
roomRules?: string;
requestLimit?: number;
}

/** Admin only fetched room when ~/api/rooms/:id is called */
Expand Down
Loading

0 comments on commit 167b493

Please sign in to comment.