Skip to content

Instantly share code, notes, and snippets.

@tkers
Created January 10, 2020 08:09
Show Gist options
  • Save tkers/fdf9bc5d5f22bc1640246ce8eb438e3f to your computer and use it in GitHub Desktop.
Save tkers/fdf9bc5d5f22bc1640246ce8eb438e3f to your computer and use it in GitHub Desktop.
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