Skip to content

Commit

Permalink
Merge branch 'questions_info_api' of github.uniovi.com:Arquisoft/wiq_…
Browse files Browse the repository at this point in the history
…es3b into questions_info_api
  • Loading branch information
UO287687 committed Apr 1, 2024
2 parents fd19e6d + 9b43712 commit fdf6826
Show file tree
Hide file tree
Showing 18 changed files with 205 additions and 47 deletions.
1 change: 1 addition & 0 deletions users/authservice/auth-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: String,
password: String,
profileImage: String,
createdAt: Date,
});

Expand Down
2 changes: 1 addition & 1 deletion users/authservice/auth-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ app.post('/login', async (req, res) => {
// Generate a JWT token
const token = jwt.sign({ userId: user._id }, 'your-secret-key', { expiresIn: '1h' });
// Respond with the token and user information
res.json({ token: token, username: username, createdAt: user.createdAt });
res.json({ token: token, username: username, createdAt: user.createdAt, profileImage: user.profileImage });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
Expand Down
4 changes: 4 additions & 0 deletions users/userservice/user-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const userSchema = new mongoose.Schema({
type: String,
required: true,
},
profileImage: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now,
Expand Down
3 changes: 2 additions & 1 deletion users/userservice/user-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ function validateRequiredFields(req, requiredFields) {
app.post('/adduser', async (req, res) => {
try {
// Check if required fields are present in the request body
validateRequiredFields(req, ['username', 'password']);
validateRequiredFields(req, ['username', 'password', 'profileImage']);

// Encrypt the password before saving it
const hashedPassword = await bcrypt.hash(req.body.password, 10);

const newUser = new User({
username: req.body.username,
password: hashedPassword,
profileImage: req.body.profileImage,
});

await newUser.save();
Expand Down
1 change: 1 addition & 0 deletions users/userservice/user-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('User Service', () => {
const newUser = {
username: 'testuser',
password: 'testpassword',
profileImage: 'perfil2.jpg',
};

const response = await request(app).post('/adduser').send(newUser);
Expand Down
Binary file added webapp/src/assets/perfil2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/src/assets/perfil3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/src/assets/perfil4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/src/assets/perfil5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 55 additions & 3 deletions webapp/src/components/AddUser.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
// src/components/AddUser.js
import React, { useState } from 'react';
import axios from 'axios';
import { Container, Typography, TextField, Button, Snackbar } from '@mui/material';
import { Container, Typography, TextField, Button, Snackbar, IconButton } from '@mui/material';

import profileImg1 from '../assets/defaultImgProfile.jpg';
import profileImg2 from '../assets/perfil2.jpg';
import profileImg3 from '../assets/perfil3.jpg';
import profileImg4 from '../assets/perfil4.jpg';
import profileImg5 from '../assets/perfil5.jpg';

const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000';

const AddUser = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [profileImage, setProfileImage] = useState('defaultImgProfile.jpg');

const [error, setError] = useState('');
const [openSnackbar, setOpenSnackbar] = useState(false);

const addUser = async () => {
try {
await axios.post(`${apiEndpoint}/adduser`, { username, password });
if (password !== confirmPassword) {
setError('Passwords do not match');
return;
}

await axios.post(`${apiEndpoint}/adduser`, { username, password, profileImage });
setOpenSnackbar(true);
} catch (error) {
setError(error.response.data.error);
Expand All @@ -24,8 +38,12 @@ const AddUser = () => {
setOpenSnackbar(false);
};

const handleImageClick = (imageName) => {
setProfileImage(imageName);
}

return (
<Container component="main" maxWidth="xs" sx={{ marginTop: 4 }}>
<Container component="div" maxWidth="xs" sx={{ marginTop: 4 }}>
<Typography component="h1" variant="h5">
Add User
</Typography>
Expand All @@ -46,6 +64,40 @@ const AddUser = () => {
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<TextField
name="confirmPassword"
margin="normal"
fullWidth
label="Confirm Password"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<Typography component="h2" variant="h5">
Select a profile image
</Typography>
<div id='fotosPerfil'>
<IconButton className={`fotoPerfilBtn`} onClick={() => handleImageClick('defaultImgProfile.jpg')}>
<img className={`fotoPerfil ${profileImage === 'defaultImgProfile.jpg' ? 'selectedImg' : ''}`}
src={profileImg1} alt='Imagen Perfil 1' />
</IconButton>
<IconButton className={`fotoPerfilBtn`} onClick={() => handleImageClick('perfil2.jpg')}>
<img className={`fotoPerfil ${profileImage === 'perfil2.jpg' ? 'selectedImg' : ''}`}
src={profileImg2} alt='Imagen Perfil 2' />
</IconButton>
<IconButton className={`fotoPerfilBtn`} onClick={() => handleImageClick('perfil3.jpg')}>
<img className={`fotoPerfil ${profileImage === 'perfil3.jpg' ? 'selectedImg' : ''}`}
src={profileImg3} alt='Imagen Perfil 3' />
</IconButton>
<IconButton className={`fotoPerfilBtn`} onClick={() => handleImageClick('perfil4.jpg')}>
<img className={`fotoPerfil ${profileImage === 'perfil4.jpg' ? 'selectedImg' : ''}`}
src={profileImg4} alt='Imagen Perfil 4' />
</IconButton>
<IconButton className={`fotoPerfilBtn`} onClick={() => handleImageClick('perfil5.jpg')}>
<img className={`fotoPerfil ${profileImage === 'perfil5.jpg' ? 'selectedImg' : ''}`}
src={profileImg5} alt='Imagen Perfil 5' />
</IconButton>
</div>
<Button variant="contained" color="primary" onClick={addUser}>
Add User
</Button>
Expand Down
34 changes: 4 additions & 30 deletions webapp/src/components/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ const Login = ({ goTo }) => {

const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [profileImage, setProfileImage] = useState('');
const [error, setError] = useState('');
const [loginSuccess, setLoginSuccess] = useState(false);
const [createdAt, setCreatedAt] = useState('');
const [openSnackbar, setOpenSnackbar] = useState(false);
const [timeStart, setTimeStart] = useState(0);
const [timeElapsed, setTimeElapsed] = useState(0);

const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000';

Expand All @@ -24,26 +24,18 @@ const Login = ({ goTo }) => {
const response = await axios.post(`${apiEndpoint}/login`, { username, password });

// Extract data from the response
const { createdAt: userCreatedAt, username: loggedInUsername, token:token } = response.data;
const { createdAt: userCreatedAt, username: loggedInUsername, token:token, profileImage: profileImage } = response.data;

setTimeStart(Date.now());
setCreatedAt(userCreatedAt);
setLoginSuccess(true);
saveSessionData({ username: loggedInUsername, createdAt: userCreatedAt, token: token });
saveSessionData({ username: loggedInUsername, createdAt: userCreatedAt, token: token, profileImage: profileImage });
setOpenSnackbar(true);
} catch (error) {
setError(error.response.data.error);
}
};

const calculateTime = async () => {
try {
setTimeElapsed((Date.now() - timeStart) / 1000);
} catch (error) {
setError(error.response.data.error);
}
};

const handleCloseSnackbar = () => {
setOpenSnackbar(false);
};
Expand All @@ -55,24 +47,7 @@ const Login = ({ goTo }) => {
}, [loginSuccess, goTo]);

return (
<Container component="main" maxWidth="xs" sx={{ marginTop: 4 }}>
{loginSuccess ? (

<div>
<Typography component="h1" variant="h5" sx={{ textAlign: 'center' }}>
Hello {username}!
</Typography>
<Typography component="p" variant="body1" sx={{ textAlign: 'center', marginTop: 2 }}>
Your account was created on {new Date(createdAt).toLocaleDateString()}.
</Typography>
<Typography component="p" variant="body1" sx={{ textAlign: 'center', marginTop: 2 }}>
Han pasado {timeElapsed} segundos.
</Typography>
<Button variant="contained" color="primary" onClick={calculateTime}>
Calcular tiempo
</Button>
</div>
) : (
<Container component="div" maxWidth="xs" sx={{ marginTop: 4 }}>
<div>
<Typography component="h1" variant="h5">
Login :D
Expand Down Expand Up @@ -100,7 +75,6 @@ const Login = ({ goTo }) => {
<Snackbar open={!!error} autoHideDuration={6000} onClose={() => setError('')} message={`Error: ${error}`} />
)}
</div>
)}
</Container>
);
};
Expand Down
6 changes: 4 additions & 2 deletions webapp/src/components/Nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import AdbIcon from '@mui/icons-material/Adb';

import { useContext } from 'react';
import { SessionContext } from '../SessionContext';
import profileImage from '../assets/defaultImgProfile.jpg';
import defaultProfileImg from '../assets/defaultImgProfile.jpg';

function Nav({ goTo }) {

const { sessionData } = useContext(SessionContext);
const username = sessionData ? sessionData.username : 'noUser';
const profileImgSrc = sessionData && sessionData.profileImage ?
require(`../assets/${sessionData.profileImage}`) : defaultProfileImg;

const [anchorElNav, setAnchorElNav] = React.useState(null);
const [anchorElUser, setAnchorElUser] = React.useState(null);
Expand Down Expand Up @@ -122,7 +124,7 @@ function Nav({ goTo }) {
<Typography sx={{ marginRight: 2, fontFamily: 'Roboto Slab'}} >{username}</Typography>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar alt="Remy Sharp" src={profileImage}/>
<Avatar alt="Remy Sharp" src={profileImgSrc}/>
</IconButton>
</Tooltip>
<Menu
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/components/User.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function User({ goTo }) {
<Container component="main" maxWidth="xs">
<CssBaseline />
<Typography component="h1" variant="h5" align="center" sx={{ marginTop: 2 }}>
Welcome to the 2024 edition of the Software Architecture course
ASW - WIQ Quiz
</Typography>
{showLogin ? <Login goTo={(x) => goTo(x)} /> : <AddUser />}
<Typography component="div" align="center" sx={{ marginTop: 2 }}>
Expand Down
30 changes: 30 additions & 0 deletions webapp/src/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,36 @@ button:focus-visible {
vertical-align: middle;
}

#fotosPerfil {
display: grid;
justify-content: center;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
padding: 20px 0 20px 0;
}

.fotoPerfilBtn {
border-radius: 0% !important;
border: none;
cursor: pointer;
width: 3.5em;
padding: 0 !important;
}

.fotoPerfil {
background-color: #3498db;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
width: 3.5em;
border-radius: 10%;
}

.selectedImg {
border: 4px solid #FFF !important;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
Expand Down
64 changes: 62 additions & 2 deletions webapp/src/test/AddUser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import MockAdapter from 'axios-mock-adapter';
import AddUser from '../components/AddUser';

const mockAxios = new MockAdapter(axios);
const handleImageClick = jest.fn();

describe('AddUser component', () => {
beforeEach(() => {
Expand All @@ -15,7 +16,8 @@ describe('AddUser component', () => {
render(<AddUser />);

const usernameInput = screen.getByLabelText(/Username/i);
const passwordInput = screen.getByLabelText(/Password/i);
const passwordInput = screen.getByLabelText("Password");
const passwordConfirmInput = screen.getByLabelText(/Confirm/i);
const addUserButton = screen.getByRole('button', { name: /Add User/i });

// Mock the axios.post request to simulate a successful response
Expand All @@ -24,6 +26,7 @@ describe('AddUser component', () => {
// Simulate user input
fireEvent.change(usernameInput, { target: { value: 'testUser' } });
fireEvent.change(passwordInput, { target: { value: 'testPassword' } });
fireEvent.change(passwordConfirmInput, { target: { value: 'testPassword' } });

// Trigger the add user button click
fireEvent.click(addUserButton);
Expand All @@ -34,11 +37,48 @@ describe('AddUser component', () => {
});
});

it('passwords dont match', async () => {
render(<AddUser />);

await waitFor(() => {
//const imageDiv = screen.getByTestId('fotosPerfil');
//expect(imageDiv).toBeInTheDocument();

const iconButtonElements = screen.getAllByRole('button', { className: /fotoPerfilBtn/i });
expect(iconButtonElements.length).toBeGreaterThan(4);

const iconElements = screen.getAllByRole('img', { className: "fotoPerfil" });
expect(iconElements.length).toBeGreaterThan(4);
});

const usernameInput = screen.getByLabelText(/Username/i);
const passwordInput = screen.getByLabelText("Password");
const passwordConfirmInput = screen.getByLabelText(/Confirm/i);
const addUserButton = screen.getByRole('button', { name: /Add User/i });

// Mock the axios.post request to simulate a successful response
mockAxios.onPost('http://localhost:8000/adduser').reply(200);

// Simulate user input
fireEvent.change(usernameInput, { target: { value: 'testUser' } });
fireEvent.change(passwordInput, { target: { value: 'testPassword' } });
fireEvent.change(passwordConfirmInput, { target: { value: 'randomPass' } });

// Trigger the add user button click
fireEvent.click(addUserButton);

// Wait for the Snackbar to be open
await waitFor(() => {
expect(screen.getByText(/Passwords do not match/i)).toBeInTheDocument();
});
});

it('should handle error when adding user', async () => {
render(<AddUser />);

const usernameInput = screen.getByLabelText(/Username/i);
const passwordInput = screen.getByLabelText(/Password/i);
const passwordInput = screen.getByLabelText("Password");
const passwordConfirmInput = screen.getByLabelText(/Confirm/i);
const addUserButton = screen.getByRole('button', { name: /Add User/i });

// Mock the axios.post request to simulate an error response
Expand All @@ -47,6 +87,7 @@ describe('AddUser component', () => {
// Simulate user input
fireEvent.change(usernameInput, { target: { value: 'testUser' } });
fireEvent.change(passwordInput, { target: { value: 'testPassword' } });
fireEvent.change(passwordConfirmInput, { target: { value: 'testPassword' } });

// Trigger the add user button click
fireEvent.click(addUserButton);
Expand All @@ -56,4 +97,23 @@ describe('AddUser component', () => {
expect(screen.getByText(/Error: Internal Server Error/i)).toBeInTheDocument();
});
});

test('selección de imagen de perfil', async () => {
const { getByAltText } = render(<AddUser />);

// Encuentra los botones de imagen de perfil
const button1 = getByAltText('Imagen Perfil 1');
const button2 = getByAltText('Imagen Perfil 2');
const button3 = getByAltText('Imagen Perfil 3');
const button4 = getByAltText('Imagen Perfil 4');
const button5 = getByAltText('Imagen Perfil 5');

// Simula hacer clic en cada botón
fireEvent.click(button1);
fireEvent.click(button2);
fireEvent.click(button3);
fireEvent.click(button4);
fireEvent.click(button5);

});
});
Loading

0 comments on commit fdf6826

Please sign in to comment.