feat: move website/location fields into custom user fields

This commit is contained in:
Barış Soner Uşaklı
2024-11-25 18:29:48 -05:00
parent 206613dd63
commit 669c9c5027
14 changed files with 73 additions and 59 deletions

View File

@@ -107,10 +107,10 @@
"nodebb-plugin-ntfy": "1.7.7",
"nodebb-plugin-spam-be-gone": "2.2.2",
"nodebb-rewards-essentials": "1.0.0",
"nodebb-theme-harmony": "1.2.87",
"nodebb-theme-harmony": "1.2.88",
"nodebb-theme-lavender": "7.1.16",
"nodebb-theme-peace": "2.2.21",
"nodebb-theme-persona": "13.3.53",
"nodebb-theme-peace": "2.2.22",
"nodebb-theme-persona": "13.3.54",
"nodebb-widget-essentials": "7.0.31",
"nodemailer": "6.9.16",
"nprogress": "0.2.0",

View File

@@ -20,7 +20,6 @@
"min-rep-chat": "Minimum reputation to send chat messages",
"min-rep-post-links": "Minimum reputation to post links",
"min-rep-flag": "Minimum reputation to flag posts",
"min-rep-website": "Minimum reputation to add \"Website\" to user profile",
"min-rep-aboutme": "Minimum reputation to add \"About me\" to user profile",
"min-rep-signature": "Minimum reputation to add \"Signature\" to user profile",
"min-rep-profile-picture": "Minimum reputation to add \"Profile Picture\" to user profile",

View File

@@ -211,6 +211,7 @@
"not-enough-reputation-custom-field": "You need %1 reputation for %2",
"custom-user-field-value-too-long": "Custom field value too long, %1",
"custom-user-field-select-value-invalid": "Custom field selected option is invalid, %1",
"custom-user-field-invalid-text": "Custom field text is invalid, %1",
"custom-user-field-invalid-link": "Custom field link is invalid, %1",
"custom-user-field-invalid-number": "Custom field number is invalid, %1",
"custom-user-field-invalid-date": "Custom field date is invalid, %1",

View File

@@ -41,19 +41,11 @@ UserObject:
type: string
description: This is either username or fullname depending on forum and user settings
example: Dragon Fruit
location:
type: string
example: 'Toronto, Canada'
nullable: true
birthday:
type: string
description: A birthdate given in an ISO format parseable by the Date object
example: 03/27/2020
nullable: true
website:
type: string
example: 'https://example.org'
nullable: true
aboutme:
type: string
example: |
@@ -172,9 +164,7 @@ UserObject:
- joindate
- lastonline
- picture
- location
- birthday
- website
- aboutme
- signature
- uploadedpicture
@@ -245,16 +235,10 @@ UserObjectFull:
type: string
description: This is either username or fullname depending on forum and user settings
example: Dragon Fruit
location:
type: string
example: 'Toronto, Canada'
birthday:
type: string
description: A birthdate given in an ISO format parseable by the Date object
example: 03/27/2020
website:
type: string
example: 'https://example.org'
aboutme:
type: string
example: |
@@ -508,10 +492,6 @@ UserObjectFull:
- name
- visibility
- public
websiteLink:
type: string
websiteName:
type: string
username:disableEdit:
type: number
email:disableEdit:

View File

@@ -15,6 +15,9 @@ get:
type: array
items:
$ref: ../../../components/schemas/UserObject.yaml#/UserObjectACP
customUserFields:
type: array
description: array of custom user fields
page:
type: number
pageCount:

View File

@@ -37,8 +37,6 @@ get:
type: boolean
allowAccountDelete:
type: boolean
allowWebsite:
type: boolean
allowAboutMe:
type: boolean
allowSignature:

View File

