openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
313 lines (258 loc) • 10.5 kB
JavaScript
import logger from 'winston'
import { TaskModelAPI } from '../model/tasks'
import { TransactionModelAPI } from '../model/transactions'
import { AutoRetryModelAPI } from '../model/autoRetry'
import * as Channels from '../model/channels'
import * as authorisation from './authorisation'
import * as utils from '../utils'
import { promisify } from 'util'
const { ChannelModelAPI } = Channels
/**
* Function to check if rerun task creation is valid
*/
function isRerunPermissionsValid (user, transactions, callback) {
// if 'admin' - set rerun permissions to true
if (authorisation.inGroup('admin', user) === true) {
// admin user allowed to rerun any transactions
return callback(null, true)
} else {
return TransactionModelAPI.distinct('channelID', { _id: { $in: transactions.tids } }, (err, transChannels) => {
if (err) { return callback(err) }
ChannelModelAPI.distinct('_id', { txRerunAcl: { $in: user.groups } }, (err, allowedChannels) => {
if (err) { return callback(err) }
// for each transaction channel found to be rerun
for (const trx of Array.from(transChannels)) {
// assume transaction channnel is not allowed at first
let matchFound = false
// for each user allowed channel to be rerun
for (const chan of Array.from(allowedChannels)) {
if (trx.equals(chan)) { matchFound = true }
}
// if one channel not allowed then rerun NOT allowed
if (!matchFound) { return callback(null, false) }
}
return callback(null, true)
})
}
)
}
}
/**
* Retrieves the list of active tasks
*/
export async function getTasks (ctx) {
// Must be admin
if (!authorisation.inGroup('admin', ctx.authenticated)) {
utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getTasks denied.`, 'info')
return
}
try {
const filtersObject = ctx.request.query
// get limit and page values
const { filterLimit } = filtersObject
const { filterPage } = filtersObject
// determine skip amount
const filterSkip = filterPage * filterLimit
// get filters object
const filters = JSON.parse(filtersObject.filters)
// parse date to get it into the correct format for querying
if (filters.created) {
filters.created = JSON.parse(filters.created)
}
// exclude transactions object from tasks list
const projectionFiltersObject = { transactions: 0 }
// execute the query
ctx.body = await TaskModelAPI
.find(filters, projectionFiltersObject)
.skip(filterSkip)
.limit(parseInt(filterLimit, 10))
.sort({ created: -1 })
} catch (err) {
utils.logAndSetResponse(ctx, 500, `Could not fetch all tasks via the API: ${err}`, 'error')
}
}
const areTransactionChannelsValid = (transactions, callback) =>
TransactionModelAPI.distinct('channelID', { _id: { $in: transactions.tids } }, (err, trxChannelIDs) => {
if (err) { return callback(err) }
return ChannelModelAPI.find({ _id: { $in: trxChannelIDs } }, { status: 1 }, (err, trxChannels) => {
if (err) { return callback(err) }
for (const chan of Array.from(trxChannels)) {
if (!Channels.isChannelEnabled(chan)) {
return callback(null, false)
}
}
return callback(null, true)
})
})
/**
* Creates a new Task
*/
export async function addTask (ctx) {
// Get the values to use
const transactions = ctx.request.body
try {
const taskObject = {}
const transactionsArr = []
taskObject.remainingTransactions = transactions.tids.length
taskObject.user = ctx.authenticated.email
if (transactions.batchSize != null) {
if (transactions.batchSize <= 0) {
return utils.logAndSetResponse(ctx, 400, 'Invalid batch size specified', 'info')
}
taskObject.batchSize = transactions.batchSize
}
if (transactions.paused) {
taskObject.status = 'Paused'
}
// check rerun permission and whether to create the rerun task
const isRerunPermsValid = promisify(isRerunPermissionsValid)
const allowRerunTaskCreation = await isRerunPermsValid(ctx.authenticated, transactions)
// the rerun task may be created
if (allowRerunTaskCreation === true) {
const areTrxChannelsValid = promisify(areTransactionChannelsValid)
const trxChannelsValid = await areTrxChannelsValid(transactions)
if (!trxChannelsValid) {
utils.logAndSetResponse(ctx, 400, 'Cannot queue task as there are transactions with disabled or deleted channels', 'info')
return
}
for (const tid of Array.from(transactions.tids)) { transactionsArr.push({ tid }) }
taskObject.transactions = transactionsArr
taskObject.totalTransactions = transactionsArr.length
const task = await new TaskModelAPI(taskObject).save()
// All ok! So set the result
utils.logAndSetResponse(ctx, 201, `User ${ctx.authenticated.email} created task with id ${task.id}`, 'info')
// Clear the transactions out of the auto retry queue, in case they're in there
return AutoRetryModelAPI.deleteMany({ transactionID: { $in: transactions.tids } }, (err) => { if (err) { return logger.error(err) } })
} else {
// rerun task creation not allowed
utils.logAndSetResponse(ctx, 403, 'Insufficient permissions prevents this rerun task from being created', 'error')
}
} catch (error) {
// Error! So inform the user
const err = error
utils.logAndSetResponse(ctx, 500, `Could not add Task via the API: ${err}`, 'error')
}
}
/**
* Retrieves the details for a specific Task
*/
function buildFilteredTransactionsArray (filters, transactions) {
// set tempTransactions array to return
const tempTransactions = []
let i = 0
while (i < transactions.length) {
// set filter variable to captured failed filters
let filtersFailed = false
if (filters.tstatus) {
// if tstatus doesnt equal filter then set filter failed to true
if (filters.tstatus !== transactions[i].tstatus) {
filtersFailed = true
}
}
if (filters.rerunStatus) {
// if rerunStatus doesnt equal filter then set filter failed to true
if (filters.rerunStatus !== transactions[i].rerunStatus) {
filtersFailed = true
}
}
if (filters.hasErrors) {
// if hasErrors filter 'yes' but no hasErrors exist then set filter failed to true
if ((filters.hasErrors === 'yes') && !transactions[i].hasErrors) {
filtersFailed = true
// if hasErrors filter 'no' but hasErrors does exist then set filter failed to true
} else if ((filters.hasErrors === 'no') && transactions[i].hasErrors) {
filtersFailed = true
}
}
// add transaction if all filters passed successfully
if (filtersFailed === false) {
tempTransactions.push(transactions[i])
}
// increment counter
i++
}
return tempTransactions
}
export async function getTask (ctx, taskId) {
// Get the values to use
taskId = unescape(taskId)
try {
const filtersObject = ctx.request.query
// get limit and page values
const { filterLimit } = filtersObject
const { filterPage } = filtersObject
// determine skip amount
const filterSkip = filterPage * filterLimit
// get filters object
const filters = JSON.parse(filtersObject.filters)
const result = await TaskModelAPI.findById(taskId).lean().exec()
let tempTransactions = result.transactions
// are filters present
if (Object.keys(filters).length > 0) {
tempTransactions = buildFilteredTransactionsArray(filters, result.transactions)
}
// get new transactions filters length
const totalFilteredTransactions = tempTransactions.length
// assign new transactions filters length to result property
result.totalFilteredTransactions = totalFilteredTransactions
// work out where to slice from and till where
const sliceFrom = filterSkip
const sliceTo = filterSkip + parseInt(filterLimit, 10)
// slice the transactions array to return only the correct amount of records at the correct index
result.transactions = tempTransactions.slice(sliceFrom, sliceTo)
// Test if the result if valid
if (result === null) {
// task not found! So inform the user
return utils.logAndSetResponse(ctx, 404, `We could not find a Task with this ID: ${taskId}.`, 'info')
} else {
ctx.body = result
}
// All ok! So set the result
} catch (err) {
utils.logAndSetResponse(ctx, 500, `Could not fetch Task by ID {taskId} via the API: ${err}`, 'error')
}
}
/**
* Updates the details for a specific Task
*/
export async function updateTask (ctx, taskId) {
// Must be admin
if (!authorisation.inGroup('admin', ctx.authenticated)) {
utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to updateTask denied.`, 'info')
return
}
// Get the values to use
taskId = unescape(taskId)
const taskData = ctx.request.body
// Ignore _id if it exists, user cannot change the internal id
if (taskData._id != null) { delete taskData._id }
try {
await TaskModelAPI.findOneAndUpdate({ _id: taskId }, taskData).exec()
// All ok! So set the result
ctx.body = 'The Task was successfully updated'
logger.info(`User ${ctx.authenticated.email} updated task with id ${taskId}`)
} catch (err) {
utils.logAndSetResponse(ctx, 500, `Could not update Task by ID {taskId} via the API: ${err}`, 'error')
}
}
/**
* Deletes a specific Tasks details
*/
export async function removeTask (ctx, taskId) {
// Must be admin
if (!authorisation.inGroup('admin', ctx.authenticated)) {
utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to removeTask denied.`, 'info')
return
}
// Get the values to use
taskId = unescape(taskId)
try {
// Try to get the Task (Call the function that emits a promise and Koa will wait for the function to complete)
await TaskModelAPI.deleteOne({ _id: taskId }).exec()
// All ok! So set the result
ctx.body = 'The Task was successfully deleted'
logger.info(`User ${ctx.authenticated.email} removed task with id ${taskId}`)
} catch (err) {
utils.logAndSetResponse(ctx, 500, `Could not remove Task by ID {taskId} via the API: ${err}`, 'error')
}
}