Skip to content

Commit

Permalink
Sprint 3 Release - Backend (#29)
Browse files Browse the repository at this point in the history
This is the release for Sprint 3

---------

Co-authored-by: David Santiago Ortiz Almanza <[email protected]>
Co-authored-by: Gotty <[email protected]>
  • Loading branch information
3 people authored Apr 27, 2024
1 parent 9621503 commit d4c7ca5
Show file tree
Hide file tree
Showing 33 changed files with 4,988 additions and 142 deletions.
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -420,4 +420,15 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux,python,jupyternotebooks,java,node
# End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux,python,jupyternotebooks,java,node

# Planner Executable
Planner/planner.exe

# Coverage Report
Planner/coverage.out
Planner/coverage.html


unischedule-5ee93-firebase-adminsdk-ci6y9-6cde344deb.json
unischedule-5ee93-ac56b2db69b3.json
19 changes: 19 additions & 0 deletions AvailableSpaces/.gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# This file specifies files that are *not* uploaded to Google Cloud
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore

# Node.js dependencies:
node_modules/
dist/

158 changes: 158 additions & 0 deletions AvailableSpaces/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# AvailableSpaces Service

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.

## Installation Guide

To install and run the AvailableSpaces service locally or in your environment, follow these steps:

1. Clone the Repository: `git clone https://github.com/ISIS3510-202410-Team-13/Backend.git`
2. Navigate to the Backend Directory: `cd Backend/AvailableSpaces`
3. Build the Docker Image: `docker build -t available_spaces .`
4. Run the Docker Container: `docker run -d -p 3000:3000 available_spaces`
5. Verify Installation: `docker ps`

## Usage Guide

### Health Checkpoint

Make a GET request to the following endpoint:

```
GET https://available-spaces-dot-unischedule-5ee93.uc.r.appspot.com/health
```

If the service is running correctly, you will receive the message `Available Spaces Server is running`.

### Request Schema

To query the AvailableSpaces service, make a POST request to the following endpoint:

```
POST https://available-spaces-dot-unischedule-5ee93.uc.r.appspot.com/spaces
```

Include the following JSON body in your request:

```json
{
"dayOfWeek": "l|m|i|j|v|s|d",
"startTime": "hhmm",
"endTime": "hhmm"
}
```


- `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).
- `startTime`: A four-digit string representing the start time in 24-hour format (e.g., "0800" for 8:00 AM).
- `endTime`: A four-digit string representing the end time in 24-hour format (e.g., "1700" for 5:00 PM).


### Response Schema

The response will be a JSON array containing objects with the following structure:

```json
[
{
"building": "string",
"room": "string",
"availableFrom": "hhmm",
"availableUntil": "hhmm",
"minutesAvailable": "integer"
},
]
```


- `building`: The building code where the space is located (e.g., "ML" for Mario Laserna building).
- `room`: The room number or identifier.
- `availableFrom`: The time the space is available from in 24-hour format.
- `availableUntil`: The time the space is available until in 24-hour format.
- `minutesAvailable`: The duration of availability in minutes.

> Notice that the endpoint returns a list of these objects (an empty list is possible)
### Example Call

```bash
curl -X POST https://available-spaces-dot-unischedule-5ee93.uc.r.appspot.com/spaces \
-H "Content-Type: application/json" \
-d '{"dayOfWeek": "m", "startTime": "0800", "endTime": "1200"}'
```

## Troubleshooting

If you encounter any issues while using the AvailableSpaces service, refer to the following troubleshooting guide to identify and resolve common problems:

### Google's App Engine Bad Request:

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.

```html
<!DOCTYPE html>
<html lang=en>
<meta charset=utf-8>
<meta name=viewport
content="initial-scale=1, minimum-scale=1, width=device-width">
<title>Error 400 (Bad Request)!!1</title>
<style>
*{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}
</style>
<a href=//www.google.com/><span id=logo aria-label=Google></span></a>
<p><b>400.</b> <ins>That’s an error.</ins>
<p>Your client has issued a malformed or illegal request. <ins>That’s all we
know.</ins>
```

### Parameters

You will get the following errors if the parameters of your request are invalid. You can check `src/validate.ts` for more information.

1. Request Body Missing:

- Error Message: `{ "message": "Request body is missing" }`
- Cause: The request sent to the service is missing the JSON body.
- Solution: Ensure that your request includes a valid JSON body with the required parameters.

2. Missing Required Fields:

- Error Message: `{ "message": "Missing required fields in request body" }`
- Cause: The request body is missing one or more required fields (dayOfWeek, startTime, endTime).
- Solution: Include all required fields in the request body before sending the request.

3. Invalid Data Types:

- Error Message: `{ "message": "Invalid data types in request body" }`
- Cause: One or more fields in the request body have invalid data types.
- Solution: Ensure that all fields in the request body have the correct data types (dayOfWeek and time fields should be strings).

4. Invalid Day of Week:

- Error Message: `{ "message": "Invalid day of week" }`
- Cause: The dayOfWeek parameter in the request body is not a valid single-letter string representing a day of the week.
- Solution: Check that the dayOfWeek parameter is a single-letter string ('l', 'm', 'i', 'j', 'v', 's', or 'd').

5. Invalid Time Format:

- Error Message: `{ "message": "Invalid time format" }`
- Cause: The startTime or endTime parameter in the request body does not match the expected four-digit string format (hhmm).
- Solution: Ensure that the time parameters follow the four-digit string format (e.g., "0800" for 8:00 AM).

6. Start Time Must be Before End Time:

- Error Message: `{ "message": "Start time must be before end time" }`
- Cause: The startTime parameter is equal to or later than the endTime parameter in the request body.
- Solution: Adjust the startTime and endTime parameters to ensure that the start time comes before the end time.

7. Invalid Time Range:

- Error Message: `{ "message": "Invalid time range" }`
- Cause: The startTime or endTime parameter in the request body is outside the valid time range (00:00 - 23:59).
- Solution: Verify that the time parameters fall within the valid range (00:00 - 23:59).

8. Invalid Minutes:

- Error Message: `{ "message": "Invalid minutes" }`
- Cause: The minutes portion of the startTime or endTime parameter in the request body is greater than 59.
- Solution: Ensure that the minutes portion of the time parameters does not exceed 59.
7 changes: 7 additions & 0 deletions AvailableSpaces/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
runtime: nodejs18
service: "available-spaces"
env: standard
instance_class: B1
manual_scaling:
instances: 1

1 change: 1 addition & 0 deletions AvailableSpaces/data/fallback_courses.json

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions AvailableSpaces/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import express, { Express, Request, Response } from "express";
import dotenv from "dotenv";
import { MeetingRequest, AvailableSpace } from "./types";
import { findAvailableSpaces } from "./service";
import { validateBody } from "./validate";

dotenv.config();

const app: Express = express();
const port = Number(process.env.PORT) || 3000;
const host = process.env.HOST || "localhost";
const port = process.env.PORT || 3000;

app.use(express.json());
app.use(validateBody);

app.get("/", async (req: Request<{}, {}, MeetingRequest>, res: Response<AvailableSpace[]>) => {
app.get("/health", (req: Request, res: Response) => {
res.status(200).send("Available Spaces Server is running").end();
})

app.post("/spaces", validateBody)
app.post("/spaces", async (req: Request<{}, {}, MeetingRequest>, res: Response<AvailableSpace[]>) => {
const { dayOfWeek, startTime, endTime } = req.body;
const availableSpaces: AvailableSpace[] = await findAvailableSpaces(dayOfWeek, startTime, endTime);
res.send(availableSpaces);
res.status(200).send(availableSpaces).end();
});

app.listen(port, host, () => {
console.log(`[available-spaces] 🚀 Available Spaces Server is running at http://${host}:${port}`);
app.listen(port, () => {
console.log(`[available-spaces] 🚀 Available Spaces Server is running at https://localhost:${port}`);
});
6 changes: 5 additions & 1 deletion AvailableSpaces/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ const joinOverlappingTimeBlocks = (timeBlocks: TimeBlock[]): TimeBlock[] => {
const fetchUniandesAPI = () => {
axios.get('https://ofertadecursos.uniandes.edu.co/api/courses')
.then(response => response.data)
.catch(error => {
console.error('[available-spaces] 🚨 Falling back to local data:', error)
return require('../data/fallback_courses.json');
})
.then((data: UniandesCourseSection[]) => data.flatMap(sectionToReservation))
.then(reservations => reservations.filter(reservation => buildingsWhiteList.includes(reservation!.building)))
.then(reservations => roomsReservations = mergeReservationsByRoom(reservations as RoomReservations[]))
Expand Down Expand Up @@ -96,7 +100,7 @@ const calculateAvailableSpace = (roomReservations: RoomReservations, dayOfWeek:
}
return acc;
}, 0);
const endAvailable = dayReservations.reverse().reduce((acc, reservation) => {
const endAvailable = dayReservations.sort((a, b) => a.endMinute - b.endMinute).reverse().reduce((acc, reservation) => {
if (endMinute <= reservation.startMinute) {
return reservation.startMinute;
}
Expand Down
17 changes: 9 additions & 8 deletions AvailableSpaces/src/validate.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,46 @@
import { Request, Response, NextFunction } from "express";

const validateBody = (req: Request, res: Response, next: NextFunction) => {

if (!req.body) {
res.status(400).send({ message: 'Request body is missing' });
res.status(400).send({ message: 'Request body is missing' }).end();
return;
}

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

if (dayOfWeek === undefined || startTime === undefined || endTime === undefined) {
res.status(400).send({ message: 'Missing required fields in request body' });
res.status(400).send({ message: 'Missing required fields in request body' }).end();
return;
}

if (typeof dayOfWeek !== "string" || typeof startTime !== "string" || typeof endTime !== "string") {
res.status(400).send({ message: 'Invalid data types in request body' });
res.status(400).send({ message: 'Invalid data types in request body' }).end();
return;
}

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

if (!/^\d{4}$/.test(startTime) || !/^\d{4}$/.test(endTime)) {
res.status(400).send({ message: 'Invalid time format' });
res.status(400).send({ message: 'Invalid time format' }).end();
return;
}

if (startTime >= endTime) {
res.status(400).send({ message: 'Start time must be before end time' });
res.status(400).send({ message: 'Start time must be before end time' }).end();
return;
}

if (startTime < "0000" || startTime >= "2400" || endTime < "0000" || endTime >= "2400") {
res.status(400).send({ message: 'Invalid time range' });
res.status(400).send({ message: 'Invalid time range' }).end();
return;
}

if (startTime.slice(-2) > "59" || endTime.slice(-2) > "59") {
res.status(400).send({ message: 'Invalid minutes' });
res.status(400).send({ message: 'Invalid minutes' }).end();
return;
}

Expand Down
Loading

0 comments on commit d4c7ca5

Please sign in to comment.