Skip to content

Commit

Permalink
Merge pull request #13 from Propo41/feature/DEV-7
Browse files Browse the repository at this point in the history
DEV-7: Make generating room.ts file generic to make the app usable by different organizations
  • Loading branch information
ali-ahnaf committed Sep 11, 2024
2 parents 7eb496f + e44b554 commit 0c22f63
Show file tree
Hide file tree
Showing 19 changed files with 247 additions and 221 deletions.
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
SQLITE_DB=bookify_db.sqlite
TYPEORM_CLI=true
APP_PORT=3000
NODE_ENV=development
APP_DOMAIN=http://localhost:3000

OAUTH_CLIENT_SECRET=<place-yours>
OAUTH_CLIENT_ID=<place-yours>
OAUTH_REDIRECT_URL=<place-yours>
JWT_SECRET=<place-yours>
143 changes: 33 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,51 @@
![image](https://github.com/user-attachments/assets/c624fbc6-5673-4c85-ae4a-74d298b73089)

# Get started
1. Place the `.env.development` file or `.env` file in the root dir
2. To obtain the list of meeting spaces that are allocated for your organization, use the [Google directory API](https://developers.google.com/admin-sdk/directory/reference/rest/v1/resources.calendars/list?apix_params=%7B%22customer%22%3A%22my_customer%22%2C%22maxResults%22%3A20%7D) to obtain the list and format them according to `src/calender/interfaces/room.interface.ts`. Finally place the file as `rooms.ts` file in `src/config`.
3. Run `npm run migration:run` to create the migrations
4. Run the app using: `npm run start:dev`
5. Run the client using `npm run start:client`
1. Copy the `.env.example` file as `.env` file in the root dir and fill the required keys. Obtain the OAuth credentials by following this [guide](#hosting-yourself)
2. Run `npm run migration:run` to create the migrations
3. Run the app using: `npm run start:dev`


### List available rooms
# Use cases

#### CASE I: Quick Client Meeting Scheduling

```bash
curl
--location --globoff '{{baseUrl}}/rooms' \
--header 'Authorization: Bearer <token>'
```
Scenario: A team member gets a sudden request to set up a meeting in a conference room.
### Book room
Action: The team member opens the tool, selects the start time and minimum number of seats required, and optionally chooses a specific floor if needed.
```bash
curl
--location --globoff '{{baseUrl}}/room' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
"startTime": "2024-08-31T10:30:00+06:00",
"duration": 30,
"seats": 1,
"floor": 1,
"createConference": true,
"title": "Quick meeting API",
"attendees": []
}'
Outcome: A suitable meeting room is booked immediately, saving the hassle of running through a bunch of options from the Google Calender.
```

### Update room

```bash
# todo
#### CASE II: Overrunning Meeting
```
Scenario: A team/team member is running a meeting in a room X that exceeds the scheduled time, and they need to find another room to continue without interruptions.
Action: The team member opens the tool, and has the option to either increase the time of the current room (if no collisions exists) or quickly book another room with just a click
### Delete room
Outcome: The system quickly books a room, and the team transitions smoothly without the hassle of manually browsing for room availability.
```

```bash
#### CASE III: Floor-Specific Room Requirement
```
*Scenario*: A manager prefers to book rooms on a particular floor to maintain proximity to their team.
curl
--location --globoff --request DELETE '{{baseUrl}}/room' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
"eventId": "4r4bddp2bfkgg1tic1vh84sit8"
}'
Action: The manager uses the tool, inputs the necessary seats, and specifies the floor.
Outcome: The tool books a room on the specified floor, optimizing convenience for the manager and their team.
```
#### CASE IV: Booking During a High-Demand Period
```
Scenario: During a peak time, meeting rooms are in high demand. Manually searching for a room would take time.
## Todo
Action: The user enters their seat requirements and start time. The tool searches and books the best available room.
- add some sort of mutex or a buffer to prevent race conditions
Outcome: A room is secured swiftly, even in high-demand periods, preventing frustration and delays.
```


## Github actions
# Github actions

The following env secrets needs to be configured in the github repository:

Expand All @@ -73,7 +57,7 @@ SQLITE_DB=
TYPEORM_CLI=
```

## Deployment
# Deployment

Make sure to create the following environment secrets in the Azure App service:

Expand All @@ -89,7 +73,7 @@ TYPEORM_CLI=
APP_DOMAIN=
```

### Sqlite file restore & backup
# Sqlite file restore & backup

From the Azure portal, head over to SSH and copy the sqlite file from `/home/site/wwwroot/bookify.sqlite` to `/home/bookify.sqlite`.

Expand All @@ -106,75 +90,14 @@ docker exec -it <containerid> sh # to enter a container's bash
mysql -uroot -proot # to enter mysql
```

### Migrations

Once you get into production you'll need to synchronize model changes into the database. Typically, it is unsafe to use `synchronize: true` for schema synchronization on production once you get data in your database. Here is where migrations come to help.

A migration is just a single file with sql queries to update a database schema and apply new changes to an existing database. There are two methods you must fill with your migration code: **up** and **down**. up has to contain the code you need to perform the migration. down has to revert whatever up changed. down method is used to revert the last migration.
# Hosting yourself

More:

- [NestJs Database](https://docs.nestjs.com/techniques/database)
- [TypeORM](https://typeorm.io/migration)

### Creating new migrations

Let's say we want to change the User.username to User.fullname. We would run:
```bash
npm run migration:create --name=UserNameChange
```
After you run the command you can see a new file generated in the "migration" directory named `{TIMESTAMP}-UserNameChange.ts` where `{TIMESTAMP}` is the current timestamp when the migration was generated. Now you can open the file and add your migration sql queries there.

```ts
import { MigrationInterface, QueryRunner } from "typeorm"

export class UserNameChangeTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "users" RENAME COLUMN "username" TO "fullname"`,
)
}

async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "users" RENAME COLUMN "fullname" TO "username"`,
) // reverts things made in "up" method
}
}
```

### Running migrations

Once you have a migration to run on production, you can run them using a CLI command:

```ts
npm run migration:run
```

**Note**: The `migration:run` and `migration:revert` commands only work on .js files. Thus the typescript files need to be compiled before running the commands. Alternatively, you can use `ts-node` in conjunction with `typeorm` to run .ts migration files. This has already been done in the `package.json`

This command will execute all pending migrations and run them in a sequence ordered by their timestamps. This means all sql queries written in the up methods of your created migrations will be executed. That's all! Now you have your database schema up-to-date.

If for some reason you want to revert the changes, you can run:

```ts
npm run migration:revert
```
This command will execute down in the latest executed migration. If you need to revert multiple migrations you must call this command multiple times.



### Syncing code changes

TypeORM is able to automatically generate migration files with schema changes you made in your **code**. Let's say you have a Post entity with a title column, and you have changed the name title to name. You can run following command:

```ts
npm run migration:generate
```
You don't need to write the queries on your own. The rule of thumb for generating migrations is that you generate them after **each** change you made to your models.
1. Create a Google cloud project or follow this [guide](https://developers.google.com/calendar/api/quickstart/js#set_up_your_environment)
1. Enable the [Admin SDK API](https://console.cloud.google.com/apis/api/admin.googleapis.com/overview)
2. Enable the [Calender API](https://console.cloud.google.com/flows/enableapi?apiid=calendar-json.googleapis.com)


## Reference
# Reference

- [Google Free busy API](https://developers.google.com/calendar/api/v3/reference/freebusy/query?apix_params=%7B%22resource%22%3A%7B%22timeMin%22%3A%222024-08-27T00%3A00%3A00%2B02%3A00%22%2C%22timeMax%22%3A%222024-09-27T23%3A59%3A59%2B02%3A00%22%2C%22items%22%3A%5B%7B%22id%22%3A%22Ada%20Bit%2010%40resource.calendar.google.com%22%7D%2C%7B%22id%22%3A%22c_1888flqi3ecr4gb0k9armpk8k9ics%40resource.calendar.google.com%22%7D%2C%7B%22id%22%3A%22RESOURCE_ID_3%40resource.calendar.google.com%22%7D%5D%7D%7D )

Expand Down
23 changes: 15 additions & 8 deletions client/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

let duration = 15;
let seatCount = 1;
let floor = 1;
let floor = 1; // Make floors a dropdown with user's own organization's floors which are strings in the format F1, FF2 etc
let currentEvent = {};

const CLIENT_ID = '1043931677993-j15eelb1golb8544ehi2meeru35q3fo4.apps.googleusercontent.com';
const REDIRECT_URI = window.location.origin;
const BACKEND_ENDPOINT = REDIRECT_URI;
const SCOPES = [
'https://www.googleapis.com/auth/admin.directory.resource.calendar.readonly',
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
];

async function openPage(pageName, elmnt) {
var i, tabcontent, tablinks;
Expand Down Expand Up @@ -64,17 +70,17 @@ function populateTimeOptions() {
}
}

function populateRoomOptions(availableRooms, roomId) {
function populateRoomOptions(availableRooms, roomEmail) {
const roomOptionsSelect = document.getElementById('roomOptions');
roomOptionsSelect.innerHTML = '';

for (const room of availableRooms) {
const option = document.createElement('option');
option.value = room.name + ' | S: ' + room.seats;
option.text = room.name + ' | S: ' + room.seats;
option.id = room.id;
option.id = room.email;

if (room.id === roomId) {
if (room.email === roomEmail) {
option.selected = true;
}

Expand Down Expand Up @@ -163,7 +169,8 @@ async function makeRequest(path, method, body, params) {

function login() {
console.log('login clicked');
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile&access_type=offline`;
const scopes = SCOPES.join(" ").trim();
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=${scopes}&access_type=offline`;
window.location.href = authUrl;
}

Expand Down Expand Up @@ -197,7 +204,7 @@ async function bookRoom() {
startTime: formattedStartTime,
duration: parseInt(duration),
seats: parseInt(seats),
floor: parseInt(floor),
floor: `F${floor}`,
timeZone: getTimeZoneString(),
});

Expand All @@ -207,8 +214,8 @@ async function bookRoom() {
}

currentEvent.eventId = res.eventId;
currentEvent.roomId = res.roomId;
populateRoomOptions(res.availableRooms || [], res.roomId);
currentEvent.roomId = res.email;
populateRoomOptions(res.availableRooms || [], res.email);
createRoomAlert(res.room, convertToLocaleTime(res.start), convertToLocaleTime(res.end), res.summary, 'info');

bookBtn.disabled = false;
Expand Down
8 changes: 7 additions & 1 deletion src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OAuth2Client } from 'google-auth-library';
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginResponse } from './dto';
import { AuthGuard } from './auth.guard';
Expand All @@ -19,4 +19,10 @@ export class AuthController {
async logout(@_OAuth2Client() client: OAuth2Client): Promise<boolean> {
return await this.authService.logout(client);
}

@UseGuards(AuthGuard)
@Post('/resource/tesst')
async createResources(@_OAuth2Client() client: OAuth2Client): Promise<void> {
return await this.authService.createCalenderResources(client, "cefalo.com");
}
}
4 changes: 2 additions & 2 deletions src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Logger, Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { Auth, User } from './entities';
import { Auth, ConferenceRoom, User } from './entities';
import { TypeOrmModule } from '@nestjs/typeorm';
import { JwtService } from '@nestjs/jwt';
import { AuthGuard } from './auth.guard';

@Module({
imports: [TypeOrmModule.forFeature([User, Auth])],
imports: [TypeOrmModule.forFeature([User, Auth, ConferenceRoom])],
controllers: [AuthController],
providers: [AuthService, JwtService, AuthGuard, Logger],
exports: [AuthService, AuthGuard],
Expand Down
Loading

0 comments on commit 0c22f63

Please sign in to comment.