Skip to content

Commit

Permalink
Merge pull request #82 from Arquisoft/69-making-the-webapp-use-the-re…
Browse files Browse the repository at this point in the history
…cord-service-through-the-gateway-service

69 making the webapp use the record service through the gateway service
  • Loading branch information
uo289267 authored Apr 7, 2024
2 parents ee85720 + 360f401 commit 9e11fd4
Show file tree
Hide file tree
Showing 71 changed files with 9,852 additions and 1,916 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm --prefix questionservice ci
- run: npm --prefix users/recordservice ci
- run: npm --prefix users/authservice ci
- run: npm --prefix users/userservice ci
- run: npm --prefix gatewayservice ci
- run: npm --prefix webapp ci
- run: npm --prefix questionservice test -- --coverage
- run: npm --prefix users/recordservice test -- --coverage
- run: npm --prefix users/authservice test -- --coverage
- run: npm --prefix users/userservice test -- --coverage
- run: npm --prefix gatewayservice test -- --coverage
- run: npm --prefix webapp test -- --coverage
- name: Analyze with SonarCloud
uses: sonarsource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
31 changes: 31 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,34 @@ on:
types: [published]

jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm --prefix questionservice ci
- run: npm --prefix users/recordservice ci
- run: npm --prefix users/authservice ci
- run: npm --prefix users/userservice ci
- run: npm --prefix gatewayservice ci
- run: npm --prefix webapp ci
- run: npm --prefix questionservice test -- --coverage
- run: npm --prefix users/recordservice test -- --coverage
- run: npm --prefix users/authservice test -- --coverage
- run: npm --prefix users/userservice test -- --coverage
- run: npm --prefix gatewayservice test -- --coverage
- run: npm --prefix webapp test -- --coverage
- name: Analyze with SonarCloud
uses: sonarsource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
docker-push-webapp:
name: Push webapp Docker Image to GitHub Packages
runs-on: ubuntu-latest
needs: [unit-tests]
permissions:
contents: read
packages: write
Expand All @@ -27,6 +52,7 @@ jobs:
docker-push-authservice:
name: Push auth service Docker Image to GitHub Packages
runs-on: ubuntu-latest
needs: [unit-tests]
permissions:
contents: read
packages: write
Expand All @@ -43,6 +69,7 @@ jobs:
docker-push-userservice:
name: Push user service Docker Image to GitHub Packages
runs-on: ubuntu-latest
needs: [unit-tests]
permissions:
contents: read
packages: write
Expand All @@ -59,6 +86,7 @@ jobs:
docker-push-recordservice:
name: Push record service Docker Image to GitHub Packages
runs-on: ubuntu-latest
needs: [unit-tests]
permissions:
contents: read
packages: write
Expand All @@ -75,6 +103,7 @@ jobs:
docker-push-gatewayservice:
name: Push gateway service Docker Image to GitHub Packages
runs-on: ubuntu-latest
needs: [unit-tests]
permissions:
contents: read
packages: write
Expand All @@ -91,6 +120,7 @@ jobs:
docker-push-questionservice:
name: Push question service Docker Image to GitHub Packages
runs-on: ubuntu-latest
needs: [unit-tests]
permissions:
contents: read
packages: write
Expand All @@ -107,6 +137,7 @@ jobs:
docker-push-questiongenerator:
name: Push question generator Docker Image to GitHub Packages
runs-on: ubuntu-latest
needs: [unit-tests]
permissions:
contents: read
packages: write
Expand Down
86 changes: 3 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

