Skip to content

Instantly share code, notes, and snippets.

@serhatsezer
Created February 16, 2023 06:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save serhatsezer/7f627c9fe71625dba1c73378c5168ccf to your computer and use it in GitHub Desktop.
Save serhatsezer/7f627c9fe71625dba1c73378c5168ccf 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