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:
type: string
description: The category's name/title
nickname:
type: string
description: A nickname for the category.
handle:
type: string
description: |

View File

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

View File

@@ -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, '');
},

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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();
});
});