Skip to content

Commit 6ad4ae2

Browse files
Merge pull request #295 from CodeForBaltimore/188
Issue 188 - Return number of emails contacted
2 parents 98924d4 + 454b84a commit 6ad4ae2

File tree

6 files changed

+1890
-1455
lines changed

6 files changed

+1890
-1455
lines changed

Bmore-Responsive.postman_collection.json

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@
569569
"response": []
570570
},
571571
{
572-
"name": "Contact Send",
572+
"name": "Contact Send Bulk",
573573
"event": [
574574
{
575575
"listen": "test",
@@ -620,6 +620,60 @@
620620
},
621621
"response": []
622622
},
623+
{
624+
"name": "Contact Send Single",
625+
"event": [
626+
{
627+
"listen": "test",
628+
"script": {
629+
"id": "b38d3eef-47f0-4f9e-a710-d82c4fef5492",
630+
"exec": [
631+
"//get the 36 character id of the new contact and save it to env variable",
632+
"//this will allow deletion of this in the Delete transaction",
633+
"pm.environment.set(\"newContactId\", pm.response.text().slice(0,36));",
634+
"",
635+
"//confirm that request returns a success code of 200",
636+
"pm.test(\"Status code is 200\", function () {",
637+
" pm.response.to.have.status(200);",
638+
"});"
639+
],
640+
"type": "text/javascript"
641+
}
642+
}
643+
],
644+
"request": {
645+
"method": "POST",
646+
"header": [
647+
{
648+
"key": "token",
649+
"type": "text",
650+
"value": "{{token}}"
651+
}
652+
],
653+
"body": {
654+
"mode": "raw",
655+
"raw": "",
656+
"options": {
657+
"raw": {
658+
"language": "json"
659+
}
660+
}
661+
},
662+
"url": {
663+
"raw": "{{baseUrl}}/contact/send/{{modelType}}/{{id}}",
664+
"host": [
665+
"{{baseUrl}}"
666+
],
667+
"path": [
668+
"contact",
669+
"send",
670+
"{{modelType}}",
671+
"{{id}}"
672+
]
673+
}
674+
},
675+
"response": []
676+
},
623677
{
624678
"name": "Update Contact",
625679
"event": [
@@ -1406,4 +1460,4 @@
14061460
}
14071461
],
14081462
"protocolProfileBehavior": {}
1409-
}
1463+
}