@@ -43,12 +43,15 @@ define('admin/manage/users', [
{ label: '[[admin/manage/users:export-field-followercount]]', field: 'followerCount', selected: false },
{ label: '[[admin/manage/users:export-field-followingcount]]', field: 'followingCount', selected: false },
{ label: '[[admin/manage/users:export-field-fullname]]', field: 'fullname', selected: false },
{ label: '[[admin/manage/users:export-field-website]]', field: 'website', selected: false },
{ label: '[[admin/manage/users:export-field-location]]', field: 'location', selected: false },
{ label: '[[admin/manage/users:export-field-birthday]]', field: 'birthday', selected: false },
{ label: '[[admin/manage/users:export-field-signature]]', field: 'signature', selected: false },
{ label: '[[admin/manage/users:export-field-aboutme]]', field: 'aboutme', selected: false },
];
].concat(ajaxify.data.customUserFields.map(field => ({
label: field.name,
field: field.key,
selected: false,
})));
const options = defaultFields.map((field, i) => (`
<div class="form-check mb-2">
<input data-field="${field.field}" class="form-check-input" type="checkbox" id="option-${i}" ${field.selected ? 'checked' : ''}>

View File

@@ -38,7 +38,6 @@ editController.get = async function (req, res, next) {
userData.maximumProfileImageSize = meta.config.maximumProfileImageSize;
userData.allowMultipleBadges = meta.config.allowMultipleBadges === 1;
userData.allowAccountDelete = meta.config.allowAccountDelete === 1;
userData.allowWebsite = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:website'];
userData.allowAboutMe = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:aboutme'];
userData.allowSignature = canUseSignature && (!isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:signature']);
userData.profileImageDimension = meta.config.profileImageDimension;

View File

@@ -104,12 +104,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
userData.banned = Boolean(userData.banned);
userData.muted = parseInt(userData.mutedUntil, 10) > Date.now();
userData.website = escape(userData.website);
userData.websiteLink = !userData.website.startsWith('http') ? `http://${userData.website}` : userData.website;
userData.websiteName = userData.website.replace(validator.escape('http://'), '').replace(validator.escape('https://'), '');
userData.fullname = escape(userData.fullname);
userData.location = escape(userData.location);
userData.signature = escape(userData.signature);
userData.birthday = validator.escape(String(userData.birthday || ''));
userData.moderationNote = validator.escape(String(userData.moderationNote || ''));
@@ -148,6 +143,9 @@ helpers.getCustomUserFields = async function (userData) {
if (f.type === 'select-multi' && userValue) {
userValue = JSON.parse(userValue || '[]');
}
if (f.type === 'input-link' && userValue) {
f.linkValue = validator.escape(String(userValue.replace('http://', '').replace('https://', '')));
}
f['select-options'] = f['select-options'].split('\n').filter(Boolean).map(
opt => ({
value: opt,

View File

@@ -94,9 +94,10 @@ async function getUsers(req, res) {
const set = buildSet();
const uids = await getUids(set);
const [count, users] = await Promise.all([
const [count, users, customUserFields] = await Promise.all([
getCount(set),
loadUserInfo(req.uid, uids),
getCustomUserFields(),
]);
await render(req, res, {
@@ -106,9 +107,15 @@ async function getUsers(req, res) {
resultsPerPage: resultsPerPage,
reverse: reverse,
sortBy: sortBy,
customUserFields,
});
}
async function getCustomUserFields() {
const keys = await db.getSortedSetRange('user-custom-fields', 0, -1);
return (await db.getObjects(keys.map(k => `user-custom-field:${k}`))).filter(Boolean);
}
usersController.search = async function (req, res) {
const sortDirection = req.query.sortDirection || 'desc';
const reverse = sortDirection === 'desc';

View File

@@ -0,0 +1,44 @@
/* eslint-disable no-await-in-loop */
'use strict';
const db = require('../../database');
module.exports = {
name: 'Add website and location as custom profile fields',
timestamp: Date.UTC(2024, 10, 25),
method: async function () {
const minRepWebsite = parseInt(await db.getObjectField('config', 'min:rep:website'), 10) || 0;
const website = {
icon: 'fa-solid fa-globe',
key: 'website',
'min:rep': minRepWebsite,
name: '[[user:website]]',
'select-options': '',
type: 'input-link',
};
const location = {
icon: 'fa-solid fa-map-pin',
key: 'location',
'min:rep': 0,
name: '[[user:location]]',
'select-options': '',
type: 'input-text',
};
await db.sortedSetAdd(
`user-custom-fields`,
[0, 1],
['website', 'location']
);
await db.setObjectBulk([
[`user-custom-field:website`, website],
[`user-custom-field:location`, location],
]);
await db.deleteObjectField('config', 'min:rep:website');
},
};

View File

@@ -21,7 +21,7 @@ const intFields = [
module.exports = function (User) {
const fieldWhitelist = [
'uid', 'username', 'userslug', 'email', 'email:confirmed', 'joindate',
'lastonline', 'picture', 'icon:bgColor', 'fullname', 'location', 'birthday', 'website',
'lastonline', 'picture', 'icon:bgColor', 'fullname', 'birthday',
'aboutme', 'signature', 'uploadedpicture', 'profileviews', 'reputation',
'postcount', 'topiccount', 'lastposttime', 'banned', 'banned:expire',
'status', 'flags', 'followerCount', 'followingCount', 'cover:url',

View File

@@ -77,11 +77,9 @@ module.exports = function (User) {
async function validateData(callerUid, data) {
await isEmailValid(data);
await isUsernameAvailable(data, data.uid);
await isWebsiteValid(callerUid, data);
await isAboutMeValid(callerUid, data);
await isSignatureValid(callerUid, data);
isFullnameValid(data);
isLocationValid(data);
isBirthdayValid(data);
isGroupTitleValid(data);
await validateCustomFields(data);
@@ -113,6 +111,10 @@ module.exports = function (User) {
throw new Error(tx.compile(
'error:custom-user-field-invalid-number', field.name
));
} else if (value && type === 'input-text' && validator.isURL(value)) {
throw new Error(tx.compile(
'error:custom-user-field-invalid-text', field.name
));
} else if (value && type === 'input-date' && !validator.isDate(value)) {
throw new Error(tx.compile(
'error:custom-user-field-invalid-date', field.name
@@ -197,16 +199,6 @@ module.exports = function (User) {
}
User.checkUsername = async username => isUsernameAvailable({ username });
async function isWebsiteValid(callerUid, data) {
if (!data.website) {
return;
}
if (data.website.length > 255) {
throw new Error('[[error:invalid-website]]');
}
await User.checkMinReputation(callerUid, data.uid, 'min:rep:website');
}
async function isAboutMeValid(callerUid, data) {
if (!data.aboutme) {
return;
@@ -235,12 +227,6 @@ module.exports = function (User) {
}
}
function isLocationValid(data) {
if (data.location && (validator.isURL(data.location) || data.location.length > 255)) {
throw new Error('[[error:invalid-location]]');
}
}
function isBirthdayValid(data) {
if (!data.birthday) {
return;

View File

@@ -73,10 +73,6 @@
<label class="form-label" for="min:rep:flag">[[admin/settings/reputation:min-rep-flag]]</label>
<input type="number" class="form-control" placeholder="0" data-field="min:rep:flag" id="min:rep:flag">
</div>
<div class="mb-3">
<label class="form-label" for="min:rep:website">[[admin/settings/reputation:min-rep-website]]</label>
<input type="number" class="form-control" placeholder="0" data-field="min:rep:website" id="min:rep:website">
</div>
<div class="mb-3">
<label class="form-label" for="min:rep:aboutme">[[admin/settings/reputation:min-rep-aboutme]]</label>
<input type="number" class="form-control" placeholder="0" data-field="min:rep:aboutme" id="min:rep:aboutme">