Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Friends management #189

Merged
merged 19 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ jobs:
- run: npm --prefix question_generator ci
- run: npm --prefix questionservice ci
- run: npm --prefix gameservice ci
- run: npm --prefix users/friendsservice ci
- run: npm --prefix gatewayservice ci
- run: npm --prefix webapp ci
- run: npm --prefix users/authservice test -- --coverage
- run: npm --prefix users/userservice test -- --coverage
- run: npm --prefix question_generator test
- run: npm --prefix questionservice test -- --coverage
- run: npm --prefix gameservice test -- --coverage
- run: npm --prefix users/friendsservice test -- --coverage
- run: npm --prefix gatewayservice test -- --coverage
- run: npm --prefix webapp test -- --coverage
- name: Analyze with SonarCloud
Expand All @@ -45,6 +47,7 @@ jobs:
- run: npm --prefix question_generator install
- run: npm --prefix questionservice install
- run: npm --prefix gameservice install
- run: npm --prefix users/friendsservice install
- run: npm --prefix gatewayservice install
- run: npm --prefix webapp install
- run: npm --prefix webapp run build
Expand Down
22 changes: 21 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ jobs:
- run: npm --prefix question_generator ci
- run: npm --prefix questionservice ci
- run: npm --prefix gameservice ci
- run: npm --prefix users/friendsservice ci
- run: npm --prefix gatewayservice ci
- run: npm --prefix webapp ci
- run: npm --prefix users/authservice test -- --coverage
- run: npm --prefix users/userservice test -- --coverage
- run: npm --prefix question_generator test
- run: npm --prefix questionservice test -- --coverage
- run: npm --prefix gameservice test -- --coverage
- run: npm --prefix users/friendsservice test -- --coverage
- run: npm --prefix gatewayservice test -- --coverage
- run: npm --prefix webapp test -- --coverage
- name: Analyze with SonarCloud
Expand All @@ -44,6 +46,7 @@ jobs:
- run: npm --prefix question_generator install
- run: npm --prefix questionservice install
- run: npm --prefix gameservice install
- run: npm --prefix users/friendsservice install
- run: npm --prefix gatewayservice install
- run: npm --prefix webapp install
- run: npm --prefix webapp run build
Expand Down Expand Up @@ -153,6 +156,23 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
workdir: gameservice
docker-push-friendsservice:
name: Push friends service Docker Image to GitHub Packages
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [e2e-tests]
steps:
- uses: actions/checkout@v4
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@v5
with:
name: arquisoft/wiq_es3b/friendsservice
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
workdir: users/friendsservice
docker-push-gatewayservice:
name: Push gateway service Docker Image to GitHub Packages
runs-on: ubuntu-latest
Expand All @@ -173,7 +193,7 @@ jobs:
deploy:
name: Deploy over SSH
runs-on: ubuntu-latest
needs: [docker-push-userservice,docker-push-authservice,docker-push-questiongenerationservice,docker-push-questionservice,docker-push-gameservice,docker-push-gatewayservice,docker-push-webapp]
needs: [docker-push-userservice,docker-push-authservice,docker-push-questiongenerationservice,docker-push-questionservice,docker-push-gameservice,docker-push-friendsservice,docker-push-gatewayservice,docker-push-webapp]
steps:
- name: Deploy over SSH
uses: fifsky/ssh-action@master
Expand Down
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,21 @@ services:
MONGODB_URI: mongodb://mongodb:27017/gamesdb
USER_SERVICE_URL: http://userservice:8001
restart: always
friendsservice:
container_name: friendsservice-${teamname:-defaultASW}
image: ghcr.io/arquisoft/wiq_es3b/friendsservice:latest
profiles: ["dev", "prod"]
build: ./users/friendsservice
depends_on:
- mongodb
ports:
- "8006:8006"
networks:
- mynetwork
environment:
MONGODB_URI: mongodb://mongodb:27017/friendsdb
USER_SERVICE_URL: http://userservice:8001
restart: always

gatewayservice:
container_name: gatewayservice-${teamname:-defaultASW}
Expand All @@ -109,6 +124,7 @@ services:
QUESTION_GENERATION_SERVICE_URL: http://questiongenerationservice:8003
QUESTIONS_SERVICE_URL: http://questionservice:8004
GAME_SERVICE_URL: http://gameservice:8005
FRIENDS_SERVICE_URL: http://friendsservice:8006
restart: always

