feat: add invitedBy to user info page, closes #13972, closes #13997

This commit is contained in:
Barış Soner Uşaklı
2026-02-17 19:58:10 -05:00
parent 2015777fd1
commit 1dae3d222f
7 changed files with 56 additions and 14 deletions

View File

@@ -107,10 +107,10 @@
"nodebb-plugin-spam-be-gone": "2.3.2",
"nodebb-plugin-web-push": "0.7.6",
"nodebb-rewards-essentials": "1.0.2",
"nodebb-theme-harmony": "2.2.15",
"nodebb-theme-harmony": "2.2.16",
"nodebb-theme-lavender": "7.1.21",
"nodebb-theme-peace": "2.2.51",
"nodebb-theme-persona": "14.2.9",
"nodebb-theme-persona": "14.2.10",
"nodebb-widget-essentials": "7.0.42",
"nodemailer": "8.0.1",
"nprogress": "0.2.0",

View File

@@ -194,6 +194,7 @@
"sso.dissociate-confirm-title": "Confirm Dissociation",
"sso.dissociate-confirm": "Are you sure you wish to dissociate your account from %1?",
"info.invited-by": "Invited by",
"info.latest-flags": "Latest Flags",
"info.profile": "Profile",
"info.post": "Post",

View File

@@ -20,6 +20,23 @@ get:
- $ref: ../../../components/schemas/UserObject.yaml#/UserObjectFull
- type: object
properties:
invitedBy:
type: object
nullable: true
properties:
username:
type: string
userslug:
type: string
picture:
type: string
uid:
type: number
icon:text:
type: string
icon:bgColor:
type: string
history:
type: object
properties:

View File

@@ -11,6 +11,14 @@ define('forum/account/info', ['forum/account/header', 'alerts', 'forum/account/s
};
function handleModerationNote() {
const noteList = $('[component="account/moderation-note/list"]');
function adjustTextareaHeight(textarea) {
textarea.css({
height: textarea.prop('scrollHeight') + 'px',
});
}
$('[component="account/save-moderation-note"]').on('click', function () {
const noteEl = $('[component="account/moderation-note"]');
const note = noteEl.val();
@@ -24,23 +32,24 @@ define('forum/account/info', ['forum/account/header', 'alerts', 'forum/account/s
noteEl.val('');
app.parseAndTranslate('account/info', 'moderationNotes', { moderationNotes: notes }, function (html) {
$('[component="account/moderation-note/list"]').prepend(html);
noteList.prepend(html);
html.find('.timeago').timeago();
});
});
});
$('[component="account/moderation-note/edit"]').on('click', function () {
noteList.on('click', '[component="account/moderation-note/edit"]', function () {
const parent = $(this).parents('[data-id]');
const contentArea = parent.find('[component="account/moderation-note/content-area"]');
const editArea = parent.find('[component="account/moderation-note/edit-area"]');
contentArea.addClass('hidden');
editArea.removeClass('hidden');
adjustTextareaHeight(editArea.find('textarea'));
editArea.find('textarea').trigger('focus').putCursorAtEnd();
});
$('[component="account/moderation-note/save-edit"]').on('click', function () {
noteList.on('click', '[component="account/moderation-note/save-edit"]', function () {
const parent = $(this).parents('[data-id]');
const contentArea = parent.find('[component="account/moderation-note/content-area"]');
const editArea = parent.find('[component="account/moderation-note/edit-area"]');
@@ -63,20 +72,13 @@ define('forum/account/info', ['forum/account/header', 'alerts', 'forum/account/s
});
});
$('[component="account/moderation-note/cancel-edit"]').on('click', function () {
noteList.on('click', '[component="account/moderation-note/cancel-edit"]', function () {
const parent = $(this).parents('[data-id]');
const contentArea = parent.find('[component="account/moderation-note/content-area"]');
const editArea = parent.find('[component="account/moderation-note/edit-area"]');
contentArea.removeClass('hidden');
editArea.addClass('hidden');
});
$('[component="account/moderation-note/edit-area"] textarea').each((i, el) => {
const $el = $(el);
$el.css({
height: $el.prop('scrollHeight') + 'px',
}).parent().addClass('hidden');
});
}
return Info;

View File

@@ -15,12 +15,13 @@ infoController.get = async function (req, res) {
const payload = res.locals.userData;
const { username, userslug } = payload;
const [isPrivileged, history, sessions, usernames, emails] = await Promise.all([
const [isPrivileged, history, sessions, usernames, emails, invitedBy] = await Promise.all([
user.isPrivileged(req.uid),
user.getModerationHistory(res.locals.uid),
user.auth.getSessions(res.locals.uid, req.sessionID),
user.getHistory(`user:${res.locals.uid}:usernames`),
user.getHistory(`user:${res.locals.uid}:emails`),
getInvitedBy(res.locals.uid),
]);
const notes = await getNotes({ uid: res.locals.uid, isPrivileged }, start, stop);
@@ -29,6 +30,7 @@ infoController.get = async function (req, res) {
payload.sessions = sessions;
payload.usernames = usernames;
payload.emails = emails;
payload.invitedBy = invitedBy;
if (isPrivileged) {
payload.moderationNotes = notes.notes;
@@ -51,3 +53,12 @@ async function getNotes({ uid, isPrivileged }, start, stop) {
]);
return { notes: notes, count: count };
}
async function getInvitedBy(uid) {
const invitedBy = await user.getUserField(uid, 'invitedBy');
if (!invitedBy) {
return null;
}
const inviterData = await user.getUserFields(invitedBy, ['uid', 'username', 'userslug', 'picture']);
return inviterData.userslug ? inviterData : null;
};

View File

@@ -59,6 +59,7 @@ async function registerAndLoginUser(req, res, userData) {
await Promise.all([
user.confirmIfInviteEmailIsUsed(userData.token, userData.email, uid),
user.joinGroupsFromInvitation(uid, userData.token),
user.setInviterUid(uid, userData.token),
]);
}
await user.deleteInvitationKey(userData.email, userData.token);

View File

@@ -83,6 +83,16 @@ module.exports = function (User) {
return await db.getObjectField(`invitation:token:${token}`, 'email');
};
User.setInviterUid = async function (uid, token) {
if (!token) {
return;
}
const inviterUid = await db.getObjectField(`invitation:token:${token}`, 'inviter');
if (inviterUid) {
await User.setUserField(uid, 'invitedBy', inviterUid);
}
};
User.confirmIfInviteEmailIsUsed = async function (token, enteredEmail, uid) {
if (!enteredEmail) {
return;