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
This commit is contained in:
Barış Uşaklı
2026-02-11 11:38:03 -05:00
committed by GitHub
parent 52a807e795
commit c65af19985
9 changed files with 131 additions and 65 deletions

View File

@@ -8,6 +8,9 @@ CategoryObject:
name: name:
type: string type: string
description: The category's name/title description: The category's name/title
nickname:
type: string
description: A nickname for the category.
handle: handle:
type: string type: string
description: | description: |

View File

@@ -42,6 +42,8 @@ get:
type: string type: string
bgColor: bgColor:
type: string type: string
backgroundImage:
type: string
descriptionParsed: descriptionParsed:
type: string type: string
depth: depth:

View File

@@ -321,7 +321,12 @@ const utils = {
} }
return tag; return tag;
}, },
createFieldChecker: function (fields = []) {
const allFields = !fields.length;
return function hasField(field) {
return allFields || fields.includes(field);
};
},
removePunctuation: function (str) { removePunctuation: function (str) {
return str.replace(/[.,-/#!$%^&*;:{}=\-_`<>'"~()?]/g, ''); return str.replace(/[.,-/#!$%^&*;:{}=\-_`<>'"~()?]/g, '');
}, },

View File

@@ -95,8 +95,8 @@ module.exports = function (Categories) {
}; };
}; };
function defaultIntField(category, fields, fieldName, defaultField) { function defaultIntField(category, hasField, fieldName, defaultField) {
if (!fields.length || fields.includes(fieldName)) { if (hasField(fieldName)) {
const useDefault = !category.hasOwnProperty(fieldName) || const useDefault = !category.hasOwnProperty(fieldName) ||
category[fieldName] === null || category[fieldName] === null ||
category[fieldName] === '' || category[fieldName] === '' ||
@@ -111,32 +111,37 @@ function modifyCategory(category, fields) {
return; return;
} }
defaultIntField(category, fields, 'minTags', 'minimumTagsPerTopic'); const hasField = utils.createFieldChecker(fields);
defaultIntField(category, fields, 'maxTags', 'maximumTagsPerTopic');
defaultIntField(category, fields, 'postQueue', 'postQueue'); defaultIntField(category, hasField, 'minTags', 'minimumTagsPerTopic');
defaultIntField(category, hasField, 'maxTags', 'maximumTagsPerTopic');
defaultIntField(category, hasField, 'postQueue', 'postQueue');
db.parseIntFields(category, intFields, fields); 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) => { escapeFields.forEach((field) => {
if (category.hasOwnProperty(field)) { if (hasField(field)) {
category[field] = validator.escape(String(category[field] || '')); category[field] = validator.escape(String(category[field] || ''));
} }
}); });
if (category.hasOwnProperty('icon')) { if (hasField('icon')) {
category.icon = category.icon || 'hidden'; category.icon = category.icon || 'hidden';
} }
if (category.hasOwnProperty('post_count')) { if (hasField('post_count')) {
category.totalPostCount = category.post_count; category.totalPostCount = category.post_count;
} }
if (category.hasOwnProperty('topic_count')) { if (hasField('topic_count')) {
category.totalTopicCount = category.topic_count; category.totalTopicCount = category.topic_count;
} }
if (category.description) { if (hasField('description')) {
category.descriptionParsed = category.descriptionParsed || category.description; category.descriptionParsed = category.descriptionParsed || category.description;
} }

View File

@@ -72,38 +72,70 @@ module.exports = function (Groups) {
function modifyGroup(group, fields) { function modifyGroup(group, fields) {
if (group) { 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); db.parseIntFields(group, intFields, fields);
escapeGroupData(group); escapeGroupData(group, hasField);
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);
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'] ? if (hasField('textColor')) {
prependRelativePath(group['cover:url']) : group.textColor = validator.escape(String(group.textColor || '#ffffff'));
coverPhoto.getDefaultGroupCover(group.name); }
group['cover:thumb:url'] = group['cover:thumb:url'] ? if (hasField('icon')) {
prependRelativePath(group['cover:thumb:url']) : group.icon = validator.escape(String(group.icon || ''));
coverPhoto.getDefaultGroupCover(group.name); }
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) { if (group) {
group.nameEncoded = encodeURIComponent(group.name); if (hasField('name')) {
group.displayName = validator.escape(String(group.name)); group.nameEncoded = encodeURIComponent(group.name);
group.description = validator.escape(String(group.description || '')); group.displayName = validator.escape(String(group.name));
group.userTitle = validator.escape(String(group.userTitle || '')); }
group.userTitleEscaped = translator.escape(group.userTitle); 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);
}
} }
} }

