SectionList on home screen with API
This commit is contained in:
91
server/package-lock.json
generated
91
server/package-lock.json
generated
@@ -1197,9 +1197,9 @@
|
||||
}
|
||||
},
|
||||
"bson": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz",
|
||||
"integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw="
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.0.6.tgz",
|
||||
"integrity": "sha512-D8zmlb46xfuK2gGvKmUjIklQEouN2nQ0LEHHeZ/NoHM2LDiMk2EYzZ5Ntw/Urk+bgMDosOZxaRzXxvhI5TcAVQ=="
|
||||
},
|
||||
"buffer": {
|
||||
"version": "4.9.1",
|
||||
@@ -3324,11 +3324,6 @@
|
||||
"os-tmpdir": "1.0.2"
|
||||
}
|
||||
},
|
||||
"hooks-fixed": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.2.tgz",
|
||||
"integrity": "sha512-YurCM4gQSetcrhwEtpQHhQ4M7Zo7poNGqY4kQGeBS6eZtOcT3tnNs01ThFa0jYBByAiYt1MjMjP/YApG0EnAvQ=="
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
|
||||
@@ -4424,9 +4419,9 @@
|
||||
}
|
||||
},
|
||||
"kareem": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-1.5.0.tgz",
|
||||
"integrity": "sha1-4+QQHZ3P3imXadr0tNtk2JXRdEg="
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.0.6.tgz",
|
||||
"integrity": "sha512-/C+l8gABdHsAIfNpykJNWmYodpTnDRyn+JhORkP2VgEf1GgdAc+oTHjVADwISwCJKta031EOIwY6+Hki5z8SpQ=="
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "3.2.2",
|
||||
@@ -4648,12 +4643,12 @@
|
||||
"integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg=="
|
||||
},
|
||||
"mongodb": {
|
||||
"version": "2.2.34",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.34.tgz",
|
||||
"integrity": "sha1-o09Zu+thdUrsQy3nLD/iFSakTBo=",
|
||||
"version": "2.2.35",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.35.tgz",
|
||||
"integrity": "sha512-3HGLucDg/8EeYMin3k+nFWChTA85hcYDCw1lPsWR6yV9A6RgKb24BkLiZ9ySZR+S0nfBjWoIUS7cyV6ceGx5Gg==",
|
||||
"requires": {
|
||||
"es6-promise": "3.2.1",
|
||||
"mongodb-core": "2.1.18",
|
||||
"mongodb-core": "2.1.19",
|
||||
"readable-stream": "2.2.7"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -4687,34 +4682,56 @@
|
||||
}
|
||||
},
|
||||
"mongodb-core": {
|
||||
"version": "2.1.18",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.18.tgz",
|
||||
"integrity": "sha1-TEYTm986HwMt7ZHbSfOO7AFlkFA=",
|
||||
"version": "2.1.19",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.19.tgz",
|
||||
"integrity": "sha512-Jt4AtWUkpuW03kRdYGxga4O65O1UHlFfvvInslEfLlGi+zDMxbBe3J2NVmN9qPJ957Mn6Iz0UpMtV80cmxCVxw==",
|
||||
"requires": {
|
||||
"bson": "1.0.4",
|
||||
"bson": "1.0.6",
|
||||
"require_optional": "1.0.1"
|
||||
}
|
||||
},
|
||||
"mongoose": {
|
||||
"version": "4.13.11",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.13.11.tgz",
|
||||
"integrity": "sha512-OgXmFc3vzXwq4zWp41XfSBDnKZLqnBc4Kh7mwwGjBE5iWH5tfkixaPK0uFtpEuzDzUvAIg33bgniyTsmc00olA==",
|
||||
"version": "5.0.13",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.0.13.tgz",
|
||||
"integrity": "sha512-VCiutgdxwhTuNHIuUgMRWVYvv0GFw6FUi4j14B7um/Wcy1uhuwF552a6XVKUCth/AY8C+PjVU9fVGJ5K0JmrmQ==",
|
||||
"requires": {
|
||||
"async": "2.1.4",
|
||||
"bson": "1.0.4",
|
||||
"hooks-fixed": "2.0.2",
|
||||
"kareem": "1.5.0",
|
||||
"bson": "1.0.6",
|
||||
"kareem": "2.0.6",
|
||||
"lodash.get": "4.4.2",
|
||||
"mongodb": "2.2.34",
|
||||
"mongodb": "3.0.4",
|
||||
"mongoose-legacy-pluralize": "1.0.2",
|
||||
"mpath": "0.3.0",
|
||||
"mpromise": "0.5.5",
|
||||
"mquery": "2.3.3",
|
||||
"mquery": "3.0.0",
|
||||
"ms": "2.0.0",
|
||||
"muri": "1.3.0",
|
||||
"regexp-clone": "0.0.1",
|
||||
"sliced": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"mongodb": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.4.tgz",
|
||||
"integrity": "sha512-90YIIs7A4ko4kCGafxxXj3foexCAlJBC0YLwwIKgSLoE7Vni2IqUMz6HSsZ3zbXOfR1KWtxfnc0RyAMAY/ViLg==",
|
||||
"requires": {
|
||||
"mongodb-core": "3.0.4"
|
||||
}
|
||||
},
|
||||
"mongodb-core": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.4.tgz",
|
||||
"integrity": "sha512-OTH267FjfwBdEufSnrgd+u8HuLWRuQ6p8DR0XirPl2BdlLEMh4XwjJf1RTlruILp5p2m1w8dDC8rCxibC3W8qQ==",
|
||||
"requires": {
|
||||
"bson": "1.0.6",
|
||||
"require_optional": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongoose-legacy-pluralize": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
|
||||
"integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
|
||||
},
|
||||
"mongoose-merge-plugin": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/mongoose-merge-plugin/-/mongoose-merge-plugin-0.0.5.tgz",
|
||||
@@ -4751,15 +4768,10 @@
|
||||
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.3.0.tgz",
|
||||
"integrity": "sha1-elj3iem1/TyUUgY0FXlg8mvV70Q="
|
||||
},
|
||||
"mpromise": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.5.tgz",
|
||||
"integrity": "sha1-9bJCWddjrMIlewoMjG2Gb9UXMuY="
|
||||
},
|
||||
"mquery": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-2.3.3.tgz",
|
||||
"integrity": "sha512-NC8L14kn+qxJbbJ1gbcEMDxF0sC3sv+1cbRReXXwVvowcwY1y9KoVZFq0ebwARibsadu8lx8nWGvm3V0Pf0ZWQ==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-3.0.0.tgz",
|
||||
"integrity": "sha512-WL1Lk8v4l8VFSSwN3yCzY9TXw+fKVYKn6f+w86TRzOLSE8k1yTgGaLBPUByJQi8VcLbOdnUneFV/y3Kv874pnQ==",
|
||||
"requires": {
|
||||
"bluebird": "3.5.0",
|
||||
"debug": "2.6.9",
|
||||
@@ -4784,11 +4796,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"muri": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/muri/-/muri-1.3.0.tgz",
|
||||
"integrity": "sha512-FiaFwKl864onHFFUV/a2szAl7X0fxVlSKNdhTf+BM8i8goEgYut8u5P9MqQqIYwvaMxjzVESsoEm/2kfkFH1rg=="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
"http-errors": "^1.6.1",
|
||||
"json5": "^0.5.1",
|
||||
"jsonwebtoken": "^7.4.0",
|
||||
"mongodb": "^2.2.31",
|
||||
"mongoose": "^4.11.7",
|
||||
"mongodb": "^2.2.35",
|
||||
"mongoose": "^5.0.13",
|
||||
"mongoose-merge-plugin": "0.0.5",
|
||||
"nodemailer": "^4.0.1",
|
||||
"passport": "^0.3.2",
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
import config from 'config'
|
||||
import express from 'express'
|
||||
import pino from 'pino'
|
||||
import * as pinoExpress from 'pino-pretty-express'
|
||||
import http from 'http'
|
||||
import { DB } from '../database'
|
||||
import { MQ } from './MQ'
|
||||
import { RS } from './RS'
|
||||
import { WS } from './WS'
|
||||
import bodyParser from 'body-parser'
|
||||
import cors from 'cors'
|
||||
import passport from 'passport'
|
||||
import { Strategy as BearerStrategy } from 'passport-http-bearer'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import createError from 'http-errors'
|
||||
import * as Routes from './routes'
|
||||
import config from "config"
|
||||
import express from "express"
|
||||
import pino from "pino"
|
||||
import * as pinoExpress from "pino-pretty-express"
|
||||
import http from "http"
|
||||
import { DB } from "../database"
|
||||
import { MQ } from "./MQ"
|
||||
import { RS } from "./RS"
|
||||
import { WS } from "./WS"
|
||||
import bodyParser from "body-parser"
|
||||
import cors from "cors"
|
||||
import passport from "passport"
|
||||
import { Strategy as BearerStrategy } from "passport-http-bearer"
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
import createError from "http-errors"
|
||||
import * as Routes from "./routes"
|
||||
|
||||
let app = express()
|
||||
let server = http.createServer(app)
|
||||
let container = { app, server }
|
||||
const serviceName = 'dar-api'
|
||||
const isProduction = (process.env.NODE_ENV == 'production')
|
||||
const serviceName = "dar-api"
|
||||
const isProduction = process.env.NODE_ENV == "production"
|
||||
let log = null
|
||||
|
||||
if (isProduction) {
|
||||
log = pino( { name: serviceName },
|
||||
fs.createWriteStream(path.join(config.get('logDir'), serviceName + '.log'))
|
||||
log = pino(
|
||||
{ name: serviceName },
|
||||
fs.createWriteStream(path.join(config.get("logDir"), serviceName + ".log"))
|
||||
)
|
||||
} else {
|
||||
const pretty = pinoExpress.pretty({})
|
||||
@@ -35,12 +36,12 @@ if (isProduction) {
|
||||
|
||||
container.log = log
|
||||
app.use(pinoExpress.config({ log }))
|
||||
app.set('etag', false) // Not wanted for _all_ routes. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag.
|
||||
app.options('*', cors()) // Enable all pre-flight CORS requests
|
||||
app.set("etag", false) // Not wanted for _all_ routes. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag.
|
||||
app.options("*", cors()) // Enable all pre-flight CORS requests
|
||||
app.use(cors())
|
||||
app.use(bodyParser.urlencoded({ extended: true }))
|
||||
app.use(bodyParser.json())
|
||||
app.use(bodyParser.raw({ type: 'application/octet-stream'})) // TODO: Support gzip, etc.. here
|
||||
app.use(bodyParser.raw({ type: "application/octet-stream" })) // TODO: Support gzip, etc.. here
|
||||
app.use(passport.initialize())
|
||||
|
||||
const rs = new RS(container)
|
||||
@@ -54,55 +55,61 @@ container.mq = mq
|
||||
|
||||
passport.use(new BearerStrategy(db.lookupToken))
|
||||
|
||||
let mongoUri = config.get('uri.mongo')
|
||||
let amqpUri = config.get('uri.amqp')
|
||||
let redisUri = config.get('uri.redis')
|
||||
let mongoUri = config.get("uri.mongo")
|
||||
let amqpUri = config.get("uri.amqp")
|
||||
let redisUri = config.get("uri.redis")
|
||||
|
||||
Promise.all([
|
||||
db.connect(mongoUri, isProduction),
|
||||
mq.connect(amqpUri),
|
||||
rs.connect(redisUri),
|
||||
ws.listen()
|
||||
]).then(() => {
|
||||
log.info(`Connected to MongoDB at ${mongoUri}`)
|
||||
log.info(`Connected to RabbitMQ at ${amqpUri}`)
|
||||
log.info(`Connected to Redis at ${redisUri}`)
|
||||
ws.listen(),
|
||||
])
|
||||
.then(() => {
|
||||
log.info(`Connected to MongoDB at ${mongoUri}`)
|
||||
log.info(`Connected to RabbitMQ at ${amqpUri}`)
|
||||
log.info(`Connected to Redis at ${redisUri}`)
|
||||
|
||||
try {
|
||||
container.authRoutes = new Routes.AuthRoutes(container)
|
||||
container.userRoutes = new Routes.UserRoutes(container)
|
||||
container.assetRoutes = new Routes.AssetRoutes(container)
|
||||
container.workItemRoutes = new Routes.WorkItemRoutes(container)
|
||||
container.teamRoutes = new Routes.TeamRoutes(container)
|
||||
container.activityRoutes = new Routes.ActivityRoutes(container)
|
||||
try {
|
||||
container.authRoutes = new Routes.AuthRoutes(container)
|
||||
container.userRoutes = new Routes.UserRoutes(container)
|
||||
container.assetRoutes = new Routes.AssetRoutes(container)
|
||||
container.workItemRoutes = new Routes.WorkItemRoutes(container)
|
||||
container.teamRoutes = new Routes.TeamRoutes(container)
|
||||
container.activityRoutes = new Routes.ActivityRoutes(container)
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
res.status(404).json({
|
||||
message: 'Not found'
|
||||
app.use(function(req, res, next) {
|
||||
res.status(404).json({
|
||||
message: "Not found",
|
||||
})
|
||||
})
|
||||
})
|
||||
app.use(function(err, req, res, next) {
|
||||
if (!isProduction) {
|
||||
log.error(err)
|
||||
}
|
||||
app.use(function(err, req, res, next) {
|
||||
if (!isProduction) {
|
||||
log.error(err)
|
||||
}
|
||||
|
||||
if (!err.status) {
|
||||
err = createError.InternalServerError(err.message)
|
||||
}
|
||||
if (!err.status) {
|
||||
err = createError.InternalServerError(err.message)
|
||||
}
|
||||
|
||||
res.status(err.status).json({
|
||||
message: err.message
|
||||
res.status(err.status).json({
|
||||
message: err.message,
|
||||
})
|
||||
})
|
||||
})
|
||||
} catch(error) {
|
||||
console.error(error)
|
||||
process.exit(-1)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
let port = config.get('api.port')
|
||||
server.listen(port)
|
||||
log.info(`Deight AR API started on port ${port}`)
|
||||
}).catch((err) => {
|
||||
log.error(err.message)
|
||||
process.exit(1)
|
||||
})
|
||||
let port = config.get("api.port")
|
||||
server.listen(port)
|
||||
log.info(`Deight AR API started on port ${port}`)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (isProduction) {
|
||||
log.error(err.message)
|
||||
} else {
|
||||
console.log(err)
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import passport from 'passport'
|
||||
import createError from 'http-errors'
|
||||
import autobind from 'autobind-decorator'
|
||||
import passport from "passport"
|
||||
import createError from "http-errors"
|
||||
import autobind from "autobind-decorator"
|
||||
import { catchAll } from "."
|
||||
|
||||
@autobind
|
||||
export class ActivityRoutes {
|
||||
@@ -12,59 +13,72 @@ export class ActivityRoutes {
|
||||
this.mq = container.mq
|
||||
this.ws = container.ws
|
||||
|
||||
app.route('/activities')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.listActivitys)
|
||||
.post(passport.authenticate('bearer', { session: false }), this.createActivity)
|
||||
.put(passport.authenticate('bearer', { session: false }), this.updateActivity)
|
||||
app
|
||||
.route("/activities")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.listActivities)
|
||||
)
|
||||
.post(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.createActivity)
|
||||
)
|
||||
.put(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.updateActivity)
|
||||
)
|
||||
|
||||
app.route('/activities/:_id([a-f0-9]{24})')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.getActivity)
|
||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteActivity)
|
||||
app
|
||||
.route("/activities/:_id([a-f0-9]{24})")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.getActivity)
|
||||
)
|
||||
.delete(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.deleteActivity)
|
||||
)
|
||||
}
|
||||
|
||||
listActivitys(req, res, next) {
|
||||
async listActivities(req, res, next) {
|
||||
const Activity = this.db.Activity
|
||||
const limit = req.query.limit || 20
|
||||
const skip = req.query.skip || 0
|
||||
const partial = !!req.query.partial
|
||||
const branch = req.query.branch
|
||||
const query = {}
|
||||
let query = {}
|
||||
|
||||
if (branch) {
|
||||
query.branch = branch
|
||||
}
|
||||
const total = await Activity.count({})
|
||||
|
||||
Activity.count({}).then((total) => {
|
||||
let activities = []
|
||||
let cursor = Activity.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||
let Activities = []
|
||||
let cursor = Activity.find(query)
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
.cursor()
|
||||
.map((doc) => {
|
||||
return doc.toClient(partial)
|
||||
})
|
||||
|
||||
cursor.on('data', (doc) => {
|
||||
activities.push(doc)
|
||||
cursor.on("data", (doc) => {
|
||||
Activities.push(doc)
|
||||
})
|
||||
cursor.on("end", () => {
|
||||
res.json({
|
||||
total: total,
|
||||
offset: skip,
|
||||
count: activities.length,
|
||||
items: activities,
|
||||
})
|
||||
cursor.on('end', () => {
|
||||
res.json({
|
||||
total: total,
|
||||
offset: skip,
|
||||
count: activities.length,
|
||||
items: activities
|
||||
})
|
||||
})
|
||||
cursor.on('error', (err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
cursor.on("error", (err) => {
|
||||
throw createError.InternalServerError(err.message)
|
||||
})
|
||||
}
|
||||
|
||||
createActivity(req, res, next) {
|
||||
const role = req.user.role
|
||||
async createActivity(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
// If user's role is not Executive or Administrator, return an error
|
||||
if (role !== 'executive' && role !== 'administrator') {
|
||||
return next(new createError.Forbidden())
|
||||
if (!isAdmin) {
|
||||
return new createError.Forbidden()
|
||||
}
|
||||
|
||||
// Create a new Activity template then assign it to a value in the req.body
|
||||
@@ -72,24 +86,21 @@ export class ActivityRoutes {
|
||||
let activity = new Activity(req.body)
|
||||
|
||||
// Save the activity (with promise) - If it doesnt, catch and throw error
|
||||
activity.save().then((newActivity) => {
|
||||
res.json(newActivity.toClient())
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
const newActivity = await activity.save()
|
||||
|
||||
res.json(newActivity.toClient())
|
||||
}
|
||||
|
||||
updateActivity(req, res, next) {
|
||||
const role = req.user.role
|
||||
async updateActivity(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
// If user's role is not Executive or Administrator, return an error
|
||||
if (role !== 'executive' && role !== 'administrator') {
|
||||
if (!isAdmin) {
|
||||
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'))
|
||||
throw createError.BadRequest("No _id given in body")
|
||||
}
|
||||
|
||||
let Activity = this.db.Activity
|
||||
@@ -98,56 +109,51 @@ export class ActivityRoutes {
|
||||
try {
|
||||
activityUpdates = new Activity(req.body)
|
||||
} catch (err) {
|
||||
return next(createError.BadRequest('Invalid data'))
|
||||
throw createError.BadRequest("Invalid data")
|
||||
}
|
||||
|
||||
Activity.findById(activityUpdates._id).then((foundActivity) => {
|
||||
if (!foundActivity) {
|
||||
return next(createError.NotFound(`Activity with _id ${_id} was not found`))
|
||||
}
|
||||
foundActivity.merge(activityUpdates)
|
||||
return foundActivity.save()
|
||||
}).then((savedActivity) => {
|
||||
res.json(savedActivity.toClient())
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
const foundActivity = await Activity.findById(activityUpdates._id)
|
||||
|
||||
if (!foundActivity) {
|
||||
return next(
|
||||
createError.NotFound(`Activity with _id ${_id} was not found`)
|
||||
)
|
||||
}
|
||||
|
||||
foundActivity.merge(activityUpdates)
|
||||
|
||||
const savedActivity = await foundActivity.save()
|
||||
|
||||
res.json(savedActivity.toClient())
|
||||
}
|
||||
|
||||
getActivity(req, res, next) {
|
||||
async getActivity(req, res, next) {
|
||||
const Activity = this.db.Activity
|
||||
const _id = req.params._id
|
||||
const activity = await Activity.findById(_id)
|
||||
|
||||
Activity.findById(_id).then((activity) => {
|
||||
if (!activity) {
|
||||
return next(createError.NotFound(`Activity with _id ${_id} not found`))
|
||||
}
|
||||
if (!activity) {
|
||||
throw createError.NotFound(`Activity with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json(activity.toClient())
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
res.json(activity.toClient())
|
||||
}
|
||||
|
||||
deleteActivity(req, res, next) {
|
||||
const role = req.user.role
|
||||
async deleteActivity(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
// If user's role is not Executive or Administrator, return an error
|
||||
if (role !== 'executive' && role !== 'administrator') {
|
||||
if (!isAdmin) {
|
||||
return new createError.Forbidden()
|
||||
}
|
||||
|
||||
const Activity = this.db.Activity
|
||||
const _id = req.params._id
|
||||
const activity = await Activity.remove({ _id })
|
||||
|
||||
Activity.remove({ _id }).then((activity) => {
|
||||
if (!activity) {
|
||||
return next(createError.NotFound(`Activity with _id ${_id} not found`))
|
||||
}
|
||||
if (!activity) {
|
||||
throw createError.NotFound(`Activity with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
res.json({})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import passport from 'passport'
|
||||
import redis from 'redis'
|
||||
import redisReadStream from 'redis-rstream'
|
||||
import createError from 'http-errors'
|
||||
import path from 'path'
|
||||
import util from 'util'
|
||||
import config from 'config'
|
||||
import autobind from 'autobind-decorator'
|
||||
import passport from "passport"
|
||||
import redis from "redis"
|
||||
import redisReadStream from "redis-rstream"
|
||||
import createError from "http-errors"
|
||||
import path from "path"
|
||||
import util from "util"
|
||||
import config from "config"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
function pipeToGridFS(readable, gfsWriteable) {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
readable.on('error', (error) => {
|
||||
readable.on("error", (error) => {
|
||||
reject(error)
|
||||
})
|
||||
gfsWriteable.on('error', (error) => {
|
||||
gfsWriteable.on("error", (error) => {
|
||||
reject(error)
|
||||
})
|
||||
gfsWriteable.on('close', (file) => {
|
||||
gfsWriteable.on("close", (file) => {
|
||||
resolve(file)
|
||||
})
|
||||
})
|
||||
@@ -32,55 +32,85 @@ export class AssetRoutes {
|
||||
|
||||
this.db = container.db
|
||||
this.rs = container.rs
|
||||
this.uploadTimeout = config.get('api.uploadTimout')
|
||||
app.route('/assets/:_id')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.getAsset)
|
||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteAsset)
|
||||
this.uploadTimeout = config.get("api.uploadTimout")
|
||||
app
|
||||
.route("/assets/:_id")
|
||||
.get(passport.authenticate("bearer", { session: false }), this.getAsset)
|
||||
.delete(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
this.deleteAsset
|
||||
)
|
||||
|
||||
app.route('/assets/upload')
|
||||
.post(passport.authenticate('bearer', { session: false }), this.beginAssetUpload)
|
||||
app
|
||||
.route("/assets/upload")
|
||||
.post(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
this.beginAssetUpload
|
||||
)
|
||||
|
||||
app.route('/assets/upload/:_id')
|
||||
.post(passport.authenticate('bearer', { session: false }), this.continueAssetUpload)
|
||||
app
|
||||
.route("/assets/upload/:_id")
|
||||
.post(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
this.continueAssetUpload
|
||||
)
|
||||
}
|
||||
|
||||
getAsset(req, res, next) {
|
||||
const assetId = req.params._id
|
||||
|
||||
this.db.gridfs.findOneAsync({ _id: assetId }).then((file) => {
|
||||
if (!file) {
|
||||
return next(createError.NotFound(`Asset ${assetId} was not found`))
|
||||
}
|
||||
this.db.gridfs
|
||||
.findOneAsync({ _id: assetId })
|
||||
.then((file) => {
|
||||
if (!file) {
|
||||
return next(createError.NotFound(`Asset ${assetId} was not found`))
|
||||
}
|
||||
|
||||
const ifNoneMatch = req.get('If-None-Match')
|
||||
const ifNoneMatch = req.get("If-None-Match")
|
||||
|
||||
if (ifNoneMatch && ifNoneMatch === file.md5) {
|
||||
res.status(304).set({
|
||||
'ETag': file.md5,
|
||||
'Cache-Control': 'private,max-age=86400'
|
||||
}).end()
|
||||
return
|
||||
}
|
||||
if (ifNoneMatch && ifNoneMatch === file.md5) {
|
||||
res
|
||||
.status(304)
|
||||
.set({
|
||||
ETag: file.md5,
|
||||
"Cache-Control": "private,max-age=86400",
|
||||
})
|
||||
.end()
|
||||
return
|
||||
}
|
||||
|
||||
res.status(200).set({
|
||||
'Content-Type': file.contentType,
|
||||
'Content-Length': file.length,
|
||||
'ETag': file.md5})
|
||||
res.status(200).set({
|
||||
"Content-Type": file.contentType,
|
||||
"Content-Length": file.length,
|
||||
ETag: file.md5,
|
||||
})
|
||||
|
||||
this.db.gridfs.createReadStream({ _id: file._id }).pipe(res)
|
||||
}).catch((err) => {
|
||||
next(createError.BadRequest(`Error returning asset '${assetId}'. ${err.message}`))
|
||||
})
|
||||
this.db.gridfs.createReadStream({ _id: file._id }).pipe(res)
|
||||
})
|
||||
.catch((err) => {
|
||||
next(
|
||||
createError.BadRequest(
|
||||
`Error returning asset '${assetId}'. ${err.message}`
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
deleteAsset(req, res, next) {
|
||||
const assetId = req.params._id
|
||||
|
||||
this.db.gridfs.removeAsync({ _id: assetId }).then(() => {
|
||||
res.json({})
|
||||
}).catch((err) => {
|
||||
next(createError.BadRequest(`Unable to delete asset '${assetId}'. ${err.message}`))
|
||||
})
|
||||
this.db.gridfs
|
||||
.removeAsync({ _id: assetId })
|
||||
.then(() => {
|
||||
res.json({})
|
||||
})
|
||||
.catch((err) => {
|
||||
next(
|
||||
createError.BadRequest(
|
||||
`Unable to delete asset '${assetId}'. ${err.message}`
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
beginAssetUpload(req, res, next) {
|
||||
@@ -88,112 +118,148 @@ export class AssetRoutes {
|
||||
let { fileName, fileSize, numberOfChunks, contentType } = req.body
|
||||
|
||||
if (!fileName || !fileSize || !numberOfChunks || !contentType) {
|
||||
return next(createError.BadRequest('Must specify fileName, fileSize, numberOfChunks and Content-Type header'))
|
||||
return next(
|
||||
createError.BadRequest(
|
||||
"Must specify fileName, fileSize, numberOfChunks and Content-Type header"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fileName = uploadId + '-' + path.basename(fileName)
|
||||
fileName = uploadId + "-" + path.basename(fileName)
|
||||
|
||||
this.rs.setAsync(
|
||||
uploadId, JSON.stringify({
|
||||
fileName, fileSize, numberOfChunks, contentType
|
||||
}), 'EX', this.uploadTimeout).then(() => {
|
||||
res.json({ uploadId })
|
||||
}).catch((error) => {
|
||||
next(createError.InternalServerError(error.message))
|
||||
})
|
||||
this.rs
|
||||
.setAsync(
|
||||
uploadId,
|
||||
JSON.stringify({
|
||||
fileName,
|
||||
fileSize,
|
||||
numberOfChunks,
|
||||
contentType,
|
||||
}),
|
||||
"EX",
|
||||
this.uploadTimeout
|
||||
)
|
||||
.then(() => {
|
||||
res.json({ uploadId })
|
||||
})
|
||||
.catch((error) => {
|
||||
next(createError.InternalServerError(error.message))
|
||||
})
|
||||
}
|
||||
|
||||
continueAssetUpload(req, res, next) {
|
||||
if (!(req.body instanceof Buffer)) {
|
||||
return next(createError.BadRequest('Body must be of type application/octet-stream'))
|
||||
return next(
|
||||
createError.BadRequest("Body must be of type application/octet-stream")
|
||||
)
|
||||
}
|
||||
|
||||
const range = req.get('Range')
|
||||
const contentLength = req.get('Content-Length')
|
||||
const range = req.get("Range")
|
||||
const contentLength = req.get("Content-Length")
|
||||
let match = range.match(AssetRoutes.rangeRegex)
|
||||
let offset = null
|
||||
|
||||
if (!match || match.length < 2 || (offset = parseInt(match[1])) === NaN) {
|
||||
return next(createError.BadRequest('Range header must be supplied and of form \'byte <offset>\''))
|
||||
return next(
|
||||
createError.BadRequest(
|
||||
"Range header must be supplied and of form 'byte <offset>'"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (parseInt(contentLength, 10) !== req.body.length) {
|
||||
return next(createError.BadRequest('Must supply Content-Length header matching length of request body'))
|
||||
return next(
|
||||
createError.BadRequest(
|
||||
"Must supply Content-Length header matching length of request body"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const uploadId = req.params._id
|
||||
const uploadCountId = uploadId + '$#'
|
||||
const uploadDataId = uploadId + '$@'
|
||||
const uploadCountId = uploadId + "$#"
|
||||
const uploadDataId = uploadId + "$@"
|
||||
|
||||
this.rs.getAsync(uploadId).then((content) => {
|
||||
let uploadData = null
|
||||
this.rs
|
||||
.getAsync(uploadId)
|
||||
.then((content) => {
|
||||
let uploadData = null
|
||||
|
||||
try {
|
||||
uploadData = JSON.parse(content)
|
||||
} catch (error){
|
||||
return Promise.reject(new Error('Could not parse upload data'))
|
||||
}
|
||||
|
||||
if (offset < 0 || offset + req.body.length > uploadData.fileSize) {
|
||||
return Promise.reject(new Error(`Illegal range offset ${offset} given`))
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
this.rs.setrangeAsync(uploadDataId, offset, req.body),
|
||||
this.rs.incrAsync(uploadCountId)
|
||||
]).then((arr) => {
|
||||
const uploadedChunks = arr[1]
|
||||
let chunkInfo = {
|
||||
numberOfChunks: uploadData.numberOfChunks,
|
||||
uploadedChunks
|
||||
try {
|
||||
uploadData = JSON.parse(content)
|
||||
} catch (error) {
|
||||
return Promise.reject(new Error("Could not parse upload data"))
|
||||
}
|
||||
|
||||
if (uploadedChunks >= uploadData.numberOfChunks) {
|
||||
let readable = redisReadStream(this.rs.client, Buffer(uploadDataId))
|
||||
let writeable = this.db.gridfs.createWriteStream({
|
||||
_id: uploadId,
|
||||
filename: uploadData.fileName,
|
||||
content_type: uploadData.contentType
|
||||
})
|
||||
|
||||
let promise = pipeToGridFS(readable, writeable).then((file) => {
|
||||
return Promise.all([
|
||||
Promise.resolve(file),
|
||||
this.rs.del(uploadId),
|
||||
this.rs.del(uploadCountId),
|
||||
this.rs.del(uploadDataId)
|
||||
])
|
||||
}).then((arr) => {
|
||||
const [file] = arr
|
||||
res.json({
|
||||
assetId: file._id,
|
||||
fileName: file.filename,
|
||||
contentType: file.contentType,
|
||||
uploadDate: file.uploadDate,
|
||||
md5: file.md5,
|
||||
...chunkInfo
|
||||
})
|
||||
}) // TODO: Test that this will be caught...
|
||||
return promise
|
||||
} else {
|
||||
return Promise.all([
|
||||
this.rs.expireAsync(uploadId, this.uploadTimeout),
|
||||
this.rs.expireAsync(uploadCountId, this.uploadTimeout),
|
||||
this.rs.expireAsync(uploadDataId, this.uploadTimeout)
|
||||
]).then(() => {
|
||||
res.json(chunkInfo)
|
||||
})
|
||||
if (offset < 0 || offset + req.body.length > uploadData.fileSize) {
|
||||
return Promise.reject(
|
||||
new Error(`Illegal range offset ${offset} given`)
|
||||
)
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.rs.del(uploadId)
|
||||
this.rs.del(uploadCountId)
|
||||
this.rs.del(uploadDataId)
|
||||
console.error(error) // TODO: This should go into log file
|
||||
next(createError.BadRequest('Unable to upload data chunk'))
|
||||
|
||||
Promise.all([
|
||||
this.rs.setrangeAsync(uploadDataId, offset, req.body),
|
||||
this.rs.incrAsync(uploadCountId),
|
||||
])
|
||||
.then((arr) => {
|
||||
const uploadedChunks = arr[1]
|
||||
let chunkInfo = {
|
||||
numberOfChunks: uploadData.numberOfChunks,
|
||||
uploadedChunks,
|
||||
}
|
||||
|
||||
if (uploadedChunks >= uploadData.numberOfChunks) {
|
||||
let readable = redisReadStream(
|
||||
this.rs.client,
|
||||
Buffer(uploadDataId)
|
||||
)
|
||||
let writeable = this.db.gridfs.createWriteStream({
|
||||
_id: uploadId,
|
||||
filename: uploadData.fileName,
|
||||
content_type: uploadData.contentType,
|
||||
})
|
||||
|
||||
let promise = pipeToGridFS(readable, writeable)
|
||||
.then((file) => {
|
||||
return Promise.all([
|
||||
Promise.resolve(file),
|
||||
this.rs.del(uploadId),
|
||||
this.rs.del(uploadCountId),
|
||||
this.rs.del(uploadDataId),
|
||||
])
|
||||
})
|
||||
.then((arr) => {
|
||||
const [file] = arr
|
||||
res.json({
|
||||
assetId: file._id,
|
||||
fileName: file.filename,
|
||||
contentType: file.contentType,
|
||||
uploadDate: file.uploadDate,
|
||||
md5: file.md5,
|
||||
...chunkInfo,
|
||||
})
|
||||
}) // TODO: Test that this will be caught...
|
||||
return promise
|
||||
} else {
|
||||
return Promise.all([
|
||||
this.rs.expireAsync(uploadId, this.uploadTimeout),
|
||||
this.rs.expireAsync(uploadCountId, this.uploadTimeout),
|
||||
this.rs.expireAsync(uploadDataId, this.uploadTimeout),
|
||||
]).then(() => {
|
||||
res.json(chunkInfo)
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.rs.del(uploadId)
|
||||
this.rs.del(uploadCountId)
|
||||
this.rs.del(uploadDataId)
|
||||
console.error(error) // TODO: This should go into log file
|
||||
next(createError.BadRequest("Unable to upload data chunk"))
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error) // TODO: This should go into log file
|
||||
next(createError.BadRequest(error.message))
|
||||
})
|
||||
}).catch((error) => {
|
||||
console.error(error) // TODO: This should go into log file
|
||||
next(createError.BadRequest(error.message))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import passport from 'passport'
|
||||
import credential from 'credential'
|
||||
import createError from 'http-errors'
|
||||
import config from 'config'
|
||||
import crypto from 'crypto'
|
||||
import urlSafeBase64 from 'urlsafe-base64'
|
||||
import util from 'util'
|
||||
import * as loginToken from './loginToken'
|
||||
import autobind from 'autobind-decorator'
|
||||
import url from 'url'
|
||||
import passport from "passport"
|
||||
import credential from "credential"
|
||||
import createError from "http-errors"
|
||||
import config from "config"
|
||||
import crypto from "crypto"
|
||||
import urlSafeBase64 from "urlsafe-base64"
|
||||
import util from "util"
|
||||
import * as loginToken from "./loginToken"
|
||||
import autobind from "autobind-decorator"
|
||||
import url from "url"
|
||||
import { catchAll } from "."
|
||||
|
||||
@autobind
|
||||
export class AuthRoutes {
|
||||
@@ -16,44 +17,60 @@ export class AuthRoutes {
|
||||
|
||||
this.mq = container.mq
|
||||
this.db = container.db
|
||||
this.maxEmailTokenAgeInHours = config.get('email.maxEmailTokenAgeInHours')
|
||||
this.maxPasswordTokenAgeInHours = config.get('email.maxPasswordTokenAgeInHours')
|
||||
this.sendEmailDelayInSeconds = config.get('email.sendEmailDelayInSeconds')
|
||||
this.supportEmail = config.get('email.supportEmail')
|
||||
this.sendEmail = config.get('email.sendEmail')
|
||||
app.route('/auth/login')
|
||||
this.maxEmailTokenAgeInHours = config.get("email.maxEmailTokenAgeInHours")
|
||||
this.maxPasswordTokenAgeInHours = config.get(
|
||||
"email.maxPasswordTokenAgeInHours"
|
||||
)
|
||||
this.sendEmailDelayInSeconds = config.get("email.sendEmailDelayInSeconds")
|
||||
this.supportEmail = config.get("email.supportEmail")
|
||||
this.sendEmail = config.get("email.sendEmail")
|
||||
app
|
||||
.route("/auth/login")
|
||||
// Used to login. Email must be confirmed.
|
||||
.post(this.login)
|
||||
.post(catchAll(this.login))
|
||||
// Used to logout
|
||||
.delete(passport.authenticate('bearer', { session: false }), this.logout)
|
||||
.delete(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.logout)
|
||||
)
|
||||
|
||||
// Send change email confirmation email
|
||||
app.route('/auth/email/send')
|
||||
.post(passport.authenticate('bearer', { session: false }), this.sendChangeEmailEmail)
|
||||
app
|
||||
.route("/auth/email/send")
|
||||
.post(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.sendChangeEmailEmail)
|
||||
)
|
||||
|
||||
// Confirm email address
|
||||
app.route('/auth/email/confirm')
|
||||
.post(this.confirmEmail)
|
||||
app.route("/auth/email/confirm").post(catchAll(this.confirmEmail))
|
||||
|
||||
// Change the logged in users password, leaving user still logged in
|
||||
app.route('/auth/password/change')
|
||||
.post(passport.authenticate('bearer', { session: false }), this.changePassword)
|
||||
app
|
||||
.route("/auth/password/change")
|
||||
.post(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.changePassword)
|
||||
)
|
||||
|
||||
// Send a password reset email
|
||||
app.route('/auth/password/send')
|
||||
.post(this.sendPasswordResetEmail)
|
||||
app.route("/auth/password/send").post(catchAll(this.sendPasswordResetEmail))
|
||||
|
||||
// Confirm a password reset token is valid
|
||||
app.route('/auth/password/confirm')
|
||||
.post(this.confirmPasswordToken)
|
||||
app
|
||||
.route("/auth/password/confirm")
|
||||
.post(catchAll(this.confirmPasswordToken))
|
||||
|
||||
// Finish a password reset
|
||||
app.route('/auth/password/reset')
|
||||
.post(this.resetPassword)
|
||||
app.route("/auth/password/reset").post(catchAll(this.resetPassword))
|
||||
|
||||
// Indicate who the currently logged in user is
|
||||
app.route('/auth/who')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.whoAmI)
|
||||
app
|
||||
.route("/auth/who")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.whoAmI)
|
||||
)
|
||||
}
|
||||
|
||||
async login(req, res, next) {
|
||||
@@ -61,62 +78,53 @@ export class AuthRoutes {
|
||||
const password = req.body.password
|
||||
let User = this.db.User
|
||||
|
||||
try {
|
||||
if (!email || !password) {
|
||||
createError.BadRequest('Must supply user name and password')
|
||||
}
|
||||
if (!email || !password) {
|
||||
createError.BadRequest("Must supply user name and password")
|
||||
}
|
||||
|
||||
// Lookup the user
|
||||
const user = await User.findOne({ email })
|
||||
// Lookup the user
|
||||
const user = await User.findOne({ email })
|
||||
|
||||
if (!user) {
|
||||
// NOTE: Don't return NotFound as that gives too much information away to hackers
|
||||
throw createError.BadRequest("Email or password incorrect")
|
||||
} else if (user.emailToken || !user.passwordHash) {
|
||||
throw createError.Forbidden("Must confirm email and set password")
|
||||
}
|
||||
if (!user) {
|
||||
// NOTE: Don't return NotFound as that gives too much information away to hackers
|
||||
throw createError.BadRequest("Email or password incorrect")
|
||||
} else if (user.emailToken || !user.passwordHash) {
|
||||
throw createError.Forbidden("Must confirm email and set password")
|
||||
}
|
||||
|
||||
let cr = credential()
|
||||
const isValid = await cr.verify(JSON.stringify(user.passwordHash), req.body.password)
|
||||
let cr = credential()
|
||||
const isValid = await cr.verify(
|
||||
JSON.stringify(user.passwordHash),
|
||||
req.body.password
|
||||
)
|
||||
|
||||
if (isValid) {
|
||||
user.loginToken = loginToken.pack(user._id.toString(), user.email)
|
||||
} else {
|
||||
user.loginToken = null // A bad login removes existing token for this user...
|
||||
}
|
||||
if (isValid) {
|
||||
user.loginToken = loginToken.pack(user._id.toString(), user.email)
|
||||
} else {
|
||||
user.loginToken = null // A bad login removes existing token for this user...
|
||||
}
|
||||
|
||||
const savedUser = await user.save()
|
||||
const savedUser = await user.save()
|
||||
|
||||
if (savedUser.loginToken) {
|
||||
res.set('Authorization', `Bearer ${savedUser.loginToken}`)
|
||||
res.json(savedUser.toClient())
|
||||
} else {
|
||||
throw createError.BadRequest('email or password incorrect')
|
||||
}
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (savedUser.loginToken) {
|
||||
res.set("Authorization", `Bearer ${savedUser.loginToken}`)
|
||||
res.json(savedUser.toClient())
|
||||
} else {
|
||||
throw createError.BadRequest("email or password incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
async logout(req, res, next) {
|
||||
let User = this.db.User
|
||||
|
||||
try {
|
||||
const user = await User.findById({ _id: req.user._id })
|
||||
const user = await User.findById({ _id: req.user._id })
|
||||
|
||||
if (user) {
|
||||
user.loginToken = null
|
||||
await user.save()
|
||||
}
|
||||
|
||||
res.json({})
|
||||
} catch(err) {
|
||||
next(createError.InternalServerError(err.message))
|
||||
if (user) {
|
||||
user.loginToken = null
|
||||
await user.save()
|
||||
}
|
||||
|
||||
res.json({})
|
||||
}
|
||||
|
||||
whoAmI(req, res, next) {
|
||||
@@ -129,175 +137,163 @@ export class AuthRoutes {
|
||||
let User = this.db.User
|
||||
const isAdmin = !!req.user.administrator
|
||||
|
||||
try {
|
||||
if (existingEmail) {
|
||||
if (!isAdmin) {
|
||||
throw createError.Forbidden('Only admins can resend change email to any user')
|
||||
}
|
||||
} else {
|
||||
existingEmail = req.user.email
|
||||
if (existingEmail) {
|
||||
if (!isAdmin) {
|
||||
throw createError.Forbidden(
|
||||
"Only admins can resend change email to any user"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
existingEmail = req.user.email
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email: existingEmail })
|
||||
let conflictingUser = null
|
||||
const user = await User.findOne({ email: existingEmail })
|
||||
let conflictingUser = null
|
||||
|
||||
if (newEmail) {
|
||||
conflictingUser = await User.findOne({ email: newEmail })
|
||||
}
|
||||
if (newEmail) {
|
||||
conflictingUser = await User.findOne({ email: newEmail })
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw createError.NotFound(`User with email '${existingEmail}' was not found`)
|
||||
} else if (conflictingUser) {
|
||||
throw createError.BadRequest(`A user with '${newEmail}' already exists`)
|
||||
} else if (!isAdmin && user.emailToken && (new Date() - user.emailToken.created) < this.sendEmailDelayInSeconds) {
|
||||
throw createError.BadRequest('Cannot request email confirmation again so soon')
|
||||
}
|
||||
if (!user) {
|
||||
throw createError.NotFound(
|
||||
`User with email '${existingEmail}' was not found`
|
||||
)
|
||||
} else if (conflictingUser) {
|
||||
throw createError.BadRequest(`A user with '${newEmail}' already exists`)
|
||||
} else if (
|
||||
!isAdmin &&
|
||||
user.emailToken &&
|
||||
new Date() - user.emailToken.created < this.sendEmailDelayInSeconds
|
||||
) {
|
||||
throw createError.BadRequest(
|
||||
"Cannot request email confirmation again so soon"
|
||||
)
|
||||
}
|
||||
|
||||
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||
|
||||
user.emailToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date()
|
||||
}
|
||||
user.emailToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date(),
|
||||
}
|
||||
|
||||
if (newEmail) {
|
||||
user.newEmail = newEmail
|
||||
}
|
||||
if (newEmail) {
|
||||
user.newEmail = newEmail
|
||||
}
|
||||
|
||||
const savedUser = await user.save()
|
||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||
const siteUrl = url.parse(req.headers.referer)
|
||||
let msgs = []
|
||||
|
||||
if (savedUser.newEmail) {
|
||||
msgs.push({
|
||||
toEmail: savedUser.email,
|
||||
templateName: 'changeEmailOld',
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
recipientNewEmail: savedUser.newEmail,
|
||||
supportEmail: this.supportEmail
|
||||
}
|
||||
})
|
||||
}
|
||||
const savedUser = await user.save()
|
||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||
const siteUrl = url.parse(req.headers.referer)
|
||||
let msgs = []
|
||||
|
||||
if (savedUser.newEmail) {
|
||||
msgs.push({
|
||||
toEmail: savedUser.newEmail || savedUser.email,
|
||||
templateName: 'changeEmailNew',
|
||||
toEmail: savedUser.email,
|
||||
templateName: "changeEmailOld",
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
confirmEmailLink: `${siteUrl.protocol}//${siteUrl.host}/confirm-email?email-token%3D${savedUser.emailToken.value}`,
|
||||
supportEmail: this.supportEmail
|
||||
}
|
||||
recipientNewEmail: savedUser.newEmail,
|
||||
supportEmail: this.supportEmail,
|
||||
},
|
||||
})
|
||||
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request('dar-email', 'sendEmail', msgs)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
|
||||
msgs.push({
|
||||
toEmail: savedUser.newEmail || savedUser.email,
|
||||
templateName: "changeEmailNew",
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
confirmEmailLink: `${siteUrl.protocol}//${
|
||||
siteUrl.host
|
||||
}/confirm-email?email-token%3D${savedUser.emailToken.value}`,
|
||||
supportEmail: this.supportEmail,
|
||||
},
|
||||
})
|
||||
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request("dar-email", "sendEmail", msgs)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
}
|
||||
|
||||
async confirmEmail(req, res, next) {
|
||||
const token = req.body.emailToken
|
||||
let User = this.db.User
|
||||
|
||||
try {
|
||||
if (!token) {
|
||||
throw createError.BadRequest('Invalid request parameters')
|
||||
}
|
||||
if (!token) {
|
||||
throw createError.BadRequest("Invalid request parameters")
|
||||
}
|
||||
|
||||
const user = await User.findOne({ 'emailToken.value': token })
|
||||
const user = await User.findOne({ "emailToken.value": token })
|
||||
|
||||
if (!user) {
|
||||
throw createError.BadRequest(`The token was not found`)
|
||||
}
|
||||
if (!user) {
|
||||
throw createError.BadRequest(`The token was not found`)
|
||||
}
|
||||
|
||||
// Token must not be too old
|
||||
const ageInHours = (new Date() - user.emailToken.created) / 3600000
|
||||
// Token must not be too old
|
||||
const ageInHours = (new Date() - user.emailToken.created) / 3600000
|
||||
|
||||
if (ageInHours > this.maxEmailTokenAgeInHours) {
|
||||
throw createError.BadRequest(`Token has expired`)
|
||||
}
|
||||
if (ageInHours > this.maxEmailTokenAgeInHours) {
|
||||
throw createError.BadRequest(`Token has expired`)
|
||||
}
|
||||
|
||||
// Remove the email token & any login token as it will become invalid
|
||||
user.emailToken = undefined
|
||||
user.loginToken = undefined
|
||||
// Remove the email token & any login token as it will become invalid
|
||||
user.emailToken = undefined
|
||||
user.loginToken = undefined
|
||||
|
||||
// Switch in any new email now
|
||||
if (user.newEmail) {
|
||||
user.email = user.newEmail
|
||||
user.newEmail = undefined
|
||||
}
|
||||
// Switch in any new email now
|
||||
if (user.newEmail) {
|
||||
user.email = user.newEmail
|
||||
user.newEmail = undefined
|
||||
}
|
||||
|
||||
let buf = null
|
||||
let buf = null
|
||||
|
||||
// If user has no password, create reset token for them
|
||||
if (!user.passwordHash) {
|
||||
buf = await util.promisify(crypto.randomBytes)(32)
|
||||
// If user has no password, create reset token for them
|
||||
if (!user.passwordHash) {
|
||||
buf = await util.promisify(crypto.randomBytes)(32)
|
||||
|
||||
user.passwordToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date()
|
||||
}
|
||||
}
|
||||
|
||||
const savedUser = await user.save()
|
||||
let obj = {}
|
||||
|
||||
// Only because the user has sent us a valid email reset token
|
||||
// can we respond with an password reset token.
|
||||
if (savedUser.passwordToken) {
|
||||
obj.passwordToken = savedUser.passwordToken.value
|
||||
}
|
||||
|
||||
res.json(obj)
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
user.passwordToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date(),
|
||||
}
|
||||
}
|
||||
|
||||
const savedUser = await user.save()
|
||||
let obj = {}
|
||||
|
||||
// Only because the user has sent us a valid email reset token
|
||||
// can we respond with an password reset token.
|
||||
if (savedUser.passwordToken) {
|
||||
obj.passwordToken = savedUser.passwordToken.value
|
||||
}
|
||||
|
||||
res.json(obj)
|
||||
}
|
||||
|
||||
async confirmPasswordToken(req, res, next) {
|
||||
const token = req.body.passwordToken
|
||||
let User = this.db.User
|
||||
|
||||
try {
|
||||
if (!token) {
|
||||
throw createError.BadRequest('Invalid request parameters')
|
||||
}
|
||||
|
||||
const user = await User.findOne({ 'passwordToken.value': token })
|
||||
|
||||
if (!user) {
|
||||
throw createError.BadRequest(`The token was not found`)
|
||||
}
|
||||
|
||||
// Token must not be too old
|
||||
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
|
||||
|
||||
if (ageInHours > this.maxPasswordTokenAgeInHours) {
|
||||
throw createError.BadRequest(`Token has expired`)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!token) {
|
||||
throw createError.BadRequest("Invalid request parameters")
|
||||
}
|
||||
|
||||
const user = await User.findOne({ "passwordToken.value": token })
|
||||
|
||||
if (!user) {
|
||||
throw createError.BadRequest(`The token was not found`)
|
||||
}
|
||||
|
||||
// Token must not be too old
|
||||
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
|
||||
|
||||
if (ageInHours > this.maxPasswordTokenAgeInHours) {
|
||||
throw createError.BadRequest(`Token has expired`)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
}
|
||||
|
||||
async resetPassword(req, res, next) {
|
||||
@@ -306,118 +302,102 @@ export class AuthRoutes {
|
||||
let User = this.db.User
|
||||
let cr = credential()
|
||||
|
||||
try {
|
||||
if (!token || !newPassword) {
|
||||
throw createError.BadRequest('Invalid request parameters')
|
||||
}
|
||||
|
||||
const user = await User.findOne({ 'passwordToken.value': token })
|
||||
|
||||
if (!user) {
|
||||
throw createError.BadRequest(`The token was not found`)
|
||||
}
|
||||
|
||||
// Token must not be too old
|
||||
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
|
||||
|
||||
if (ageInHours > this.maxPasswordTokenAgeInHours) {
|
||||
throw createError.BadRequest(`Token has expired`)
|
||||
}
|
||||
|
||||
// Remove the password token & any login token
|
||||
user.passwordToken = undefined
|
||||
user.loginToken = undefined
|
||||
|
||||
const json = await cr.hash(newPassword)
|
||||
|
||||
user.passwordHash = JSON.parse(json)
|
||||
await user.save()
|
||||
res.json({})
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!token || !newPassword) {
|
||||
throw createError.BadRequest("Invalid request parameters")
|
||||
}
|
||||
|
||||
const user = await User.findOne({ "passwordToken.value": token })
|
||||
|
||||
if (!user) {
|
||||
throw createError.BadRequest(`The token was not found`)
|
||||
}
|
||||
|
||||
// Token must not be too old
|
||||
const ageInHours = (new Date() - user.passwordToken.created) / (3600 * 1000)
|
||||
|
||||
if (ageInHours > this.maxPasswordTokenAgeInHours) {
|
||||
throw createError.BadRequest(`Token has expired`)
|
||||
}
|
||||
|
||||
// Remove the password token & any login token
|
||||
user.passwordToken = undefined
|
||||
user.loginToken = undefined
|
||||
|
||||
const json = await cr.hash(newPassword)
|
||||
|
||||
user.passwordHash = JSON.parse(json)
|
||||
await user.save()
|
||||
res.json({})
|
||||
}
|
||||
|
||||
async changePassword(req, res, next) {
|
||||
let User = this.db.User
|
||||
let cr = credential()
|
||||
|
||||
try {
|
||||
const user = await User.findById({ _id: req.user._id })
|
||||
const user = await User.findById({ _id: req.user._id })
|
||||
|
||||
if (!user) {
|
||||
throw createError.NotFound(`User ${req.user._id} not found`)
|
||||
}
|
||||
|
||||
const ok = await cr.verify(JSON.stringify(user.passwordHash), req.body.oldPassword)
|
||||
const obj = await cr.hash(req.body.newPassword)
|
||||
|
||||
user.passwordHash = JSON.parse(obj)
|
||||
await user.save()
|
||||
res.json({})
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!user) {
|
||||
throw createError.NotFound(`User ${req.user._id} not found`)
|
||||
}
|
||||
|
||||
const ok = await cr.verify(
|
||||
JSON.stringify(user.passwordHash),
|
||||
req.body.oldPassword
|
||||
)
|
||||
const obj = await cr.hash(req.body.newPassword)
|
||||
|
||||
user.passwordHash = JSON.parse(obj)
|
||||
await user.save()
|
||||
res.json({})
|
||||
}
|
||||
|
||||
async sendPasswordResetEmail(req, res, next){
|
||||
async sendPasswordResetEmail(req, res, next) {
|
||||
const email = req.body.email
|
||||
let User = this.db.User
|
||||
|
||||
try {
|
||||
if (!email) {
|
||||
throw createError.BadRequest('Invalid request parameters')
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email })
|
||||
|
||||
// User must exist and their email must be confirmed
|
||||
if (!user || user.emailToken) {
|
||||
// Don't give away any information about why we rejected the request
|
||||
throw createError.BadRequest('Not a valid request')
|
||||
} else if (user.passwordToken && user.passwordToken.created &&
|
||||
(new Date() - user.passwordToken.created) < this.sendEmailDelayInSeconds) {
|
||||
throw createError.BadRequest('Cannot request password reset so soon')
|
||||
}
|
||||
|
||||
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||
|
||||
user.passwordToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date()
|
||||
}
|
||||
|
||||
const savedUser = await user.save()
|
||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||
const siteUrl = url.parse(req.headers.referer)
|
||||
const msg = {
|
||||
toEmail: savedUser.email,
|
||||
templateName: 'forgotPassword',
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
resetPasswordLink: `${siteUrl.protocol}//${siteUrl.host}/reset-password?password-token%3D${savedUser.passwordToken.value}`,
|
||||
supportEmail: this.supportEmail
|
||||
}
|
||||
}
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request('dar-email', 'sendEmail', msg)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(`Unable to send password reset email. ${err.message}`))
|
||||
}
|
||||
if (!email) {
|
||||
throw createError.BadRequest("Invalid request parameters")
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email })
|
||||
|
||||
// User must exist and their email must be confirmed
|
||||
if (!user || user.emailToken) {
|
||||
// Don't give away any information about why we rejected the request
|
||||
throw createError.BadRequest("Not a valid request")
|
||||
} else if (
|
||||
user.passwordToken &&
|
||||
user.passwordToken.created &&
|
||||
new Date() - user.passwordToken.created < this.sendEmailDelayInSeconds
|
||||
) {
|
||||
throw createError.BadRequest("Cannot request password reset so soon")
|
||||
}
|
||||
|
||||
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||
|
||||
user.passwordToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date(),
|
||||
}
|
||||
|
||||
const savedUser = await user.save()
|
||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||
const siteUrl = url.parse(req.headers.referer)
|
||||
const msg = {
|
||||
toEmail: savedUser.email,
|
||||
templateName: "forgotPassword",
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
resetPasswordLink: `${siteUrl.protocol}//${
|
||||
siteUrl.host
|
||||
}/reset-password?password-token%3D${savedUser.passwordToken.value}`,
|
||||
supportEmail: this.supportEmail,
|
||||
},
|
||||
}
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request("dar-email", "sendEmail", msg)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import passport from 'passport'
|
||||
import createError from 'http-errors'
|
||||
import autobind from 'autobind-decorator'
|
||||
import passport from "passport"
|
||||
import createError from "http-errors"
|
||||
import autobind from "autobind-decorator"
|
||||
import { catchAll } from "."
|
||||
|
||||
@autobind
|
||||
export class TeamRoutes {
|
||||
@@ -12,14 +13,31 @@ export class TeamRoutes {
|
||||
this.mq = container.mq
|
||||
this.ws = container.ws
|
||||
|
||||
app.route('/teams')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.listTeams)
|
||||
.post(passport.authenticate('bearer', { session: false }), this.createTeam)
|
||||
.put(passport.authenticate('bearer', { session: false }), this.updateTeam)
|
||||
app
|
||||
.route("/teams")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.listTeams)
|
||||
)
|
||||
.post(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.createTeam)
|
||||
)
|
||||
.put(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.updateTeam)
|
||||
)
|
||||
|
||||
app.route('/teams/:_id([a-f0-9]{24})')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.getTeam)
|
||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteTeam)
|
||||
app
|
||||
.route("/teams/:_id([a-f0-9]{24})")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.getTeam)
|
||||
)
|
||||
.delete(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.deleteTeam)
|
||||
)
|
||||
}
|
||||
|
||||
async listTeams(req, res, next) {
|
||||
@@ -29,139 +47,103 @@ export class TeamRoutes {
|
||||
let partial = !!req.query.partial
|
||||
let query = {}
|
||||
|
||||
try {
|
||||
const total = await Team.count({})
|
||||
let teams = []
|
||||
let cursor = Team.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||
const total = await Team.count({})
|
||||
let teams = []
|
||||
let cursor = Team.find(query)
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
.cursor()
|
||||
.map((doc) => {
|
||||
return doc.toClient(partial)
|
||||
})
|
||||
|
||||
cursor.on('data', (doc) => {
|
||||
teams.push(doc)
|
||||
cursor.on("data", (doc) => {
|
||||
teams.push(doc)
|
||||
})
|
||||
cursor.on("end", () => {
|
||||
res.json({
|
||||
total: total,
|
||||
offset: skip,
|
||||
count: teams.length,
|
||||
items: teams,
|
||||
})
|
||||
cursor.on('end', () => {
|
||||
res.json({
|
||||
total: total,
|
||||
offset: skip,
|
||||
count: teams.length,
|
||||
items: teams
|
||||
})
|
||||
})
|
||||
cursor.on('error', (err) => {
|
||||
throw err
|
||||
})
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
})
|
||||
cursor.on("error", (err) => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
async createTeam(req, res, next) {
|
||||
try {
|
||||
if (!req.user.administrator) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
// Create a new Team template then assign it to a value in the req.body
|
||||
const Team = this.db.Team
|
||||
let team = new Team(req.body)
|
||||
|
||||
const newTeam = await team.save()
|
||||
|
||||
res.json(newTeam.toClient())
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!req.user.administrator) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
// Create a new Team template then assign it to a value in the req.body
|
||||
const Team = this.db.Team
|
||||
let team = new Team(req.body)
|
||||
|
||||
const newTeam = await team.save()
|
||||
|
||||
res.json(newTeam.toClient())
|
||||
}
|
||||
|
||||
async updateTeam(req, res, next) {
|
||||
try {
|
||||
if (!req.user.administrator) {
|
||||
throw 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 Team = this.db.Team
|
||||
let teamUpdates = null
|
||||
|
||||
try {
|
||||
teamUpdates = new Team(req.body)
|
||||
} catch (err) {
|
||||
throw createError.BadRequest('Invalid data')
|
||||
}
|
||||
|
||||
const foundTeam = await Team.findById(teamUpdates._id)
|
||||
|
||||
if (!foundTeam) {
|
||||
throw createError.NotFound(`Team with _id ${_id} was not found`)
|
||||
}
|
||||
foundTeam.merge(teamUpdates)
|
||||
const savedTeam = await foundTeam.save()
|
||||
|
||||
res.json(savedTeam.toClient())
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!req.user.administrator) {
|
||||
throw 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 Team = this.db.Team
|
||||
let teamUpdates = null
|
||||
|
||||
try {
|
||||
teamUpdates = new Team(req.body)
|
||||
} catch (err) {
|
||||
throw createError.BadRequest("Invalid data")
|
||||
}
|
||||
|
||||
const foundTeam = await Team.findById(teamUpdates._id)
|
||||
|
||||
if (!foundTeam) {
|
||||
throw createError.NotFound(`Team with _id ${_id} was not found`)
|
||||
}
|
||||
foundTeam.merge(teamUpdates)
|
||||
const savedTeam = await foundTeam.save()
|
||||
|
||||
res.json(savedTeam.toClient())
|
||||
}
|
||||
|
||||
async getTeam(req, res, next) {
|
||||
const Team = this.db.Team
|
||||
const _id = req.params._id
|
||||
|
||||
try {
|
||||
const team = await Team.findById(_id)
|
||||
const team = await Team.findById(_id)
|
||||
|
||||
if (!team) {
|
||||
throw createError.NotFound(`Team with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json(team.toClient())
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!team) {
|
||||
throw createError.NotFound(`Team with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json(team.toClient())
|
||||
}
|
||||
|
||||
async deleteTeam(req, res, next) {
|
||||
try {
|
||||
if (!req.user.administrator) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const Team = this.db.Team
|
||||
const _id = req.params._id
|
||||
|
||||
const removedTeam = await Team.remove({ _id })
|
||||
|
||||
if (!removedTeam) {
|
||||
throw createError.NotFound(`Team with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!req.user.administrator) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const Team = this.db.Team
|
||||
const _id = req.params._id
|
||||
|
||||
const removedTeam = await Team.remove({ _id })
|
||||
|
||||
if (!removedTeam) {
|
||||
throw createError.NotFound(`Team with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import passport from 'passport'
|
||||
import createError from 'http-errors'
|
||||
import crypto from 'crypto'
|
||||
import urlSafeBase64 from 'urlsafe-base64'
|
||||
import url from 'url'
|
||||
import util from 'util'
|
||||
import autobind from 'autobind-decorator'
|
||||
import config from 'config'
|
||||
import passport from "passport"
|
||||
import createError from "http-errors"
|
||||
import crypto from "crypto"
|
||||
import urlSafeBase64 from "urlsafe-base64"
|
||||
import url from "url"
|
||||
import util from "util"
|
||||
import autobind from "autobind-decorator"
|
||||
import config from "config"
|
||||
import { catchAll } from "."
|
||||
|
||||
@autobind
|
||||
export class UserRoutes {
|
||||
@@ -16,23 +17,48 @@ export class UserRoutes {
|
||||
this.db = container.db
|
||||
this.mq = container.mq
|
||||
this.ws = container.ws
|
||||
this.maxEmailTokenAgeInHours = config.get('email.maxEmailTokenAgeInHours')
|
||||
this.sendEmail = config.get('email.sendEmail')
|
||||
app.route('/users')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.listUsers)
|
||||
this.maxEmailTokenAgeInHours = config.get("email.maxEmailTokenAgeInHours")
|
||||
this.sendEmail = config.get("email.sendEmail")
|
||||
app
|
||||
.route("/users")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.listUsers)
|
||||
)
|
||||
// Add a new user, send email confirmation email
|
||||
.post(passport.authenticate('bearer', { session: false }), this.createUser)
|
||||
.put(passport.authenticate('bearer', { session: false }), this.updateUser)
|
||||
.post(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.createUser)
|
||||
)
|
||||
.put(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.updateUser)
|
||||
)
|
||||
|
||||
app.route('/users/:_id([a-f0-9]{24})')
|
||||
.get(passport.authenticate('bearer', { session: false }), this.getUser)
|
||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteUser)
|
||||
app
|
||||
.route("/users/:_id([a-f0-9]{24})")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.getUser)
|
||||
)
|
||||
.delete(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.deleteUser)
|
||||
)
|
||||
|
||||
app.route('/users/enter-room/:roomName')
|
||||
.put(passport.authenticate('bearer', { session: false }), this.enterRoom)
|
||||
app
|
||||
.route("/users/enter-room/:roomName")
|
||||
.put(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.enterRoom)
|
||||
)
|
||||
|
||||
app.route('/users/leave-room')
|
||||
.put(passport.authenticate('bearer', { session: false }), this.leaveRoom)
|
||||
app
|
||||
.route("/users/leave-room")
|
||||
.put(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.leaveRoom)
|
||||
)
|
||||
}
|
||||
|
||||
async listUsers(req, res, next) {
|
||||
@@ -44,169 +70,145 @@ export class UserRoutes {
|
||||
const team = req.query.team
|
||||
let query = {}
|
||||
|
||||
try {
|
||||
if (team) {
|
||||
query = { team }
|
||||
}
|
||||
if (team) {
|
||||
query = { team }
|
||||
}
|
||||
|
||||
if (!isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
if (!isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const total = await User.count({})
|
||||
let users = []
|
||||
let cursor = User.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||
const total = await User.count({})
|
||||
let users = []
|
||||
let cursor = User.find(query)
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
.cursor()
|
||||
.map((doc) => {
|
||||
return doc.toClient(req.user)
|
||||
})
|
||||
|
||||
cursor.on('data', (doc) => {
|
||||
users.push(doc)
|
||||
cursor.on("data", (doc) => {
|
||||
users.push(doc)
|
||||
})
|
||||
cursor.on("end", () => {
|
||||
res.json({
|
||||
total: total,
|
||||
offset: skip,
|
||||
count: users.length,
|
||||
items: users,
|
||||
})
|
||||
cursor.on('end', () => {
|
||||
res.json({
|
||||
total: total,
|
||||
offset: skip,
|
||||
count: users.length,
|
||||
items: users
|
||||
})
|
||||
})
|
||||
cursor.on('error', (err) => {
|
||||
throw err
|
||||
})
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
})
|
||||
cursor.on("error", (err) => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
async getUser(req, res, next) {
|
||||
let User = this.db.User
|
||||
const _id = req.params._id
|
||||
const isSelf = (_id === req.user._id)
|
||||
const isSelf = _id === req.user._id
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
try {
|
||||
// User can see themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const user = await User.findById(_id)
|
||||
|
||||
if (!user) {
|
||||
return Promise.reject(createError.NotFound(`User with _id ${_id} was not found`))
|
||||
}
|
||||
|
||||
res.json(user.toClient(req.user))
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
// User can see themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const user = await User.findById(_id)
|
||||
|
||||
if (!user) {
|
||||
return Promise.reject(
|
||||
createError.NotFound(`User with _id ${_id} was not found`)
|
||||
)
|
||||
}
|
||||
|
||||
res.json(user.toClient(req.user))
|
||||
}
|
||||
|
||||
async createUser(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
try {
|
||||
if (!isAdmin) {
|
||||
throw new createError.Forbidden()
|
||||
}
|
||||
if (!isAdmin) {
|
||||
throw new createError.Forbidden()
|
||||
}
|
||||
|
||||
let User = this.db.User
|
||||
let user = new User(req.body)
|
||||
let User = this.db.User
|
||||
let user = new User(req.body)
|
||||
|
||||
// Add email confirmation required token
|
||||
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||
// Add email confirmation required token
|
||||
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||
|
||||
user.emailToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date()
|
||||
}
|
||||
const savedUser = await user.save()
|
||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
|
||||
const siteUrl = url.parse(req.headers.referer)
|
||||
const msg = {
|
||||
toEmail: savedUser.email,
|
||||
templateName: 'welcome',
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
senderFullName: senderFullName,
|
||||
confirmEmailLink: `${siteUrl.protocol}//${siteUrl.host}/confirm-email?email-token%3D${savedUser.emailToken.value}`,
|
||||
confirmEmailLinkExpirationHours: this.maxEmailTokenAgeInHours,
|
||||
supportEmail: this.supportEmail
|
||||
}
|
||||
}
|
||||
user.emailToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date(),
|
||||
}
|
||||
const savedUser = await user.save()
|
||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
|
||||
const siteUrl = url.parse(req.headers.referer)
|
||||
const msg = {
|
||||
toEmail: savedUser.email,
|
||||
templateName: "welcome",
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
senderFullName: senderFullName,
|
||||
confirmEmailLink: `${siteUrl.protocol}//${
|
||||
siteUrl.host
|
||||
}/confirm-email?email-token%3D${savedUser.emailToken.value}`,
|
||||
confirmEmailLinkExpirationHours: this.maxEmailTokenAgeInHours,
|
||||
supportEmail: this.supportEmail,
|
||||
},
|
||||
}
|
||||
|
||||
res.json(savedUser.toClient())
|
||||
res.json(savedUser.toClient())
|
||||
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request('dar-email', 'sendEmail', msg)
|
||||
}
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request("dar-email", "sendEmail", msg)
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
try {
|
||||
// Do this here because Mongoose will add it automatically otherwise
|
||||
if (!req.body._id) {
|
||||
throw createError.BadRequest('No user _id given in body')
|
||||
}
|
||||
|
||||
const isSelf = (req.body._id === req.user._id.toString())
|
||||
|
||||
// User can change themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const User = this.db.User
|
||||
let userUpdates = null
|
||||
|
||||
try {
|
||||
userUpdates = new User(req.body)
|
||||
} catch (err) {
|
||||
throw createError.BadRequest('Invalid data')
|
||||
}
|
||||
|
||||
if (isSelf && !isAdmin) {
|
||||
throw createError.BadRequest('Cannot modify own administrator level')
|
||||
}
|
||||
|
||||
const foundUser = await User.findById(userUpdates._id)
|
||||
|
||||
if (!foundUser) {
|
||||
throw createError.NotFound(`User with _id ${user._id} was not found`)
|
||||
}
|
||||
|
||||
// We don't allow direct updates to the email field so remove it if present
|
||||
delete userUpdates.email
|
||||
|
||||
foundUser.merge(userUpdates)
|
||||
const savedUser = await foundUser.save()
|
||||
|
||||
res.json(savedUser.toClient(req.user))
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
// Do this here because Mongoose will add it automatically otherwise
|
||||
if (!req.body._id) {
|
||||
throw createError.BadRequest("No user _id given in body")
|
||||
}
|
||||
|
||||
const isSelf = req.body._id === req.user._id.toString()
|
||||
|
||||
// User can change themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const User = this.db.User
|
||||
let userUpdates = null
|
||||
|
||||
try {
|
||||
userUpdates = new User(req.body)
|
||||
} catch (err) {
|
||||
throw createError.BadRequest("Invalid data")
|
||||
}
|
||||
|
||||
if (isSelf && !isAdmin) {
|
||||
throw createError.BadRequest("Cannot modify own administrator level")
|
||||
}
|
||||
|
||||
const foundUser = await User.findById(userUpdates._id)
|
||||
|
||||
if (!foundUser) {
|
||||
throw createError.NotFound(`User with _id ${user._id} was not found`)
|
||||
}
|
||||
|
||||
// We don't allow direct updates to the email field so remove it if present
|
||||
delete userUpdates.email
|
||||
|
||||
foundUser.merge(userUpdates)
|
||||
const savedUser = await foundUser.save()
|
||||
|
||||
res.json(savedUser.toClient(req.user))
|
||||
}
|
||||
|
||||
enterRoom(req, res, next) {
|
||||
@@ -222,41 +224,33 @@ export class UserRoutes {
|
||||
async deleteUser(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
try {
|
||||
if (!isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
if (!isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
let User = this.db.User
|
||||
const _id = req.params._id
|
||||
const deletedUser = await User.remove({ _id })
|
||||
let User = this.db.User
|
||||
const _id = req.params._id
|
||||
const deletedUser = await User.remove({ _id })
|
||||
|
||||
if (!deletedUser) {
|
||||
throw createError.NotFound(`User with _id ${_id} was not found`)
|
||||
}
|
||||
if (!deletedUser) {
|
||||
throw createError.NotFound(`User with _id ${_id} was not found`)
|
||||
}
|
||||
|
||||
const userFullName = `${deletedUser.firstName} ${deletedUser.lastName}`
|
||||
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
|
||||
const msg = {
|
||||
toEmail: deletedUser.newEmail,
|
||||
templateName: 'accountDeleted',
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
senderFullName: senderFullName,
|
||||
supportEmail: this.supportEmail
|
||||
}
|
||||
}
|
||||
res.json({})
|
||||
const userFullName = `${deletedUser.firstName} ${deletedUser.lastName}`
|
||||
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
|
||||
const msg = {
|
||||
toEmail: deletedUser.newEmail,
|
||||
templateName: "accountDeleted",
|
||||
templateData: {
|
||||
recipientFullName: userFullName,
|
||||
senderFullName: senderFullName,
|
||||
supportEmail: this.supportEmail,
|
||||
},
|
||||
}
|
||||
res.json({})
|
||||
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request('dar-email', 'sendEmail', msg)
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request("dar-email", "sendEmail", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import passport from "passport"
|
||||
import createError from "http-errors"
|
||||
import autobind from "autobind-decorator"
|
||||
import { catchAll } from "."
|
||||
|
||||
@autobind
|
||||
export class WorkItemRoutes {
|
||||
@@ -16,183 +17,176 @@ export class WorkItemRoutes {
|
||||
.route("/workitems")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
this.listWorkItems
|
||||
catchAll(this.listWorkItems)
|
||||
)
|
||||
.post(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
this.createWorkItem
|
||||
catchAll(this.createWorkItem)
|
||||
)
|
||||
.put(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
this.updateWorkItem
|
||||
catchAll(this.updateWorkItem)
|
||||
)
|
||||
|
||||
app
|
||||
.route("/workitems/activities")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
catchAll(this.listWorkItemActivities)
|
||||
)
|
||||
|
||||
app
|
||||
.route("/workitems/:_id([a-f0-9]{24})")
|
||||
.get(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
this.getWorkItem
|
||||
catchAll(this.getWorkItem)
|
||||
)
|
||||
.delete(
|
||||
passport.authenticate("bearer", { session: false }),
|
||||
this.deleteWorkItem
|
||||
catchAll(this.deleteWorkItem)
|
||||
)
|
||||
}
|
||||
|
||||
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 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({})
|
||||
const total = await WorkItem.count({})
|
||||
|
||||
let workItems = []
|
||||
let cursor = WorkItem.find(query)
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
.cursor()
|
||||
.map((doc) => {
|
||||
return doc.toClient(partial)
|
||||
})
|
||||
let workItems = []
|
||||
let cursor = WorkItem.find(query)
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
.cursor()
|
||||
.map((doc) => {
|
||||
return doc.toClient(partial)
|
||||
})
|
||||
|
||||
cursor.on("data", (doc) => {
|
||||
workItems.push(doc)
|
||||
cursor.on("data", (doc) => {
|
||||
workItems.push(doc)
|
||||
})
|
||||
cursor.on("end", () => {
|
||||
res.json({
|
||||
total: total,
|
||||
offset: skip,
|
||||
count: workItems.length,
|
||||
items: workItems,
|
||||
})
|
||||
cursor.on("end", () => {
|
||||
res.json({
|
||||
total: total,
|
||||
offset: skip,
|
||||
count: workItems.length,
|
||||
items: workItems,
|
||||
})
|
||||
})
|
||||
cursor.on("error", (err) => {
|
||||
throw createError.InternalServerError(err.message)
|
||||
})
|
||||
}
|
||||
|
||||
async listWorkItemActivities(req, res, next) {
|
||||
const WorkItem = this.db.WorkItem
|
||||
const aggregate = WorkItem.aggregate()
|
||||
.sort({ ticketNumber: 1 })
|
||||
.lookup({
|
||||
from: "activities",
|
||||
localField: "_id",
|
||||
foreignField: "workItem",
|
||||
as: "data",
|
||||
})
|
||||
cursor.on("error", (err) => {
|
||||
throw createError.InternalServerError(err.message)
|
||||
.project({
|
||||
workItemType: 1,
|
||||
ticketNumber: 1,
|
||||
address: 1,
|
||||
"coordinate.longitude": { $arrayElemAt: ["$location.coordinates", 0] },
|
||||
"coordinate.latitude": { $arrayElemAt: ["$location.coordinates", 1] },
|
||||
"data._id": 1,
|
||||
"data.resolution": 1,
|
||||
"data.status": 1,
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
|
||||
const items = await aggregate.exec()
|
||||
|
||||
res.json({ items })
|
||||
}
|
||||
|
||||
async createWorkItem(req, res, next) {
|
||||
try {
|
||||
const isAdmin = req.user.administrator
|
||||
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))
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
async updateWorkItem(req, res, next) {
|
||||
try {
|
||||
const isAdmin = req.user.administrator
|
||||
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)
|
||||
|
||||
if (!foundWorkItem) {
|
||||
return next(
|
||||
createError.NotFound(`WorkItem with _id ${_id} was not found`)
|
||||
)
|
||||
}
|
||||
|
||||
foundWorkItem.merge(workItemUpdates)
|
||||
|
||||
const savedWorkItem = await foundWorkItem.save()
|
||||
|
||||
res.json(savedWorkItem.toClient())
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
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)
|
||||
|
||||
if (!foundWorkItem) {
|
||||
return next(
|
||||
createError.NotFound(`WorkItem with _id ${_id} was not found`)
|
||||
)
|
||||
}
|
||||
|
||||
foundWorkItem.merge(workItemUpdates)
|
||||
|
||||
const savedWorkItem = await foundWorkItem.save()
|
||||
|
||||
res.json(savedWorkItem.toClient())
|
||||
}
|
||||
|
||||
async getWorkItem(req, res, next) {
|
||||
try {
|
||||
const WorkItem = this.db.WorkItem
|
||||
const _id = req.params._id
|
||||
const workItem = await WorkItem.findById(_id)
|
||||
const WorkItem = this.db.WorkItem
|
||||
const _id = req.params._id
|
||||
const workItem = await WorkItem.findById(_id)
|
||||
|
||||
if (!workItem) {
|
||||
throw createError.NotFound(`WorkItem with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json(workItem.toClient())
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!workItem) {
|
||||
throw createError.NotFound(`WorkItem with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json(workItem.toClient())
|
||||
}
|
||||
|
||||
async deleteWorkItem(req, res, next) {
|
||||
try {
|
||||
const isAdmin = req.user.administrator
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
if (!isAdmin) {
|
||||
return new createError.Forbidden()
|
||||
}
|
||||
|
||||
const WorkItem = this.db.WorkItem
|
||||
const _id = req.params._id
|
||||
const workItem = await WorkItem.remove({ _id })
|
||||
|
||||
if (!workItem) {
|
||||
throw createError.NotFound(`WorkItem with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
if (!isAdmin) {
|
||||
return new createError.Forbidden()
|
||||
}
|
||||
|
||||
const WorkItem = this.db.WorkItem
|
||||
const _id = req.params._id
|
||||
const workItem = await WorkItem.remove({ _id })
|
||||
|
||||
if (!workItem) {
|
||||
throw createError.NotFound(`WorkItem with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json({})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
export { AuthRoutes } from './AuthRoutes'
|
||||
export { AssetRoutes } from './AssetRoutes'
|
||||
export { UserRoutes } from './UserRoutes'
|
||||
export { WorkItemRoutes } from './WorkItemRoutes'
|
||||
export { ActivityRoutes } from './ActivityRoutes'
|
||||
export { TeamRoutes } from './TeamRoutes'
|
||||
export { AuthRoutes } from "./AuthRoutes"
|
||||
export { AssetRoutes } from "./AssetRoutes"
|
||||
export { UserRoutes } from "./UserRoutes"
|
||||
export { WorkItemRoutes } from "./WorkItemRoutes"
|
||||
export { ActivityRoutes } from "./ActivityRoutes"
|
||||
export { TeamRoutes } from "./TeamRoutes"
|
||||
import createError from "http-errors"
|
||||
|
||||
export function catchAll(routeHandler) {
|
||||
return (req, res, next) => {
|
||||
try {
|
||||
routeHandler(req, res, next)
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,31 +11,24 @@ Grid.mongo = mongoose.mongo
|
||||
@autobind
|
||||
export class DB {
|
||||
constructor() {
|
||||
mongoose.Promise = Promise
|
||||
mongoose.plugin(merge)
|
||||
}
|
||||
|
||||
connect(mongoUri, isProduction) {
|
||||
return mongoose
|
||||
.connect(mongoUri, {
|
||||
useMongoClient: true,
|
||||
config: { autoIndex: !isProduction },
|
||||
})
|
||||
.then((connection) => {
|
||||
this.connection = connection
|
||||
async connect(mongoUri, isProduction) {
|
||||
const connection = await mongoose.createConnection(mongoUri, {
|
||||
promiseLibrary: Promise,
|
||||
autoIndex: !isProduction,
|
||||
})
|
||||
|
||||
this.gridfs = Grid(connection.db)
|
||||
this.gridfs.findOneAsync = util.promisify(this.gridfs.findOne)
|
||||
this.gridfs.removeAsync = util.promisify(this.gridfs.remove)
|
||||
this.gridfs = Grid(connection.db)
|
||||
this.gridfs.findOneAsync = util.promisify(this.gridfs.findOne)
|
||||
this.gridfs.removeAsync = util.promisify(this.gridfs.remove)
|
||||
|
||||
this.User = connection.model("User", Schemas.userSchema)
|
||||
this.WorkItem = connection.model("WorkItem", Schemas.workItemSchema)
|
||||
this.Activity = connection.model("Activity", Schemas.activitySchema)
|
||||
this.Team = connection.model("Team", Schemas.teamSchema)
|
||||
this.Counter = connection.model("Counter", Schemas.counterSchema)
|
||||
|
||||
return Promise.resolve(this)
|
||||
})
|
||||
this.User = connection.model("User", Schemas.userSchema)
|
||||
this.WorkItem = connection.model("WorkItem", Schemas.workItemSchema)
|
||||
this.Activity = connection.model("Activity", Schemas.activitySchema)
|
||||
this.Team = connection.model("Team", Schemas.teamSchema)
|
||||
this.Counter = connection.model("Counter", Schemas.counterSchema)
|
||||
}
|
||||
|
||||
newObjectId(s) {
|
||||
@@ -43,17 +36,16 @@ export class DB {
|
||||
return new mongodb.ObjectID(s).toString()
|
||||
}
|
||||
|
||||
lookupToken(token, done) {
|
||||
this.User.findOne({ loginToken: token })
|
||||
.then((user) => {
|
||||
if (!user) {
|
||||
done(null, false)
|
||||
} else {
|
||||
done(null, user)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
done(err)
|
||||
})
|
||||
async lookupToken(token, done) {
|
||||
try {
|
||||
const user = await this.User.findOne({ loginToken: token })
|
||||
if (!user) {
|
||||
done(null, false)
|
||||
} else {
|
||||
done(null, user)
|
||||
}
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
import { Schema } from 'mongoose'
|
||||
import { Schema } from "mongoose"
|
||||
|
||||
export let activitySchema = new Schema({
|
||||
_id: { type: Schema.Types.ObjectId, required: true, auto: true },
|
||||
resolution: String,
|
||||
workItem: { type: Schema.Types.ObjectId, required: true },
|
||||
status: { type: String, required: true, enum: {
|
||||
values: [ 'planned', 'open', 'onHold', 'closed'],
|
||||
message: 'enum validator failed for path `{PATH}` with value `{VALUE}`'
|
||||
}},
|
||||
notes: String,
|
||||
when: Date,
|
||||
loc: {
|
||||
type: { type: String },
|
||||
coordinates: [Number],
|
||||
export let activitySchema = new Schema(
|
||||
{
|
||||
_id: { type: Schema.Types.ObjectId, required: true, auto: true },
|
||||
resolution: { type: String, required: true },
|
||||
workItem: { type: Schema.Types.ObjectId, required: true },
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: {
|
||||
values: ["planned", "open", "onHold", "closed"],
|
||||
message: "enum validator failed for path `{PATH}` with value `{VALUE}`",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
notes: { type: String, required: true },
|
||||
when: { type: Date, required: true },
|
||||
location: {
|
||||
type: { type: String },
|
||||
coordinates: [Number],
|
||||
},
|
||||
address: String,
|
||||
fromStreetNumber: Number,
|
||||
toStreetNumber: Number,
|
||||
photos: [Schema.Types.ObjectId],
|
||||
},
|
||||
address: String,
|
||||
fromStreetNumber: Number,
|
||||
toStreetNumber: Number,
|
||||
photos: [ Schema.Types.ObjectId ],
|
||||
}, { timestamps: true, id: false })
|
||||
{ timestamps: true, id: false }
|
||||
)
|
||||
|
||||
activitySchema.methods.toClient = function() {
|
||||
return this.toObject()
|
||||
|
||||
@@ -40,6 +40,13 @@ workItemSchema.pre("save", async function(next) {
|
||||
next()
|
||||
})
|
||||
|
||||
workItemSchema.virtual("activities", {
|
||||
ref: "Activity",
|
||||
localField: "_id",
|
||||
foreignField: "workItem",
|
||||
justOne: false,
|
||||
})
|
||||
|
||||
workItemSchema.methods.toClient = function() {
|
||||
return this.toObject()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user