Skip to content

Commit

Permalink
S8 signup flow basic profile (#213)
Browse files Browse the repository at this point in the history
* signup now in two stages; create firebase account, and create profile

* signup flow more logical now ... handles most edge cases pretty well

* basic profile page - still buggy, non functional update profile button

* local default picture

* update user backend added... no tests

* lint errors fixed

* working profile update

* fixed textarea stuff, watcher to fix refresh profile problem

* profile picture update added

* working password reset from login form

* nice error message for non-unique username

* made hasProfile dependent on isLoggedIn to alleviate some potential bad

* removed TODO

* fixed post review/question bug when logged in

* loading state for nav, split auth and profile get into two bits

* reverted some stuff ... working again

* better auth code, profile cache, global loading spinner in nav, reroutes

* lint

* page size fixaroo

* I'v messed something up

* protecting routes with guards

* fixed some PR things

* smol fixes

* removed preAuth route check - not needed rn

* smol fix to login page reroute
  • Loading branch information
NunoDasNeves authored Oct 13, 2018
1 parent 43e726d commit d639762
Show file tree
Hide file tree
Showing 27 changed files with 622 additions and 195 deletions.
20 changes: 10 additions & 10 deletions backend/src/controllers/course.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { ANONYMOUS } = require('../models/constants')
const { ANONYMOUS, PAGE_SIZE } = require('../models/constants')
const courseModel = require('../models/course')()
const questionModel = require('../models/question')()
const reviewModel = require('../models/review')()
Expand All @@ -19,9 +19,9 @@ exports.getCourse = function ({ params }, res) {

/* Get all questions for a course */
exports.getCourseQuestions = function ({ params, query }, res) {
let p = parseInt(query.p)
const pageNumber = p || ANONYMOUS
const pageSize = 10
const pageNumber = parseInt(query.p) || 1
// TODO get page size from query
const pageSize = PAGE_SIZE

const getCourseQuestions = Promise.all([
questionModel.getQuestions(params.code, pageNumber, pageSize),
Expand All @@ -45,9 +45,9 @@ exports.getCourseQuestions = function ({ params, query }, res) {

/* Get all reviews for a course */
exports.getCourseReviews = function ({ params, query }, res) {
let p = parseInt(query.p)
const pageNumber = p || ANONYMOUS
const pageSize = 10
const pageNumber = parseInt(query.p) || 1
// TODO get page size from query
const pageSize = PAGE_SIZE

const getCourseReviews = new Promise((resolve, reject) => {
Promise.all([
Expand All @@ -59,7 +59,7 @@ exports.getCourseReviews = function ({ params, query }, res) {
'meta': {
'curr': pageNumber,
'last': lastPage,
'pageSize': pageSize
'pageSize':pageSize
},
'data': values[0]
})
Expand All @@ -71,14 +71,14 @@ exports.getCourseReviews = function ({ params, query }, res) {
}

exports.postQuestion = function ({ user, params, body }, res) {
body.userID = user || ANONYMOUS
body.userID = user && user.id || ANONYMOUS
responseHandler(questionModel.postQuestion(params.code, body), res)
.catch(errorHandler(res))
}

/* POST new review */
exports.postReview = function ({ user, params, body }, res) {
body.userID = user || ANONYMOUS
body.userID = user && user.id || ANONYMOUS
responseHandler(reviewModel.postReview(params.code, body), res)
.catch(errorHandler(res))
}
8 changes: 4 additions & 4 deletions backend/src/controllers/question.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ exports.getQuestionAnswers = function ({ params, query }, res) {

/* POST new answer. */
exports.postAnswer = function ({ user, params, query, body }, res) {
body.userID = user || ANONYMOUS
body.userID = user && user.id || ANONYMOUS
commentModel.postComment({ questionID: params.id }, body)
.then(exports.getQuestionAnswers({ params, query }, res))
.catch(errorHandler(res))
Expand All @@ -56,7 +56,7 @@ exports.getQuestionLikes = function ({ params }, res) {

/* PUT updated question likes value */
exports.putQuestionLikes = function ({ user, params, body }, res) {
body.userID = user || ANONYMOUS
body.userID = user && user.id || ANONYMOUS
responseHandler(likesModel.putLikes({ type: 'question', ...params, ...body }), res)
.catch(errorHandler(res))
}
Expand All @@ -69,7 +69,7 @@ exports.getAnswerLikes = function ({ params }, res) {

/* PUT updated answer likes value */
exports.putAnswerLikes = function ({ user, params, body, query }, res) {
body.userID = user || ANONYMOUS
body.userID = user && user.id || ANONYMOUS
likesModel.putLikes({ type: 'answer', id: params.answerID, ...body })
.then(exports.getQuestionAnswers({ params, query }, res))
}
}
8 changes: 4 additions & 4 deletions backend/src/controllers/review.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ exports.getReviewComments = function ({ params, query }, res) {

/* POST new comment. */
exports.postComment = function ({ user, params, query, body }, res) {
body.userID = user || ANONYMOUS
body.userID = user && user.id || ANONYMOUS
commentModel.postComment({ reviewID: params.id }, body)
.then(exports.getReviewComments({ params, query }, res))
.catch(errorHandler(res))
Expand All @@ -55,7 +55,7 @@ exports.getReviewLikes = function ({ params }, res) {

/* PUT updated likes value */
exports.putReviewLikes = function ({ user, params, body }, res) {
body.userID = user || ANONYMOUS
body.userID = user && user.id || ANONYMOUS
responseHandler(likesModel.putLikes({ type: 'review', ...params, ...body }), res)
.catch(errorHandler(res))
}
Expand All @@ -68,7 +68,7 @@ exports.getReplyLikes = function ({ params }, res) {

/* PUT updated reply likes value */
exports.putReplyLikes = function ({ user, params, body, query }, res) {
body.userID = user || ANONYMOUS
body.userID = user && user.id || ANONYMOUS
likesModel.putLikes({ type: 'reply', id: params.replyID, ...body })
.then(exports.getReviewComments({ params, query }, res))
}
}
27 changes: 12 additions & 15 deletions backend/src/controllers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,26 @@ const userModel = require('../models/user')()
const { responseHandler } = require('../utils/helpers')

/* Get data for a specific user */
exports.getUser = function(_, res) {
return res.json({
userID: 1,
firstName: 'Walker',
lastName: 'Francis',
email: '[email protected]'
})
exports.getUser = function({id}, res) {
return responseHandler(userModel.getPublicProfile(id), res)
.catch(errorHandler(res))
}

/**
* user must be auth'd,
* provide frontend with any user specific data
*/
exports.getSelf = function(req, res) {
const errorResponse = errorHandler(res)
if (!req.user) {
return errorResponse({ message: 'Invalid Credentials' })
}
// fine for now, should include profile data
return res.json(req.user)
return responseHandler(userModel.getProfile(req.user.id), res)
.catch(errorHandler(res))
}

exports.createUser = function({ authorized: { email, uid }, body: { displayName } }, res) {
return responseHandler(userModel.createUser({ email, uid, displayName }), res)
.catch(errorHandler(res))
}

exports.createUser = function({ authorized }, res) {
return responseHandler(userModel.createUser(authorized), res)
exports.updateUser = function({ user: { id }, body: { degree, gradYear, description, picture } }, res) {
return responseHandler(userModel.updateUser(id, { degree, gradYear, description, picture }), res)
.catch(errorHandler(res))
}
3 changes: 3 additions & 0 deletions backend/src/models/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ exports.MIN_ENJOY = 1
exports.MAX_ENJOY = 5
exports.MIN_OPTION = 0
exports.MAX_OPTION = 3

// Paging Constants
exports.PAGE_SIZE = 10
9 changes: 5 additions & 4 deletions backend/src/models/db/tables.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ function createUserTable (db) {
return new Promise((resolve, reject) => {
db.run(`CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid TEXT NOT NULL,
displayName TEXT DEFAULT 'ANON',
uid TEXT UNIQUE NOT NULL,
displayName TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
joined TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
reputation INTEGER DEFAULT '0',
degree TEXT,
gradYear TIMESTAMP DEFAULT '2018',
description TEXT
gradYear TIMESTAMP,
description TEXT,
picture TEXT
)`,
(err) => err ? reject(err) : resolve('Created User Table'))
})
Expand Down
2 changes: 1 addition & 1 deletion backend/src/models/question.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Question {

/**
* Gets the total number of questions for a course
* @param {string} code The code of the course
* @param {string} code The code of the course duh
* @returns {object}
*/
getQuestionCount(code) {
Expand Down
23 changes: 18 additions & 5 deletions backend/src/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ class User {
}

/**
* TODO Return specialsed information for auth'd user
* Return specialised information for auth'd user
* @param {string} id The id of the auth'd user
*/
getProfile(id) {
return this.db
.query('SELECT * FROM user WHERE id=?', [id])
.query('SELECT id, email, displayName, degree, gradYear, description, picture, joined FROM user WHERE id=?', [id])
}

/**
Expand All @@ -20,13 +20,13 @@ class User {
* @param {number} id Required id param.
* @returns {object}
*/
getUser(id) {
getPublicProfile(id) {
return this.db
.query('SELECT * FROM user WHERE id=?', [id])
.query('SELECT id, displayName, degree, gradYear, description, picture, joined FROM user WHERE id=?', [id])
}

/**
* Alternative getter to get the relevant user's details
* Get all users details by UID. Used by authentication system
* @param {uid} uid uid string
* @returns {object} user object
*/
Expand All @@ -43,6 +43,19 @@ class User {
return this.db
.insert('user', { displayName, email, uid })
.then(id => this.getProfile(id))
.catch(error => {
// kinda hacky
if (error.errno === 19 && error.message.includes("displayName")) {
throw(Error("That display name is taken! Sorry!"))
}
throw(error)
})
}

updateUser(id, data) {
return this.db
.update('user', data, { id })
.then(() => this.getProfile(id))
}
}

Expand Down
17 changes: 9 additions & 8 deletions backend/src/routes/user.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
const express = require('express')
const router = express.Router()
const { isAuthorized } = require('../utils/helpers')
const { getSelf, getUser, createUser } = require('../controllers/user')
const { isAuthorized, isFirebaseAuthorized } = require('../utils/helpers')
const { getSelf, getUser, createUser, updateUser } = require('../controllers/user')

/* Get data for a specific user */
/* Get public data for a specific user */
router.get('/:id', getUser)

/* no valid jwt then don't allow user to be created */
router.use(isFirebaseAuthorized)
router.post('/', createUser)

/* full auth check */
router.use(isAuthorized)

/**
* Both of these require an authorization header
* provide frontend with any user specific data
*/
/* provide frontend with any user specific data */
router.get('/', getSelf)
router.put('/', updateUser)

router.post('/', createUser)

module.exports = router
9 changes: 8 additions & 1 deletion backend/src/utils/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@ exports.responseHandler = function(fn, response) {

exports.toLowerCase = str => str.toLowerCase()

exports.isAuthorized = function(req, res, next) {
exports.isFirebaseAuthorized = function(req, res, next) {
if (!req.authorized) {
return res.status(401).json({ code: 401, message: 'Unauthorized' })
}
next()
}
exports.isAuthorized = function(req, res, next) {
if (!req.user) {
return res.status(403).json({ code: 403, message: 'No user profile' })
}
next()
}


/**
* Convert a string to utf8
Expand Down
45 changes: 45 additions & 0 deletions backend/tests/models/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { expect } = require('chai')
// use an actual database
const userModel = require('../../src/models/user')(require('../../src/models/db'))

describe('User Model', () => {

it('compiles', function () {
expect(userModel)
})

describe('User creation and retrieval', () => {
const addUserObj = { displayName: 'mickey', email: '[email protected]', uid: '1234' }
const retrieveUserObj = { email: addUserObj.email, displayName: addUserObj.displayName, degree:null, gradYear:null, description:null, picture:null }
it('it creates a user and retrieves the profile', () => {
return userModel
.createUser(addUserObj)
.then(({ displayName, email, id }) => {
expect(displayName).to.equal(addUserObj.displayName)
expect(email).to.equal(addUserObj.email)
return userModel.getProfile(id)
})
.then(({ email, displayName, degree, gradYear, description, picture }) => {
const profile = { email, displayName, degree, gradYear, description, picture }
expect(profile).to.deep.equal(retrieveUserObj)
})
})
})

describe('User update', () => {
const credsObj = { displayName: 'mickeyMoo', email: '[email protected]' }
const addUserObj = { ...credsObj, uid: '4321' }
const updateObj = { degree: "BS in CS", gradYear: 2026, description: "testu", picture: "baaaaaa" }
it('it updates the profile correctly', () => {
return userModel
.createUser(addUserObj)
.then(({ id, description }) => {
return userModel.updateUser(id, updateObj)
})
.then(({ email, displayName, degree, gradYear, description, picture }) => {
const profile = { email, displayName, degree, gradYear, description, picture }
expect(profile).to.deep.equal({ ...credsObj, ...updateObj })
})
})
})
})
Binary file added frontend/public/defaultpicture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,21 @@ input, textarea {
border: none;
}
textarea {
border: var(--border);
border-radius: 2px;
font: inherit;
resize: none;
padding: 10px;
outline: none;
margin: 10px 0px;
width: calc(100% - 20px);
height: 100px;
transition: 0.2s border ease-in-out;
}
textarea:focus, textarea:active {
border: 1px solid #acc;
}
</style>
Loading

0 comments on commit d639762

Please sign in to comment.