From 3d899ef6a806cf2d58abd050bb4066f3ef9d62e0 Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Thu, 31 Mar 2022 05:52:37 +0100
Subject: [PATCH] Support different aggregation types in the metrics API

---
 package.json                        |  2 +-
 src/domain/data/MetricsService.js   | 25 +++++++++++++++++++++++--
 src/http/controllers/api/content.js |  2 ++
 3 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index c6f5cd0..5276081 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "jetsam-api",
-  "version": "2.4.0",
+  "version": "2.5.0",
   "description": "The Jetsam App API Server",
   "main": "server.js",
   "scripts": {
diff --git a/src/domain/data/MetricsService.js b/src/domain/data/MetricsService.js
index 6775c84..0cee2c5 100644
--- a/src/domain/data/MetricsService.js
+++ b/src/domain/data/MetricsService.js
@@ -4,9 +4,18 @@ const { User, AccessToken, Metric, Sequelize } = require('database/models')
 const { unset } = require('bootstrap')
 const HttpError = require('core/errors/HttpError')
 const moment = require("moment");
+const InputValidationError = require("../../core/errors/InputValidationError");
 
 const { Op } = Sequelize
 
+const aggregateTypeMap = {
+	count: 'COUNT',
+	min: 'MIN',
+	max: 'MAX',
+	sum: 'SUM',
+	average: 'AVG',
+}
+
 module.exports = class MetricsService extends ContextualModule {
 	static getServiceName() {
 		return 'data.metrics'
@@ -54,8 +63,13 @@ module.exports = class MetricsService extends ContextualModule {
 		return await Metric.create(payload, { transaction })
 	}
 
-	async queryAggregate(pointBuffer, types, from, to) {
+	async queryAggregate(pointBuffer, types, from, to, method = 'count') {
 		const snapClause = `ST_SNAPTOGRID("Metric"."location"::geometry, 0.001)`
+		const aggregator = aggregateTypeMap[method]
+
+		if (aggregator == null) {
+			throw new InputValidationError(['aggregate'])
+		}
 
 		return await Metric.findAll({
 			where: {
@@ -73,7 +87,7 @@ module.exports = class MetricsService extends ContextualModule {
 			},
 			attributes: [
 				'type',
-				[Sequelize.fn('COUNT', Sequelize.col('value')), 'value'],
+				[Sequelize.fn(aggregator, Sequelize.literal('"value"::numeric')), 'value'],
 				[Sequelize.literal(snapClause), 'location'],
 			],
 			group: [Sequelize.literal(snapClause), 'type'],
@@ -95,6 +109,13 @@ module.exports = class MetricsService extends ContextualModule {
 					[Op.in]: types,
 				},
 			},
+			attributes: [
+				'id',
+				'value',
+				'type',
+				'location',
+				'recorded_at'
+			]
 		})
 	}
 }
diff --git a/src/http/controllers/api/content.js b/src/http/controllers/api/content.js
index 5db0f4c..1f26cd8 100644
--- a/src/http/controllers/api/content.js
+++ b/src/http/controllers/api/content.js
@@ -120,6 +120,7 @@ exports.getWithin = async ctx => {
 		date_to = moment.utc(),
 		types = '',
 		format = 'full',
+		aggregate = 'count',
 	} = ctx.request.query
 
 	let pointBufferData = null
@@ -147,6 +148,7 @@ exports.getWithin = async ctx => {
 		fromDate.toISOString(),
 		// moment().subtract(12, 'months').toISOString(),
 		toDate.toISOString(),
+		aggregate,
 	)
 
 	ctx.body = { metrics }
-- 
GitLab