Commit 307ae233 authored by Rosanny Sihombing's avatar Rosanny Sihombing
Browse files

Merge branch 'MLAB-87' into 'testing'

Mlab 87

See merge request !83
parents 2fda31a1 957b531b
Pipeline #3641 passed with stage
in 13 seconds
const gitlab = require('../routes/gitlab') const gitlab = require('../functions/gitlab')
//const axios = require('axios') //const axios = require('axios')
//jest.mock('axios') //jest.mock('axios')
......
const methods = require('../routes/methods') const methods = require('../functions/methods')
describe("DB methohds test", () => { describe("DB methohds test", () => {
it('returns a user from DB by email', done => { it("returns a user from DB by email", async() => {
methods.getUserByEmail('litehon958@whipjoy.com', function(resp, err){ const user = await methods.getUserByEmail('litehon958@whipjoy.com')
try { expect(user).not.toBeNull()
expect(resp).not.toBeNull()
expect(err).toBeNull()
done()
} catch (error) {
done(error)
}
}) })
it("returns a null user", async() => {
const user = await methods.getUserByEmail('jondoe@nowhere.com') // a non-exist user
expect(user).toBeNull()
}) })
it("returns a user from DB by ID", done => { it("returns a user's email", async() => {
methods.getUserById(10, function(resp, err){ const email = await methods.getUserEmailById(1)
try { expect(email).not.toBeNull()
expect(resp).not.toBeNull()
expect(err).toBeNull()
done()
} catch (error) {
done(error)
}
}) })
it("returns null instead of a user's email", async() => {
const email = await methods.getUserEmailById(1005) // no user has this ID
expect(email).toBeNull()
}) })
it("checks user email", done => { it("returns null from DB by token", async() => {
methods.checkUserEmail("test@email.de", function(err, resp){ const user = await methods.getUserByToken('12345678') // unvalid token
try { expect(user).toBeNull() // for valid token = expect(user).not.toBeNull()
expect(resp).not.toBeNull()
expect(err).toBeNull()
done()
} catch (error) {
done(error)
}
}) })
it("returns a user's verification token, if any", async() => {
const token = await methods.getVerificationTokenByUserId(1)
expect(token).toBeNull()
})
it("returns a user's ID, if any", async() => {
const token = await methods.getUserIdByVerificationToken('12345678') // unvalid token
expect(token).toBeNull() // for valid token = expect(user).not.toBeNull()
}) })
it("returns a user by token", done => { it("returns a user's GitLab_ID, if any", async() => {
methods.checkUserEmail("1abc0qwerty", function(err, resp){ // token = any alphanumeric const id = await methods.getGitlabId(1)
try { expect(id).not.toBeNull()
expect(resp).not.toBeNull()
expect(err).toBeNull()
done()
} catch (error) {
done(error)
}
}) })
it("checks user email", async() => {
const user = await methods.checkUserEmail('litehon958@whipjoy.com')
expect(user).not.toBeNull()
})
it("checks user email and return null", async() => {
const user = await methods.checkUserEmail('jondoe@nowhere.com') // a non-exist user
expect(user).toBeNull()
}) })
}) })
...@@ -68,49 +68,51 @@ var methods = { ...@@ -68,49 +68,51 @@ var methods = {
getUserByEmail: async function(email) { getUserByEmail: async function(email) {
try { try {
let rows = await dbconn.user.promise().query('SELECT id, verificationStatus, salutation, title, firstname, lastname, industry, organisation, speciality, m4lab_idp FROM user WHERE email = "' +email+'"') let rows = await dbconn.user.promise().query('SELECT id, verificationStatus, salutation, title, firstname, lastname, industry, organisation, speciality, m4lab_idp FROM user WHERE email = "' +email+'"')
if (rows[0][0]) {
return rows[0][0] return rows[0][0]
}
else { return null }
} catch (err) { } catch (err) {
console.error(err) console.error(err)
return err
} }
return null
}, },
getUserById: function(userId, callback) { getUserEmailById: async function(userId) {
dbconn_OBSOLETE.user.query('SELECT verificationStatus, email, salutation, title, firstname, lastname, industry, organisation, speciality FROM user WHERE id = ' +userId, function (err, rows, fields) { try {
let user let rows = await dbconn.user.promise().query('SELECT email FROM user WHERE id = ' +userId)
if (err) { throw err } if (rows[0][0]) {
else { return rows[0][0].email
if ( rows.length > 0) {
user = rows[0];
} }
else { return null }
} catch (err) {
console.error(err)
} }
callback(user, err); return null
});
}, },
checkUserEmail: function(email, callback) { checkUserEmail: async function(email) {
let user try {
dbconn_OBSOLETE.user.query('SELECT id, email FROM user WHERE email = "' +email+'"', function (err, rows) { let rows = await dbconn.user.promise().query('SELECT id, email FROM user WHERE email = "' +email+'"')
if (err) { throw err } if (rows[0][0]) {
else { return rows[0][0]
if ( rows.length > 0) {
user = rows[0];
} }
else { return null }
} catch (err) {
console.error(err)
} }
callback(err, user) return null
});
}, },
getUserByToken: function(token, callback) { getUserByToken: async function(token) {
let user try {
dbconn_OBSOLETE.user.query('SELECT t1.user_id, t2.email FROM userdb.credential AS t1 INNER JOIN userdb.user AS t2 ON t1.user_id = t2.id AND t1.resetPasswordToken = "' let rows = await dbconn.user.promise().query('SELECT t1.user_id, t2.email FROM userdb.credential AS t1 INNER JOIN userdb.user AS t2 ON t1.user_id = t2.id AND t1.resetPasswordToken = "'
+token+'" and resetPasswordExpires > '+Date.now(), function (err, rows, fields) { +token+'" and resetPasswordExpires > '+Date.now())
if (err) { throw err } if (rows[0][0]) {
else { return rows[0][0]
if ( rows.length > 0) {
user = rows[0]
}
} }
callback(err, user) else { return null }
} catch (err) {
console.error(err)
} }
) return null
}, },
updateUserById: function(userData, callback) { updateUserById: function(userData, callback) {
dbconn_OBSOLETE.user.query('UPDATE user SET ? WHERE id = ' +userData.id, userData, function (err, rows, fields) { dbconn_OBSOLETE.user.query('UPDATE user SET ? WHERE id = ' +userData.id, userData, function (err, rows, fields) {
...@@ -124,7 +126,7 @@ var methods = { ...@@ -124,7 +126,7 @@ var methods = {
callback(err) callback(err)
}) })
}, },
getUserIdByEmail: function(email, callback) { getUserIdByEmail_OBSOLETE: function(email, callback) {
let userId let userId
dbconn_OBSOLETE.user.query('SELECT id FROM user WHERE email = "' +email+'"', function (err, rows, fields) { dbconn_OBSOLETE.user.query('SELECT id FROM user WHERE email = "' +email+'"', function (err, rows, fields) {
if (err) { if (err) {
...@@ -138,7 +140,7 @@ var methods = { ...@@ -138,7 +140,7 @@ var methods = {
callback(userId, err) callback(userId, err)
}); });
}, },
getUserProjectRole: function(userId, callback) { getUserProjectRole_OBSOLETE: function(userId, callback) {
dbconn_OBSOLETE.user.query('SELECT project_id, role_id FROM user_project_role WHERE user_id = "' +userId+'"', function (err, rows, fields) { dbconn_OBSOLETE.user.query('SELECT project_id, role_id FROM user_project_role WHERE user_id = "' +userId+'"', function (err, rows, fields) {
if (err) throw err if (err) throw err
callback(rows, err) callback(rows, err)
...@@ -150,31 +152,31 @@ var methods = { ...@@ -150,31 +152,31 @@ var methods = {
callback(err) callback(err)
}) })
}, },
getVerificationTokenByUserId: function(userId, callback) { getVerificationTokenByUserId: async function(userId) {
let token try {
dbconn_OBSOLETE.user.query('SELECT token FROM verification WHERE user_id = "' +userId+'"', function (err, rows, fields) { let rows = await dbconn.user.promise().query('SELECT token FROM verification WHERE user_id = "' +userId+'"')
if (err) { if (rows[0][0]) {
throw err return rows[0][0].token
}
else {
if (rows.length > 0) {
token = rows[0].token
} }
else { return null }
} catch (err) {
console.error(err)
} }
callback(token, err) return null
})
}, },
getUserIdByVerificationToken: function(token, callback) { getUserIdByVerificationToken: async function(token) {
let userId try {
dbconn_OBSOLETE.user.query('SELECT user_id FROM verification WHERE token = "' +token+'"', function (err, rows, fields) { let rows = await dbconn.user.promise().query('SELECT user_id FROM verification WHERE token = "' +token+'"')
if (err) { if (rows[0][0]) {
throw err return rows[0][0].user_id
}
else {
return null
} }
else if(rows[0]) { } catch (err) {
userId = rows[0].user_id console.error(err)
} }
callback(userId, err) return null
})
}, },
verifyUserAccount: function(userData, callback) { verifyUserAccount: function(userData, callback) {
dbconn_OBSOLETE.user.beginTransaction(function(err) { // START TRANSACTION dbconn_OBSOLETE.user.beginTransaction(function(err) { // START TRANSACTION
......
...@@ -89,6 +89,10 @@ module.exports = function (app, config, passport, lang) { ...@@ -89,6 +89,10 @@ module.exports = function (app, config, passport, lang) {
async function getLoggedInUserData(email) { async function getLoggedInUserData(email) {
let user = await methods.getUserByEmail(email) let user = await methods.getUserByEmail(email)
if (!user) {
console.log('no user found')
return null
} else {
let loggedInUser = new portalUser( let loggedInUser = new portalUser(
user.id, email, user.salutation, user.title, user.firstname, user.lastname, user.industry, user.organisation, user.speciality, user.m4lab_idp, null, user.verificationStatus user.id, email, user.salutation, user.title, user.firstname, user.lastname, user.industry, user.organisation, user.speciality, user.m4lab_idp, null, user.verificationStatus
) )
...@@ -97,9 +101,9 @@ module.exports = function (app, config, passport, lang) { ...@@ -97,9 +101,9 @@ module.exports = function (app, config, passport, lang) {
if (userGitlabId) { if (userGitlabId) {
loggedInUser.setGitlabUserId(userGitlabId) loggedInUser.setGitlabUserId(userGitlabId)
} }
return loggedInUser return loggedInUser
} }
}
app.get('/', async function (req, res) { app.get('/', async function (req, res) {
if ( !req.isAuthenticated() ) { if ( !req.isAuthenticated() ) {
...@@ -330,16 +334,18 @@ module.exports = function (app, config, passport, lang) { ...@@ -330,16 +334,18 @@ module.exports = function (app, config, passport, lang) {
} }
}); });
app.get("/resendVerificationEmail", function(req, res){ app.get('/resendVerificationEmail', async function(req, res){
if (req.isAuthenticated()) { if (!req.isAuthenticated) {
var emailAddress = req.user.email res.redirect('/login')
} else {
methods.getUserIdByEmail(req.user.email, function(userId, err) { let loggedInUser = await getLoggedInUserData(req.user.email)
if (!err) { if (!loggedInUser) {
// get token res.redirect('/login')
methods.getVerificationTokenByUserId(userId, function(token, err){ } else {
if (!err) { let token = await methods.getVerificationTokenByUserId(loggedInUser.id)
if (token) { if (!token) {
res.send(false)
} else {
// send email // send email
var emailSubject = "Bitte bestätigen Sie Ihr M4_LAB Benutzerkonto" var emailSubject = "Bitte bestätigen Sie Ihr M4_LAB Benutzerkonto"
var emailContent = '<div>Lieber Nutzer,<br/><br/>' + var emailContent = '<div>Lieber Nutzer,<br/><br/>' +
...@@ -348,7 +354,7 @@ module.exports = function (app, config, passport, lang) { ...@@ -348,7 +354,7 @@ module.exports = function (app, config, passport, lang) {
'<br/><br/>' + '<br/><br/>' +
'Ohne Bestätigung Ihres Kontos müssen wir Ihr Konto leider nach 7 Tagen löschen.</p><br/>' + constants.mailSignature + 'Ohne Bestätigung Ihres Kontos müssen wir Ihr Konto leider nach 7 Tagen löschen.</p><br/>' + constants.mailSignature +
'</div>'; '</div>';
mailer.options.to = emailAddress; mailer.options.to = loggedInUser.email;
mailer.options.subject = emailSubject; mailer.options.subject = emailSubject;
mailer.options.html = emailContent; mailer.options.html = emailContent;
mailer.transport.sendMail(mailer.options, function(err) { mailer.transport.sendMail(mailer.options, function(err) {
...@@ -359,30 +365,8 @@ module.exports = function (app, config, passport, lang) { ...@@ -359,30 +365,8 @@ module.exports = function (app, config, passport, lang) {
}) })
res.send(true) res.send(true)
} }
else {
res.send(false)
}
} }
else {
console.log(err)
}
})
}
})
}
})
app.get('/email/:email', function(req, res) {
methods.checkUserEmail(req.params.email, function(err, user){
if (!err) {
if (user) {
res.send(false)
} }
else {
res.send(true)
}
}
})
}) })
// ============= NEW GITLAB PAGES =========================== // ============= NEW GITLAB PAGES ===========================
......
...@@ -98,56 +98,55 @@ module.exports = function (app, config, lang) { ...@@ -98,56 +98,55 @@ module.exports = function (app, config, lang) {
// =================== USERS VERIFICATION ========================= // =================== USERS VERIFICATION =========================
app.get("/verifyAccount", function(req, res){ app.get("/verifyAccount", async function(req, res){
methods.getUserIdByVerificationToken(req.query.token, function(userId, err){ let userId = await methods.getUserIdByVerificationToken(req.query.token)
if (userId) { if (!userId) {
// no user found
res.render(lang+'/account/verification', {
status: null
})
} else {
// a user found, verify the account
let userData = { let userData = {
id: userId, id: userId,
verificationStatus: 1 verificationStatus: 1
} }
methods.verifyUserAccount(userData, function(err){ methods.verifyUserAccount(userData, async function(err){
if (err) { if (err) {
console.log("Error: "+err) console.log("Error: "+err)
res.render(lang+'/account/verification', { res.render(lang+'/account/verification', {
status: false status: false
}); });
} } else {
else {
// send welcome email after successful account verification // send welcome email after successful account verification
methods.getUserById(userId, function(data, err){ let userEmail = await methods.getUserEmailById(userId)
if (err) { if (!userEmail) {
console.log("Error: "+err) res.render(lang+'/account/verification', {
} status: false
else { })
} else {
// send email // send email
var emailSubject = "Herzlich willkommen" var emailSubject = "Herzlich willkommen"
var emailContent = '<div>Lieber Nutzer,<br/><br/>' + var emailContent = '<div>Lieber Nutzer,<br/><br/>' +
'<p>herzlich willkommen beim Transferportal der HFT Stuttgart!<br/>' + '<p>herzlich willkommen beim Transferportal der HFT Stuttgart!<br/>' +
'Sie können nun alle Dienste des Portals nutzen.<p/><br/>' + constants.mailSignature; 'Sie können nun alle Dienste des Portals nutzen.<p/><br/>' + constants.mailSignature;
mailer.options.to = data.email; mailer.options.to = userEmail
mailer.options.subject = emailSubject; mailer.options.subject = emailSubject
mailer.options.html = emailContent; mailer.options.html = emailContent
mailer.transport.sendMail(mailer.options, function(err) { mailer.transport.sendMail(mailer.options, function(err) {
if (err) { if (err) {
console.log('cannot send email') console.log('cannot send email')
throw err throw err
} }
}) })
}
})
res.render(lang+'/account/verification', { res.render(lang+'/account/verification', {
status: true status: true
});
}
}) })
} }
else {
res.render(lang+'/account/verification', {
status: null
});
} }
}) })
}
}) })
// ==================== FORGOT PASSWORD =========================== // ==================== FORGOT PASSWORD ===========================
...@@ -157,7 +156,7 @@ module.exports = function (app, config, lang) { ...@@ -157,7 +156,7 @@ module.exports = function (app, config, lang) {
user: req.user user: req.user
}) })
}) })
app.post('/forgotPwd', function(req, res, next) { app.post('/forgotPwd', function(req, res) {
let emailAddress = req.body.inputEmail let emailAddress = req.body.inputEmail
async.waterfall([ async.waterfall([
function(done) { function(done) {
...@@ -166,9 +165,11 @@ module.exports = function (app, config, lang) { ...@@ -166,9 +165,11 @@ module.exports = function (app, config, lang) {
done(err, token) done(err, token)
}) })
}, },
function(token, done) { async function(token) {
methods.checkUserEmail(emailAddress, function(err, user){ let user = await methods.checkUserEmail(emailAddress)
if (user) { if (!user) {
console.log('no user found')
} else {
var emailSubject = "Ihre Passwort-Anfrage an das Transferportal der HFT Stuttgart"; var emailSubject = "Ihre Passwort-Anfrage an das Transferportal der HFT Stuttgart";
var emailContent = '<div>Lieber Nutzer,<br/><br/>' + var emailContent = '<div>Lieber Nutzer,<br/><br/>' +
'<p>wir haben Ihre Anfrage zur Erneuerung Ihres Passwortes erhalten. Falls Sie diese Anfrage nicht gesendet haben, ignorieren Sie bitte diese E-Mail.<br/><br/>' + '<p>wir haben Ihre Anfrage zur Erneuerung Ihres Passwortes erhalten. Falls Sie diese Anfrage nicht gesendet haben, ignorieren Sie bitte diese E-Mail.<br/><br/>' +
...@@ -181,7 +182,7 @@ module.exports = function (app, config, lang) { ...@@ -181,7 +182,7 @@ module.exports = function (app, config, lang) {
resetPasswordExpires: Date.now() + 3600000 // 1 hour resetPasswordExpires: Date.now() + 3600000 // 1 hour
} }
methods.updateCredential(credentialData, function(err) { methods.updateCredential(credentialData, function(err) {
done(err, token, user); if (err) { console.error(err) }
}) })
// send email // send email
...@@ -189,13 +190,9 @@ module.exports = function (app, config, lang) { ...@@ -189,13 +190,9 @@ module.exports = function (app, config, lang) {
mailer.options.subject = emailSubject mailer.options.subject = emailSubject
mailer.options.html = emailContent mailer.options.html = emailContent
mailer.transport.sendMail(mailer.options, function(err) { mailer.transport.sendMail(mailer.options, function(err) {
done(err, 'done') if (err) { console.error(err) }
}); })
}
else {
done(err, 'no user found')
} }
});
} }
], function(err) { ], function(err) {
if (err) { if (err) {
...@@ -205,12 +202,12 @@ module.exports = function (app, config, lang) { ...@@ -205,12 +202,12 @@ module.exports = function (app, config, lang) {
res.flash('success', 'Wenn Ihre E-Mail-Adresse registriert ist, wurde eine E-Mail mit dem weiteren Vorgehen an ' + emailAddress + ' versendet.') res.flash('success', 'Wenn Ihre E-Mail-Adresse registriert ist, wurde eine E-Mail mit dem weiteren Vorgehen an ' + emailAddress + ' versendet.')
} }
res.redirect('/account/forgotPwd') res.redirect('/account/forgotPwd')
}); })
}) })
// reset // reset
app.get('/reset/:token', function(req, res) { app.get('/reset/:token', async function(req, res) {
methods.getUserByToken(req.params.token, function(err, user){ let user = await methods.getUserByToken(req.params.token)
if (!user) { if (!user) {
res.flash('error', 'Der Schlüssel zum zurücksetzen des Passworts ist ungültig oder abgelaufen.') res.flash('error', 'Der Schlüssel zum zurücksetzen des Passworts ist ungültig oder abgelaufen.')
res.redirect('/account/forgotPwd') res.redirect('/account/forgotPwd')
...@@ -218,11 +215,14 @@ module.exports = function (app, config, lang) { ...@@ -218,11 +215,14 @@ module.exports = function (app, config, lang) {
res.render(lang+'/account/reset') res.render(lang+'/account/reset')
} }
}) })
}) app.post('/reset/:token', async function(req, res) {
app.post('/reset/:token', function(req, res) {
var newPwd = req.body.inputNewPwd var newPwd = req.body.inputNewPwd
methods.getUserByToken(req.params.token, function(err, user){
if (user) { let user = await methods.getUserByToken(req.params.token)
if (!user) {
res.flash('error', "User not found.")
res.redirect('/login')
} else {
// encrypt password // encrypt password
bcrypt.genSalt(saltRounds, function(err, salt) { bcrypt.genSalt(saltRounds, function(err, salt) {
bcrypt.hash(newPwd, salt, function(err, hash) { bcrypt.hash(newPwd, salt, function(err, hash) {
...@@ -235,30 +235,24 @@ module.exports = function (app, config, lang) { ...@@ -235,30 +235,24 @@ module.exports = function (app, config, lang) {
if (err) { if (err) {
res.flash('error', "Datenbankfehler: Passwort kann nicht geändert werden.") res.flash('error', "Datenbankfehler: Passwort kann nicht geändert werden.")
throw err throw err
} } else {
else {
res.flash('success', "Passwort aktualisiert!") res.flash('success', "Passwort aktualisiert!")
// send notifiaction email // send notifiaction email
mailer.options.to = user.email mailer.options.to = user.email
mailer.options.subject = constants.updatePasswordMailSubject mailer.options.subject = constants.updatePasswordMailSubject
mailer.options.html = constants.updatePasswordMailContent+'<div>'+constants.mailSignature+'</div>' mailer.options.html = constants.updatePasswordMailContent+'<div>'+constants.mailSignature+'</div>'
mailer.transport.sendMail(mailer.options, function(err) { mailer.transport.sendMail(mailer.options, function(err) {
if (err) { if (err) { console.log(err) }
console.log(err) })
}
});
// redirect to login page
res.redirect('/login') res.redirect('/login')
} }
}) })
}); });
}); });
} }
else {
res.flash('error', "User not found.")
res.redirect('/login')
}
})
}) })
// ======================= CONTACT FORM =========================== // ======================= CONTACT FORM ===========================
......
...@@ -60,13 +60,8 @@ html(lang="de") ...@@ -60,13 +60,8 @@ html(lang="de")
function verify() { function verify() {
$(".spinner-border").show() $(".spinner-border").show()
$.get( "/resendVerificationEmail", function( data ) { $.get( "/resendVerificationEmail", function( data ) {
console.log(data) if (data) { alert( "Email sent!" ) }
if (data) { else { alert("Please contact support-transfer@hft-stuttgart.de to verify your account.") }
alert( "Email sent!" )
}
else {
alert("Please contact support-transfer@hft-stuttgart.de to verify your account.")
}
}) })
.fail(function() { .fail(function() {
alert( "Something went wrong. Please try again." ) // todo: to DE alert( "Something went wrong. Please try again." ) // todo: to DE
......
...@@ -22,15 +22,15 @@ html(lang="de") ...@@ -22,15 +22,15 @@ html(lang="de")
body body
div(class="container") div(class="container")
div(class="center", align="center") div(class="center", align="center")
a(href="https://m4lab.hft-stuttgart.de") a(href="https://transfer.hft-stuttgart.de")
img(src="https://transfer.hft-stuttgart.de/images/demo/m4lab_logo.jpg", class="img-responsive center-block", width="185", height="192") img(src="https://transfer.hft-stuttgart.de/images/demo/m4lab_logo.jpg", class="img-responsive center-block", width="185", height="192")
br br
br br
if status == true if status == true
p(class="h5") Ihr Benutzerkonto wurde bestätigt. Bitte <a href="https://m4lab.hft-stuttgart.de/account/">melden Sie sich an</a>. p(class="h5") Ihr Benutzerkonto wurde bestätigt. Bitte <a href="https://transfer.hft-stuttgart.de/account/">melden Sie sich an</a>.
else if status == false else if status == false
p(class="h5") Ihr Benutzerkonto konnte nicht bestätigt werden, bitte versuchen Sie es erneut. p(class="h5") Ihr Benutzerkonto konnte nicht bestätigt werden, bitte versuchen Sie es erneut.
else else
p(class="h5") Ihr Benutzerkonto wude nicht gefunden. p(class="h5") Ihr Benutzerkonto wurde nicht gefunden.
// Bootstrap // Bootstrap
script(src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous") script(src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous")
\ No newline at end of file
Supports Markdown
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