Skip to content

Commit

Permalink
feat: Create back-end for the new users panel page (#31898)
Browse files Browse the repository at this point in the history
  • Loading branch information
rique223 authored Apr 20, 2024
1 parent 17bc631 commit 845fd64
Show file tree
Hide file tree
Showing 12 changed files with 554 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changeset/fifty-cups-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/rest-typings": minor
---

Created a new endpoint to get a filtered and paginated list of users.
6 changes: 6 additions & 0 deletions .changeset/pink-parrots-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/rest-typings": minor
---

Created a new endpoint to resend the welcome email to a given user
97 changes: 96 additions & 1 deletion apps/meteor/app/api/server/lib/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IUser } from '@rocket.chat/core-typings';
import { Users, Subscriptions } from '@rocket.chat/models';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import type { Mongo } from 'meteor/mongo';
import type { Filter } from 'mongodb';
import type { Filter, RootFilterOperators } from 'mongodb';

import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { settings } from '../../../settings/server';
Expand Down Expand Up @@ -119,3 +119,98 @@ export function getNonEmptyQuery<T extends IUser>(query: Mongo.Query<T> | undefi

return { ...defaultQuery, ...query };
}

type FindPaginatedUsersByStatusProps = {
uid: string;
offset: number;
count: number;
sort: Record<string, 1 | -1>;
status: 'active' | 'deactivated';
roles: string[] | null;
searchTerm: string;
hasLoggedIn: boolean;
type: string;
};

export async function findPaginatedUsersByStatus({
uid,
offset,
count,
sort,
status,
roles,
searchTerm,
hasLoggedIn,
type,
}: FindPaginatedUsersByStatusProps) {
const projection = {
name: 1,
username: 1,
emails: 1,
roles: 1,
status: 1,
active: 1,
avatarETag: 1,
lastLogin: 1,
type: 1,
reason: 1,
};

const actualSort: Record<string, 1 | -1> = sort || { username: 1 };
if (sort?.status) {
actualSort.active = sort.status;
}
if (sort?.name) {
actualSort.nameInsensitive = sort.name;
}
const match: Filter<IUser & RootFilterOperators<IUser>> = {};
switch (status) {
case 'active':
match.active = true;
break;
case 'deactivated':
match.active = false;
break;
}

if (hasLoggedIn !== undefined) {
match.lastLogin = { $exists: hasLoggedIn };
}

if (type) {
match.type = type;
}

const canSeeAllUserInfo = await hasPermissionAsync(uid, 'view-full-other-user-info');

match.$or = [
...(canSeeAllUserInfo ? [{ 'emails.address': { $regex: escapeRegExp(searchTerm || ''), $options: 'i' } }] : []),
{
username: { $regex: escapeRegExp(searchTerm || ''), $options: 'i' },
},
{
name: { $regex: escapeRegExp(searchTerm || ''), $options: 'i' },
},
];
if (roles?.length && !roles.includes('all')) {
match.roles = { $in: roles };
}
const { cursor, totalCount } = await Users.findPaginated(
{
...match,
},
{
sort: actualSort,
skip: offset,
limit: count,
projection,
},
);
const [users, total] = await Promise.all([cursor.toArray(), totalCount]);
return {
users,
count: users.length,
offset,
total,
};
}
5 changes: 2 additions & 3 deletions apps/meteor/app/api/server/v1/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP
import { passwordPolicy } from '../../../lib/server';
import { settings } from '../../../settings/server';
import { getDefaultUserFields } from '../../../utils/server/functions/getDefaultUserFields';
import { isSMTPConfigured } from '../../../utils/server/functions/isSMTPConfigured';
import { getURL } from '../../../utils/server/getURL';
import { API } from '../api';
import { getLoggedInUser } from '../helpers/getLoggedInUser';
Expand Down Expand Up @@ -636,9 +637,7 @@ API.v1.addRoute(
{ authRequired: true },
{
async get() {
const isMailURLSet = !(process.env.MAIL_URL === 'undefined' || process.env.MAIL_URL === undefined);
const isSMTPConfigured = Boolean(settings.get('SMTP_Host')) || isMailURLSet;
return API.v1.success({ isSMTPConfigured });
return API.v1.success({ isSMTPConfigured: isSMTPConfigured() });
},
},
);
Expand Down
59 changes: 58 additions & 1 deletion apps/meteor/app/api/server/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
isUserSetActiveStatusParamsPOST,
isUserDeactivateIdleParamsPOST,
isUsersInfoParamsGetProps,
isUsersListStatusProps,
isUsersSendWelcomeEmailProps,
isUserRegisterParamsPOST,
isUserLogoutParamsPOST,
isUsersListTeamsProps,
Expand All @@ -24,6 +26,7 @@ import type { Filter } from 'mongodb';

