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

S8 signup flow basic profile #213

Merged
merged 28 commits into from
Oct 13, 2018
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
afdd476
signup now in two stages; create firebase account, and create profile
NunoDasNeves Oct 9, 2018
787eb20
signup flow more logical now ... handles most edge cases pretty well
NunoDasNeves Oct 9, 2018
6c642d3
basic profile page - still buggy, non functional update profile button
NunoDasNeves Oct 10, 2018
cc0e33d
local default picture
NunoDasNeves Oct 10, 2018
bfb209f
update user backend added... no tests
NunoDasNeves Oct 10, 2018
e5d0168
lint errors fixed
NunoDasNeves Oct 10, 2018
4054dd3
working profile update
NunoDasNeves Oct 10, 2018
bb48c8b
fixed textarea stuff, watcher to fix refresh profile problem
NunoDasNeves Oct 10, 2018
daaa287
profile picture update added
NunoDasNeves Oct 10, 2018
5293d8c
working password reset from login form
NunoDasNeves Oct 10, 2018
7bb0fd4
nice error message for non-unique username
NunoDasNeves Oct 10, 2018
0d5b3bb
made hasProfile dependent on isLoggedIn to alleviate some potential bad
NunoDasNeves Oct 10, 2018
6acbb76
removed TODO
NunoDasNeves Oct 10, 2018
e493132
Merge remote-tracking branch 'origin' into s8-signup-flow-basic-profile
NunoDasNeves Oct 11, 2018
2734f76
fixed post review/question bug when logged in
NunoDasNeves Oct 11, 2018
dc2d329
loading state for nav, split auth and profile get into two bits
NunoDasNeves Oct 11, 2018
300d3a7
reverted some stuff ... working again
NunoDasNeves Oct 11, 2018
08b0a35
better auth code, profile cache, global loading spinner in nav, reroutes
NunoDasNeves Oct 12, 2018
0ebe595
lint
NunoDasNeves Oct 12, 2018
eab3230
page size fixaroo
NunoDasNeves Oct 12, 2018
0621fe2
I'v messed something up
Oct 12, 2018
2678b5a
protecting routes with guards
Oct 12, 2018
1b0ae62
fixed some PR things
NunoDasNeves Oct 12, 2018
160fc19
Merge branch 's8-nuno-helper-branch' into s8-signup-flow-basic-profile
NunoDasNeves Oct 12, 2018
d628f8d
smol fixes
NunoDasNeves Oct 12, 2018
b9f9551
removed preAuth route check - not needed rn
NunoDasNeves Oct 13, 2018
d4c8a38
Merge remote-tracking branch 'origin' into s8-signup-flow-basic-profile
NunoDasNeves Oct 13, 2018
b3f6134
smol fix to login page reroute
NunoDasNeves Oct 13, 2018
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
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) {
NunoDasNeves marked this conversation as resolved.
Show resolved Hide resolved
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))
}
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,
NunoDasNeves marked this conversation as resolved.
Show resolved Hide resolved
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
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
* TODO Return specialised information for auth'd user
NunoDasNeves marked this conversation as resolved.
Show resolved Hide resolved
* @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])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious what picture is being stored as? Base64?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's just a URL for now :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kk may not ever be used XD

}

/**
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>
9 changes: 5 additions & 4 deletions frontend/src/components/AppNavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,21 @@
<div v-else/>
<h3 v-if="!isLoggedIn"><router-link class="link-item" to="/login">Login</router-link></h3>
<h3 v-if="!isLoggedIn"><router-link class="link-item" to="/signup">Sign Up</router-link></h3>
<h3 v-else @click="$store.dispatch('auth/logout')" class="link-item">Logout</h3>
<h3 v-if="isLoggedIn && !hasProfile"><router-link class="link-item" to="/signup">Profile</router-link></h3>
<h3 v-if="hasProfile"><router-link class="link-item" to="/profile">Profile</router-link></h3>
<h3 v-if="isLoggedIn" @click="$store.dispatch('auth/logout')" class="link-item">Logout</h3>
</div>
</div>
</template>

<script>
import Search from '@/components/AppSearch'
import { mapGetters } from 'vuex'

export default {
components: { Search },
computed: {
isLoggedIn() {
return this.$store.getters.isLoggedIn
}
...mapGetters([ 'isLoggedIn', 'hasProfile' ])
}
}
</script>
Expand Down
17 changes: 0 additions & 17 deletions frontend/src/components/questions-answers/QuestionForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,4 @@ export default {
.content {
padding: 20px;
}

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>
17 changes: 0 additions & 17 deletions frontend/src/components/reviews-replies/ReviewForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,6 @@ export default {
padding: 20px;
}

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;
}

.submit {
background-color: var(--black);
width: 50%;
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ export default new Router({
name: 'Login',
component: () => import('./views/Login')
},
{
path: '/profile',
name: 'Profile',
component: () => import('./views/Profile')
},
{
path: '/password-reset',
name: 'Forgot Password',
Expand Down
Loading