Skip to content

Commit d4c7ca5

Browse files
jsurreaSnowArtziGotty
authored
Sprint 3 Release - Backend (#29)
This is the release for Sprint 3 --------- Co-authored-by: David Santiago Ortiz Almanza <69651671+SnowArtz@users.noreply.github.com> Co-authored-by: Gotty <78111224+iGotty@users.noreply.github.com>
1 parent 9621503 commit d4c7ca5

33 files changed

+4988
-142
lines changed

.gitignore

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,4 +420,15 @@ $RECYCLE.BIN/
420420
# Windows shortcuts
421421
*.lnk
422422

423-
# End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux,python,jupyternotebooks,java,node
423+
# End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux,python,jupyternotebooks,java,node
424+
425+
# Planner Executable
426+
Planner/planner.exe
427+
428+
# Coverage Report
429+
Planner/coverage.out
430+
Planner/coverage.html
431+
432+
433+
unischedule-5ee93-firebase-adminsdk-ci6y9-6cde344deb.json
434+
unischedule-5ee93-ac56b2db69b3.json

AvailableSpaces/.gcloudignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This file specifies files that are *not* uploaded to Google Cloud
2+
# using gcloud. It follows the same syntax as .gitignore, with the addition of
3+
# "#!include" directives (which insert the entries of the given .gitignore-style
4+
# file at that point).
5+
#
6+
# For more information, run:
7+
# $ gcloud topic gcloudignore
8+
#
9+
.gcloudignore
10+
# If you would like to upload your .git directory, .gitignore file or files
11+
# from your .gitignore file, remove the corresponding line
12+
# below:
13+
.git
14+
.gitignore
15+
16+
# Node.js dependencies:
17+
node_modules/
18+
dist/
19+

AvailableSpaces/README.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# AvailableSpaces Service
2+
3+
The AvailableSpaces service, a critical component of our application, facilitates the retrieval of information regarding available classrooms within specified time frames and days of the week at Universidad de los Andes' campus. Developed in TypeScript using Express.js and Node.js, this service serves as an API endpoint to support scheduling and resource allocation for university activities. It is deployed on Google Cloud Platform's App Engine.
4+
5+
## Installation Guide
6+
7+
To install and run the AvailableSpaces service locally or in your environment, follow these steps:
8+
9+
1. Clone the Repository: `git clone https://github.com/ISIS3510-202410-Team-13/Backend.git`
10+
2. Navigate to the Backend Directory: `cd Backend/AvailableSpaces`
11+
3. Build the Docker Image: `docker build -t available_spaces .`
12+
4. Run the Docker Container: `docker run -d -p 3000:3000 available_spaces`
13+
5. Verify Installation: `docker ps`
14+
15+
## Usage Guide
16+
17+
### Health Checkpoint
18+
19+
Make a GET request to the following endpoint:
20+
21+
```
22+
GET https://available-spaces-dot-unischedule-5ee93.uc.r.appspot.com/health
23+
```
24+
25+
If the service is running correctly, you will receive the message `Available Spaces Server is running`.
26+
27+
### Request Schema
28+
29+
To query the AvailableSpaces service, make a POST request to the following endpoint:
30+
31+
```
32+
POST https://available-spaces-dot-unischedule-5ee93.uc.r.appspot.com/spaces
33+
```
34+
35+
Include the following JSON body in your request:
36+
37+
```json
38+
{
39+
"dayOfWeek": "l|m|i|j|v|s|d",
40+
"startTime": "hhmm",
41+
"endTime": "hhmm"
42+
}
43+
```
44+
45+
46+
- `dayOfWeek`: A single letter string representing the day of the week. Valid values are 'l' (Monday), 'm' (Tuesday), 'i' (Wednesday), 'j' (Thursday), 'v' (Friday), 's' (Saturday), and 'd' (Sunday).
47+
- `startTime`: A four-digit string representing the start time in 24-hour format (e.g., "0800" for 8:00 AM).
48+
- `endTime`: A four-digit string representing the end time in 24-hour format (e.g., "1700" for 5:00 PM).
49+
50+
51+
### Response Schema
52+
53+
The response will be a JSON array containing objects with the following structure:
54+
55+
```json
56+
[
57+
{
58+
"building": "string",
59+
"room": "string",
60+
"availableFrom": "hhmm",
61+
"availableUntil": "hhmm",
62+
"minutesAvailable": "integer"
63+
},
64+
]
65+
```
66+
67+
68+
- `building`: The building code where the space is located (e.g., "ML" for Mario Laserna building).
69+
- `room`: The room number or identifier.
70+
- `availableFrom`: The time the space is available from in 24-hour format.
71+
- `availableUntil`: The time the space is available until in 24-hour format.
72+
- `minutesAvailable`: The duration of availability in minutes.
73+
74+
> Notice that the endpoint returns a list of these objects (an empty list is possible)
75+
76+
### Example Call
77+
78+
```bash
79+
curl -X POST https://available-spaces-dot-unischedule-5ee93.uc.r.appspot.com/spaces \
80+
-H "Content-Type: application/json" \
81+
-d '{"dayOfWeek": "m", "startTime": "0800", "endTime": "1200"}'
82+
```
83+
84+
## Troubleshooting
85+
86+
If you encounter any issues while using the AvailableSpaces service, refer to the following troubleshooting guide to identify and resolve common problems:
87+
88+
### Google's App Engine Bad Request:
89+
90+
You will get the following error if you send any data over the body of a GET request. If you're querying `/health`, remove the body completely; if you're querying `/spaces`, change the method type to POST.
91+
92+
```html
93+
<!DOCTYPE html>
94+
<html lang=en>
95+
<meta charset=utf-8>
96+
<meta name=viewport
97+
content="initial-scale=1, minimum-scale=1, width=device-width">
98+
<title>Error 400 (Bad Request)!!1</title>
99+
<style>
100+
*{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
101+
</style>
102+
<a href=//www.google.com/><span id=logo aria-label=Google></span></a>
103+
<p><b>400.</b> <ins>That’s an error.</ins>
104+
<p>Your client has issued a malformed or illegal request. <ins>That’s all we
105+
know.</ins>
106+
```
107+
108+
### Parameters
109+
110+
You will get the following errors if the parameters of your request are invalid. You can check `src/validate.ts` for more information.
111+
112+
1. Request Body Missing:
113+
114+
- Error Message: `{ "message": "Request body is missing" }`
115+
- Cause: The request sent to the service is missing the JSON body.
116+
- Solution: Ensure that your request includes a valid JSON body with the required parameters.
117+
118+
2. Missing Required Fields:
119+
120+
- Error Message: `{ "message": "Missing required fields in request body" }`
121+
- Cause: The request body is missing one or more required fields (dayOfWeek, startTime, endTime).
122+
- Solution: Include all required fields in the request body before sending the request.
123+
124+
3. Invalid Data Types:
125+
126+
- Error Message: `{ "message": "Invalid data types in request body" }`
127+
- Cause: One or more fields in the request body have invalid data types.
128+
- Solution: Ensure that all fields in the request body have the correct data types (dayOfWeek and time fields should be strings).
129+
130+
4. Invalid Day of Week:
131+
132+
- Error Message: `{ "message": "Invalid day of week" }`
133+
- Cause: The dayOfWeek parameter in the request body is not a valid single-letter string representing a day of the week.
134+
- Solution: Check that the dayOfWeek parameter is a single-letter string ('l', 'm', 'i', 'j', 'v', 's', or 'd').
135+
136+
5. Invalid Time Format:
137+
138+
- Error Message: `{ "message": "Invalid time format" }`
139+
- Cause: The startTime or endTime parameter in the request body does not match the expected four-digit string format (hhmm).
140+
- Solution: Ensure that the time parameters follow the four-digit string format (e.g., "0800" for 8:00 AM).
141+
142+
6. Start Time Must be Before End Time:
143+
144+
- Error Message: `{ "message": "Start time must be before end time" }`
145+
- Cause: The startTime parameter is equal to or later than the endTime parameter in the request body.
146+
- Solution: Adjust the startTime and endTime parameters to ensure that the start time comes before the end time.
147+
148+
7. Invalid Time Range:
149+
150+
- Error Message: `{ "message": "Invalid time range" }`
151+
- Cause: The startTime or endTime parameter in the request body is outside the valid time range (00:00 - 23:59).
152+
- Solution: Verify that the time parameters fall within the valid range (00:00 - 23:59).
153+
154+
8. Invalid Minutes:
155+
156+
- Error Message: `{ "message": "Invalid minutes" }`
157+
- Cause: The minutes portion of the startTime or endTime parameter in the request body is greater than 59.
158+
- Solution: Ensure that the minutes portion of the time parameters does not exceed 59.

AvailableSpaces/app.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
runtime: nodejs18
2+
service: "available-spaces"
3+
env: standard
4+
instance_class: B1
5+
manual_scaling:
6+
instances: 1
7+

AvailableSpaces/data/fallback_courses.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

AvailableSpaces/src/index.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import express, { Express, Request, Response } from "express";
2-
import dotenv from "dotenv";
32
import { MeetingRequest, AvailableSpace } from "./types";
43
import { findAvailableSpaces } from "./service";
54
import { validateBody } from "./validate";
65

7-
dotenv.config();
8-
96
const app: Express = express();
10-
const port = Number(process.env.PORT) || 3000;
11-
const host = process.env.HOST || "localhost";
7+
const port = process.env.PORT || 3000;
128

139
app.use(express.json());
14-
app.use(validateBody);
1510

16-
app.get("/", async (req: Request<{}, {}, MeetingRequest>, res: Response<AvailableSpace[]>) => {
11+
app.get("/health", (req: Request, res: Response) => {
12+
res.status(200).send("Available Spaces Server is running").end();
13+
})
14+
15+
app.post("/spaces", validateBody)
16+
app.post("/spaces", async (req: Request<{}, {}, MeetingRequest>, res: Response<AvailableSpace[]>) => {
1717
const { dayOfWeek, startTime, endTime } = req.body;
1818
const availableSpaces: AvailableSpace[] = await findAvailableSpaces(dayOfWeek, startTime, endTime);
19-
res.send(availableSpaces);
19+
res.status(200).send(availableSpaces).end();
2020
});
2121

22-
app.listen(port, host, () => {
23-
console.log(`[available-spaces] 🚀 Available Spaces Server is running at http://${host}:${port}`);
22+
app.listen(port, () => {
23+
console.log(`[available-spaces] 🚀 Available Spaces Server is running at https://localhost:${port}`);
2424
});

AvailableSpaces/src/service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ const joinOverlappingTimeBlocks = (timeBlocks: TimeBlock[]): TimeBlock[] => {
6666
const fetchUniandesAPI = () => {
6767
axios.get('https://ofertadecursos.uniandes.edu.co/api/courses')
6868
.then(response => response.data)
69+
.catch(error => {
70+
console.error('[available-spaces] 🚨 Falling back to local data:', error)
71+
return require('../data/fallback_courses.json');
72+
})
6973
.then((data: UniandesCourseSection[]) => data.flatMap(sectionToReservation))
7074
.then(reservations => reservations.filter(reservation => buildingsWhiteList.includes(reservation!.building)))
7175
.then(reservations => roomsReservations = mergeReservationsByRoom(reservations as RoomReservations[]))
@@ -96,7 +100,7 @@ const calculateAvailableSpace = (roomReservations: RoomReservations, dayOfWeek:
96100
}
97101
return acc;
98102
}, 0);
99-
const endAvailable = dayReservations.reverse().reduce((acc, reservation) => {
103+
const endAvailable = dayReservations.sort((a, b) => a.endMinute - b.endMinute).reverse().reduce((acc, reservation) => {
100104
if (endMinute <= reservation.startMinute) {
101105
return reservation.startMinute;
102106
}

AvailableSpaces/src/validate.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,46 @@
11
import { Request, Response, NextFunction } from "express";
22

33
const validateBody = (req: Request, res: Response, next: NextFunction) => {
4+
45
if (!req.body) {
5-
res.status(400).send({ message: 'Request body is missing' });
6+
res.status(400).send({ message: 'Request body is missing' }).end();
67
return;
78
}
89

910
const { dayOfWeek, startTime, endTime } = req.body;
1011

1112
if (dayOfWeek === undefined || startTime === undefined || endTime === undefined) {
12-
res.status(400).send({ message: 'Missing required fields in request body' });
13+
res.status(400).send({ message: 'Missing required fields in request body' }).end();
1314
return;
1415
}
1516

1617
if (typeof dayOfWeek !== "string" || typeof startTime !== "string" || typeof endTime !== "string") {
17-
res.status(400).send({ message: 'Invalid data types in request body' });
18+
res.status(400).send({ message: 'Invalid data types in request body' }).end();
1819
return;
1920
}
2021

2122
if (dayOfWeek.length !== 1 || !["l", "m", "i", "j", "v", "s", "d"].includes(dayOfWeek)) {
22-
res.status(400).send({ message: 'Invalid day of week' });
23+
res.status(400).send({ message: 'Invalid day of week' }).end();
2324
return;
2425
}
2526

2627
if (!/^\d{4}$/.test(startTime) || !/^\d{4}$/.test(endTime)) {
27-
res.status(400).send({ message: 'Invalid time format' });
28+
res.status(400).send({ message: 'Invalid time format' }).end();
2829
return;
2930
}
3031

3132
if (startTime >= endTime) {
32-
res.status(400).send({ message: 'Start time must be before end time' });
33+
res.status(400).send({ message: 'Start time must be before end time' }).end();
3334
return;
3435
}
3536

3637
if (startTime < "0000" || startTime >= "2400" || endTime < "0000" || endTime >= "2400") {
37-
res.status(400).send({ message: 'Invalid time range' });
38+
res.status(400).send({ message: 'Invalid time range' }).end();
3839
return;
3940
}
4041

4142
if (startTime.slice(-2) > "59" || endTime.slice(-2) > "59") {
42-
res.status(400).send({ message: 'Invalid minutes' });
43+
res.status(400).send({ message: 'Invalid minutes' }).end();
4344
return;
4445
}
4546

0 commit comments

Comments
 (0)