From f803dde5c94b89ffb876d89b188d59a57a512c3c Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Wed, 8 Dec 2021 21:55:21 +0000
Subject: [PATCH] Add routes and models for classification roots

---
 src/database/models/ClassificationRoot.js     | 64 +++++++++++++++++
 .../controllers/api/v2/classifications.js     | 71 +++++++++++++++++++
 src/http/params/classification.js             |  7 ++
 src/http/routers/routes_v2.js                 |  4 ++
 4 files changed, 146 insertions(+)
 create mode 100644 src/database/models/ClassificationRoot.js
 create mode 100644 src/http/controllers/api/v2/classifications.js
 create mode 100644 src/http/params/classification.js

diff --git a/src/database/models/ClassificationRoot.js b/src/database/models/ClassificationRoot.js
new file mode 100644
index 0000000..fe18ca2
--- /dev/null
+++ b/src/database/models/ClassificationRoot.js
@@ -0,0 +1,64 @@
+const BaseModel = require('./BaseModel')
+const timestamps = require('./properties/timestamps')
+
+class ClassificationRoot extends BaseModel {
+	static associate(models) {
+		this.belongsTo(models.Upload, { foreignKey: 'upload_id' })
+	}
+
+	toJSON() {
+		return {
+			metric_id: this.metric_id,
+			upload_id: this.upload_id,
+			image_id: this.image_id,
+			url: this.url,
+			status: this.status,
+		}
+	}
+
+}
+
+module.exports = (sequelize, DataTypes) => {
+	ClassificationRoot.init(Object.assign(
+		{
+			metric_id: {
+				type: DataTypes.UUID,
+				primaryKey: true,
+				defaultValue: DataTypes.UUIDV4,
+				validate: {
+					isUUID: 4,
+				},
+			},
+			upload_id: {
+				type: DataTypes.UUID,
+				validate: {
+					isUUID: 4,
+				},
+			},
+			image_id: {
+				type: DataTypes.UUID,
+				validate: {
+					isUUID: 4,
+				},
+			},
+			url: {
+				type: DataTypes.TEXT,
+			},
+			status: {
+				type: DataTypes.TEXT,
+			},
+			meta: {
+				type: DataTypes.JSONB,
+			},
+		},
+		timestamps(DataTypes),
+	), {
+		sequelize,
+		paranoid: true,
+		tableName: 'classification_roots',
+	})
+
+	return ClassificationRoot
+}
+
+module.exports.Model = ClassificationRoot
diff --git a/src/http/controllers/api/v2/classifications.js b/src/http/controllers/api/v2/classifications.js
new file mode 100644
index 0000000..00f5d55
--- /dev/null
+++ b/src/http/controllers/api/v2/classifications.js
@@ -0,0 +1,71 @@
+const publicStatus = new Set(['accepted'])
+const privateStatus = new Set(['pending', 'rejected'])
+
+const { Sequelize, ClassificationRoot } = require('database/models')
+const NotFoundError = require("core/errors/NotFoundError");
+const UnauthorizedError = require("core/errors/UnauthorizedError");
+const InputValidationError = require("core/errors/InputValidationError");
+
+exports.listRoots = async ctx => {
+	const { status = '', limit = 25, after = null } = ctx.query
+
+	const requestedStatus = status.split(',')
+		.map(s => s.trim())
+		.filter(s => publicStatus.has(s) || privateStatus.has(s))
+
+	const query = {
+		order: ['metric_id', 'created_at'],
+		where: {
+			status: {
+				[Sequelize.Op.in]: requestedStatus,
+			}
+		},
+		limit,
+	}
+
+	const count = await ClassificationRoot.count(query)
+
+	if (after) {
+		query.where.metric_id = {
+			[Sequelize.Op.gt]: after,
+		}
+	}
+
+	const roots = await ClassificationRoot.findAll(query)
+
+	ctx.body = {
+		roots,
+		meta: {
+			total: count,
+		}
+	}
+}
+
+exports.putRootStatus = async ctx => {
+	const user = await ctx.services['core.auth'].getUser()
+
+	if (!user) {
+		throw new UnauthorizedError()
+	}
+
+	if (ctx.models.classification == null) {
+		throw new NotFoundError('Classification Root')
+	}
+
+	const { status } = ctx.request.body
+	const { classification } = ctx.models
+
+	if (!publicStatus.has(status) && !privateStatus.has(status)) {
+		throw new InputValidationError(['status'])
+	}
+
+	classification.status = status
+	classification.meta = {
+		approved_by: user.id,
+	}
+	await classification.save()
+
+	ctx.body = {
+		root: classification,
+	}
+}
\ No newline at end of file
diff --git a/src/http/params/classification.js b/src/http/params/classification.js
new file mode 100644
index 0000000..23362fe
--- /dev/null
+++ b/src/http/params/classification.js
@@ -0,0 +1,7 @@
+const { ClassificationRoot } = require('database/models')
+
+module.exports = async (id, ctx, next) => {
+	ctx.models = ctx.models ?? {}
+	ctx.models.classification = await ClassificationRoot.findByPk(id)
+	return await next()
+}
diff --git a/src/http/routers/routes_v2.js b/src/http/routers/routes_v2.js
index 45691f1..3a1e040 100644
--- a/src/http/routers/routes_v2.js
+++ b/src/http/routers/routes_v2.js
@@ -54,6 +54,7 @@ router.put('/uploads/:upload_id/:property', noop)
 
 router.param('survey', param('survey'))
 router.param('excerpt', param('survey_excerpt'))
+router.param('classification', param('classification'))
 
 router.get('/surveys', controller('api/v2/surveys', 'list'))
 router.get('/excerpts', controller('api/v2/surveys', 'listExcerpts'))
@@ -65,6 +66,9 @@ if (config('app.dev')) {
 	router.post('/surveys/factory', safemode, controller('api/v2/factories', 'survey'))
 }
 
+router.get('/classifications/roots', controller('api/v2/classifications', 'listRoots'))
+router.put('/classifications/roots/:classification/status', controller('api/v2/classifications', 'putRootStatus'))
+
 router.post('/an/ev', safemode, controller('api/analytics', 'track'))
 
 module.exports = router
-- 
GitLab