From f789f9d1aa27c802f4cb55e282ea0f65ebb35ccd Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Wed, 30 Mar 2022 20:37:12 +0100
Subject: [PATCH] Increase range of first party metric types supported

---
 helm/api/Chart.yaml                      |  4 +--
 package.json                             |  2 +-
 src/core/errors/InputValidationError.js  | 18 ++++++++++++
 src/database/models/Metric.js            | 12 ++++++++
 src/domain/auth/AuthenticationService.js |  5 +++-
 src/domain/auth/oidc/DBAdapter.js        |  5 +++-
 src/domain/auth/oidc/OIDCServer.js       |  2 +-
 src/http/controllers/api/content.js      | 37 ++++++++++--------------
 src/http/middleware/DeviceProperties.js  |  6 ++--
 src/http/routers/routes_v2.js            |  1 +
 10 files changed, 61 insertions(+), 31 deletions(-)

diff --git a/helm/api/Chart.yaml b/helm/api/Chart.yaml
index a072fe4..cdf0ae7 100644
--- a/helm/api/Chart.yaml
+++ b/helm/api/Chart.yaml
@@ -15,10 +15,10 @@ type: application
 # This is the chart version. This version number should be incremented each time you make changes
 # to the chart and its templates, including the app version.
 # Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.3.2
+version: 0.3.3
 
 # This is the version number of the application being deployed. This version number should be
 # incremented each time you make changes to the application. Versions are not expected to
 # follow Semantic Versioning. They should reflect the version the application is using.
 # It is recommended to use it with quotes.
