Skip to content
Snippets Groups Projects
Commit 1b3be269 authored by FALKE JANOS's avatar FALKE JANOS :island:
Browse files

New version qui marche

parent efd23e83
Branches
1 merge request!3Feature/modify profile
Showing with 5499 additions and 40 deletions
MYSQL_USER=localhost
MYSQL_PASSWORD=root
MYSQL_DATABASE=student
MYSQL_HOST=localhost
MYSQL_ROOT_PASSWORD=root
MYSQL_HOST_IP=mysql
REACT_APP_SERVER_PORT=8000
NODE_PATH=src
PMA_HOST=mysql
/client/node_modules
/server/node_modules
.env
package-lock.json
.idea/
FROM node:10.9.0 as builder
WORKDIR /usr/app
COPY . .
WORKDIR /usr/app/frontend
RUN npm install
RUN npm run build
WORKDIR /usr/app/backend
RUN npm install
# APPLICATION - STUDENT
# jwt-express-react-example
## Front Student Module pour Projet ERP 2021
A two part example application with a frontend and backend.
#### Avant d'executer l'application :
Try it out at: http://jwt-express-react-example.trina.si/
Renommer le fichier .env-model en .env
#### Executer l'application :
## Functionality
docker-compose up
- Sign up
- Log in
- [Logged in] Log out
- [Logged in] Change password
- List of all users in the application (sorted by most-liked first)
- Show all users that are currently in the system
- Show number of likes
- [logged in] Like/unlike a user
- jwt token authentication with passport
- At least one test for all endpoints
## Tools used
#### Une fois le build terminé et les services lancés :
- [Node.js](https://nodejs.org/en/) v8 (async/await functionality)
- [mongodb](https://www.mongodb.com/), [mongoose](https://mongoosejs.com/)
- [Express.js](https://expressjs.com/)
- [Passport.js](http://www.passportjs.org/) for authentication
- [ReactJS](https://reactjs.org/)
- [Redux](https://redux.js.org/) for state management
- [Mocha](https://mochajs.org/) for testing
Le client (l'interface) :
## REST endpoints
Features the following REST endpoints:
localhost:3000
- /signup
Sign up to the system (username, password)
- /login
Le serveur (API) :
Logs in an existing user with a password
- **/me**
localhost:8000/api
Get the currently logged in user information
- **/me/update-password**
Visualisation de base de données via PhpMyAdmin
Update the current user's password
- /user/:username/
localhost:8080
List username & number of likes of a user
- **/user/:username/like**
Like a user
- **/user/:username/unlike**
Unlike a user
- /most-liked
List users in a most liked to least liked
Each user can like another only once, and they can unlike each other.
The bolded endpoints are authenticated calls.
## Usage
### Development
- Run `npm start` in `frontend/`, server will run at `http://localhost:3000`
- Run `npm start` in `backend/`, server will run at `http://localhost:8000`
node_modules/
/node_modules
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var logger = require('morgan');
var passport = require('passport');
const mongoose = require('mongoose');
var usersRouter = require('./routes/users');
var authRouter = require('./routes/auth');
var likesRouter = require('./routes/likes');
var app = express();
const port = process.env.PORT || 3000;
const dbPort = process.env.DB_PORT || 27017;
const dbUrl = process.env.MONGO_NAME || "localhost";
const dbCollection = process.env.DB_COLLECTION || "dev";
mongoose.connect(`mongodb://${dbUrl}/${dbCollection}`, {useNewUrlParser: true})
.then(_ => console.log('MongoDB connection success'))
.catch(err => console.error(err));
mongoose.set('useCreateIndex', true);
app.use(passport.initialize());
require('./passport-config')(passport);
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
var env = process.env.NODE_ENV || 'dev';
if (env == 'production') {
app.use("/", express.static(path.join(path.dirname(__dirname), '/frontend/build')))
}
app.use('/api/', usersRouter);
app.use('/api/', authRouter);
app.use('/api/', likesRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
return res.status(404).json(err);
});
module.exports = app;
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('backend:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '8000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
module.exports={tokenKey:"djghhhhuuwiwuewieuwieuriwu"}
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const LikeSchema = new mongoose.Schema({
liker: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
likee: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
}
});
LikeSchema.plugin(uniqueValidator);
module.exports = mongoose.model('Like', LikeSchema);
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
minlength:1,
unique: true,
trim: true,
},
email: {
type: String,
required: true,
minlength:1,
unique: true,
trim: true,
},
password: {
type: String,
required: true,
minlength: 8,
select: false,
},
});
UserSchema.virtual('likes', {
ref: 'Like',
localField: '_id',
foreignField: 'likee',
count: true,
});
UserSchema.virtual('liked', {
ref: 'Like',
localField: '_id',
foreignField: 'likee',
count: true,
});
UserSchema.set('toJSON', {
virtuals: true
});
UserSchema.plugin(uniqueValidator);
UserSchema.pre('save', function(next) {
let user = this;
if (!user.isModified('password')) {
return next();
} else {
}
bcrypt
.genSalt(12)
.then((salt) => {
return bcrypt.hash(user.password, salt);
})
.then((hash) => {
user.password = hash;
next();
})
.catch((err) => {
console.log(err);
next(err);
});
});
module.exports = mongoose.model('User', UserSchema);
This diff is collapsed.
{
"name": "backend",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "nodemon ./bin/www",
"start:prod": "node ./bin/www",
"test": "mocha --exit ./tests --recursive"
},
"dependencies": {
"bcrypt": "^5.0.0",
"bcryptjs": "^2.4.3",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.7.5",
"mongoose-unique-validator": "^2.0.3",
"morgan": "~1.9.1",
"passport": "^0.4.0",
"passport-jwt": "^4.0.0",
"pug": "2.0.0-beta11"
},
"devDependencies": {
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"mocha": "^6.1.4",
"nodemon": "^1.19.0",
"supertest": "^4.0.2"
}
}
const {Strategy, ExtractJwt} = require('passport-jwt');
//require('dotenv').config();
const secret = process.env.SECRET || 'some other secret as default';
const mongoose = require('mongoose');
const User = require('./models/user');
const opts = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: secret
};
module.exports = passport => {
passport.use(
new Strategy(opts, (payload, done) => {
User.findById(payload.id)
.then(user => {
if(user){
return done(null, {
id: user.id,
username: user.username,
});
}
return done(null, false);
}).catch(err => console.error(err));
})
);
};
var express = require('express');
var router = express.Router();
const User = require('../models/user');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const secret = process.env.SECRET || 'some other secret as default';
const passport = require('passport');
router.post('/signup', async (req, res) => {
var errors = {};
const user = await User.findOne({username: req.body.username});
const newUser = new User({...req.body});
try {
await newUser.save();
} catch(e) {
errors = e;
return res.status(400).json(e);
}
return res.status(200).json({});
});
router.post('/login', async (req, res) => {
const errors = {};
const username = req.body.username
const password = req.body.password;
const user = await User.findOne({ username }).select("+password");
// return if there was no user with this username found in the database
if (!user) {
errors.message = "No Account Found";
return res.status(400).json(errors);
}
isMatch = await bcrypt.compare(password, user.password);
// return 400 if password does not match
if (!isMatch) {
errors.message = "Password is incorrect";
return res.status(400).json(errors);
}
const payload = {
id: user._id,
username: user.username
};
token = await jwt.sign(payload, secret, { expiresIn: 36000 });
// return 500 if token is incorrect
if (!token) {
return res.status(500)
.json({ error: "Error signing token",
raw: err });
}
return res.json({
success: true,
token: `Bearer ${token}` });
});
router.get('/me', passport.authenticate('jwt', {session: false}), async function(req, res, next) {
const username = req.user.username;
const dbUser = await User.findOne({ username });
res.status(200).json(dbUser);
});
router.post('/me/update-password', passport.authenticate('jwt', {session: false}), async function(req, res, next) {
const username = req.user.username;
const oldPassword = req.body.oldpassword;
const newPassword = req.body.password;
const dbUser = await User.findOne({ username }).select("+password");
const passwordMatch = await new Promise((resolve, reject) => {
bcrypt.compare(oldPassword, dbUser.password,function(err, isMatch){
console.log(err)
if(err) return reject(err);
resolve(isMatch)
})
})
if (!passwordMatch) {
return res.status(400).json({message:"Old password incorrect."});
}
console.log(dbUser)
dbUser.password = newPassword;
console.log(dbUser.password)
try {
await dbUser.save();
} catch (e) {
return res.status(400).json(e);
}
res.status(200).json();
});
module.exports = router;
var express = require('express');
var router = express.Router();
const passport = require('passport');
const Like = require('../models/like');
const User = require('../models/user');
router.post('/user/:username/like', passport.authenticate('jwt', {session: false}), async function(req, res, next) {
const errors = {};
const liker = req.user;
const likeeUsername = req.params.username;
if (!likeeUsername) {
errors.username = 'No username to like supplied';
return res.status(400).json(errors);
}
const likee = await User.findOne({username: likeeUsername});
if (!likee) {
errors.username = 'No user with this username exists for you to like';
return res.status(400).json(errors);
}
const existingLike = await Like.findOne({
liker: liker.id,
likee: likee.id
});
if (existingLike) {
errors.username = 'You\'ve already liked this person';
return res.status(400).json(errors);
}
const newLike = new Like({
liker: liker.id,
likee: likee.id
});
newLike.save();
return res.status(200).json();
});
router.post('/user/:username/unlike', passport.authenticate('jwt', {session: false}), async function(req, res, next) {
const errors = {};
const liker = req.user;
const likeeUsername = req.params.username;
if (!likeeUsername) {
errors.username = 'No username to unlike supplied';
return res.status(400).json(errors);
}
const likee = await User.findOne({username: likeeUsername});
if (!likee) {
errors.username = 'No user with this username exists for you to unlike';
return res.status(400).json(errors);
}
const existingLike = await Like.findOne({
liker: liker.id,
likee: likee.id
});
if (!existingLike) {
errors.username = 'Like doesn\'t exist';
return res.status(400).json(errors);
}
existingLike.delete();
return res.status(200).json();
});
module.exports = router;
var express = require('express');
var router = express.Router();
const passport = require('passport');
const User = require('../models/user');
router.get('/most-liked', async function(req, res, next) {
// handle authentication manually, as we want the API to work in both cases,
// but to return 'liked' property if logged in
passport.authenticate('jwt', async function(err, user, info) {
var match = {}
if (user) {
match = {liker: user.id};
}
const users = await User.find(
).populate({path:'liked', match: match}).populate({path:'likes'}).sort({'likes': -1});
res.status(200).json(users);
})(req, res, next);
});
router.get('/user/:username', async function(req, res, next) {
const user = await User.findOne({username: req.params.username}).populate('likes');
res.status(200).json(user);
});
module.exports = router;
var expect = require('chai').expect;
const supertest = require('supertest');
const mocha = require('mocha')
var app = require('../app')
const mongoose = require('mongoose');
const userCredentials = {
username: 'trina',
email:'trina@trina.si',
password: 'monday123456'
};
process.env.DB_COLLECTION = "test";
const baseUrl = supertest.agent(app);
describe('app', function() {
var res, body, token;
before(function (done) {
this.timeout(3000);
mongoose.connect(`mongodb://localhost/${process.env.DB_COLLECTION}`, {useNewUrlParser: true}, function(){
mongoose.connection.db.dropDatabase(function(){
done()
})
})
})
describe('POST /signup', function() {
this.timeout(3000);
before(async function () {
res = await baseUrl.post('/api/signup')
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send(userCredentials);
});
it('respond with 200', function(done) {
expect(res.status).to.equal(200);
expect(res.body).to.be.an('object');
token = res.body;
done();
});
})
describe('GET /most-liked', function() {
this.timeout(3000);
before(async function () {
res = await baseUrl.get('/api/most-liked')
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send();
});
it('respond with an array of most liked users', function(done) {
expect(res.status).to.equal(200);
expect(res.body).to.be.an('array');
done();
});
});
describe('POST /login', function() {
this.timeout(3000);
before(async function () {
res = await baseUrl.post('/api/login')
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send(userCredentials);
});
it('respond with token', function(done) {
expect(res.status).to.equal(200);
expect(res.body).to.be.an('object');
expect(res.body).to.have.property('token');
done();
});
});
});
node_modules
build
\ No newline at end of file
FROM node:10-alpine
RUN mkdir -p /app/
WORKDIR /app/
COPY . .
RUN npm install
CMD ["npm", "start"]
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment