Skip to content

Commit

Permalink
Merge pull request #736 from dhis2/HUB-143/feat/appversion-by-id-api
Browse files Browse the repository at this point in the history
feat(appversions): add endpoint for getting a single app-version
  • Loading branch information
Birkbjo authored Mar 20, 2023
2 parents 942d7b0 + b5ec694 commit b0dfaae
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 21 deletions.
8 changes: 5 additions & 3 deletions server/src/models/v2/AppVersion.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const Joi = require('../../utils/CustomJoi')
const { definition: defaultDefinition } = require('./Default')
const { createDefaultValidator } = require('./helpers')
const { AppStatuses } = require('../../enums/index.js')

const definition = defaultDefinition
.append({
appId: Joi.string(),
appId: Joi.string().guid({ version: 'uuidv4' }),
version: Joi.string(),
channel: Joi.string().required(),
demoUrl: Joi.string().uri().allow(null, ''),
Expand All @@ -13,14 +14,15 @@ const definition = defaultDefinition
minDhisVersion: Joi.string().required(),
maxDhisVersion: Joi.string().allow(null, ''),
slug: Joi.string(),
status: Joi.string().valid(...AppStatuses),
})
.alter({
db: s =>
db: (s) =>
s
.rename('minDhisVersion', 'min_dhis2_version')
.rename('maxDhisVersion', 'max_dhis2_version')
.rename('demoUrl', 'demo_url'),
external: s => s.strip('slug'),
external: (s) => s.strip('slug'),
})
.label('AppVersion')

Expand Down
21 changes: 20 additions & 1 deletion server/src/query/executeQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ const defaultOptions = {
pagingStrategy: pagingStrategies.WINDOW,
}

const selectMethods = {
select: 'select',
first: 'first',
pluck: 'pluck',
}

/**
* Check if the query is a select-query
* @param {*} query the knex query
* @returns true if the query is a "select"-query
*/
const isSelectQuery = (query) => {
const method = query._method
// the actual "command" is only available after the query has been executed
// through listening to the `query`-event (see https://knexjs.org/guide/interfaces.html#query-response)
// that would also make it async, so it's more flexible to check the _method property.
return selectMethods[method] !== undefined
}

/**
* Executes the knex-query, applying filters and paging if present
*
Expand Down Expand Up @@ -57,7 +76,7 @@ async function executeQuery(
} else if (model) {
// parse if it's a "getter" - ie is a select-query
// else we format it to db-format
if (query._method === 'select') {
if (isSelectQuery(query)) {
result = model.parseDatabaseJson(result)
} else {
result = model.formatDatabaseJson(result)
Expand Down
52 changes: 51 additions & 1 deletion server/src/routes/v2/appVersions.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
//const Boom = require('@hapi/boom')
const Boom = require('@hapi/boom')
const AppVersionModel = require('../../models/v2/AppVersion')
const {
withPagingResultSchema,
withPagingQuerySchema,
} = require('../../query/Pager')
const Joi = require('../../utils/CustomJoi')
const App = require('../../services/app')
const { AppStatus } = require('../../enums')

const CHANNELS = ['stable', 'development', 'canary']

module.exports = [
{
method: 'GET',
path: '/v2/appVersions/{appVersionId}',
config: {
tags: ['api', 'v2'],
response: {
modify: true,
schema: AppVersionModel.externalDefinition,
},
validate: {
params: Joi.object({
appVersionId: Joi.string()
.guid({ version: 'uuidv4' })
.required(),
}),
},
},
handler: async (request, h) => {
const { db } = h.context
const { appVersionId } = request.params
const { appVersionService } = request.services(true)

const setDownloadUrl =
appVersionService.createSetDownloadUrl(request)

const version = await appVersionService.findOne(appVersionId, db)
await checkVersionAccess(version, request, db)

setDownloadUrl(version)
return version
},
},
{
method: 'GET',
path: '/v2/apps/{appId}/versions',
Expand Down Expand Up @@ -67,3 +101,19 @@ module.exports = [
},
},
]

async function checkVersionAccess(version, request, db) {
// check if the user is allowed to see the app
if (version.status !== AppStatus.APPROVED) {
const user = request.getUser()
const canSeeApp =
user &&
(user.isManager ||
(await App.canEditApp(user.id, version.appId, db)))

if (!canSeeApp) {
// TODO: should this return a 404 instead of a 403? (to avoid leaking info)
throw Boom.forbidden()
}
}
}
35 changes: 31 additions & 4 deletions server/src/services/appVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ const Schmervice = require('@hapipal/schmervice')
const AppVersionModel = require('../models/v2/AppVersion')
const { executeQuery } = require('../query/executeQuery')
const { getServerUrl } = require('../utils')
const { NotFoundError } = require('../utils/errors')

const getAppVersionQuery = knex =>
const getAppVersionQuery = (knex) =>
knex('app_version')
.innerJoin(
knex.ref('app_version_localised').as('avl'),
Expand Down Expand Up @@ -33,16 +34,42 @@ const getAppVersionQuery = knex =>
knex.ref('ac.min_dhis2_version').as('minDhisVersion'),
knex.ref('ac.max_dhis2_version').as('maxDhisVersion'),
knex.ref('avl.slug'),
knex.ref('app_version.download_count').as('downloadCount')
knex.ref('app_version.download_count').as('downloadCount'),
knex.ref('as.status').as('status')
)
.where('language_code', 'en') // only english is supported for now
.orderBy('app_version.created_at', 'desc')

const TABLE_NAME = 'app_version'

class AppVersionService extends Schmervice.Service {
constructor(server, schmerviceOptions) {
super(server, schmerviceOptions)
}

async findOneByColumn(
columnValue,
{ tableName = TABLE_NAME, columnName = 'id' } = {},
knex
) {
const whereColumn = `${tableName}.${columnName}`
const query = getAppVersionQuery(knex)
.where(whereColumn, columnValue)
.first()

const { result } = await executeQuery(query, { model: AppVersionModel })

if (!result) {
throw new NotFoundError('App Version Not Found.')
}

return result
}

async findOne(id, knex) {
return this.findOneByColumn(id, { columnName: 'id' }, knex)
}

async findByAppId(appId, { filters, pager } = {}, knex) {
const query = getAppVersionQuery(knex).where(
'app_version.app_id',
Expand Down Expand Up @@ -81,7 +108,7 @@ class AppVersionService extends Schmervice.Service {

const { result } = await executeQuery(query)

return result.map(c => c.name)
return result.map((c) => c.name)
}

getDownloadUrl(request, appVersion) {
Expand All @@ -98,7 +125,7 @@ class AppVersionService extends Schmervice.Service {
* @returns a function with signature (appVersion) => appVersionWithDownloadUrl
*/
createSetDownloadUrl(request) {
return appVersion => {
return (appVersion) => {
appVersion.downloadUrl = this.getDownloadUrl(request, appVersion)
return appVersion
}
Expand Down
Loading

0 comments on commit b0dfaae

Please sign in to comment.