View File

@@ -196,11 +196,12 @@ module.exports = function (Messaging) {
async function modifyMessage(message, fields, mid) { async function modifyMessage(message, fields, mid) {
if (message) { if (message) {
const hasField = utils.createFieldChecker(fields);
db.parseIntFields(message, intFields, fields); db.parseIntFields(message, intFields, fields);
if (message.hasOwnProperty('timestamp')) { if (hasField('timestamp')) {
message.timestampISO = utils.toISOString(message.timestamp); message.timestampISO = utils.toISOString(message.timestamp);
} }
if (message.hasOwnProperty('edited')) { if (hasField('edited')) {
message.editedISO = utils.toISOString(message.edited); message.editedISO = utils.toISOString(message.edited);
} }
} }

View File

@@ -58,20 +58,26 @@ module.exports = function (Posts) {
function modifyPost(post, fields) { function modifyPost(post, fields) {
if (post) { if (post) {
db.parseIntFields(post, intFields, fields); 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; post.votes = post.upvotes - post.downvotes;
} }
if (post.hasOwnProperty('timestamp')) {
if (hasField('timestamp')) {
post.timestampISO = utils.toISOString(post.timestamp); post.timestampISO = utils.toISOString(post.timestamp);
} }
if (post.hasOwnProperty('edited')) {
if (hasField('edited')) {
post.editedISO = post.edited !== 0 ? utils.toISOString(post.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); post.attachments = (post.attachments || '').split(',').filter(Boolean);
} }
if (!fields.length || fields.includes('uploads')) { if (hasField('uploads')) {
try { try {
post.uploads = post.uploads ? JSON.parse(post.uploads) : []; post.uploads = post.uploads ? JSON.parse(post.uploads) : [];
} catch (err) { } catch (err) {

View File

@@ -80,13 +80,11 @@ module.exports = function (Topics) {
}; };
}; };
function escapeTitle(topicData) { function escapeTitle(topicData, hasField) {
if (topicData) { if (topicData) {
if (topicData.title) { if (hasField('title')) {
topicData.title = translator.escape(validator.escape(topicData.title)); topicData.title = translator.escape(validator.escape(topicData.title));
} topicData.titleRaw = translator.escape(topicData.titleRaw || '');
if (topicData.titleRaw) {
topicData.titleRaw = translator.escape(topicData.titleRaw);
} }
} }
} }
@@ -96,39 +94,41 @@ function modifyTopic(topic, fields) {
return; return;
} }
const hasField = utils.createFieldChecker(fields);
db.parseIntFields(topic, intFields, fields); db.parseIntFields(topic, intFields, fields);
if (topic.hasOwnProperty('title')) { if (hasField('title')) {
topic.titleRaw = topic.title; topic.titleRaw = topic.title;
topic.title = String(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); topic.timestampISO = utils.toISOString(topic.timestamp);
if (!fields.length || fields.includes('scheduled')) { if (hasField('scheduled')) {
topic.scheduled = topic.timestamp > Date.now(); topic.scheduled = topic.timestamp > Date.now();
} }
} }
if (topic.hasOwnProperty('lastposttime')) { if (hasField('lastposttime')) {
topic.lastposttimeISO = utils.toISOString(topic.lastposttime); topic.lastposttimeISO = utils.toISOString(topic.lastposttime);
} }
if (topic.hasOwnProperty('pinExpiry')) { if (hasField('pinExpiry')) {
topic.pinExpiryISO = utils.toISOString(topic.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; topic.votes = topic.upvotes - topic.downvotes;
} }
if (fields.includes('teaserPid') || !fields.length) { if (hasField('teaserPid')) {
topic.teaserPid = topic.teaserPid || null; topic.teaserPid = topic.teaserPid || null;
} }
if (fields.includes('tags') || !fields.length) { if (hasField('tags')) {
const tags = String(topic.tags || ''); const tags = String(topic.tags || '');
topic.tags = tags.split(',').filter(Boolean).map((tag) => { topic.tags = tags.split(',').filter(Boolean).map((tag) => {
const escaped = validator.escape(String(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 { try {
topic.thumbs = topic.thumbs ? JSON.parse(String(topic.thumbs || '[]')) : []; topic.thumbs = topic.thumbs ? JSON.parse(String(topic.thumbs || '[]')) : [];
} catch (e) { } catch (e) {

View File

@@ -131,6 +131,18 @@ describe('Groups', () => {
done(); 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()', () => { describe('.search()', () => {
@@ -139,7 +151,7 @@ describe('Groups', () => {
it('should return empty array if query is falsy', (done) => { it('should return empty array if query is falsy', (done) => {
Groups.search(null, {}, (err, groups) => { Groups.search(null, {}, (err, groups) => {
assert.ifError(err); assert.ifError(err);
assert.equal(0, groups.length); assert.equal(groups.length, 0);
done(); done();
}); });
}); });
@@ -147,7 +159,7 @@ describe('Groups', () => {
it('should return the groups when search query is empty', (done) => { it('should return the groups when search query is empty', (done) => {
socketGroups.search({ uid: adminUid }, { query: '' }, (err, groups) => { socketGroups.search({ uid: adminUid }, { query: '' }, (err, groups) => {
assert.ifError(err); assert.ifError(err);
assert.equal(5, groups.length); assert.equal(groups.length, 6);
done(); done();
}); });
}); });
@@ -155,8 +167,8 @@ describe('Groups', () => {
it('should return the "Test" group when searched for', (done) => { it('should return the "Test" group when searched for', (done) => {
socketGroups.search({ uid: adminUid }, { query: 'test' }, (err, groups) => { socketGroups.search({ uid: adminUid }, { query: 'test' }, (err, groups) => {
assert.ifError(err); assert.ifError(err);
assert.equal(2, groups.length); assert.equal(groups.length, 2);
assert.strictEqual('Test', groups[0].name); assert.strictEqual(groups[0].name, 'Test');
done(); done();
}); });
}); });
@@ -164,8 +176,8 @@ describe('Groups', () => {
it('should return the "Test" group when searched for and sort by member count', (done) => { it('should return the "Test" group when searched for and sort by member count', (done) => {
Groups.search('test', { filterHidden: true, sort: 'count' }, (err, groups) => { Groups.search('test', { filterHidden: true, sort: 'count' }, (err, groups) => {
assert.ifError(err); assert.ifError(err);
assert.equal(2, groups.length); assert.equal(groups.length, 2);
assert.strictEqual('Test', groups[0].name); assert.strictEqual(groups[0].name, 'Test');
done(); done();
}); });
}); });
@@ -173,8 +185,8 @@ describe('Groups', () => {
it('should return the "Test" group when searched for and sort by creation time', (done) => { it('should return the "Test" group when searched for and sort by creation time', (done) => {
Groups.search('test', { filterHidden: true, sort: 'date' }, (err, groups) => { Groups.search('test', { filterHidden: true, sort: 'date' }, (err, groups) => {
assert.ifError(err); assert.ifError(err);
assert.equal(2, groups.length); assert.equal(groups.length, 2);
assert.strictEqual('Test', groups[1].name); assert.strictEqual(groups[1].name, 'Test');
done(); done();
}); });
}); });