Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Create back-end for the new users panel page #31898

Merged
merged 30 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
35a3b76
refactor: :recycle: Create isSMTPConfigured helper
rique223 Mar 5, 2024
c7ea10e
feat: :sparkles: Create users.listByStatys and users.sendWelcomeEmail…
rique223 Mar 5, 2024
ed5cfec
test: :white_check_mark: Create tests for the users.listByStatus and …
rique223 Mar 5, 2024
4b15497
Typecheck
rique223 Mar 5, 2024
ab3f578
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 6, 2024
f5d3068
Reviews first part
rique223 Mar 8, 2024
f3e1731
Reviews second part
rique223 Mar 8, 2024
1208a88
Reviews third part
rique223 Mar 8, 2024
60876b8
Review
rique223 Mar 14, 2024
3b91f00
Create fifty-cups-sort.md
rique223 Mar 14, 2024
fe4b650
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 15, 2024
6b2ab3e
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 18, 2024
ae7e828
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 19, 2024
a6d49b2
Review
rique223 Mar 19, 2024
eea8f9c
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 20, 2024
1010637
Create pink-parrots-end.md
rique223 Mar 21, 2024
19a12b4
refactor: ♻️ Refactor listUsersByStatus endpoint to be more flexible …
rique223 Mar 22, 2024
8923dca
Update listByStatus typing
rique223 Mar 22, 2024
fbb307b
Update fifty-cups-sort.md
rique223 Apr 1, 2024
afd5108
Update pink-parrots-end.md
rique223 Apr 1, 2024
fe43f5b
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Apr 3, 2024
ccae8e3
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Apr 8, 2024
ff2f272
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Apr 16, 2024
9ec14f7
Merge branch 'develop' into feat/new-user-panel-backend
kodiakhq[bot] Apr 16, 2024
9899bd8
Merge branch 'develop' into feat/new-user-panel-backend
kodiakhq[bot] Apr 16, 2024
dfe2139
Merge branch 'develop' into feat/new-user-panel-backend
kodiakhq[bot] Apr 16, 2024
b22e33d
Merge branch 'develop' into feat/new-user-panel-backend
kodiakhq[bot] Apr 16, 2024
7e3441a
Merge branch 'develop' into feat/new-user-panel-backend
tassoevan Apr 17, 2024
c172aca
Merge branch 'develop' into feat/new-user-panel-backend
tassoevan Apr 18, 2024
d203d48
Merge branch 'develop' into feat/new-user-panel-backend
kodiakhq[bot] Apr 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 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,95 @@ export function getNonEmptyQuery<T extends IUser>(query: Mongo.Query<T> | undefi

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

type findPaginatedUsersByStatusProps = {
rique223 marked this conversation as resolved.
Show resolved Hide resolved
uid: string;
offset: number;
count: number;
sort: Record<string, 1 | -1>;
status: 'active' | 'all' | 'deactivated' | 'pending';
roles: string[] | null;
searchTerm: string;
};

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

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;
match.lastLogin = { $exists: true };
break;
case 'all':
break;
case 'deactivated':
match.active = false;
match.lastLogin = { $exists: true };
break;
case 'pending':
match.lastLogin = { $exists: false };
match.type = { $nin: ['bot', 'app'] };
projection.reason = 1;
break;
}

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 @@ -27,6 +27,7 @@ import { passwordPolicy } from '../../../lib/server';
import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
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 @@ -634,9 +635,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
57 changes: 56 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 @@ -48,7 +51,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 @@ -555,6 +558,58 @@ 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, roles, searchTerm } = this.queryParams;

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

API.v1.addRoute(
'users.sendWelcomeEmail',
rique223 marked this conversation as resolved.
Show resolved Hide resolved
{
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 { PickedUser } 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<PickedUser>;
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
Loading