src/email/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const sendMail = async (to, subject, html, text) => {
3636
* Send a forgot password email.
3737
* @param {string} userEmail email address of the user we're sending to
3838
* @param {string} resetPasswordToken temporary token for the reset password link
39-
*
39+
*
4040
* @returns {Boolean}
4141
*/
4242
const sendForgotPassword = async (userEmail, resetPasswordToken) => {
@@ -45,8 +45,8 @@ const sendForgotPassword = async (userEmail, resetPasswordToken) => {
4545
await sendMail(
4646
userEmail,
4747
'Password Reset - Healthcare Roll Call',
48-
nunjucks.render('forgot_password_html.njk', { emailResetLink }),
49-
nunjucks.render('forgot_password_text.njk', { emailResetLink })
48+
nunjucks.render('forgot_password_html.njk', {emailResetLink}),
49+
nunjucks.render('forgot_password_text.njk', {emailResetLink})
5050
)
5151
return true
5252
} catch (e) {
@@ -74,4 +74,4 @@ const sendContactCheckInEmail = async (info) => {
7474
}
7575
}
7676

77-
export default { sendForgotPassword, sendContactCheckInEmail }
77+
export default {sendForgotPassword, sendContactCheckInEmail}

src/routes/contact.js

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import email from '../email'
22
import models from '../models'
33
import utils from '../utils'
44
import validator from 'validator'
5-
import { Router } from 'express'
5+
import {Router} from 'express'
66

77
const router = new Router()
88
router.use(utils.authMiddleware)
@@ -33,7 +33,7 @@ router.get('/', async (req, res) => {
3333
}
3434
}
3535

36-
const contacts = await models.Contact.findAll({ where })
36+
const contacts = await models.Contact.findAll({where})
3737

3838
// Temp fix for JSON types and Sequelize.
3939
let results = []
@@ -103,7 +103,7 @@ router.post('/', async (req, res) => {
103103
const response = new utils.Response()
104104
try {
105105
if (req.body.name !== undefined && req.body.name !== '') {
106-
const { name, phone, email, UserId, entities, attributes } = req.body
106+
const {name, phone, email, UserId, entities, attributes} = req.body
107107

108108
// Validating emails
109109
if (email) {
@@ -115,7 +115,7 @@ router.post('/', async (req, res) => {
115115
}
116116
}
117117

118-
const contact = await models.Contact.create({ name, email, phone, UserId, attributes })
118+
const contact = await models.Contact.create({name, email, phone, UserId, attributes})
119119
let ec
120120

121121
if (entities) {
@@ -154,10 +154,10 @@ router.post('/send', async (req, res) => {
154154
try {
155155
/** @todo allow for passing entity and contact arrays */
156156
const emails = []
157-
const { entityIds, contactIds, relationshipTitle } = req.body
157+
const {entityIds, contactIds, relationshipTitle} = req.body
158158

159159
if (entityIds === undefined && contactIds === undefined) {
160-
const whereClause = (relationshipTitle !== undefined) ? { where: { relationshipTitle } } : {}
160+
const whereClause = (relationshipTitle !== undefined) ? {where: {relationshipTitle}} : {}
161161
const associations = await models.EntityContact.findAll(whereClause)
162162

163163
if (associations.length < 1) {
@@ -191,12 +191,71 @@ router.post('/send', async (req, res) => {
191191
email.sendContactCheckInEmail(e)
192192
})
193193

194-
response.setMessage('Contacts emailed')
194+
response.setMessage({
195+
results: {
196+
message: 'Contacts emailed',
197+
total: emails.length
198+
}
199+
})
200+
} catch (e) {
201+
console.error(e)
202+
response.setCode(500)
203+
}
204+
205+
return res.status(response.getCode()).send(response.getMessage())
206+
})
207+
208+
209+
router.post('/send/:type/:id', async (req, res) => {
210+
const response = new utils.Response()
211+
212+
try {
213+
let entity
214+
if (req.params.type.toLowerCase() === 'entity') {
215+
entity = await models.Entity.findById(req.params.id)
216+
} else if (req.params.type.toLowerCase() === 'contact') {
217+
entity = await models.Contact.findOne({
218+
where: {
219+
id: req.params.id
220+
}
221+
})
222+
}
223+
224+
if (entity.email !== null) {
225+
const primary = entity.email.filter(e => e.isPrimary === 'true').length ? entity.email.filter(e => e.isPrimary === 'true')[0] : entity.email[0]
226+
// short-lived temporary token that only lasts one hour
227+
const temporaryToken = await utils.getToken(req.params.id, primary.address, 'Entity')
228+
229+
const e = {
230+
email: primary.address,
231+
name: entity.name,
232+
entityName: entity.name,
233+
entityId: entity.id,
234+
token: temporaryToken
235+
}
236+
email.sendContactCheckInEmail(e).then(() => {
237+
response.setMessage(`${entity.name} emailed sent.`)
238+
response.setCode(200)
239+
}, err => {
240+
response.setMessage('There was an error: ' + err)
241+
response.setCode(500)
242+
})
243+
} else {
244+
response.setMessage('Email Address not found.')
245+
response.setCode(404)
246+
}
247+
248+
response.setMessage({
249+
results: {
250+
message: `${entity.name} emailed.`,
251+
}
252+
})
195253
} catch (e) {
196254
console.error(e)
197255
response.setCode(500)
198256
}
199257

258+
200259
return res.status(response.getCode()).send(response.getMessage())
201260
})
202261

@@ -205,7 +264,7 @@ router.put('/', async (req, res) => {
205264
const response = new utils.Response()
206265
try {
207266
if (validator.isUUID(req.body.id)) {
208-
const { id, name, phone, email, UserId, entities, attributes } = req.body
267+
const {id, name, phone, email, UserId, entities, attributes} = req.body
209268

210269
const contact = await models.Contact.findOne({
211270
where: {
@@ -299,7 +358,7 @@ router.post('/link/:contact_id', async (req, res) => {
299358
}
300359
})
301360

302-
let i=0
361+
let i = 0
303362
for (const entity of req.body.entities) {
304363
const entityToLink = await models.Entity.findOne({
305364
where: {
@@ -312,11 +371,11 @@ router.post('/link/:contact_id', async (req, res) => {
312371
entityId: entityToLink.id,
313372
contactId: contact.id,
314373
}
315-
374+
316375
if (entity.title) {
317376
ec.relationshipTitle = contact.title
318377
}
319-
378+
320379
await models.EntityContact.createIfNew(ec)
321380
i++
322381
}
@@ -350,7 +409,7 @@ router.post('/unlink/:contact_id', async (req, res) => {
350409
}
351410
})
352411

353-
let i=0
412+
let i = 0
354413
for (const entity of req.body.entities) {
355414
const entityToUnLink = await models.Entity.findOne({
356415
where: {
@@ -366,7 +425,7 @@ router.post('/unlink/:contact_id', async (req, res) => {
366425
contactId: contact.id
367426
}
368427
})
369-
428+
370429
await ec.destroy()
371430
i++
372431
}

src/routes/csv.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,65 @@
1-
import { Router } from 'express'
1+
import {Router} from 'express'
22
import utils from '../utils'
3-
import { parseAsync } from 'json2csv'
3+
import {parseAsync} from 'json2csv'
4+
import {Op} from 'sequelize'
45

56
const router = new Router()
67
router.use(utils.authMiddleware)
78

89
// Gets a data dump from the passed in model (if it exists).
910
router.get('/:model_type', async (req, res) => {
11+
1012
const response = new utils.Response()
1113
const modelType = req.params.model_type
14+
1215
try {
16+
1317
/** @todo refactor this when we change how CSV's are delivered. */
1418
// eslint-disable-next-line no-prototype-builtins
1519
if (req.context.models.hasOwnProperty(modelType) && modelType !== 'User' && modelType !== 'UserRole') {
16-
/** @todo add filtering */
17-
const results = await req.context.models[modelType].findAll({ raw: true })
20+
let options = {raw: true}
21+
// search by name if filter query param is included
22+
if (req.query && req.query.filter && req.query.filter.length) {
23+
options.where = {
24+
name: {
25+
[Op.iLike]: '%' + req.query.filter + '%'
26+
}
27+
}
28+
}
1829

19-
const processedResults = await utils.processResults(results, modelType)
30+
/** @todo add a search filter for status once data has a status field. */
31+
let results = await req.context.models[modelType].findAll(options)
2032

2133
if (results.length !== 0) {
22-
response.setMessage = await parseAsync(JSON.parse(JSON.stringify(processedResults)), Object.keys(results[0]), {})
34+
const processedResults = await utils.processResults(results, modelType)
35+
const fields = Object.keys(results[0])
36+
parseAsync(processedResults, {fields}).then(csv => {
37+
response.setMessage(csv)
38+
const dateObj = new Date()
39+
const dateStr = `${dateObj.getUTCMonth() + 1}_${dateObj.getUTCDate()}_${dateObj.getUTCFullYear()}`
40+
res.setHeader('Content-disposition', `attachment; filename=HCRC_${modelType}_${dateStr}.csv`)
41+
res.set('Content-Type', 'text/csv')
42+
return res.status(response.getCode()).send(response.getMessage())
43+
}, err => {
44+
response.setCode(500)
45+
response.setMessage('Not able to parse data: ' + err)
46+
return res.status(response.getCode()).send(response.getMessage())
47+
})
48+
} else {
49+
response.setCode(200)
50+
response.setMessage('No results found')
51+
return res.status(response.getCode()).send(response.getMessage())
2352
}
2453
} else {
2554
response.setCode(400)
2655
response.setMessage('Model type is invalid')
56+
return res.status(response.getCode()).send(response.getMessage())
2757
}
2858
} catch (e) {
2959
console.error(e)
3060
response.setCode(500)
61+
return res.status(response.getCode()).send(response.getMessage())
3162
}
32-
33-
return res.status(response.getCode()).send(response.getMessage())
3463
})
3564

3665
export default router

src/tests/contact.routes.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,11 @@ describe('Contact tests', function() {
138138
.post('/contact/send')
139139
.set('Accept', 'application/json')
140140
.set('authorization', authHeader)
141-
.expect('Content-Type', 'text/html; charset=utf-8')
141+
.expect('Content-Type', 'application/json; charset=utf-8')
142142
.expect(200)
143143
.end((err, res) => {
144144
if (err) return done(err)
145-
expect(res.text).to.equal('Contacts emailed')
145+
expect(res.body.results.message).to.equal('Contacts emailed')
146146
done()
147147
})
148148
} catch(e) {
@@ -259,7 +259,7 @@ describe('Contact tests', function() {
259259
.get('/csv/Contact')
260260
.set('Accept', 'application/json')
261261
.set('authorization', authHeader)
262-
.expect('Content-Type', 'text/html; charset=utf-8')
262+
.expect('Content-Type', 'text/csv; charset=utf-8')
263263
.expect(200)
264264
// eslint-disable-next-line no-unused-vars
265265
.end((err, res) => {

0 commit comments

Comments
 (0)