mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 19:15:58 +01:00 
			
		
		
		
	feat: #3783, min/max tags per category
This commit is contained in:
		| @@ -62,4 +62,10 @@ CategoryObject: | ||||
|       description: The number of posts in the category | ||||
|     totalTopicCount: | ||||
|       type: number | ||||
|       description: The number of topics in the category | ||||
|       description: The number of topics in the category | ||||
|     minTags: | ||||
|       type: number | ||||
|       description: Minimum tags per topic in this category | ||||
|     maxTags: | ||||
|       type: number | ||||
|       description: Maximum tags per topic in this category | ||||
| @@ -3130,6 +3130,10 @@ paths: | ||||
|                     type: number | ||||
|                   totalTopicCount: | ||||
|                     type: number | ||||
|                   minTags: | ||||
|                     type: number | ||||
|                   maxTags: | ||||
|                     type: number | ||||
|   /api/categories: | ||||
|     get: | ||||
|       tags: | ||||
| @@ -3655,6 +3659,10 @@ paths: | ||||
|                         type: array | ||||
|                         items: | ||||
|                           type: string | ||||
|                       minTags: | ||||
|                         type: number | ||||
|                       maxTags: | ||||
|                         type: number | ||||
|                       thread_tools: | ||||
|                         type: array | ||||
|                         items: | ||||
|   | ||||
| @@ -143,6 +143,8 @@ module.exports = function (Categories) { | ||||
| 		destination.class = source.class; | ||||
| 		destination.image = source.image; | ||||
| 		destination.imageClass = source.imageClass; | ||||
| 		destination.minTags = source.minTags; | ||||
| 		destination.maxTags = source.maxTags; | ||||
|  | ||||
| 		if (copyParent) { | ||||
| 			destination.parentCid = source.parentCid || 0; | ||||
|   | ||||
| @@ -1,12 +1,14 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var validator = require('validator'); | ||||
| const validator = require('validator'); | ||||
|  | ||||
| var db = require('../database'); | ||||
| const db = require('../database'); | ||||
| const meta = require('../meta'); | ||||
|  | ||||
| const intFields = [ | ||||
| 	'cid', 'parentCid', 'disabled', 'isSection', 'order', | ||||
| 	'topic_count', 'post_count', 'numRecentReplies', | ||||
| 	'minTags', 'maxTags', | ||||
| ]; | ||||
|  | ||||
| module.exports = function (Categories) { | ||||
| @@ -59,6 +61,21 @@ function modifyCategory(category, fields) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (!fields.length || fields.includes('minTags')) { | ||||
| 		const useDefault = !category.hasOwnProperty('minTags') || | ||||
| 			category.minTags === null || | ||||
| 			category.minTags === '' || | ||||
| 			!parseInt(category.minTags, 10); | ||||
| 		category.minTags = useDefault ? meta.config.minimumTagsPerTopic : category.minTags; | ||||
| 	} | ||||
| 	if (!fields.length || fields.includes('maxTags')) { | ||||
| 		const useDefault = !category.hasOwnProperty('maxTags') || | ||||
| 			category.maxTags === null || | ||||
| 			category.maxTags === '' || | ||||
| 			!parseInt(category.maxTags, 10); | ||||
| 		category.maxTags = useDefault ? meta.config.maximumTagsPerTopic : category.maxTags; | ||||
| 	} | ||||
|  | ||||
| 	db.parseIntFields(category, intFields, fields); | ||||
|  | ||||
| 	if (category.hasOwnProperty('name')) { | ||||
|   | ||||
| @@ -117,6 +117,8 @@ module.exports = function (Posts) { | ||||
| 				throw new Error('[[error:no-privileges]]'); | ||||
| 			} | ||||
| 		} | ||||
| 		await topics.validateTags(data.tags, topicData.cid); | ||||
|  | ||||
| 		const results = await plugins.fireHook('filter:topic.edit', { req: data.req, topic: newTopicData, data: data }); | ||||
| 		await db.setObject('topic:' + tid, results.topic); | ||||
| 		await topics.updateTopicTags(tid, data.tags); | ||||
|   | ||||
| @@ -25,10 +25,6 @@ module.exports = function (SocketPosts) { | ||||
| 			throw new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]'); | ||||
| 		} else if (data.title && data.title.length > meta.config.maximumTitleLength) { | ||||
| 			throw new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]'); | ||||
| 		} else if (data.tags && data.tags.length < meta.config.minimumTagsPerTopic) { | ||||
| 			throw new Error('[[error:not-enough-tags, ' + meta.config.minimumTagsPerTopic + ']]'); | ||||
| 		} else if (data.tags && data.tags.length > meta.config.maximumTagsPerTopic) { | ||||
| 			throw new Error('[[error:too-many-tags, ' + meta.config.maximumTagsPerTopic + ']]'); | ||||
| 		} else if (meta.config.minimumPostLength !== 0 && contentLen < meta.config.minimumPostLength) { | ||||
| 			throw new Error('[[error:content-too-short, ' + meta.config.minimumPostLength + ']]'); | ||||
| 		} else if (contentLen > meta.config.maximumPostLength) { | ||||
|   | ||||
| @@ -68,7 +68,7 @@ module.exports = function (Topics) { | ||||
| 			data.content = utils.rtrim(data.content); | ||||
| 		} | ||||
| 		check(data.title, meta.config.minimumTitleLength, meta.config.maximumTitleLength, 'title-too-short', 'title-too-long'); | ||||
| 		check(data.tags, meta.config.minimumTagsPerTopic, meta.config.maximumTagsPerTopic, 'not-enough-tags', 'too-many-tags'); | ||||
| 		await Topics.validateTags(data.tags, data.cid); | ||||
| 		check(data.content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long'); | ||||
|  | ||||
| 		const [categoryExists, canCreate, canTag] = await Promise.all([ | ||||
|   | ||||
| @@ -160,6 +160,8 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev | ||||
| 	topicData.posts = posts; | ||||
| 	topicData.category = category; | ||||
| 	topicData.tagWhitelist = tagWhitelist[0]; | ||||
| 	topicData.minTags = category.minTags; | ||||
| 	topicData.maxTags = category.maxTags; | ||||
| 	topicData.thread_tools = threadTools.tools; | ||||
| 	topicData.isFollowing = followData[0].following; | ||||
| 	topicData.isNotFollowing = !followData[0].following && !followData[0].ignoring; | ||||
|   | ||||
| @@ -19,7 +19,7 @@ module.exports = function (Topics) { | ||||
| 			return; | ||||
| 		} | ||||
| 		const result = await plugins.fireHook('filter:tags.filter', { tags: tags, tid: tid }); | ||||
| 		tags = _.uniq(result.tags).slice(0, meta.config.maximumTagsPerTopic || 5) | ||||
| 		tags = _.uniq(result.tags) | ||||
| 			.map(tag => utils.cleanUpTag(tag, meta.config.maximumTagLength)) | ||||
| 			.filter(tag => tag && tag.length >= (meta.config.minimumTagLength || 3)); | ||||
|  | ||||
| @@ -32,6 +32,19 @@ module.exports = function (Topics) { | ||||
| 		await Promise.all(tags.map(tag => updateTagCount(tag))); | ||||
| 	}; | ||||
|  | ||||
| 	Topics.validateTags = async function (tags, cid) { | ||||
| 		if (!Array.isArray(tags)) { | ||||
| 			throw new Error('[[error:invalid-data]]'); | ||||
| 		} | ||||
| 		tags = _.uniq(tags); | ||||
| 		const categoryData = await categories.getCategoryFields(cid, ['minTags', 'maxTags']); | ||||
| 		if (tags.length < parseInt(categoryData.minTags, 10)) { | ||||
| 			throw new Error('[[error:not-enough-tags, ' + categoryData.minTags + ']]'); | ||||
| 		} else if (tags.length > parseInt(categoryData.maxTags, 10)) { | ||||
| 			throw new Error('[[error:too-many-tags, ' + categoryData.maxTags + ']]'); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	async function filterCategoryTags(tags, tid) { | ||||
| 		const cid = await Topics.getTopicField(tid, 'cid'); | ||||
| 		const tagWhitelist = await categories.getTagWhitelist([cid]); | ||||
|   | ||||
| @@ -95,9 +95,29 @@ | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</fieldset> | ||||
| 				<fieldset> | ||||
| 					<label for="tag-whitelist">Tag Whitelist</label><br /> | ||||
| 					<input id="tag-whitelist" type="text" class="form-control" placeholder="Enter category tags here" data-name="tagWhitelist" value="" /> | ||||
| 				<fieldset class="row"> | ||||
| 					<div class="col-sm-6 col-xs-12"> | ||||
| 						<div class="form-group"> | ||||
| 							<label for="cid-min-tags"> | ||||
| 								[[admin/settings/tags:min-per-topic]] | ||||
| 							</label> | ||||
| 							<input id="cid-min-tags" type="text" class="form-control" data-name="minTags" value="{category.minTags}" /> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					<div class="col-sm-6 col-xs-12"> | ||||
| 						<div class="form-group"> | ||||
| 							<label for="cid-max-tags"> | ||||
| 								[[admin/settings/tags:max-per-topic]] | ||||
| 							</label> | ||||
| 							<input id="cid-max-tags" type="text" class="form-control" data-name="maxTags" value="{category.maxTags}" /> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</fieldset> | ||||
| 				<fieldset class="row"> | ||||
| 					<div class="col-lg-12"> | ||||
| 						<label for="tag-whitelist">Tag Whitelist</label><br /> | ||||
| 						<input id="tag-whitelist" type="text" class="form-control" placeholder="Enter category tags here" data-name="tagWhitelist" value="" /> | ||||
| 					</div> | ||||
| 				</fieldset> | ||||
| 			</div> | ||||
| 		</div> | ||||
|   | ||||
| @@ -1782,6 +1782,58 @@ describe('Topic\'s', function () { | ||||
| 			tags = await topics.getTopicTags(tid); | ||||
| 			assert.deepStrictEqual(tags, ['tag2', 'tag4', 'tag6']); | ||||
| 		}); | ||||
|  | ||||
| 		it('should respect minTags', async () => { | ||||
| 			const oldValue = meta.config.minimumTagsPerTopic; | ||||
| 			meta.config.minimumTagsPerTopic = 2; | ||||
| 			let err; | ||||
| 			try { | ||||
| 				await topics.post({ uid: adminUid, tags: ['tag4'], title: 'tag topic', content: 'topic 1 content', cid: topic.categoryId }); | ||||
| 			} catch (_err) { | ||||
| 				err = _err; | ||||
| 			} | ||||
| 			assert.equal(err.message, '[[error:not-enough-tags, ' + meta.config.minimumTagsPerTopic + ']]'); | ||||
| 			meta.config.minimumTagsPerTopic = oldValue; | ||||
| 		}); | ||||
|  | ||||
| 		it('should respect maxTags', async () => { | ||||
| 			const oldValue = meta.config.maximumTagsPerTopic; | ||||
| 			meta.config.maximumTagsPerTopic = 2; | ||||
| 			let err; | ||||
| 			try { | ||||
| 				await topics.post({ uid: adminUid, tags: ['tag1', 'tag2', 'tag3'], title: 'tag topic', content: 'topic 1 content', cid: topic.categoryId }); | ||||
| 			} catch (_err) { | ||||
| 				err = _err; | ||||
| 			} | ||||
| 			assert.equal(err.message, '[[error:too-many-tags, ' + meta.config.maximumTagsPerTopic + ']]'); | ||||
| 			meta.config.maximumTagsPerTopic = oldValue; | ||||
| 		}); | ||||
|  | ||||
| 		it('should respect minTags per category', async () => { | ||||
| 			const minTags = 2; | ||||
| 			await categories.setCategoryField(topic.categoryId, 'minTags', minTags); | ||||
| 			let err; | ||||
| 			try { | ||||
| 				await topics.post({ uid: adminUid, tags: ['tag4'], title: 'tag topic', content: 'topic 1 content', cid: topic.categoryId }); | ||||
| 			} catch (_err) { | ||||
| 				err = _err; | ||||
| 			} | ||||
| 			assert.equal(err.message, '[[error:not-enough-tags, ' + minTags + ']]'); | ||||
| 			await db.deleteObjectField('category:' + topic.categoryId, 'minTags'); | ||||
| 		}); | ||||
|  | ||||
| 		it('should respect maxTags per category', async () => { | ||||
| 			const maxTags = 2; | ||||
| 			await categories.setCategoryField(topic.categoryId, 'maxTags', maxTags); | ||||
| 			let err; | ||||
| 			try { | ||||
| 				await topics.post({ uid: adminUid, tags: ['tag1', 'tag2', 'tag3'], title: 'tag topic', content: 'topic 1 content', cid: topic.categoryId }); | ||||
| 			} catch (_err) { | ||||
| 				err = _err; | ||||
| 			} | ||||
| 			assert.equal(err.message, '[[error:too-many-tags, ' + maxTags + ']]'); | ||||
| 			await db.deleteObjectField('category:' + topic.categoryId, 'maxTags'); | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	describe('follow/unfollow', function () { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user