Skip to content

Commit

Permalink
Merge pull request #247 from utmgdsc/develop
Browse files Browse the repository at this point in the history
dev
  • Loading branch information
logonoff authored Nov 7, 2024
2 parents f6ab2bd + ba882a7 commit 197c00f
Show file tree
Hide file tree
Showing 56 changed files with 4,573 additions and 37,847 deletions.
16 changes: 14 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Checks

on: [push, pull_request]
jobs:
build:
backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Expand All @@ -10,4 +11,15 @@ jobs:
- name: Run ESLint
run: npm run eslint . --fix --prefix ./backend
- name: Build typescript
run: npm run build --prefix ./backend
run: npm run build --prefix ./backend

frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install modules
run: npm ci --prefix ./frontend
- name: Run ESLint
run: npm run lint --prefix ./frontend
- name: Build typescript
run: npm run build --prefix ./frontend
37 changes: 37 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
DOCKER := docker

COMPOSE_VERSION := $(shell $(DOCKER) compose --version 2>/dev/null)

check_docker:
ifndef COMPOSE_VERSION
$(error docker-compose is not installed. Please install docker-compose)
endif

# install dependencies for Intellisense to work,
# not needed for running the app (project is dockerized)
init:
cd ./backend && npm ci
cd ./frontend && npm ci

# remove output files
clean:
cd ./frontend && rm -rf node_modules
cd ./backend && rm -rf node_modules

# prod docker
prod_up: check_docker
$(DOCKER) compose -f "docker-compose.yml" up -d --build

prod_down: check_docker
$(DOCKER) compose -f "docker-compose.yml" down

prod_restart: docker_down docker_up

# dev docker (includes hot reload for backend and frontend)
dev_up: check_docker
$(DOCKER) compose -f "docker-compose.dev.yml" up -d --build

dev_down: check_docker
$(DOCKER) compose -f "docker-compose.dev.yml" down

dev_restart: dev_down dev_up
4 changes: 2 additions & 2 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node
FROM node:18-alpine

RUN npm install -g typescript

Expand All @@ -10,4 +10,4 @@ COPY . .

RUN npm run build

CMD ["bash", "prod.sh"]
CMD ["sh", "prod.sh"]
2 changes: 1 addition & 1 deletion backend/src/api/routes/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ router.delete('/:id', async (req, res) => {
sendResponse(res, await requestsModel.setRequestStatus(req.params.id, req.user, RequestStatus.cancelled));
});
router.put('/:id/approve', permissionMiddleware(PermissionLevel.approver), async (req, res) => {
sendResponse(res, await requestsModel.approveRequest(req.params.id, req.body.reason));
sendResponse(res, await requestsModel.approveRequest(req.params.id, req.user, req.body.reason));
});
router.put('/:id/deny', permissionMiddleware(PermissionLevel.staff), async (req, res) => {
sendResponse(
Expand Down
6 changes: 6 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ common.db
logger.error(`${e.name}\n${e.message}\n${e.stack}`);
process.exit(1);
});

process.on('exit', () => {
common.db.$disconnect().then(() => {
process.exit(0);
});
});
168 changes: 95 additions & 73 deletions backend/src/models/requestsModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccountRole, RequestStatus, User, Request } from '@prisma/client';
import { AccountRole, RequestStatus, User } from '@prisma/client';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
import db from '../common/db';
import logger from '../common/logger';
Expand All @@ -7,13 +7,12 @@ import Model from '../types/Model';
import { ModelResponseError } from '../types/ModelResponse';
import { triggerAdminNotification, triggerMassNotification, triggerTCardNotification } from '../notifications';
import EventTypes from '../types/EventTypes';

import { BaseBookingContext } from '../types/NotificationContext';
import { userSelector } from './utils';
import {
generateBaseRequestNotificationContext as generateBaseNotificationContext,
generateUserActionContext,
} from '../notifications/generateContext';

const requestCounter = () => {
return {
select: {
Expand Down Expand Up @@ -75,6 +74,83 @@ const validateRequest = async (request: CreateRequest): Promise<ModelResponseErr
};
}
};

const approveRequest = async (id: string, approver: User, reason?: string) => {
const request = await db.request.findUnique({
where: { id },
include: {
room: {
include: {
userAccess: { select: { utorid: true } },
approvers: { select: { utorid: true } },
},
},
author: { select: { utorid: true } },
},
});
if (!request) {
return { status: 404, message: 'Request ID not found.' };
}
if (!request.room.approvers.some((user: { utorid: string }) => user.utorid === approver.utorid)) {
return { status: 403, message: 'You cannot approve requests for this room.' };
}
if (request.status !== RequestStatus.pending) {
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)) {
status = RequestStatus.completed;
}
const requestFetched = await db.request.update({
where: { id: request.id },
data: {
reason,
status: status,
},
include: {
group: true,
room: true,
author: true,
},
});
const { startDate, endDate, roomName } = request;
await db.request.updateMany({
where: {
OR: [
{ startDate: { lte: startDate }, endDate: { gte: endDate } },
{
startDate: { gte: startDate, lte: endDate },
},
{
endDate: { gte: startDate, lte: endDate },
},
],
roomName: roomName,
status: RequestStatus.pending,
},
data: {
status: RequestStatus.denied,
},
});
const context = {
...(await generateBaseNotificationContext(requestFetched)),
changer_utorid: approver.utorid,
changer_full_name: approver.name,
status,
reason,
};
await triggerMassNotification(EventTypes.BOOKING_STATUS_CHANGED, [requestFetched.author], context);
await triggerAdminNotification(EventTypes.ADMIN_BOOKING_STATUS_CHANGED, context);
if (status !== RequestStatus.completed) {
await triggerTCardNotification(EventTypes.ROOM_ACCESS_REQUESTED, {
...generateUserActionContext(requestFetched.author),
room: requestFetched.room.roomName,
room_friendly: requestFetched.room.friendlyName,
});
}
return { status: 200, data: {} };
};

