Skip to content

Commit

Permalink
Merge pull request #1205 from jembi/TB-151-apps-endpoints
Browse files Browse the repository at this point in the history
Tb 151 apps endpoints
  • Loading branch information
bradsawadye authored Sep 15, 2023
2 parents 563d83c + 7d4d45f commit 6f53407
Show file tree
Hide file tree
Showing 6 changed files with 458 additions and 2 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "openhim-core",
"description": "The OpenHIM core application that provides logging and routing of http requests",
"version": "8.1.2",
"version": "8.2.0",
"main": "./lib/server.js",
"bin": {
"openhim-core": "./bin/openhim-core.js"
Expand Down
151 changes: 151 additions & 0 deletions src/api/apps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
'use strict'

import logger from 'winston'

import * as authorisation from './authorisation'
import {AppModelAPI} from '../model/apps'

/*
Checks admin permission for create, update and delete operations.
Throws error if user does not have admin access
*/
const checkUserPermission = (ctx, operation) => {
if (!authorisation.inGroup('admin', ctx.authenticated)) {
ctx.statusCode = 403
throw Error(
`User ${ctx.authenticated.email} is not an admin, API access to ${operation} an app denied.`
)
}
}

/*
Returns app if it exists, if not it throws an error
*/
const checkAppExists = async (ctx, appId) => {
const app = await AppModelAPI.findById(appId)

if (!app) {
ctx.statusCode = 404
throw Error(`App with id ${appId} does not exist`)
}

return app
}

// Creates error response for operations create, read, update and delete
const createErrorResponse = (ctx, operation, error) => {
logger.error(`Could not ${operation} an app via the API: ${error.message}`)

ctx.body = {
error: error.message
}
ctx.status = ctx.statusCode ? ctx.statusCode : 500
}

const validateId = (ctx, id) => {
if (!id.match(/^[0-9a-fA-F]{24}$/)) {
ctx.statusCode = 400
throw Error(`App id "${id}" is invalid. ObjectId should contain 24 characters`)
}
}

export async function addApp(ctx) {
try {
checkUserPermission(ctx, 'add')

const app = new AppModelAPI(ctx.request.body)

await app
.save()
.then(app => {
logger.info(`User ${ctx.request.email} created app ${app.name}`)

ctx.status = 201
ctx.body = app
})
.catch(e => {
ctx.statusCode = 400
throw e
})
} catch (e) {
createErrorResponse(ctx, 'add', e)
}
}

export async function updateApp(ctx, appId) {
try {
checkUserPermission(ctx, 'update')

validateId(ctx, appId)

await checkAppExists(ctx, appId)

const update = ctx.request.body

await AppModelAPI.findOneAndUpdate({_id: appId}, update, {
new: true,
runValidators: true
})
.then(app => {
logger.info(`User ${ctx.authenticated.email} updated app ${app.name}`)

ctx.body = app
ctx.status = 200
})
.catch(e => {
ctx.statusCode = 400
throw e
})
} catch (e) {
createErrorResponse(ctx, 'update', e)
}
}

export async function getApps(ctx) {
try {
const apps = await AppModelAPI.find(ctx.request.query)

logger.info(`User ${ctx.authenticated.email} fetched ${apps.length} apps`)

ctx.body = apps
ctx.status = 200
} catch (e) {
createErrorResponse(ctx, 'retrieve', e)
}
}

export async function getApp(ctx, appId) {
try {
validateId(ctx, appId)

const app = await checkAppExists(ctx, appId)

logger.info(`User ${ctx.authenticated.email} app fetched ${appId}`)

ctx.body = app
ctx.status = 200
} catch (e) {
createErrorResponse(ctx, 'retrieve', e)
}
}

export async function deleteApp(ctx, appId) {
try {
checkUserPermission(ctx, 'delete')

validateId(ctx, appId)

await checkAppExists(ctx, appId)

await AppModelAPI.deleteOne({_id: appId}).then(() => {
logger.info(`User ${ctx.authenticated.email} deleted app ${appId}`)

ctx.status = 200
ctx.body = {
success: true
}
})
} catch (e) {
createErrorResponse(ctx, 'delete', e)
}
}
7 changes: 7 additions & 0 deletions src/koaApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import session from 'koa-session'
import compose from 'koa-compose'

import * as about from './api/about'
import * as apps from './api/apps'
import * as audits from './api/audits'
import * as authentication from './api/authentication'
import * as certificateAuthority from './api/certificateAuthority'
Expand Down Expand Up @@ -136,6 +137,12 @@ export function setupApp(done) {
app.use(route.get('/logout', users.logout))

// Define the api routes
app.use(route.get('/apps', apps.getApps))
app.use(route.get('/apps/:appId', apps.getApp))
app.use(route.put('/apps/:appId', apps.updateApp))
app.use(route.post('/apps', apps.addApp))
app.use(route.delete('/apps/:appId', apps.deleteApp))

app.use(route.get('/users', users.getUsers))
app.use(route.get('/users/:email', users.getUser))
app.use(route.post('/users', users.addUser))
Expand Down
33 changes: 33 additions & 0 deletions src/model/apps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict'

import {Schema} from 'mongoose'

import {connectionAPI, connectionDefault} from '../config'

const AppSchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
description: String,
icon: {
data: Buffer,
contentType: String
},
category: String,
access_roles: [String],
url: {
type: String,
unique: true,
required: true
},
showInPortal: {
type: Boolean,
default: true
},
showInSideBar: Boolean
})

export const AppModelAPI = connectionAPI.model('App', AppSchema)
export const AppModel = connectionDefault.model('App', AppSchema)
Loading

0 comments on commit 6f53407

Please sign in to comment.