From aec39ae17d78147a8e94b636a518a4f916d2360f Mon Sep 17 00:00:00 2001 From: John Lyon-Smith Date: Thu, 5 Apr 2018 15:28:57 -0700 Subject: [PATCH] Hooked up Work Item screen to API --- design/Deighton AR Design.sketch | Bin 253329 -> 253328 bytes mobile/src/API.js | 19 ++- mobile/src/Home/Home.js | 5 +- mobile/src/Modal/MessageModal.js | 12 +- mobile/src/WorkItem/WorkItem.js | 78 ++++++++-- server/src/api/routes/WorkItemRoutes.js | 182 ++++++++++++++---------- server/src/database/schemas/workItem.js | 2 +- 7 files changed, 195 insertions(+), 103 deletions(-) diff --git a/design/Deighton AR Design.sketch b/design/Deighton AR Design.sketch index dd63102ba7a720153de1b93cf8aa5cb5ecff6179..b669cee6a50231aee8c074310f1e732566ba7bd1 100644 GIT binary patch delta 422 zcmV;X0a^Z$yAP1N4}i1*0l$A?{IG~d0RRBk0RR9A0001Wb7gWaYIARHZIR7tBQX$! zUnS@~9n?~{TJlY;ANCN)A&^VR!7+m~FuM~R2eNDY?kiuHy*3|J)m0Z0Lao`Q2vkY~ zk|vOnC#V@MXYrz`nBB#E9!}f3KAx82^2_YZ1)WTH;N)s1^Jm+BKJS0mui5PI-Y19N zkwZq$Azdd^w6>O`g`w+#Qb$N#GN||gX`smAX7RB<&JWAy`NzB*-_JuU&^=FP{{Glb z=l$C{KkR?cr>A+H59ehYDP9aWv0WI!lFLAi6;f~kF#3o-IxN~oz!ZyO?FHDzfn}+X zF;(!%hGGj@j5;t|yVYUt9r#`l##KR!7l^fcBz2m(4h)qo5haM0`Y@FanMwr3Wx0c-hCh+X1 zJ97Tt39i?_hhe}0hhe}1w_(5odK?L1{IG~d0RRBkm*m_7NC9A%E!_h)1Jl9*m+`^@ Q6_WR0cvBQb8pmqbACn9vQEv^YXmDUEeS3`P5d*|w2=l6-76oz}5lMqqZc~QJo`?!$Ph*D4fJ&A@u>>or zSZ8^-bs9QM9&A&_^LJ$%Fdk`M6Pu9xGW%gnqh=J4;AcNUJyd0ZKq$HQ(R&I1+aU7X zfI|Fd0=(b<0*7G00f%6~0=Hnm19}_@eDh5qM*#o;*q7tn14sd3mn_`_HUrec0hjQ? R0Tq{e-2)H?b=v~~007Qd!O8#t diff --git a/mobile/src/API.js b/mobile/src/API.js index 2009db2..cce99b5 100644 --- a/mobile/src/API.js +++ b/mobile/src/API.js @@ -248,9 +248,6 @@ class API extends EventEmitter { deleteUser(_id) { return this.delete('/users/' + _id) } - setUserImage(details) { - return this.put('/users/set-image', details) - } enterRoom(roomName) { return this.put('/users/enter-room/' + (roomName || '')) } @@ -258,6 +255,22 @@ class API extends EventEmitter { return this.put('/users/leave-room') } + getWorkItem(_id) { + return this.get('/workitems/' + _id) + } + listWorkItems() { + return this.get('/workitems') + } + createWorkItem(workItem) { + return this.post('/workitems', workItem) + } + updateWorkItem(workItem) { + return this.put('/workitems', workItem) + } + deleteWorkItem(_id) { + return this.delete('/workitems/' + _id) + } + upload(file, progressCallback) { return new Promise((resolve, reject) => { const chunkSize = 32 * 1024 diff --git a/mobile/src/Home/Home.js b/mobile/src/Home/Home.js index 56290b8..3901cc4 100644 --- a/mobile/src/Home/Home.js +++ b/mobile/src/Home/Home.js @@ -86,7 +86,10 @@ export class Home extends React.Component { render() { return ( -
+
{ this.map = ref }} style={{ diff --git a/mobile/src/Modal/MessageModal.js b/mobile/src/Modal/MessageModal.js index 1225f29..3431147 100644 --- a/mobile/src/Modal/MessageModal.js +++ b/mobile/src/Modal/MessageModal.js @@ -35,14 +35,14 @@ export class MessageModal extends Component { return ( - - - - {message} - {detail} + + + + {message} + {detail} (`${Math.abs(lng).toFixed(4)}°${lng > 0 ? 'S' : 'N'}, ${Math.abs(lat).toFixed(4)}°${lat > 0 ? 'W' : 'E'}`) +const latLngStringToPoint = (str) => { + const parts = str.split(', ') + return { + type: 'Point', + coordinates: [ + new Number(parts[0].substring(0, parts[0].length - 2)), + new Number(parts[1].substring(0, parts[1].length - 2)), + ] + } +} + export class WorkItem extends React.Component { static bindings = { header: { @@ -67,6 +83,8 @@ export class WorkItem extends React.Component { }, workItemType: { isValid: true, + initValue: 'order', + alwaysGet: true, } } @@ -91,28 +109,51 @@ export class WorkItem extends React.Component { @autobind handleDonePress() { + const { binder } = this.state + let obj = binder.getModifiedFieldValues() + obj.location = latLngStringToPoint(obj.location) + + if (!obj._id) { + api.createWorkItem(obj).then((workItem) => { + this.handleBackPress() + }).catch((error) => { + this.setState({ messageModal: { + icon: 'hand', + message: 'Unable to create work item', + detail: error.message, + }}) + }) + } else { + api.updateWorkItem(obj).then((workItem) => { + this.handleBackPress() + }).catch((error) => { + this.setState({ messageModal: { + icon: 'hand', + message: 'Unable to update work item', + detail: error.message, + }}) + }) + } + } + + @autobind + handleMessageDismiss() { + this.setState({ messageModal: null }) } @autobind handleRegionChange(region) { const { binder } = this.state - // NOTE: For some reason, object destructuring does not work here :( - const lon = region.longitude - const lat = region.latitude - this.setState(binder.updateFieldValue('location', - `${Math.abs(lon).toFixed(4)}°${lon > 0 ? 'S' : 'N'}, ${Math.abs(lat).toFixed(4)}°${lat > 0 ? 'W' : 'E'}`)) + this.setState(binder.updateFieldValue('location', latLngToString(region.latitude, region.longitude))) } render() { - const { binder } = this.state + const { binder, messageModal } = this.state return ( - + + + + - - - { isIphoneX ? : null } - + + + ) } } diff --git a/server/src/api/routes/WorkItemRoutes.js b/server/src/api/routes/WorkItemRoutes.js index f26dc75..8d06829 100644 --- a/server/src/api/routes/WorkItemRoutes.js +++ b/server/src/api/routes/WorkItemRoutes.js @@ -22,14 +22,16 @@ export class WorkItemRoutes { .delete(passport.authenticate('bearer', { session: false }), this.deleteWorkItem) } - listWorkItems(req, res, next) { - const WorkItem = this.db.WorkItem - const limit = req.query.limit || 20 - const skip = req.query.skip || 0 - const partial = !!req.query.partial - let query = {} + async listWorkItems(req, res, next) { + try { + const WorkItem = this.db.WorkItem + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial + let query = {} + + const total = await WorkItem.count({}) - WorkItem.count({}).then((total) => { let workItems = [] let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => { return doc.toClient(partial) @@ -47,102 +49,128 @@ export class WorkItemRoutes { }) }) cursor.on('error', (err) => { - next(createError.InternalServerError(err.message)) + throw createError.InternalServerError(err.message) }) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - createWorkItem(req, res, next) { - const role = req.user.role - - // If user's role is not Executive or Administrator, return an error - if (role !== 'executive' && role !== 'administrator') { - return next(new createError.Forbidden()) - } - - // Create a new WorkItem template then assign it to a value in the req.body - const WorkItem = this.db.WorkItem - let workItem = new WorkItem(req.body) - - // Save the workItem (with promise) - If it doesnt, catch and throw error - workItem.save().then((newWorkItem) => { - res.json(newWorkItem.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) - } - - updateWorkItem(req, res, next) { - const role = req.user.role - - // If user's role is not Executive or Administrator, return an error - if (role !== 'executive' && role !== 'administrator') { - return 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 WorkItem = this.db.WorkItem - let workItemUpdates = null + async createWorkItem(req, res, next) { try { - workItemUpdates = new WorkItem(req.body) - } catch (err) { - return next(createError.BadRequest('Invalid data')) - } + const isAdmin = req.user.administrator + + if (!isAdmin) { + return new createError.Forbidden() + } + + // Create a new WorkItem template then assign it to a value in the req.body + const WorkItem = this.db.WorkItem + let workItem = new WorkItem(req.body) + + // Save the workItem (with promise) - If it doesnt, catch and throw error + const newWorkItem = await workItem.save() + + res.json(newWorkItem.toClient()) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } + } + + async updateWorkItem(req, res, next) { + try { + const isAdmin = req.user.administrator + + if (!isAdmin) { + return new 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 WorkItem = this.db.WorkItem + let workItemUpdates = null + + try { + workItemUpdates = new WorkItem(req.body) + } catch (err) { + throw createError.BadRequest('Invalid data') + } + + const foundWorkItem = await WorkItem.findById(workItemUpdates._id) - WorkItem.findById(workItemUpdates._id).then((foundWorkItem) => { if (!foundWorkItem) { return next(createError.NotFound(`WorkItem with _id ${_id} was not found`)) } + foundWorkItem.merge(workItemUpdates) - return foundWorkItem.save() - }).then((savedWorkItem) => { + + const savedWorkItem = await foundWorkItem.save() + res.json(savedWorkItem.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - getWorkItem(req, res, next) { - const WorkItem = this.db.WorkItem - const _id = req.params._id + async getWorkItem(req, res, next) { + try { + const WorkItem = this.db.WorkItem + const _id = req.params._id + const workItem = await WorkItem.findById(_id) - WorkItem.findById(_id).then((workItem) => { if (!workItem) { - return next(createError.NotFound(`WorkItem with _id ${_id} not found`)) + throw createError.NotFound(`WorkItem with _id ${_id} not found`) } res.json(workItem.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - deleteWorkItem(req, res, next) { - const role = req.user.role + async deleteWorkItem(req, res, next) { + try { + const isAdmin = req.user.administrator - // If user's role is not Executive or Administrator, return an error - if (role !== 'executive' && role !== 'administrator') { - return new createError.Forbidden() - } + if (!isAdmin) { + return new createError.Forbidden() + } - const WorkItem = this.db.WorkItem - const _id = req.params._id + const WorkItem = this.db.WorkItem + const _id = req.params._id + const workItem = await WorkItem.remove({ _id }) - WorkItem.remove({ _id }).then((workItem) => { if (!workItem) { - return next(createError.NotFound(`WorkItem with _id ${_id} not found`)) + throw createError.NotFound(`WorkItem 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/database/schemas/workItem.js b/server/src/database/schemas/workItem.js index 3f7b934..84f0fe8 100644 --- a/server/src/database/schemas/workItem.js +++ b/server/src/database/schemas/workItem.js @@ -8,7 +8,7 @@ export let workItemSchema = new Schema({ message: 'enum validator failed for path `{PATH}` with value `{VALUE}`' }}, number: Number, - loc: { + location: { type: { type: String }, coordinates: [Number], },