Created
January 10, 2020 08:09
-
-
Save tkers/fdf9bc5d5f22bc1640246ce8eb438e3f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { readFile } from "fs"; | |
import aws from "aws-sdk"; | |
import Promise from "bluebird"; | |
import cors from "cors"; | |
import express from "express"; | |
import formidable from "formidable"; | |
import gm from "gm"; | |
import { today } from "../../common/src/date"; | |
import { errorWithStatus } from "./error"; | |
import { validate, sanitize } from "./validation"; | |
import { buildEvent } from "./events"; | |
import logger from "./logger"; | |
import model from "./model"; | |
import { sendSlackMessage, formatPersonLink } from "./slack"; | |
export default function() { | |
const events = buildEvent("people"); | |
const router = express.Router(); | |
const corsOptions = { | |
origin: [/\.reaktor\.com$/, /\.reaktor\.fi$/], | |
credentials: true | |
}; | |
router.get("/people", cors(corsOptions), (req, res, next) => { | |
const toArray = v => { | |
if (v) { | |
if (v.constructor === Array) return v; | |
else return [v]; | |
} else return undefined; | |
}; | |
const uids = toArray(req.query.uids); | |
let getPeople; | |
if (req.query.format === "array") { | |
getPeople = model.getActivePeopleArray(); | |
} else if (uids) { | |
getPeople = model.getPeople(uids); | |
} else { | |
res.set("Content-Type", "application/json"); | |
model | |
.getActivePeopleJson() | |
.then(p => res.send(p)) | |
.catch(next); | |
return; | |
} | |
getPeople.then(p => res.send(p)).catch(next); | |
}); | |
router.get("/people/export/:report", (req, res, next) => { | |
switch (req.params.report) { | |
case "active-reaktorians.csv": | |
model | |
.getActiveReaktoriansArray() | |
.then(people => { | |
const headers = [ | |
"uid", | |
"name", | |
"nickname", | |
"slack", | |
"email", | |
"mobile", | |
"organization", | |
"start" | |
]; | |
const peopleRows = people.map(p => | |
headers.map(k => `"${p[k]}"`).toString() | |
); | |
const csv = [headers.toString(), ...peopleRows].join("\n"); | |
res.type("text/csv"); | |
res.setHeader("ContentType", "text/csv"); | |
res.setHeader( | |
"ContentDisposition", | |
"attachment; filename=testing.csv" | |
); | |
res.send(csv); | |
}) | |
.catch(next); | |
break; | |
default: | |
throw errorWithStatus(400, "Not a valid report"); | |
} | |
}); | |
const upsertPerson = (newPerson, sessionUid) => { | |
if (!newPerson.uid || !newPerson.name) { | |
return Promise.reject("Person object requires a uid and a name"); | |
} | |
return model.getPerson(newPerson.uid).then(person => { | |
if (person) { | |
const updateEvent = events.update(newPerson, sessionUid); | |
updateEvent.previousData = person; | |
validate(updateEvent); | |
updateEvent.data = sanitize(updateEvent); | |
return model.updatePerson(updateEvent); | |
} else { | |
const createEvent = events.create(newPerson, sessionUid); | |
validate(createEvent); | |
createEvent.data = sanitize(createEvent); | |
return model.createPerson(createEvent); | |
} | |
}); | |
}; | |
router.post("/people/", (req, res, next) => { | |
if (req.body.person) { | |
upsertPerson(req.body.person, req.session.uid) | |
.then(() => res.send()) | |
.catch(err => { | |
throw errorWithStatus(409, err); | |
}) | |
.catch(next); | |
} else { | |
throw errorWithStatus(400, "Requires a person field"); | |
} | |
}); | |
router.get("/people/work-history", (req, res, next) => { | |
res.set("Content-Type", "application/json"); | |
model | |
.getAssignmentHistory() | |
.then(p => res.send(p)) | |
.catch(next); | |
}); | |
router.get("/people/inactive", (req, res, next) => { | |
res.set("Content-Type", "application/json"); | |
return model | |
.getInactivePeople() | |
.then(people => { | |
if (req.query.format === "array") { | |
res.send(people); | |
} else { | |
res.send( | |
people.reduce((accum, value) => { | |
accum[value.uid] = value; | |
return accum; | |
}, {}) | |
); | |
} | |
}) | |
.catch(next); | |
}); | |
router.param("field", (req, res, next, field) => { | |
const fields = [ | |
"objectives", | |
"specializations", | |
"tags", | |
"urls", | |
"rotation", | |
"nickname", | |
"address", | |
"external", | |
"capacity" | |
]; | |
if (fields.includes(field)) { | |
next(); | |
} else { | |
next(errorWithStatus(400, "Invalid field")); | |
} | |
}); | |
router.post("/people/:uid/portrait", (req, res) => { | |
const canUploadPortrait = (person, user) => { | |
return ( | |
person.uid === user || | |
new Date(person.start) > new Date() || | |
person.external | |
); | |
}; | |
model | |
.getPerson(req.params.uid) | |
.then(person => { | |
if (!person) { | |
res.status(403).json({ status: 403, message: "invalid uid" }); | |
return undefined; | |
} else if (!canUploadPortrait(person, req.user.name)) { | |
res.sendStatus(401).end(); | |
return undefined; | |
} else { | |
return person; | |
} | |
}) | |
.then(person => { | |
if (!person) { | |
return; | |
} | |
const s3 = new aws.S3({ | |
region: "eu-central-1", | |
signatureVersion: "v4" | |
}); | |
const sizes = [{ width: 95, height: 95 }, { width: 400, height: 500 }]; | |
const bucket = process.env.S3_PORTRAIT_BUCKET; | |
Promise.promisifyAll(s3); | |
const s3Upload = (buffer, remotePath) => { | |
return s3 | |
.uploadAsync({ | |
Bucket: bucket, | |
Body: buffer, | |
Key: remotePath, | |
ACL: "public-read", | |
ContentType: "image/jpeg", | |
CacheControl: "max-age=315360000" | |
}) | |
.then((data, err) => { | |
if (err) { | |
logger.error(err.stack); | |
throw err; | |
} else { | |
logger.info(data); | |
return data.VersionId || data; | |
} | |
}); | |
}; | |
req.body.uid = req.params.uid; | |
const form = new formidable.IncomingForm(); | |
form.parse(req, (err, fields, files) => { | |
if (Object.keys(files).length >= 1) { | |
const filePath = files[Object.keys(files)[0]].path; | |
const readFilePromise = Promise.promisify(readFile); | |
readFilePromise(filePath).then(fileBuffer => { | |
Promise.all( | |
sizes.map( | |
size => | |
new Promise((resolve, reject) => | |
gm(fileBuffer) | |
.autoOrient() | |
.resize(size.width, size.height, "^") | |
.crop(size.width, size.height) | |
.toBuffer((err, buf) => | |
err ? reject(err) : resolve(buf) | |
) | |
) | |
) | |
) | |
.then(buffers => { | |
const files = buffers.map((buffer, idx) => { | |
return { | |
file: new Buffer(buffer), | |
size: `${sizes[idx].width}x${sizes[idx].height}` | |
}; | |
}); | |
files.push({ file: fileBuffer, size: "original" }); | |
return files.map(file => | |
s3Upload( | |
file.file, | |
`people/${file.size}/${req.body.uid}.jpg` | |
) | |
); | |
}) | |
.then(transformedFiles => Promise.all(transformedFiles)) | |
.then(uploadedVersions => { | |
logger.info(`Uploaded portrait for ${req.body.uid}`); | |
const event = events.update({}, req.session.uid); | |
event.data = person; | |
event.data.portrait_versions = uploadedVersions.slice(0, 2); | |
return model.updatePersonField("portrait_versions", event); | |
}) | |
.then(() => { | |
res.sendStatus(201).end(); | |
}) | |
.catch(err => { | |
logger.error(err.stack); | |
res.sendStatus(503).end(); | |
}); | |
}); | |
} else { | |
res.sendStatus(400).end(); | |
} | |
}); | |
}); | |
}); | |
router.post("/people/:uid/:field", (req, res, next) => { | |
const field = req.params.field; | |
const event = events.update({}, req.session.uid); | |
const fieldPostprocess = (field, event, update) => { | |
if (field === "rotation") { | |
event.data.rotation_since = event.data[field] ? new Date() : null; | |
return model.updatePersonField("rotation_since", event); | |
} | |
return update; | |
}; | |
model | |
.getPerson(req.params.uid) | |
.then(person => { | |
if (person) { | |
const editingOther = req.session.uid !== req.params.uid; | |
const startsInFuture = person.start > today(); | |
if (editingOther && !(startsInFuture || person.external)) { | |
throw errorWithStatus(403); | |
} | |
if (field === "external" && !person.external) { | |
throw errorWithStatus(403); | |
} | |
event.data = person; | |
event.previousData = JSON.parse(JSON.stringify(person)); | |
event.data[field] = req.body[field]; | |
if (field === "address" && req.body.address) { | |
const location = { | |
address: req.body.address, | |
lat: req.body.lat, | |
lon: req.body.lon | |
}; | |
return model.insertLocation(location); | |
} | |
if (field === "rotation") { | |
event.data.rotation_since = event.data[field] ? new Date() : null; | |
if (event.data["rotation"]) { | |
sendSlackMessage( | |
process.env.DAILY_DIGEST_SLACK_CHANNEL, | |
`:arrows_clockwise: Rotation enabled for ${formatPersonLink( | |
person | |
)}` | |
); | |
} | |
return model.updatePersonField("rotation_since", event); | |
} | |
} else { | |
throw errorWithStatus(404); | |
} | |
}) | |
.then(() => model.updatePersonField(field, event)) | |
.then(update => fieldPostprocess(field, event, update)) | |
.then(ok => { | |
if (ok) { | |
res.send(); | |
} else { | |
throw errorWithStatus(409); | |
} | |
}) | |
.catch(next); | |
}); | |
return router; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment