diff --git a/design/Deighton AR Design.sketch b/design/Deighton AR Design.sketch new file mode 100644 index 0000000..d174cc0 Binary files /dev/null and b/design/Deighton AR Design.sketch differ diff --git a/server/src/api/routes/ActivityRoutes.js b/server/src/api/routes/ActivityRoutes.js index de4207b..d482aa4 100644 --- a/server/src/api/routes/ActivityRoutes.js +++ b/server/src/api/routes/ActivityRoutes.js @@ -24,11 +24,11 @@ export class ActivityRoutes { listActivitys(req, res, next) { const Activity = this.db.Activity - let limit = req.params.limit || 20 - let skip = req.params.skip || 0 - let partial = !!req.params.partial - let branch = req.params.branch - let query = {} + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial + const branch = req.query.branch + const query = {} if (branch) { query.branch = branch diff --git a/server/src/api/routes/TeamRoutes.js b/server/src/api/routes/TeamRoutes.js index 38fd999..6c487be 100644 --- a/server/src/api/routes/TeamRoutes.js +++ b/server/src/api/routes/TeamRoutes.js @@ -22,19 +22,15 @@ export class TeamRoutes { .delete(passport.authenticate('bearer', { session: false }), this.deleteTeam) } - listTeams(req, res, next) { + async listTeams(req, res, next) { const Team = this.db.Team - let limit = req.params.limit || 20 - let skip = req.params.skip || 0 - let partial = !!req.params.partial - let branch = req.params.branch + let limit = req.query.limit || 20 + let skip = req.query.skip || 0 + let partial = !!req.query.partial let query = {} - if (branch) { - query.branch = branch - } - - Team.count({}).then((total) => { + try { + const total = await Team.count({}) let teams = [] let cursor = Team.find(query).limit(limit).skip(skip).cursor().map((doc) => { return doc.toClient(partial) @@ -52,93 +48,120 @@ export class TeamRoutes { }) }) cursor.on('error', (err) => { - next(createError.InternalServerError(err.message)) + throw err }) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - createTeam(req, res, next) { - if (!req.user.administrator) { - return next(new createError.Forbidden()) - } - - // Create a new Team template then assign it to a value in the req.body - const Team = this.db.Team - let team = new Team(req.body) - - // Save the team (with promise) - If it doesnt, catch and throw error - team.save().then((newTeam) => { - res.json(newTeam.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) - } - - updateTeam(req, res, next) { - if (!req.user.administrator) { - return next(new createError.Forbidden()) - } - - // Do this here because Mongoose will add it automatically otherwise - if (!req.body._id) { - return next(createError.BadRequest('No _id given in body')) - } - - let Team = this.db.Team - let teamUpdates = null - + async createTeam(req, res, next) { try { - teamUpdates = new Team(req.body) - } catch (err) { - return next(createError.BadRequest('Invalid data')) - } + if (!req.user.administrator) { + throw createError.Forbidden() + } + + // Create a new Team template then assign it to a value in the req.body + const Team = this.db.Team + let team = new Team(req.body) + + const newTeam = await team.save() + + res.json(newTeam.toClient()) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } + } + + async updateTeam(req, res, next) { + try { + if (!req.user.administrator) { + throw createError.Forbidden() + } + + // Do this here because Mongoose will add it automatically otherwise + if (!req.body._id) { + throw createError.BadRequest('No _id given in body') + } + + let Team = this.db.Team + let teamUpdates = null + + try { + teamUpdates = new Team(req.body) + } catch (err) { + throw createError.BadRequest('Invalid data') + } + + const foundTeam = await Team.findById(teamUpdates._id) - Team.findById(teamUpdates._id).then((foundTeam) => { if (!foundTeam) { - return next(createError.NotFound(`Team with _id ${_id} was not found`)) + throw createError.NotFound(`Team with _id ${_id} was not found`) } foundTeam.merge(teamUpdates) - return foundTeam.save() - }).then((savedTeam) => { + const savedTeam = await foundTeam.save() + res.json(savedTeam.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch (err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - getTeam(req, res, next) { + async getTeam(req, res, next) { const Team = this.db.Team const _id = req.params._id - Team.findById(_id).then((team) => { + try { + const team = await Team.findById(_id) + if (!team) { - return next(createError.NotFound(`Team with _id ${_id} not found`)) + throw createError.NotFound(`Team with _id ${_id} not found`) } res.json(team.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch (err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - deleteTeam(req, res, next) { - if (!req.user.administrator) { - return next(new createError.Forbidden()) - } + async deleteTeam(req, res, next) { + try { + if (!req.user.administrator) { + throw createError.Forbidden() + } - const Team = this.db.Team - const _id = req.params._id + const Team = this.db.Team + const _id = req.params._id - Team.remove({ _id }).then((team) => { - if (!project) { - return next(createError.NotFound(`Team with _id ${_id} not found`)) + const removedTeam = await Team.remove({ _id }) + + if (!removedTeam) { + throw createError.NotFound(`Team with _id ${_id} not found`) } res.json({}) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } } diff --git a/server/src/api/routes/UserRoutes.js b/server/src/api/routes/UserRoutes.js index ecb4e65..f7eb204 100644 --- a/server/src/api/routes/UserRoutes.js +++ b/server/src/api/routes/UserRoutes.js @@ -28,9 +28,6 @@ export class UserRoutes { .get(passport.authenticate('bearer', { session: false }), this.getUser) .delete(passport.authenticate('bearer', { session: false }), this.deleteUser) - app.route('/users/set-image') - .put(passport.authenticate('bearer', { session: false }), this.setImage) - app.route('/users/enter-room/:roomName') .put(passport.authenticate('bearer', { session: false }), this.enterRoom) @@ -38,19 +35,27 @@ export class UserRoutes { .put(passport.authenticate('bearer', { session: false }), this.leaveRoom) } - listUsers(req, res, next) { + async listUsers(req, res, next) { const User = this.db.User - const limit = req.params.limit || 20 - const skip = req.params.skip || 0 + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial const isAdmin = !!req.user.administrator + const team = req.query.team + let query = {} - if (!isAdmin) { - return next(new createError.Forbidden()) - } + try { + if (team) { + query = { team } + } - User.count({}).then((total) => { + if (!isAdmin) { + throw createError.Forbidden() + } + + const total = await User.count({}) let users = [] - let cursor = User.find({}).limit(limit).skip(skip).cursor().map((doc) => { + let cursor = User.find(query).limit(limit).skip(skip).cursor().map((doc) => { return doc.toClient(req.user) }) @@ -66,56 +71,64 @@ export class UserRoutes { }) }) cursor.on('error', (err) => { - next(createError.InternalServerError(err.message)) + throw err }) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) - } - - getUser(req, res, next) { - let User = this.db.User - const _id = req.params._id - const isSelf = (_id === req.user._id) - const isAdmin = req.user.administrator - - // User can see themselves, otherwise must be super user - if (!isSelf && !isAdmin) { - return next(new createError.Forbidden()) - } - - User.findById(_id).then((user) => { - if (!user) { - return Promise.reject(createError.NotFound(`User with _id ${_id} was not found`)) - } - - res.json(user.toClient(req.user)) - }).catch((err) => { + } catch(err) { if (err instanceof createError.HttpError) { next(err) } else { next(createError.InternalServerError(err.message)) } - }) + } } - createUser(req, res, next) { + async getUser(req, res, next) { + let User = this.db.User + const _id = req.params._id + const isSelf = (_id === req.user._id) const isAdmin = req.user.administrator - if (!isAdmin) { - return next(new createError.Forbidden()) - } + try { + // User can see themselves, otherwise must be super user + if (!isSelf && !isAdmin) { + throw createError.Forbidden() + } + + const user = await User.findById(_id) + + if (!user) { + return Promise.reject(createError.NotFound(`User with _id ${_id} was not found`)) + } + + res.json(user.toClient(req.user)) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } + } + + async createUser(req, res, next) { + const isAdmin = req.user.administrator + + try { + if (!isAdmin) { + throw new createError.Forbidden() + } + + let User = this.db.User + let user = new User(req.body) + + // Add email confirmation required token + const buf = await util.promisify(crypto.randomBytes)(32) - let User = this.db.User - let user = new User(req.body) - // Add email confirmation required token - util.promisify(crypto.randomBytes)(32).then((buf) => { user.emailToken = { value: urlSafeBase64.encode(buf), created: new Date() } - return user.save() - }).then((savedUser) => { + const savedUser = await user.save() const userFullName = `${savedUser.firstName} ${savedUser.lastName}` const senderFullName = `${req.user.firstName} ${req.user.lastName}` const siteUrl = url.parse(req.headers.referer) @@ -130,137 +143,70 @@ export class UserRoutes { supportEmail: this.supportEmail } } + res.json(savedUser.toClient()) - return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve() - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + + if (this.sendEmail) { + await this.mq.request('dar-email', 'sendEmail', msg) + } + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - updateUser(req, res, next) { + async updateUser(req, res, next) { const isAdmin = req.user.administrator - // Do this here because Mongoose will add it automatically otherwise - if (!req.body._id) { - return next(createError.BadRequest('No user _id given in body')) - } - - const isSelf = (req.body._id === req.user._id.toString()) - - // User can change themselves, otherwise must be super user - if (!isSelf && !isAdmin) { - return next(new createError.Forbidden()) - } - - const User = this.db.User - let userUpdates = null - try { - userUpdates = new User(req.body) - } catch (err) { - return next(createError.BadRequest('Invalid data')) - } + // Do this here because Mongoose will add it automatically otherwise + if (!req.body._id) { + throw createError.BadRequest('No user _id given in body') + } - if (isSelf && !isAdmin) { - return next(createError.BadRequest('Cannot modify own administrator level')) - } + const isSelf = (req.body._id === req.user._id.toString()) + + // User can change themselves, otherwise must be super user + if (!isSelf && !isAdmin) { + throw createError.Forbidden() + } + + const User = this.db.User + let userUpdates = null + + try { + userUpdates = new User(req.body) + } catch (err) { + throw createError.BadRequest('Invalid data') + } + + if (isSelf && !isAdmin) { + throw createError.BadRequest('Cannot modify own administrator level') + } + + const foundUser = await User.findById(userUpdates._id) - User.findById(userUpdates._id).then((foundUser) => { if (!foundUser) { - return Promise.reject(createError.NotFound(`User with _id ${user._id} was not found`)) + throw createError.NotFound(`User with _id ${user._id} was not found`) } // We don't allow direct updates to the email field so remove it if present delete userUpdates.email foundUser.merge(userUpdates) - return foundUser.save() - }).then((savedUser) => { + const savedUser = await foundUser.save() + res.json(savedUser.toClient(req.user)) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) - } - - setImage(req, res, next) { - const isAdmin = req.user.administrator - const { _id, imageId } = req.body - - if (!_id || !imageId) { - return next(createError.BadRequest('Must specify _id and imageId')) - } - - const isSelf = (_id === req.user._id.toString()) - - // User can change themselves, otherwise must be super user - if (!isSelf && !isAdmin) { - return next(new createError.Forbidden()) - } - - const { bigSize = {}, smallSize = {} } = req.body - - Promise.all([ - this.mq.request('dar-image', 'scaleImage', { - newWidth: bigSize.width || 200, - newHeight: bigSize.height || 200, - scaleMode: 'aspectFill', - inputAssetId: imageId, - passback: { - userId: _id, - rooms: [ req.user._id ], - routerName: 'userRoutes', - funcName: 'completeSetBigImage' - } - }), - this.mq.request('dar-image', 'scaleImage', { - newWidth: smallSize.width || 25, - newHeight: smallSize.height || 25, - scaleMode: 'aspectFill', - inputAssetId: imageId, - passback: { - userId: _id, - rooms: [ req.user._id, 'users' ], - routerName: 'userRoutes', - funcName: 'completeSetSmallImage' - } - }) - ]).then((correlationIds) => { - res.json({ correlationIds }) - }).catch((err) => { - next(createError.InternalServerError('Unable to scale user images')) - }) - } - - completeSetBigImage(passback, error, data) { - if (error || !passback.userId || !passback.rooms) { - return - } - - const User = this.db.User - - User.findByIdAndUpdate(passback.userId, { imageId: data.outputAssetId }).then((foundUser) => { - if (foundUser) { - this.ws.notify(passback.rooms, 'newProfileImage', { imageId: data.outputAssetId }) + } catch (err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) } - }).catch((err) => { - this.log.error(`Unable to notify [${passback.rooms.join(', ')}] of new image'`) - }) - } - - completeSetSmallImage(passback, error, data) { - if (error || !passback.userId || !passback.rooms) { - return } - - const User = this.db.User - - User.findByIdAndUpdate(passback.userId, { thumbnailImageId: data.outputAssetId }).then((foundUser) => { - if (foundUser) { - this.ws.notify(passback.rooms, 'newThumbnailImage', { imageId: data.outputAssetId }) - } - }).catch((err) => { - this.log.error(`Unable to notify [${passback.rooms.join(', ')}] of new thumbnail image'`) - }) } enterRoom(req, res, next) { @@ -273,19 +219,20 @@ export class UserRoutes { res.json({}) } - deleteUser(req, res, next) { + async deleteUser(req, res, next) { const isAdmin = req.user.administrator - if (!isAdmin) { - return new createError.Forbidden() - } + try { + if (!isAdmin) { + throw createError.Forbidden() + } - let User = this.db.User - const _id = req.params._id + let User = this.db.User + const _id = req.params._id + const deletedUser = await User.remove({ _id }) - User.remove({ _id }).then((deletedUser) => { if (!deletedUser) { - return next(createError.NotFound(`User with _id ${_id} was not found`)) + throw createError.NotFound(`User with _id ${_id} was not found`) } const userFullName = `${deletedUser.firstName} ${deletedUser.lastName}` @@ -299,11 +246,17 @@ export class UserRoutes { supportEmail: this.supportEmail } } - return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve() - }).then(() => { res.json({}) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + + if (this.sendEmail) { + await this.mq.request('dar-email', 'sendEmail', msg) + } + } catch (err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } } diff --git a/server/src/api/routes/WorkItemRoutes.js b/server/src/api/routes/WorkItemRoutes.js index 0769d5b..f26dc75 100644 --- a/server/src/api/routes/WorkItemRoutes.js +++ b/server/src/api/routes/WorkItemRoutes.js @@ -24,16 +24,11 @@ export class WorkItemRoutes { listWorkItems(req, res, next) { const WorkItem = this.db.WorkItem - let limit = req.params.limit || 20 - let skip = req.params.skip || 0 - let partial = !!req.params.partial - let branch = req.params.branch + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial let query = {} - if (branch) { - query.branch = branch - } - WorkItem.count({}).then((total) => { let workItems = [] let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => { diff --git a/website/src/API.js b/website/src/API.js index fba0264..d78993a 100644 --- a/website/src/API.js +++ b/website/src/API.js @@ -243,6 +243,9 @@ class API extends EventEmitter { listUsers() { return this.get('/users') } + listUsersForTeam(teamId) { + return this.get(`/users?team=${teamId}`) + } createUser(user) { return this.post('/users', user) } @@ -280,7 +283,7 @@ class API extends EventEmitter { return this.post('/teams', team) } updateTeam(team) { - this.put('/teams', team) + return this.put('/teams', team) } deleteTeam(id) { return this.delete('/teams/' + id) diff --git a/website/src/Teams/TeamForm.js b/website/src/Teams/TeamForm.js index 82e1072..5fe2b15 100644 --- a/website/src/Teams/TeamForm.js +++ b/website/src/Teams/TeamForm.js @@ -4,6 +4,7 @@ import autobind from 'autobind-decorator' import { Row, Column, BoundInput, BoundButton, List } from 'ui' import { FormBinder } from 'react-form-binder' import { sizeInfo } from 'ui/style' +import { api } from 'src/API' export class TeamForm extends React.Component { static propTypes = { @@ -36,14 +37,23 @@ export class TeamForm extends React.Component { constructor(props) { super(props) - this.users = [ - { id: 0, name: 'Meryll Streep' }, - { id: 1, name: 'Scarlett Johansenn' }, - { id: 2, name: 'John Lyon-Smith' }, - ] - this.state = { - binder: new FormBinder(props.team, TeamForm.bindings, this.props.onModifiedChanged) + binder: new FormBinder(props.team, TeamForm.bindings, this.props.onModifiedChanged), + users: [] + } + + this.getUsersForTeam(props.team._id) + } + + @autobind + getUsersForTeam(teamId) { + if (teamId) { + api.listUsersForTeam(teamId).then((list) => { + list.items.sort((a, b) => (a.lastName.localeCompare(b.lastName))) + this.setState({ users: list.items }) + }).catch(() => { + this.setState({ users: [] }) + }) } } @@ -52,6 +62,8 @@ export class TeamForm extends React.Component { this.setState({ binder: new FormBinder(nextProps.team, TeamForm.bindings, nextProps.onModifiedChanged) }) + + this.getUsersForTeam(nextProps.team._id) } } @@ -78,7 +90,7 @@ export class TeamForm extends React.Component { } render() { - const { binder } = this.state + const { binder, users } = this.state return (