From 6101bc31ab339938dec2a0a910d84fd26ac0576a Mon Sep 17 00:00:00 2001 From: joostdecock Date: Sun, 30 Apr 2023 19:01:24 +0200 Subject: [PATCH] feat(backend): Allow downloading public sets --- config/dependencies.yaml | 5 +- sites/backend/package.json | 1 + sites/backend/src/controllers/sets.mjs | 11 +++++ sites/backend/src/models/set.mjs | 63 +++++++++++++++++++++++--- sites/backend/src/routes/sets.mjs | 8 +++- 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/config/dependencies.yaml b/config/dependencies.yaml index bae15ff7f36..497e8386ee5 100644 --- a/config/dependencies.yaml +++ b/config/dependencies.yaml @@ -88,7 +88,7 @@ hugo: '@freesewing/plugin-bust': *freesewing i18n: dev: - 'js-yaml': '4.1.0' + 'js-yaml': &jsyaml '4.1.0' peer: '@freesewing/pattern-info': *freesewing jaeger: @@ -212,6 +212,7 @@ backend: 'crypto': '1.0.1' 'dotenv': '16.0.3' 'express': '4.18.2' + 'js-yaml': *jsyaml 'lodash.get': *_get 'mustache': '4.2.0' 'otplib': '12.0.1' @@ -263,7 +264,7 @@ dev: dev: &nextSiteDevDependencies 'autoprefixer': '10.4.14' 'eslint-config-next': *next - 'js-yaml': &jsYaml '4.1.0' + 'js-yaml': *jsyaml 'postcss': &postcss '8.4.21' 'remark-extract-frontmatter': '3.2.0' 'tailwindcss': &tailwindcss '3.3.1' diff --git a/sites/backend/package.json b/sites/backend/package.json index a53733bc232..53f8c45a269 100644 --- a/sites/backend/package.json +++ b/sites/backend/package.json @@ -35,6 +35,7 @@ "crypto": "1.0.1", "dotenv": "16.0.3", "express": "4.18.2", + "js-yaml": "4.1.0", "lodash.get": "4.4.2", "mustache": "4.2.0", "otplib": "12.0.1", diff --git a/sites/backend/src/controllers/sets.mjs b/sites/backend/src/controllers/sets.mjs index 32c10c9b77c..99af243c30d 100644 --- a/sites/backend/src/controllers/sets.mjs +++ b/sites/backend/src/controllers/sets.mjs @@ -70,3 +70,14 @@ SetsController.prototype.clone = async (req, res, tools) => { return Set.sendResponse(res) } + +/* + * Read a public measurements set + * See: https://freesewing.dev/reference/backend/api + */ +SetsController.prototype.readPublic = async (req, res, tools, format = 'json') => { + const Set = new SetModel(tools) + await Set.publicRead(req) + + return format === 'yaml' ? Set.sendYamlResponse(res) : Set.sendResponse(res) +} diff --git a/sites/backend/src/models/set.mjs b/sites/backend/src/models/set.mjs index 46745c18c4e..c8a8e8c455d 100644 --- a/sites/backend/src/models/set.mjs +++ b/sites/backend/src/models/set.mjs @@ -1,5 +1,6 @@ import { log } from '../utils/log.mjs' import { setSetAvatar } from '../utils/sanity.mjs' +import yaml from 'js-yaml' export function SetModel(tools) { this.config = tools.config @@ -99,6 +100,22 @@ SetModel.prototype.guardedRead = async function ({ params, user }) { }) } +/* + * Loads a measurements set from the database but only if it's public + * + * Stores result in this.record + */ +SetModel.prototype.publicRead = async function ({ params }) { + await this.read({ id: parseInt(params.id) }) + if (this.record.public !== true) { + // Note that we return 404 + // because we don't want to reveal that a non-public set exists. + return this.setResponse(404) + } + + return this.setResponse(200, false, this.asPublicSet(), true) +} + /* * Clones a measurements set * In addition prepares it for returning the set data @@ -310,18 +327,45 @@ SetModel.prototype.asSet = function () { } } +/* + * Returns record data fit for public publishing + */ +SetModel.prototype.asPublicSet = function () { + const data = { + author: 'FreeSewing.org', + type: 'measurementsSet', + about: 'Contains measurements in mm as well as metadata', + ...this.asSet(), + } + delete data.userId + data.measurements = data.measies + delete data.measies + data.units = data.imperial ? 'imperial' : 'metric' + delete data.imperial + delete data.public + + return data +} + /* * Helper method to set the response code, result, and body * * Will be used by this.sendResponse() */ -SetModel.prototype.setResponse = function (status = 200, error = false, data = {}) { +SetModel.prototype.setResponse = function ( + status = 200, + error = false, + data = {}, + rawData = false +) { this.response = { status, - body: { - result: 'success', - ...data, - }, + body: rawData + ? data + : { + result: 'success', + ...data, + }, } if (status > 201) { this.response.body.error = error @@ -333,12 +377,19 @@ SetModel.prototype.setResponse = function (status = 200, error = false, data = { } /* - * Helper method to send response + * Helper method to send response (as JSON) */ SetModel.prototype.sendResponse = async function (res) { return res.status(this.response.status).send(this.response.body) } +/* + * Helper method to send response as YAML + */ +SetModel.prototype.sendYamlResponse = async function (res) { + return res.status(this.response.status).type('yaml').send(yaml.dump(this.response.body)) +} + /* * Update method to determine whether this request is * part of a unit test diff --git a/sites/backend/src/routes/sets.mjs b/sites/backend/src/routes/sets.mjs index d0e36d20f60..ad75fb4d2bc 100644 --- a/sites/backend/src/routes/sets.mjs +++ b/sites/backend/src/routes/sets.mjs @@ -7,7 +7,7 @@ const bsc = ['basic', { session: false }] export function setsRoutes(tools) { const { app, passport } = tools - // Create a measurments set + // Create a measurements set app.post('/sets/jwt', passport.authenticate(...jwt), (req, res) => Sets.create(req, res, tools)) app.post('/sets/key', passport.authenticate(...bsc), (req, res) => Sets.create(req, res, tools)) @@ -19,7 +19,7 @@ export function setsRoutes(tools) { Sets.clone(req, res, tools) ) - // Read a measurments set + // Read a measurements set app.get('/sets/:id/jwt', passport.authenticate(...jwt), (req, res) => Sets.read(req, res, tools)) app.get('/sets/:id/key', passport.authenticate(...bsc), (req, res) => Sets.read(req, res, tools)) @@ -42,4 +42,8 @@ export function setsRoutes(tools) { app.delete('/sets/:id/key', passport.authenticate(...bsc), (req, res) => Sets.delete(req, res, tools) ) + + // Read a public measurements set as JSON or YAML (no auth needed, but will only work for public sets) + app.get('/sets/:id.json', (req, res) => Sets.readPublic(req, res, tools, 'json')) + app.get('/sets/:id.yaml', (req, res) => Sets.readPublic(req, res, tools, 'yaml')) }