From c65af1998565380bd04deee915a152e04547c875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20U=C5=9Fakl=C4=B1?= Date: Wed, 11 Feb 2026 11:38:03 -0500 Subject: [PATCH] refactor: add createFieldChecker (#13973) * refactor: add createFieldChecker * refactor: use hasField in topic/data.js * refactor: use hasField in categories/data.js * test: fix category nickname logic * test: fix spec --- .../components/schemas/CategoryObject.yaml | 3 + .../read/user/userslug/categories.yaml | 2 + public/src/utils.common.js | 7 +- src/categories/data.js | 27 ++++--- src/groups/data.js | 78 +++++++++++++------ src/messaging/data.js | 5 +- src/posts/data.js | 16 ++-- src/topics/data.js | 30 +++---- test/groups.js | 28 +++++-- 9 files changed, 131 insertions(+), 65 deletions(-) diff --git a/public/openapi/components/schemas/CategoryObject.yaml b/public/openapi/components/schemas/CategoryObject.yaml index 0b138542b0..fa377cbcdd 100644 --- a/public/openapi/components/schemas/CategoryObject.yaml +++ b/public/openapi/components/schemas/CategoryObject.yaml @@ -8,6 +8,9 @@ CategoryObject: name: type: string description: The category's name/title + nickname: + type: string + description: A nickname for the category. handle: type: string description: | diff --git a/public/openapi/read/user/userslug/categories.yaml b/public/openapi/read/user/userslug/categories.yaml index f25a168f91..17454ffa09 100644 --- a/public/openapi/read/user/userslug/categories.yaml +++ b/public/openapi/read/user/userslug/categories.yaml @@ -42,6 +42,8 @@ get: type: string bgColor: type: string + backgroundImage: + type: string descriptionParsed: type: string depth: diff --git a/public/src/utils.common.js b/public/src/utils.common.js index bf9ebfcb76..7dd3a3c0e0 100644 --- a/public/src/utils.common.js +++ b/public/src/utils.common.js @@ -321,7 +321,12 @@ const utils = { } return tag; }, - + createFieldChecker: function (fields = []) { + const allFields = !fields.length; + return function hasField(field) { + return allFields || fields.includes(field); + }; + }, removePunctuation: function (str) { return str.replace(/[.,-/#!$%^&*;:{}=\-_`<>'"~()?]/g, ''); }, diff --git a/src/categories/data.js b/src/categories/data.js index bf2ddac25f..c3a5ba9b1e 100644 --- a/src/categories/data.js +++ b/src/categories/data.js @@ -95,8 +95,8 @@ module.exports = function (Categories) { }; }; -function defaultIntField(category, fields, fieldName, defaultField) { - if (!fields.length || fields.includes(fieldName)) { +function defaultIntField(category, hasField, fieldName, defaultField) { + if (hasField(fieldName)) { const useDefault = !category.hasOwnProperty(fieldName) || category[fieldName] === null || category[fieldName] === '' || @@ -111,32 +111,37 @@ function modifyCategory(category, fields) { return; } - defaultIntField(category, fields, 'minTags', 'minimumTagsPerTopic'); - defaultIntField(category, fields, 'maxTags', 'maximumTagsPerTopic'); - defaultIntField(category, fields, 'postQueue', 'postQueue'); + const hasField = utils.createFieldChecker(fields); + + defaultIntField(category, hasField, 'minTags', 'minimumTagsPerTopic'); + defaultIntField(category, hasField, 'maxTags', 'maximumTagsPerTopic'); + defaultIntField(category, hasField, 'postQueue', 'postQueue'); db.parseIntFields(category, intFields, fields); - const escapeFields = ['name', 'nickname', 'description', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'class', 'link']; + const escapeFields = [ + 'name', 'nickname', 'description', 'color', 'bgColor', + 'backgroundImage', 'imageClass', 'class', 'link', + ]; escapeFields.forEach((field) => { - if (category.hasOwnProperty(field)) { + if (hasField(field)) { category[field] = validator.escape(String(category[field] || '')); } }); - if (category.hasOwnProperty('icon')) { + if (hasField('icon')) { category.icon = category.icon || 'hidden'; } - if (category.hasOwnProperty('post_count')) { + if (hasField('post_count')) { category.totalPostCount = category.post_count; } - if (category.hasOwnProperty('topic_count')) { + if (hasField('topic_count')) { category.totalTopicCount = category.topic_count; } - if (category.description) { + if (hasField('description')) { category.descriptionParsed = category.descriptionParsed || category.description; } diff --git a/src/groups/data.js b/src/groups/data.js index 72eaaeda7d..51647e9ef5 100644 --- a/src/groups/data.js +++ b/src/groups/data.js @@ -72,38 +72,70 @@ module.exports = function (Groups) { function modifyGroup(group, fields) { if (group) { + const hasField = utils.createFieldChecker(fields); + + if (hasField('private')) { + // Default to private if not set, as groups are private by default + group.private = ([null, undefined].includes(group.private)) ? 1 : group.private; + } + db.parseIntFields(group, intFields, fields); - escapeGroupData(group); - group.userTitleEnabled = ([null, undefined].includes(group.userTitleEnabled)) ? 1 : group.userTitleEnabled; - group.labelColor = validator.escape(String(group.labelColor || '#000000')); - group.textColor = validator.escape(String(group.textColor || '#ffffff')); - group.icon = validator.escape(String(group.icon || '')); - group.createtimeISO = utils.toISOString(group.createtime); - group.private = ([null, undefined].includes(group.private)) ? 1 : group.private; - group.memberPostCids = group.memberPostCids || ''; - group.memberPostCidsArray = group.memberPostCids.split(',').map(cid => parseInt(cid, 10)).filter(Boolean); + escapeGroupData(group, hasField); - group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url']; + if (hasField('labelColor')) { + group.labelColor = validator.escape(String(group.labelColor || '#000000')); + } - group['cover:url'] = group['cover:url'] ? - prependRelativePath(group['cover:url']) : - coverPhoto.getDefaultGroupCover(group.name); + if (hasField('textColor')) { + group.textColor = validator.escape(String(group.textColor || '#ffffff')); + } - group['cover:thumb:url'] = group['cover:thumb:url'] ? - prependRelativePath(group['cover:thumb:url']) : - coverPhoto.getDefaultGroupCover(group.name); + if (hasField('icon')) { + group.icon = validator.escape(String(group.icon || '')); + } - group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%')); + if (hasField('createtime')) { + group.createtimeISO = utils.toISOString(group.createtime); + } + + if (hasField('memberPostCids')) { + group.memberPostCids = group.memberPostCids || ''; + group.memberPostCidsArray = group.memberPostCids.split(',').map(cid => parseInt(cid, 10)).filter(Boolean); + } + + if (hasField('cover:thumb:url')) { + group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url']; + + group['cover:thumb:url'] = group['cover:thumb:url'] ? + prependRelativePath(group['cover:thumb:url']) : + coverPhoto.getDefaultGroupCover(group.name); + } + + if (hasField('cover:url')) { + group['cover:url'] = group['cover:url'] ? + prependRelativePath(group['cover:url']) : + coverPhoto.getDefaultGroupCover(group.name); + } + + if (hasField('cover:position')) { + group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%')); + } } } -function escapeGroupData(group) { +function escapeGroupData(group, hasField) { if (group) { - group.nameEncoded = encodeURIComponent(group.name); - group.displayName = validator.escape(String(group.name)); - group.description = validator.escape(String(group.description || '')); - group.userTitle = validator.escape(String(group.userTitle || '')); - group.userTitleEscaped = translator.escape(group.userTitle); + if (hasField('name')) { + group.nameEncoded = encodeURIComponent(group.name); + group.displayName = validator.escape(String(group.name)); + } + if (hasField('description')) { + group.description = validator.escape(String(group.description || '')); + } + if (hasField('userTitle')) { + group.userTitle = validator.escape(String(group.userTitle || '')); + group.userTitleEscaped = translator.escape(group.userTitle); + } } } diff --git a/src/messaging/data.js b/src/messaging/data.js index 2c3257c74f..c55f944b44 100644 --- a/src/messaging/data.js +++ b/src/messaging/data.js @@ -196,11 +196,12 @@ module.exports = function (Messaging) { async function modifyMessage(message, fields, mid) { if (message) { + const hasField = utils.createFieldChecker(fields); db.parseIntFields(message, intFields, fields); - if (message.hasOwnProperty('timestamp')) { + if (hasField('timestamp')) { message.timestampISO = utils.toISOString(message.timestamp); } - if (message.hasOwnProperty('edited')) { + if (hasField('edited')) { message.editedISO = utils.toISOString(message.edited); } } diff --git a/src/posts/data.js b/src/posts/data.js index 28e6c24aaa..32e8d5443f 100644 --- a/src/posts/data.js +++ b/src/posts/data.js @@ -58,20 +58,26 @@ module.exports = function (Posts) { function modifyPost(post, fields) { if (post) { db.parseIntFields(post, intFields, fields); - if (post.hasOwnProperty('upvotes') && post.hasOwnProperty('downvotes')) { + + const hasField = utils.createFieldChecker(fields); + + if (hasField('upvotes') && hasField('downvotes')) { post.votes = post.upvotes - post.downvotes; } - if (post.hasOwnProperty('timestamp')) { + + if (hasField('timestamp')) { post.timestampISO = utils.toISOString(post.timestamp); } - if (post.hasOwnProperty('edited')) { + + if (hasField('edited')) { post.editedISO = post.edited !== 0 ? utils.toISOString(post.edited) : ''; } - if (!fields.length || fields.includes('attachments')) { + + if (hasField('attachments')) { post.attachments = (post.attachments || '').split(',').filter(Boolean); } - if (!fields.length || fields.includes('uploads')) { + if (hasField('uploads')) { try { post.uploads = post.uploads ? JSON.parse(post.uploads) : []; } catch (err) { diff --git a/src/topics/data.js b/src/topics/data.js index 91e2adbc08..03c48a08d9 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -80,13 +80,11 @@ module.exports = function (Topics) { }; }; -function escapeTitle(topicData) { +function escapeTitle(topicData, hasField) { if (topicData) { - if (topicData.title) { + if (hasField('title')) { topicData.title = translator.escape(validator.escape(topicData.title)); - } - if (topicData.titleRaw) { - topicData.titleRaw = translator.escape(topicData.titleRaw); + topicData.titleRaw = translator.escape(topicData.titleRaw || ''); } } } @@ -96,39 +94,41 @@ function modifyTopic(topic, fields) { return; } + const hasField = utils.createFieldChecker(fields); + db.parseIntFields(topic, intFields, fields); - if (topic.hasOwnProperty('title')) { + if (hasField('title')) { topic.titleRaw = topic.title; topic.title = String(topic.title); } - escapeTitle(topic); + escapeTitle(topic, hasField); - if (topic.hasOwnProperty('timestamp')) { + if (hasField('timestamp')) { topic.timestampISO = utils.toISOString(topic.timestamp); - if (!fields.length || fields.includes('scheduled')) { + if (hasField('scheduled')) { topic.scheduled = topic.timestamp > Date.now(); } } - if (topic.hasOwnProperty('lastposttime')) { + if (hasField('lastposttime')) { topic.lastposttimeISO = utils.toISOString(topic.lastposttime); } - if (topic.hasOwnProperty('pinExpiry')) { + if (hasField('pinExpiry')) { topic.pinExpiryISO = utils.toISOString(topic.pinExpiry); } - if (topic.hasOwnProperty('upvotes') && topic.hasOwnProperty('downvotes')) { + if (hasField('upvotes') && hasField('downvotes')) { topic.votes = topic.upvotes - topic.downvotes; } - if (fields.includes('teaserPid') || !fields.length) { + if (hasField('teaserPid')) { topic.teaserPid = topic.teaserPid || null; } - if (fields.includes('tags') || !fields.length) { + if (hasField('tags')) { const tags = String(topic.tags || ''); topic.tags = tags.split(',').filter(Boolean).map((tag) => { const escaped = validator.escape(String(tag)); @@ -141,7 +141,7 @@ function modifyTopic(topic, fields) { }); } - if (fields.includes('thumbs') || !fields.length) { + if (hasField('thumbs')) { try { topic.thumbs = topic.thumbs ? JSON.parse(String(topic.thumbs || '[]')) : []; } catch (e) { diff --git a/test/groups.js b/test/groups.js index 028d3f1b8c..59d88abafd 100644 --- a/test/groups.js +++ b/test/groups.js @@ -131,6 +131,18 @@ describe('Groups', () => { done(); }); }); + + it('should return only requested fields', async () => { + await Groups.create({ + name: 'groupfields', + description: 'desc', + userTitle: 'utitle', + }); + const data = await Groups.getGroupFields('groupfields', ['description', 'icon']); + assert.strictEqual(Object.keys(data).length, 2); + assert.strictEqual(data.description, 'desc'); + assert.strictEqual(data.icon, ''); + }); }); describe('.search()', () => { @@ -139,7 +151,7 @@ describe('Groups', () => { it('should return empty array if query is falsy', (done) => { Groups.search(null, {}, (err, groups) => { assert.ifError(err); - assert.equal(0, groups.length); + assert.equal(groups.length, 0); done(); }); }); @@ -147,7 +159,7 @@ describe('Groups', () => { it('should return the groups when search query is empty', (done) => { socketGroups.search({ uid: adminUid }, { query: '' }, (err, groups) => { assert.ifError(err); - assert.equal(5, groups.length); + assert.equal(groups.length, 6); done(); }); }); @@ -155,8 +167,8 @@ describe('Groups', () => { it('should return the "Test" group when searched for', (done) => { socketGroups.search({ uid: adminUid }, { query: 'test' }, (err, groups) => { assert.ifError(err); - assert.equal(2, groups.length); - assert.strictEqual('Test', groups[0].name); + assert.equal(groups.length, 2); + assert.strictEqual(groups[0].name, 'Test'); done(); }); }); @@ -164,8 +176,8 @@ describe('Groups', () => { it('should return the "Test" group when searched for and sort by member count', (done) => { Groups.search('test', { filterHidden: true, sort: 'count' }, (err, groups) => { assert.ifError(err); - assert.equal(2, groups.length); - assert.strictEqual('Test', groups[0].name); + assert.equal(groups.length, 2); + assert.strictEqual(groups[0].name, 'Test'); done(); }); }); @@ -173,8 +185,8 @@ describe('Groups', () => { it('should return the "Test" group when searched for and sort by creation time', (done) => { Groups.search('test', { filterHidden: true, sort: 'date' }, (err, groups) => { assert.ifError(err); - assert.equal(2, groups.length); - assert.strictEqual('Test', groups[1].name); + assert.equal(groups.length, 2); + assert.strictEqual(groups[1].name, 'Test'); done(); }); });