This is a base repo for the [Software Architecture course](http://arquisoft.github.io/) in [2023/2024 edition](https://arquisoft.github.io/course2324.html).

The project can be seing [here](http://4.231.170.30:3000)

This repo is a basic application composed of several components.

Expand All @@ -18,6 +17,7 @@ This repo is a basic application composed of several components.
Both the user and auth service share a Mongo database that is accessed with mongoose.

## Quick start guide
In order to deployed it locally you can check out the docker configuration below:

### Using docker

Expand All @@ -33,88 +33,8 @@ and launch it with docker compose:
docker compose --profile dev up --build
```

### Starting Component by component

First, start the database. Either install and run Mongo or run it using docker:

```docker run -d -p 27017:27017 --name=my-mongo mongo:latest```

You can also use services like Mongo Altas for running a Mongo database in the cloud.

Now, launch the auth, user and gateway services. Just go to each directory and run `npm install` followed by `npm start`.

Lastly, go to the webapp directory and launch this component with `npm install` followed by `npm start`.

After all the components are launched, the app should be available in localhost in port 3000.

## Deployment

For the deployment, we have several options.

The first and more flexible is to deploy to a virtual machine using SSH. This will work with any cloud service (or with our own server).

Other options include using the container services that most cloud services provide. This means, deploying our Docker containers directly.

We are going to use the first approach, creating a virtual machine in a cloud service and after installing docker and docker-compose, deploy our containers there using GitHub Actions and SSH.

### Machine requirements for deployment

The machine for deployment can be created in services like Microsoft Azure or Amazon AWS. These are in general the settings that it must have:

- Linux machine with Ubuntu > 20.04.
- Docker and docker-compose installed.
- Open ports for the applications installed (in this case, ports 3000 for the webapp and 8000 for the gateway service).

Once you have the virtual machine created, you can install **docker** and **docker-compose** using the following instructions:

```ssh
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt update
sudo apt install docker-ce
sudo usermod -aG docker ${USER}
sudo curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
```

### Continuous delivery (GitHub Actions)

Once we have our machine ready, we could deploy by hand the application, taking our docker-compose file and executing it in the remote machine.

In this repository, this process is done automatically using **GitHub Actions**. The idea is to trigger a series of actions when some condition is met in the repository.

As you can see, unitary tests of each module and e2e tests are executed before pushing the docker images and deploying them. Using this approach we avoid deploying versions that do not pass the tests.

The deploy action is the following:

```yml
deploy:
name: Deploy over SSH
runs-on: ubuntu-latest
needs: [docker-push-userservice,docker-push-authservice,docker-push-gatewayservice,docker-push-webapp]
steps:
- name: Deploy over SSH
uses: fifsky/ssh-action@master
with:
host: ${{ secrets.DEPLOY_HOST }}
user: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
command: |
wget https://raw.githubusercontent.com/arquisoft/wiq_en1b/master/docker-compose.yml -O docker-compose.yml
wget https://raw.githubusercontent.com/arquisoft/wiq_en1b/master/.env
docker compose down
docker compose --profile prod up -d
```
This action uses three secrets that must be configured in the repository:
- DEPLOY_HOST: IP of the remote machine.
- DEPLOY_USER: user with permission to execute the commands in the remote machine.
- DEPLOY_KEY: key to authenticate the user in the remote machine.
Note that this action logs in the remote machine and downloads the docker-compose file from the repository and launches it. Obviously, previous actions have been executed which have uploaded the docker images to the GitHub Packages repository.
### Deployed in Cloud
In order to view the application deploy in the cloud click [here](http://172.203.216.60:3000)
### Members

- Lucía Ruiz Núñez [email protected]
Expand Down
43 changes: 36 additions & 7 deletions gatewayservice/gateway-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ const express = require('express');
const axios = require('axios');
const cors = require('cors');
const promBundle = require('express-prom-bundle');
//libraries required for OpenAPI-Swagger
const swaggerUi = require('swagger-ui-express');
const fs = require("fs")
const YAML = require('yaml')

const app = express();
const port = 8000;
Expand Down Expand Up @@ -45,35 +49,60 @@ app.post('/adduser', async (req, res) => {

app.get('/questions', async (req, res) => {
try {

// Forward the question request to the quetion service
const quetionResponse = await axios.get(questionServiceUrl+'/questions', req.params);
res.send(quetionResponse.data);
const questionResponse = await axios.get(questionServiceUrl+'/questions');
res.json(questionResponse.data);
} catch (error) {
res.status(error.response.status).json({ error: error.response.data.error });
}
});

app.post('/addrecord', async(req, res) => {
app.get('/questions/:lang', async (req, res) => {
try {
const lang = req.params.lang;
// Forward the question request to the quetion service
const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang);

res.json(questionResponse.data);
} catch (error) {

res.status(error.response.status).json({ error: error.response.data.error });
}
});

app.post('/record', async(req, res) => {
try {
// Forward the record request to the record service
const recordResponse = await axios.post(recordServiceUrl+'/addrecord', req.body);
res.send(recordResponse.data);
const recordResponse = await axios.post(recordServiceUrl+'/record', req.body);
res.json(recordResponse.data);
} catch (error) {
res.send(error);
}
});

app.get('/records/:user', async(req, res)=>{
app.get('/record/:user', async(req, res)=>{
try {
const user = req.params.user;
// Forward the record request to the record service
const recordResponse = await axios.get(recordServiceUrl + '/records/' + user);
const recordResponse = await axios.get(recordServiceUrl + '/record/' + user);
res.json(recordResponse.data);
} catch (error) {
res.send(error);
}
});

// Read the OpenAPI YAML file synchronously
const file = fs.readFileSync('./openapi.yaml', 'utf8');

// Parse the YAML content into a JavaScript object representing the Swagger document
const swaggerDocument = YAML.parse(file);

// Serve the Swagger UI documentation at the '/api-doc' endpoint
// This middleware serves the Swagger UI files and sets up the Swagger UI page
// It takes the parsed Swagger document as input
app.use('/api-doc', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

// Start the gateway service
const server = app.listen(port, () => {
console.log(`Gateway Service listening at http://localhost:${port}`);
Expand Down
58 changes: 55 additions & 3 deletions gatewayservice/gateway-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,24 @@ describe('Gateway Service', () => {
// Mock responses from external services
axios.post.mockImplementation((url, data) => {
if (url.endsWith('/login')) {
return Promise.resolve({ data: { token: 'mockedToken' } });
return Promise.resolve({ data: { token: 'mockedToken', username : 'testuser'} });
} else if (url.endsWith('/adduser')) {
return Promise.resolve({ data: { userId: 'mockedUserId' } });
return Promise.resolve({ data: { username: 'newuser' } });
} else if(url.endsWith('/record')){
return Promise.resolve({data : {user:'testuser'}})
}
});

axios.get.mockImplementation((url, data) => {
if (url.endsWith('/questions')){
return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?",
answers: ["225089","272357","267855","231841"]}] });
} else if (url.endsWith('/questions/es')){
return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?",
answers: ["225089","272357","267855","231841"]}] });
} else if(url.endsWith('/record/testuser')){
//Dont need to check a good record just that it redirects the call
return Promise.resolve({data : {record:'undefined'}})
}
});