-appVersion: "2.3.0"
+appVersion: "2.4.0"
diff --git a/package.json b/package.json
index a79e183..c6f5cd0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "jetsam-api",
-  "version": "2.3.0",
+  "version": "2.4.0",
   "description": "The Jetsam App API Server",
   "main": "server.js",
   "scripts": {
diff --git a/src/core/errors/InputValidationError.js b/src/core/errors/InputValidationError.js
index 1a97aef..115cb4d 100644
--- a/src/core/errors/InputValidationError.js
+++ b/src/core/errors/InputValidationError.js
@@ -4,4 +4,22 @@ module.exports = class InputValidationError extends HttpError {
 	constructor(fields) {
 		super(422, 'The supplied input was not valid', { fields })
 	}
+
+	toJSON() {
+		return {
+			status: this._status,
+			message: this._message,
+			...this._payload,
+		}
+	}
+
+	respondTo(ctx) {
+		ctx.status = this._status
+		ctx.body = {
+			errors: {
+				general: [this._message],
+				...this._payload,
+			}
+		}
+	}
 }
diff --git a/src/database/models/Metric.js b/src/database/models/Metric.js
index a47f28a..d19aadd 100644
--- a/src/database/models/Metric.js
+++ b/src/database/models/Metric.js
@@ -4,6 +4,18 @@ const BaseModel = require('./BaseModel')
 const metricConversionMap = {
 	trash: Number,
 	numeric: Number,
+	'pm2.5': Number,
+	co2_ppm: Number,
+	wind_mph: Number,
+	wind_kph: Number,
+	temp_c: Number,
+	temp_f: Number,
+	temp_k: Number,
+	pressure_pa: Number,
+	pressure_mbar: Number,
+	rain_mm: Number,
+	custom_string: String,
+	custom_number: Number,
 }
 
 class Metric extends BaseModel {
diff --git a/src/domain/auth/AuthenticationService.js b/src/domain/auth/AuthenticationService.js
index 6ad4ea3..57d17f8 100644
--- a/src/domain/auth/AuthenticationService.js
+++ b/src/domain/auth/AuthenticationService.js
@@ -77,8 +77,11 @@ module.exports = class AuthenticationService extends ContextualModule {
 			}
 
 			if (accessToken == null) {
-				console.log('oidc token', token)
 				accessToken = await this.ctx.services['auth.oidc'].withProvider(provider => provider.AccessToken.find(token))
+				if (accessToken) {
+					const user = await User.findByPk(accessToken.accountId)
+					accessToken.User = user
+				}
 			} else {
 				if (accessToken?.User) {
 					this.authenticateAs(accessToken.User, 'oauth_token', accessToken)
diff --git a/src/domain/auth/oidc/DBAdapter.js b/src/domain/auth/oidc/DBAdapter.js
index baf2d70..797fa27 100644
--- a/src/domain/auth/oidc/DBAdapter.js
+++ b/src/domain/auth/oidc/DBAdapter.js
@@ -2,7 +2,9 @@ const moment = require('moment')
 const { OIDCEntity, User, OAuthClient, AccessToken, RefreshToken, sequelize, Sequelize } = require('database/models')
 const debug = require('debug')('server:auth:oidc:adapter')
 
-const key = (id, name) => `urn:jetsam:resources:oidc:${name}/${id}`
+const key = (id, name) => {
+	return `urn:jetsam:resources:oidc:${name}/${id}`;
+}
 
 class DBAdapter {
 	constructor(name, { model = OIDCEntity } = {}) {
@@ -16,6 +18,7 @@ class DBAdapter {
 		const oidc = await this.model.upsert({
 			id: key(id, this.name),
 			grant_id: data.grantId ?? null,
+			related_user_id: data.accountId ?? null,
 			user_code: data.user_code ?? null,
 			uid: data.uid ?? null,
 			data,
diff --git a/src/domain/auth/oidc/OIDCServer.js b/src/domain/auth/oidc/OIDCServer.js
index 0c59af7..1d5fe26 100644
--- a/src/domain/auth/oidc/OIDCServer.js
+++ b/src/domain/auth/oidc/OIDCServer.js
@@ -8,7 +8,7 @@ module.exports = async function createOIDCServer() {
 	const {validScopes} = require("../OAuthFlow");
 
 	debug(`Creating OIDC Provider with base ${config('app.host.web')}`)
-	const provider = new Provider( /*config('app.host.web') + 'oidc/' */ 'http://trash.4l2.uk/oidc/', {
+	const provider = new Provider(config('app.host.web') + 'oidc/', {
 		clients: [
 			{
 				client_id: 'kbyuFDidLLm280LIwVFiazOqjO3ty8KH',
diff --git a/src/http/controllers/api/content.js b/src/http/controllers/api/content.js
index 0bf47a9..5db0f4c 100644
--- a/src/http/controllers/api/content.js
+++ b/src/http/controllers/api/content.js
@@ -2,6 +2,7 @@ const HttpError = require('core/errors/HttpError')
 const { Sequelize, sequelize, Metric } = require('database/models')
 const { Op } = Sequelize
 const moment = require('moment')
+const InputValidationError = require("../../../core/errors/InputValidationError");
 
 exports.postMetric = async ctx => {
 	const allowedTypes = new Set(Metric.getSupportedMetricTypes())
@@ -20,19 +21,16 @@ exports.postMetric = async ctx => {
 						transaction: t,
 						ctx,
 					})
-
 					metrics.push(result)
 				}
 			})
 		} catch (e) {
 			console.log(e)
-
-			throw new HttpError({
-				status: 400,
-				code: 'MTR-001',
-				title: 'Failed to save metric',
-				description: 'Something went wrong trying to save the metric',
-			})
+			if (e instanceof HttpError) {
+				throw e
+			} else {
+				throw new HttpError(500, 'Failed to save metric')
+			}
 		}
 
 		ctx.body = {
@@ -56,12 +54,7 @@ const save = async (
 		location.longitude == null ||
 		location.latitude == null
 	) {
-		throw new HttpError({
-			status: 400,
-			code: 'MTR-001',
-			title: 'Invalid Metric',
-			description: 'A location must be provided for metrics',
-		})
+		throw new HttpError(422, 'A location must be provided for metrics')
 	}
 
 	if (allowedTypes.has(type)) {
@@ -73,12 +66,7 @@ const save = async (
 			transaction,
 		)
 	} else {
-		throw new HttpError({
-			status: 400,
-			code: 'MTR-002',
-			title: 'Invalid Metric',
-			description: `${type} is not a supported type`,
-		})
+		throw new HttpError(422, `${type} is not a supported type`)
 	}
 }
 
@@ -134,7 +122,13 @@ exports.getWithin = async ctx => {
 		format = 'full',
 	} = ctx.request.query
 
-	const pointBufferData = within ? bufferFromPolygon(within) : bufferFromCorners(point_from, point_to)
+	let pointBufferData = null
+
+	try {
+		pointBufferData = within ? bufferFromPolygon(within) : bufferFromCorners(point_from, point_to)
+	} catch (e) {
+		throw new InputValidationError(['within', 'point_from', 'point_to'])
+	}
 	const pointBuffer = pointBufferData.map(pb => pb.map(Number).join(' ')).join(',')
 
 	const fromDate = moment.utc(date_from)
@@ -142,7 +136,6 @@ exports.getWithin = async ctx => {
 
 	const metricTypes = splitString(types)
 
-
 	const query =
 		format === 'marker'
 			? ctx.services['data.metrics'].queryAggregate
diff --git a/src/http/middleware/DeviceProperties.js b/src/http/middleware/DeviceProperties.js
index ae8f957..cbe1fee 100644
--- a/src/http/middleware/DeviceProperties.js
+++ b/src/http/middleware/DeviceProperties.js
@@ -1,7 +1,7 @@
 exports.extractDevice = async (ctx, next) => {
-	const deviceId = ctx.get('x-request-device')
-	const platform = ctx.get('x-request-platform')
-	const rawSlug = ctx.get('x-request-slug')
+	const deviceId = ctx.get('x-request-device') || ctx.get('Request-Device')
+	const platform = ctx.get('x-request-platform') || ctx.get('Request-Platform')
+	const rawSlug = ctx.get('x-request-slug') || ctx.get('Request-Info')
 
 	const slug = rawSlug ? Buffer.from(rawSlug, 'base64').toString('utf-8') : null
 
diff --git a/src/http/routers/routes_v2.js b/src/http/routers/routes_v2.js
index 7c4683d..5e1c911 100644
--- a/src/http/routers/routes_v2.js
+++ b/src/http/routers/routes_v2.js
@@ -22,6 +22,7 @@ router.get(
 	ctx =>
 		(ctx.body = {
 			name: 'Jetsam Data API',
+			version: require('../../../package.json').version,
 			prefix: ctx.path,
 		}),
 )
-- 
GitLab