mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	feat: more work on topic thumbs refactor
- addThumb and deleteThumb are now protected routes (duh) - new getThumbs route GET /api/v3/topics/<tid>/thumbs - Updated `assert.path` middleware to better handle if relative paths are received with upload_url - Slight refactor of thumbs lib to use validator to differentiate between tid and UUID
This commit is contained in:
		| @@ -4,6 +4,7 @@ const validator = require('validator'); | |||||||
|  |  | ||||||
| const api = require('../../api'); | const api = require('../../api'); | ||||||
| const topics = require('../../topics'); | const topics = require('../../topics'); | ||||||
|  | const privileges = require('../../privileges'); | ||||||
|  |  | ||||||
| const helpers = require('../helpers'); | const helpers = require('../helpers'); | ||||||
| const uploadsController = require('../uploads'); | const uploadsController = require('../uploads'); | ||||||
| @@ -89,6 +90,10 @@ Topics.deleteTags = async (req, res) => { | |||||||
| 	helpers.formatApiResponse(200, res); | 	helpers.formatApiResponse(200, res); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | Topics.getThumbs = async (req, res) => { | ||||||
|  | 	helpers.formatApiResponse(200, res, await topics.thumbs.get(req.params.tid)); | ||||||
|  | }; | ||||||
|  |  | ||||||
| Topics.addThumb = async (req, res) => { | Topics.addThumb = async (req, res) => { | ||||||
| 	// req.params.tid could be either a tid (pushing a new thumb to an existing topic) or a post UUID (a new topic being composed) | 	// req.params.tid could be either a tid (pushing a new thumb to an existing topic) or a post UUID (a new topic being composed) | ||||||
| 	const id = req.params.tid; | 	const id = req.params.tid; | ||||||
| @@ -98,23 +103,34 @@ Topics.addThumb = async (req, res) => { | |||||||
| 	if (!isUUID && (isNaN(parseInt(id, 10)) || !await topics.exists(req.params.tid))) { | 	if (!isUUID && (isNaN(parseInt(id, 10)) || !await topics.exists(req.params.tid))) { | ||||||
| 		return helpers.formatApiResponse(404, res, new Error('[[error:no-topic]]')); | 		return helpers.formatApiResponse(404, res, new Error('[[error:no-topic]]')); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// While drafts are not protected, tids are | ||||||
|  | 	if (!isUUID && !await privileges.topics.canEdit(req.params.tid, req.user.uid)) { | ||||||
|  | 		return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]')); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * todo test: | 	 * todo test: | ||||||
| 	 *   - uuid | 	 *   - uuid | ||||||
| 	 *   - tid | 	 *   - tid | ||||||
| 	 *   - number but not tid | 	 *   - number but not tid | ||||||
| 	 *   - random garbage | 	 *   - random garbage | ||||||
|  | 	 *   - wrong caller uid (unpriv) | ||||||
| 	 */ | 	 */ | ||||||
|  |  | ||||||
| 	const files = await uploadsController.uploadThumb(req, res);	// response is handled here, fix this? | 	const files = await uploadsController.uploadThumb(req, res);	// response is handled here, fix this? | ||||||
|  |  | ||||||
| 	// Add uploaded files to topic zset | 	// Add uploaded files to topic zset | ||||||
| 	await Promise.all(files.map(async (fileObj) => { | 	await Promise.all(files.map(async (fileObj) => { | ||||||
| 		await topics.thumbs.associate(id, fileObj.path, isUUID); | 		await topics.thumbs.associate(id, fileObj.path); | ||||||
| 	})); | 	})); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Topics.deleteThumb = async (req, res) => { | Topics.deleteThumb = async (req, res) => { | ||||||
|  | 	if (!await privileges.topics.canEdit(req.params.tid, req.user.uid)) { | ||||||
|  | 		return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]')); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	await topics.thumbs.delete(req.params.tid, req.body.path); | 	await topics.thumbs.delete(req.params.tid, req.body.path); | ||||||
| 	helpers.formatApiResponse(200, res, await topics.thumbs.get(req.params.tid)); | 	helpers.formatApiResponse(200, res, await topics.thumbs.get(req.params.tid)); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -58,6 +58,11 @@ Assert.path = helpers.try(async (req, res, next) => { | |||||||
| 		req.body.path = new URL(req.body.path).pathname; | 		req.body.path = new URL(req.body.path).pathname; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Strip upload_url if found | ||||||
|  | 	if (req.body.path.startsWith(nconf.get('upload_url'))) { | ||||||
|  | 		req.body.path = req.body.path.slice(nconf.get('upload_url').length); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	const pathToFile = path.join(nconf.get('upload_path'), req.body.path); | 	const pathToFile = path.join(nconf.get('upload_path'), req.body.path); | ||||||
| 	res.locals.cleanedPath = pathToFile; | 	res.locals.cleanedPath = pathToFile; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ module.exports = function () { | |||||||
| 	setupApiRoute(router, 'put', '/:tid/tags', [...middlewares, middleware.checkRequired.bind(null, ['tags']), middleware.assert.topic], controllers.write.topics.addTags); | 	setupApiRoute(router, 'put', '/:tid/tags', [...middlewares, middleware.checkRequired.bind(null, ['tags']), middleware.assert.topic], controllers.write.topics.addTags); | ||||||
| 	setupApiRoute(router, 'delete', '/:tid/tags', [...middlewares, middleware.assert.topic], controllers.write.topics.deleteTags); | 	setupApiRoute(router, 'delete', '/:tid/tags', [...middlewares, middleware.assert.topic], controllers.write.topics.deleteTags); | ||||||
|  |  | ||||||
|  | 	setupApiRoute(router, 'get', '/:tid/thumbs', [], controllers.write.topics.getThumbs); | ||||||
| 	setupApiRoute(router, 'post', '/:tid/thumbs', [multipartMiddleware, middleware.validateFiles, ...middlewares], controllers.write.topics.addThumb); | 	setupApiRoute(router, 'post', '/:tid/thumbs', [multipartMiddleware, middleware.validateFiles, ...middlewares], controllers.write.topics.addThumb); | ||||||
| 	setupApiRoute(router, 'delete', '/:tid/thumbs', [...middlewares, middleware.assert.topic, middleware.assert.path], controllers.write.topics.deleteThumb); | 	setupApiRoute(router, 'delete', '/:tid/thumbs', [...middlewares, middleware.assert.topic, middleware.assert.path], controllers.write.topics.deleteThumb); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,13 @@ | |||||||
|  |  | ||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var nconf = require('nconf'); | const nconf = require('nconf'); | ||||||
| var path = require('path'); | const path = require('path'); | ||||||
|  | const validator = require('validator'); | ||||||
|  |  | ||||||
| const db = require('../database'); | const db = require('../database'); | ||||||
| var file = require('../file'); | const file = require('../file'); | ||||||
| var plugins = require('../plugins'); | const plugins = require('../plugins'); | ||||||
|  |  | ||||||
| const Thumbs = {}; | const Thumbs = {}; | ||||||
| module.exports = Thumbs; | module.exports = Thumbs; | ||||||
| @@ -25,7 +26,7 @@ Thumbs.get = async function (tids) { | |||||||
| 		singular = true; | 		singular = true; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const sets = tids.map(tid => `topic:${tid}:thumbs`); | 	const sets = tids.map(tid => `${validator.isUUID(String(tid)) ? 'draft' : 'topic'}:${tid}:thumbs`); | ||||||
| 	const thumbs = await db.getSortedSetsMembers(sets); | 	const thumbs = await db.getSortedSetsMembers(sets); | ||||||
| 	let response = thumbs.map(thumbSet => thumbSet.map(thumb => ({ | 	let response = thumbs.map(thumbSet => thumbSet.map(thumb => ({ | ||||||
| 		url: path.join(nconf.get('upload_url'), thumb), | 		url: path.join(nconf.get('upload_url'), thumb), | ||||||
| @@ -35,8 +36,9 @@ Thumbs.get = async function (tids) { | |||||||
| 	return singular ? response.pop() : response; | 	return singular ? response.pop() : response; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Thumbs.associate = async function (id, path, isDraft) { | Thumbs.associate = async function (id, path) { | ||||||
| 	// Associates a newly uploaded file as a thumb to the passed-in tid | 	// Associates a newly uploaded file as a thumb to the passed-in draft or topic | ||||||
|  | 	const isDraft = validator.isUUID(String(id)); | ||||||
| 	const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`; | 	const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`; | ||||||
| 	const numThumbs = await db.sortedSetCard(set); | 	const numThumbs = await db.sortedSetCard(set); | ||||||
| 	path = path.replace(nconf.get('upload_path'), ''); | 	path = path.replace(nconf.get('upload_path'), ''); | ||||||
| @@ -47,7 +49,7 @@ Thumbs.commit = async function (uuid, tid) { | |||||||
| 	// Converts the draft thumb zset to the topic zset (combines thumbs if applicable) | 	// Converts the draft thumb zset to the topic zset (combines thumbs if applicable) | ||||||
| 	const set = `draft:${uuid}:thumbs`; | 	const set = `draft:${uuid}:thumbs`; | ||||||
| 	const thumbs = await db.getSortedSetRange(set, 0, -1); | 	const thumbs = await db.getSortedSetRange(set, 0, -1); | ||||||
| 	await Promise.all(thumbs.map(async path => await Thumbs.associate(tid, path, false))); | 	await Promise.all(thumbs.map(async path => await Thumbs.associate(tid, path))); | ||||||
| 	await db.delete(set); | 	await db.delete(set); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user