Expand All @@ -26,6 +41,7 @@ describe('Gateway Service', () => {

expect(response.statusCode).toBe(200);
expect(response.body.token).toBe('mockedToken');
expect(response.body.username).toBe('testuser');
});

// Test /adduser endpoint
Expand All @@ -35,6 +51,42 @@ describe('Gateway Service', () => {
.send({ username: 'newuser', password: 'newpassword' });

expect(response.statusCode).toBe(200);
expect(response.body.userId).toBe('mockedUserId');
expect(response.body.username).toBe('newuser');
});

// Test /questions endpoint
it('should forward questions request to question service', async () => {
const response = await request(app)
.get('/questions');

expect(response.statusCode).toBe(200);
expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?");
});

// Test /questions/:lang endpoint
it('should forward questions request to question service', async () => {
const response = await request(app)
.get('/questions/es');

expect(response.statusCode).toBe(200);
expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?");
});

// Test /record endpoint
it('should forward record request to record service', async () => {
const response = await request(app)
.post('/record');

expect(response.statusCode).toBe(200);
expect(response.body.user).toBe('testuser');
});

// Test /record/:user endpoint
it('should forward record request to record service', async () => {
const response = await request(app)
.get('/record/testuser');

expect(response.statusCode).toBe(200);
expect(response.body).toHaveProperty('record', "undefined");
});
});
Loading

0 comments on commit 9e11fd4

Please sign in to comment.