webapp:
Expand Down
6 changes: 3 additions & 3 deletions gameservice/game-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ const validateRequiredFields = (req, fields) => {
// Ruta para agregar un nuevo juego
app.post('/addgame', async (req, res) => {
try {
validateRequiredFields(req, ['user', 'pAcertadas', 'pFalladas', 'totalTime', 'gameMode']);
validateRequiredFields(req, ['userId', 'pAcertadas', 'pFalladas', 'totalTime', 'gameMode']);

const { user, pAcertadas, pFalladas, totalTime, gameMode } = req.body;
const { userId, pAcertadas, pFalladas, totalTime, gameMode } = req.body;

// Crea una nueva instancia del modelo de juegos
const newGame = new Game({
user: user,
user: userId,
pAcertadas: pAcertadas,
pFalladas: pFalladas,
totalTime: totalTime,
Expand Down
4 changes: 2 additions & 2 deletions gameservice/game-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('Game Service', () => {
// Test para agregar un nuevo juego con éxito
it('should add a new game on POST /addgame', async () => {
const newGame = {
user: userId,
userId: userId,
pAcertadas: 5,
pFalladas: 3,
totalTime: 1200,
Expand All @@ -61,7 +61,7 @@ describe('Game Service', () => {
expect(response.status).toBe(200);
const data = response.body;
expect(data).toHaveProperty("user");
expect(data.user.toString()).toBe(newGame.user.toString());
expect(data.user.toString()).toBe(newGame.userId.toString());
expect(data).toHaveProperty('pAcertadas', newGame.pAcertadas);
expect(data).toHaveProperty('pFalladas', newGame.pFalladas);
expect(data).toHaveProperty('totalTime', newGame.totalTime);
Expand Down
68 changes: 66 additions & 2 deletions gatewayservice/gateway-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
const questionGenerationServiceUrl = process.env.QUESTION_GENERATION_SERVICE_URL || 'http://localhost:8003';
const questionServiceUrl = process.env.QUESTIONS_SERVICE_URL || 'http://localhost:8004';
const gameServiceUrl = process.env.GAME_SERVICE_URL || 'http://localhost:8005';
const friendServiceUrl = process.env.FRIENDS_SERVICE_URL || 'http://localhost:8006';

app.use(cors());
app.use(express.json());
Expand All @@ -22,8 +23,27 @@
const metricsMiddleware = promBundle({includeMethod: true});
app.use(metricsMiddleware);

app.delete('/deletefriend/:username/:friend', async (req, res, next) => {
if (req.headers.authorization) {
try{
const response = await axios.get(`${authServiceUrl}/verify`, {
headers: {
Authorization: req.headers.authorization
}
});
if(response.status===200){
req.body.user = response.data.username;
next();
}
}catch(error){
res.status(401).json({ error: 'Unauthorized' });
}
} else {
res.status(401).json({ error: 'Unauthorized' });
}
});
// Security middleware
app.post('/addgame', async (req, res, next) => {
app.post(['/addgame','/addfriend'], async (req, res, next) => {
if (req.headers.authorization) {
try{
const response = await axios.get(`${authServiceUrl}/verify`, {
Expand All @@ -32,6 +52,8 @@
}
});
if(response.status===200){
req.body.user = response.data.username;
req.body.userId = response.data._id;
next();
}
}catch(error){
Expand Down Expand Up @@ -169,7 +191,7 @@
}
});

// Ruta para agregar una nuevo game
// Ruta para agregar una nuevo amigo
app.post('/addgame', async (req, res) => {
try {
try{
Expand All @@ -184,6 +206,48 @@
}
});

// Ruta para agregar una nuevo game
app.post('/addfriend', async (req, res) => {
try {
try{
// Forward the add game request to the games service
const friendsResponse = await axios.post(friendServiceUrl + '/addfriend', req.body);
res.json(friendsResponse.data);
}catch(error){
res.status(error.response.status).json(error.response.data);
}
} catch (error) {
res.status(500).json({ error: "Service down" });
}
});
app.delete('/deletefriend/:username/:friend', async (req, res) => {
try {
try{
if(req.body.user!==req.params.username){
throw new Error('Unauthorized');
}
const friendsResponse = await axios.delete(friendServiceUrl + '/deletefriend/'+req.body.user+'/'+req.params.friend);
Fixed Show fixed Hide fixed
res.json(friendsResponse.data);
}catch(error){
res.status(error.response.status).json(error.response.data);
}
} catch (error) {
res.status(500).json({ error: "Service down" });
}
});
app.get('/getFriends/:username', async (req, res) => {
try {
try{
const friendsResponse = await axios.get(friendServiceUrl + '/getFriends/'+req.params.username);
Fixed Show fixed Hide fixed
res.json(friendsResponse.data);
}catch(error){
res.status(error.response.status).json(error.response.data);
}
} catch (error) {
res.status(500).json({ error: "Service down" });
}
});

/// Ruta para obtener la participación del usuario
app.get('/getParticipation/:userId', async (req, res) => {
try {
Expand Down
78 changes: 78 additions & 0 deletions gatewayservice/gateway-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,84 @@ describe('addGame', () =>{
expect(response.statusCode).toBe(401);
expect(response.body).toEqual({ error: 'Unauthorized' });
});
});
describe('POST /addfriend', () => {
it('should skip the middleware and return 401 Unauthorized if authorization token is missing', async () => {
const response = await request(app).post('/addfriend');
expect(response.statusCode).toBe(401);
expect(response.body).toEqual({ error: 'Unauthorized' });
});

it('should skip the middleware and return 401 Unauthorized if authorization token is invalid', async () => {
const mockedToken = 'invalidToken';
axios.get.mockRejectedValueOnce({ response: { status: 401 } });
const response = await request(app)
.post('/addfriend')
.set('Authorization', `Bearer ${mockedToken}`);
expect(response.statusCode).toBe(401);
expect(response.body).toEqual({ error: 'Unauthorized' });
});

it('should skip the middleware and call the next middleware if authorization token is valid', async () => {
const mockedToken = 'validToken';
const mockedResponse = { status: 200, data: { username: 'mockedUser' } };
axios.post.mockResolvedValueOnce(mockedResponse);
axios.get.mockResolvedValueOnce(mockedResponse);
const nextMiddleware = jest.fn();

await request(app)
.post('/addfriend')
.send({ username: 'mockedUser', friend: 'mockedFriend'})
.set('Authorization', `Bearer ${mockedToken}`);
nextMiddleware();
expect(nextMiddleware).toHaveBeenCalled();
});
});
describe('Friend Service', () => {


// Test middleware for deletefriend endpoint
it('should call the next middleware if authorization token is valid', async () => {
const mockedToken = 'validToken';
const mockedResponse = { status: 200, data: { username: 'mockedUser' } };
axios.delete.mockResolvedValueOnce(mockedResponse);
axios.get.mockResolvedValueOnce(mockedResponse);
await request(app)
.delete('/deletefriend/testuser/friend')
.set('Authorization', `Bearer ${mockedToken}`);
});
it('should return 401 Unauthorized if authorization token is missing', async () => {
const response = await request(app).delete('/deletefriend/testuser/friend');
expect(response.statusCode).toBe(401);
expect(response.body).toEqual({ error: 'Unauthorized' });
});
it('should return 401 Unauthorized if authorization token is invalid', async () => {
const mockedToken = 'invalidToken';
axios.get.mockRejectedValueOnce({ response: { status: 401 } });
const response = await request(app)
.delete('/deletefriend/testuser/friend')
.set('Authorization', `Bearer ${mockedToken}`);
expect(response.statusCode).toBe(401);
expect(response.body).toEqual({ error: 'Unauthorized' });
});

// Test /getFriends endpoint
it('should forward get friends request to friend service', async () => {
const mockedUsername = 'testuser';
const mockedFriends = ['friend1', 'friend2'];
axios.get.mockResolvedValueOnce({ data: mockedFriends , status: 200});
const response = await request(app)
.get(`/getFriends/${mockedUsername}`);
expect(response.statusCode).toBe(200);
expect(response.body).toEqual(mockedFriends);
});
it('should return an error when the friend service is down', async () => {
const mockedUsername = 'testuser';
const mockedError = { error: 'Service down' };
jest.spyOn(axios, 'get').mockImplementation({ response: { status: 500, data: { error: 'Service down' } } });
const response = await request(app)
.get(`/getFriends/${mockedUsername}`);
expect(response.statusCode).toBe(500);
expect(response.body).toEqual(mockedError);
});
});
2 changes: 1 addition & 1 deletion users/authservice/auth-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ app.get('/verify', async (req, res) => {
const userId = decodedToken.userId;
const user = await User.findById(userId);
if (user) {
res.json({ username: user.username, createdAt: user.createdAt });
res.json({ username: user.username, createdAt: user.createdAt, _id: userId});
return;
} else {
res.status(404).json({ error: 'User not found' });
Expand Down
20 changes: 20 additions & 0 deletions users/friendsservice/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use an official Node.js runtime as a parent image
FROM node:20

# Set the working directory in the container
WORKDIR /usr/src/friendsservice

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install app dependencies
RUN npm install

# Copy the app source code to the working directory
COPY . .

# Expose the port the app runs on
EXPOSE 8006

# Define the command to run your app
CMD ["node", "friends-service.js"]
23 changes: 23 additions & 0 deletions users/friendsservice/friends-model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const mongoose = require('mongoose');

const friendsSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
},
friends: {
type: [String],
default: [],
validate: {
validator: function(arr) {
return new Set(arr).size === arr.length;
},
message: 'Each friend must be unique.'
}
},
});

module.exports = (connection) => {
return connection.model('Friends', friendsSchema);
};
Loading
Loading