import { i18n } from '../../../../server/lib/i18n';
import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey';
import { sendWelcomeEmail } from '../../../../server/lib/sendWelcomeEmail';
import { saveUserPreferences } from '../../../../server/methods/saveUserPreferences';
import { getUserForCheck, emailCheck } from '../../../2fa/server/code';
import { resetTOTP } from '../../../2fa/server/functions/resetTOTP';
Expand All @@ -49,7 +52,7 @@ import { getUserFromParams } from '../helpers/getUserFromParams';
import { isUserFromParams } from '../helpers/isUserFromParams';
import { getUploadFormData } from '../lib/getUploadFormData';
import { isValidQuery } from '../lib/isValidQuery';
import { findUsersToAutocomplete, getInclusiveFields, getNonEmptyFields, getNonEmptyQuery } from '../lib/users';
import { findPaginatedUsersByStatus, findUsersToAutocomplete, getInclusiveFields, getNonEmptyFields, getNonEmptyQuery } from '../lib/users';

API.v1.addRoute(
'users.getAvatar',
Expand Down Expand Up @@ -556,6 +559,60 @@ API.v1.addRoute(
},
);

API.v1.addRoute(
'users.listByStatus',
{
authRequired: true,
validateParams: isUsersListStatusProps,
permissionsRequired: ['view-d-room'],
},
{
async get() {
if (
settings.get('API_Apply_permission_view-outside-room_on_users-list') &&
!(await hasPermissionAsync(this.userId, 'view-outside-room'))
) {
return API.v1.unauthorized();
}

const { offset, count } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();
const { status, hasLoggedIn, type, roles, searchTerm } = this.queryParams;

return API.v1.success(
await findPaginatedUsersByStatus({
uid: this.userId,
offset,
count,
sort,
status,
roles,
searchTerm,
hasLoggedIn,
type,
}),
);
},
},
);

API.v1.addRoute(
'users.sendWelcomeEmail',
{
authRequired: true,
validateParams: isUsersSendWelcomeEmailProps,
permissionsRequired: ['send-mail'],
},
{
async post() {
const { email } = this.bodyParams;
await sendWelcomeEmail(email);

return API.v1.success();
},
},
);

API.v1.addRoute(
'users.register',
{
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/app/utils/server/functions/isSMTPConfigured.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { settings } from '../../../settings/server';

export const isSMTPConfigured = (): boolean => {
const isMailURLSet = !(process.env.MAIL_URL === 'undefined' || process.env.MAIL_URL === undefined);
return Boolean(settings.get('SMTP_Host')) || isMailURLSet;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { IRole, IUser } from '@rocket.chat/core-typings';
import type { IRole, IUser, Serialized } from '@rocket.chat/core-typings';
import { Box } from '@rocket.chat/fuselage';
import type { DefaultUserInfo } from '@rocket.chat/rest-typings';
import { capitalize } from '@rocket.chat/string-helpers';
import { UserAvatar } from '@rocket.chat/ui-avatar';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
Expand All @@ -11,7 +12,7 @@ import { Roles } from '../../../../../app/models/client';
import { GenericTableRow, GenericTableCell } from '../../../../components/GenericTable';

type UsersTableRowProps = {
user: Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'roles' | 'emails' | 'active' | 'avatarETag'>;
user: Serialized<DefaultUserInfo>;
onClick: (id: IUser['_id']) => void;
mediaQuery: boolean;
};
Expand Down
43 changes: 43 additions & 0 deletions apps/meteor/server/lib/sendWelcomeEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import * as Mailer from '../../app/mailer/server/api';
import { settings } from '../../app/settings/server';
import { isSMTPConfigured } from '../../app/utils/server/functions/isSMTPConfigured';

export async function sendWelcomeEmail(to: string): Promise<void> {
if (!isSMTPConfigured()) {
throw new Meteor.Error('error-email-send-failed', 'SMTP is not configured', {
method: 'sendWelcomeEmail',
});
}

const email = to.trim();

const user = await Users.findOneByEmailAddress(email, { projection: { _id: 1 } });

if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'sendWelcomeEmail',
});
}

try {
let html = '';
Mailer.getTemplate('Accounts_UserAddedEmail_Email', (template) => {
html = template;
});

await Mailer.send({
to: email,
from: settings.get('From_Email'),
subject: settings.get('Accounts_UserAddedEmail_Subject'),
html,
});
} catch (error: any) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${error.message}`, {
method: 'sendWelcomeEmail',
message: error.message,
});
}
}
Loading

0 comments on commit 845fd64

Please sign in to comment.