export default {
getRequests: async (filters: { [key: string]: string }, user: User) => {
const query: { [key: string]: unknown } = {};
Expand Down Expand Up @@ -173,7 +249,10 @@ export default {
newRequest.approvers = newRequest.approvers || [];
const userFetched = await db.user.findUnique({
where: { utorid: newRequest.authorUtorid },
include: { groups: { select: { id: true } } },
include: {
groups: { select: { id: true } },
roomApprover: { select: { roomName: true } },
},
});
if (!userFetched) {
return { status: 404, message: 'User not found.' };
Expand All @@ -185,6 +264,7 @@ export default {
};
}
request.approvers = request.approvers ?? [];

try {
const madeRequest = await db.request.create({
data: {
Expand All @@ -206,7 +286,11 @@ export default {
},
});
const context = await generateBaseNotificationContext(madeRequest);
await triggerMassNotification(EventTypes.BOOKING_APPROVAL_REQUESTED, request.approvers, context);
if (userFetched.roomApprover.map((x) => x.roomName).includes(request.roomName)) {
await approveRequest(madeRequest.id, user, 'Request automatically accepted by approver');
} else {
await triggerMassNotification(EventTypes.BOOKING_APPROVAL_REQUESTED, request.approvers, context);
}
await triggerAdminNotification(EventTypes.ADMIN_BOOKING_CREATED, context);
// Delete approver data from response
const tempRequest: WithOptional<typeof madeRequest, 'approvers'> = madeRequest;
Expand Down Expand Up @@ -255,6 +339,7 @@ export default {
include: {
group: { include: { managers: { select: { utorid: true } } } },
author: { select: { utorid: true } },
room: { include: { approvers: { select: { utorid: true } } } },
},
});
if (!request) {
Expand All @@ -271,8 +356,9 @@ export default {
};
}
if (
status === RequestStatus.denied &&
!(<AccountRole[]>[AccountRole.approver, AccountRole.admin]).includes(user.role)
(status === RequestStatus.denied &&
!(<AccountRole[]>[AccountRole.approver, AccountRole.admin]).includes(user.role)) ||
!request.room.approvers.some((x) => x.utorid == user.utorid)
) {
return {
status: 403,
Expand All @@ -299,72 +385,8 @@ export default {
await triggerAdminNotification(EventTypes.ADMIN_BOOKING_STATUS_CHANGED, context);
return { status: 200, data: {} };
},
approveRequest: async (id: string, reason?: string) => {
const request = await db.request.findUnique({
where: { id },
include: {
room: { include: { userAccess: { select: { utorid: true } } } },
author: { select: { utorid: true } },
},
});
if (!request) {
return { status: 404, message: 'Request ID not found.' };
}
if (request.status !== RequestStatus.pending) {
return { status: 400, message: 'Request is not pending.' };
}
let status: RequestStatus = RequestStatus.needTCard;
if (request.room.userAccess.some((user) => user.utorid === request.author.utorid)) {
status = RequestStatus.completed;
}
const requestFetched = await db.request.update({
where: { id },
data: {
reason,
status: status,
},
include: {
group: true,
room: true,
author: true,
},
});
const { startDate, endDate, roomName } = request;
await db.request.updateMany({
where: {
OR: [
{ startDate: { lte: startDate }, endDate: { gte: endDate } },
{
startDate: { gte: startDate, lte: endDate },
},
{
endDate: { gte: startDate, lte: endDate },
},
],
roomName: roomName,
status: RequestStatus.pending,
},
data: {
status: RequestStatus.denied,
},
});
const context = {
...(await generateBaseNotificationContext(requestFetched)),
changer_utorid: requestFetched.author.utorid,
changer_full_name: requestFetched.author.name,
status,
reason,
};
await triggerMassNotification(EventTypes.BOOKING_STATUS_CHANGED, [requestFetched.author], context);
await triggerAdminNotification(EventTypes.ADMIN_BOOKING_STATUS_CHANGED, context);
if (status !== RequestStatus.completed) {
await triggerTCardNotification(EventTypes.ROOM_ACCESS_REQUESTED, {
...generateUserActionContext(requestFetched.author),
room: requestFetched.room.roomName,
room_friendly: requestFetched.room.friendlyName,
});
}
return { status: 200, data: {} };
approveRequest: async (id: string, approver: User, reason?: string) => {
return approveRequest(id, approver, reason);
},
updateRequest: async (
request: CreateRequest & {
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
nginx:
restart: always
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
nginx:
restart: always
Expand Down Expand Up @@ -27,6 +25,8 @@ services:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:80"
environment:
- NODE_ENV=production

Expand Down
23 changes: 23 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local
build

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
15 changes: 9 additions & 6 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
FROM node:18-alpine
RUN npm install -g serve
FROM node:lts AS builder

WORKDIR /frontend
COPY package*.json ./
RUN npm install
WORKDIR /

COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

CMD [ "serve", "-s", "build" ]
FROM httpd:2.4 AS runner

COPY --from=builder /dist/ /usr/local/apache2/htdocs/

EXPOSE 80
Loading

0 comments on commit 197c00f

Please sign in to comment.