Skip to content

Commit 3e0e494

Browse files
committed
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat/pdf-export
2 parents 5249f57 + c992f7d commit 3e0e494

File tree

71 files changed

+3235
-443
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+3235
-443
lines changed

.changeset/pretty-jeans-warn.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@rocket.chat/apps-engine': minor
3+
'@rocket.chat/meteor': minor
4+
---
5+
6+
Fix an issue where action buttons registered by apps would be displayed even if their apps were disabled
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': patch
3+
---
4+
5+
Fixes an issue where the slash commands options are not truncating correctly in small screen sizes

.changeset/shaggy-moles-film.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@rocket.chat/model-typings': patch
3+
'@rocket.chat/models': patch
4+
'@rocket.chat/meteor': patch
5+
---
6+
7+
Fixes the `channels.counters`, `groups.counters` and `im.counters` endpoint to include only active users in members count.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': patch
3+
---
4+
5+
Fixes an issue where the `Show Message in Notification` and `Show Channel/Group/Username in Notification` setting was ignored in desktop notifications. Also ensures users are redirected to the correct room on interacting with the notifications.

.changeset/sweet-paws-doubt.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
"@rocket.chat/models": patch
4+
---
5+
6+
Fixes an issue where overlapping calendar events could cause the user status to stay busy indefinitely

.changeset/thirty-lemons-talk.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@rocket.chat/core-typings': minor
3+
'@rocket.chat/i18n': minor
4+
'@rocket.chat/meteor': minor
5+
---
6+
7+
Adds a new setting to override outlook calendar settings per user email domain

apps/meteor/.mocharc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@ module.exports = {
4141
'tests/unit/server/**/*.spec.ts',
4242
'app/api/server/lib/**/*.spec.ts',
4343
'app/file-upload/server/**/*.spec.ts',
44+
'app/lib/server/functions/notifications/**/*.spec.ts',
4445
],
4546
};
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { getUserInfo } from './getUserInfo';
2+
import type { CachedSettings } from '../../../settings/server/CachedSettings';
3+
4+
jest.mock('@rocket.chat/models', () => ({
5+
Users: {
6+
findOneById: jest.fn().mockResolvedValue({
7+
id: '123',
8+
name: 'Test User',
9+
emails: [{ address: 'test@example.com' }],
10+
}),
11+
},
12+
}));
13+
14+
const settings = new Map<string, unknown>();
15+
16+
jest.mock('../../../settings/server', () => ({
17+
settings: {
18+
getByRegexp(_id) {
19+
return [...settings].filter(([key]) => key.match(_id)) as any;
20+
},
21+
get(_id) {
22+
return settings.get(_id) as any;
23+
},
24+
set(record) {
25+
settings.set(record._id, record.value);
26+
},
27+
} satisfies Partial<CachedSettings>,
28+
}));
29+
30+
// @ts-expect-error __meteor_runtime_config__ is not defined in the type definitions
31+
global.__meteor_runtime_config__ = {
32+
ROOT_URL: 'http://localhost:3000',
33+
ROOT_URL_PATH_PREFIX: '',
34+
};
35+
36+
describe('getUserInfo', () => {
37+
let user: Parameters<typeof getUserInfo>[0];
38+
39+
beforeEach(() => {
40+
settings.clear();
41+
settings.set('Site_Url', 'http://localhost:3000');
42+
user = {
43+
_id: '123',
44+
createdAt: new Date(),
45+
roles: [],
46+
type: 'user',
47+
active: true,
48+
_updatedAt: new Date(),
49+
};
50+
});
51+
52+
it('should return user info', async () => {
53+
const userInfo = await getUserInfo(user);
54+
55+
expect(userInfo).toEqual(
56+
expect.objectContaining({
57+
_id: '123',
58+
type: 'user',
59+
roles: [],
60+
active: true,
61+
_updatedAt: expect.any(Date),
62+
createdAt: expect.any(Date),
63+
email: undefined,
64+
avatarUrl: 'http://localhost:3000/avatar/undefined',
65+
settings: {
66+
calendar: {},
67+
profile: {},
68+
preferences: {},
69+
},
70+
}),
71+
);
72+
});
73+
74+
describe('email handling', () => {
75+
it('should not include email if no emails are present', async () => {
76+
user.emails = [];
77+
const userInfo = await getUserInfo(user);
78+
expect(userInfo.email).toBe(undefined);
79+
});
80+
81+
it('should include email if one email is present and verified', async () => {
82+
user.emails = [{ address: 'test@example.com', verified: true }];
83+
const userInfo = await getUserInfo(user);
84+
expect(userInfo.email).toEqual('test@example.com');
85+
});
86+
87+
it('should not include email if one email is present and not verified', async () => {
88+
user.emails = [{ address: 'test@example.com', verified: false }];
89+
const userInfo = await getUserInfo(user);
90+
expect(userInfo.email).toBe(undefined);
91+
});
92+
93+
it('should include email if multiple emails are present and one is verified', async () => {
94+
user.emails = [
95+
{ address: 'test@example.com', verified: false },
96+
{ address: 'test2@example.com', verified: true },
97+
];
98+
const userInfo = await getUserInfo(user);
99+
expect(userInfo.email).toEqual('test2@example.com');
100+
});
101+
102+
it('should not include email if multiple emails are present and none are verified', async () => {
103+
user.emails = [
104+
{ address: 'test@example.com', verified: false },
105+
{ address: 'test2@example.com', verified: false },
106+
];
107+
const userInfo = await getUserInfo(user);
108+
expect(userInfo.email).toBe(undefined);
109+
});
110+
});
111+
112+
describe('outlook calendar settings', () => {
113+
beforeEach(() => {
114+
settings.set('Outlook_Calendar_Enabled', true);
115+
settings.set('Outlook_Calendar_Exchange_Url', 'https://outlook.office365.com/');
116+
settings.set('Outlook_Calendar_Outlook_Url', 'https://outlook.office365.com/owa/#calendar/view/month');
117+
settings.set('Outlook_Calendar_Url_Mapping', JSON.stringify({}));
118+
user.emails = [{ address: 'test@example.com', verified: true }];
119+
});
120+
121+
it('should return empty calendar settings if Outlook is disabled', async () => {
122+
settings.set('Outlook_Calendar_Enabled', false);
123+
const userInfo = await getUserInfo(user);
124+
expect(userInfo.settings?.calendar).toEqual({});
125+
});
126+
127+
it('should return calendar settings with Outlook enabled and default URLs', async () => {
128+
const userInfo = await getUserInfo(user);
129+
expect(userInfo.settings?.calendar?.outlook).toEqual({
130+
Enabled: true,
131+
Exchange_Url: 'https://outlook.office365.com/',
132+
Outlook_Url: 'https://outlook.office365.com/owa/#calendar/view/month',
133+
});
134+
});
135+
136+
it('should return calendar settings with Outlook enabled and domain mapping', async () => {
137+
settings.set(
138+
'Outlook_Calendar_Url_Mapping',
139+
JSON.stringify({
140+
'example.com': { Exchange_Url: 'https://custom.exchange.com/', Outlook_Url: 'https://custom.outlook.com/' },
141+
}),
142+
);
143+
const userInfo = await getUserInfo(user);
144+
expect(userInfo.settings?.calendar).toEqual({
145+
outlook: {
146+
Enabled: true,
147+
Exchange_Url: 'https://custom.exchange.com/',
148+
Outlook_Url: 'https://custom.outlook.com/',
149+
},
150+
});
151+
});
152+
153+
it('should return calendar settings with outlook disabled but enabled for specific domain', async () => {
154+
settings.set('Outlook_Calendar_Enabled', false);
155+
settings.set(
156+
'Outlook_Calendar_Url_Mapping',
157+
JSON.stringify({
158+
'specific.com': { Enabled: true, Exchange_Url: 'https://specific.exchange.com/', Outlook_Url: 'https://specific.outlook.com/' },
159+
}),
160+
);
161+
user.emails = [{ address: 'me@specific.com', verified: true }];
162+
const userInfo = await getUserInfo(user);
163+
expect(userInfo.settings?.calendar).toEqual({
164+
outlook: {
165+
Enabled: true,
166+
Exchange_Url: 'https://specific.exchange.com/',
167+
Outlook_Url: 'https://specific.outlook.com/',
168+
},
169+
});
170+
});
171+
172+
it('should return calendar settings with Outlook enabled and default mapping for unknown domain', async () => {
173+
user.emails = [{ address: 'unknown@example.com', verified: true }];
174+
const userInfo = await getUserInfo(user);
175+
expect(userInfo.settings?.calendar).toEqual({
176+
outlook: {
177+
Enabled: true,
178+
Exchange_Url: 'https://outlook.office365.com/',
179+
Outlook_Url: 'https://outlook.office365.com/owa/#calendar/view/month',
180+
},
181+
});
182+
});
183+
184+
it('should handle invalid JSON in Outlook_Calendar_Url_Mapping', async () => {
185+
settings.set('Outlook_Calendar_Url_Mapping', 'invalid json');
186+
const userInfo = await getUserInfo(user);
187+
expect(userInfo.settings?.calendar).toEqual({
188+
outlook: {
189+
Enabled: true,
190+
Exchange_Url: 'https://outlook.office365.com/',
191+
Outlook_Url: 'https://outlook.office365.com/owa/#calendar/view/month',
192+
},
193+
});
194+
});
195+
});
196+
});

apps/meteor/app/api/server/helpers/getUserInfo.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isOAuthUser, type IUser, type IUserEmail } from '@rocket.chat/core-typings';
1+
import { isOAuthUser, type IUser, type IUserEmail, type IUserCalendar } from '@rocket.chat/core-typings';
22

33
import { settings } from '../../../settings/server';
44
import { getURL } from '../../../utils/server/getURL';
@@ -25,13 +25,49 @@ const getUserPreferences = async (me: IUser): Promise<Record<string, unknown>> =
2525
return accumulator;
2626
};
2727

28+
/**
29+
* Returns the user's calendar settings based on their email domain and the configured mapping.
30+
* If the email is not provided or the domain is not found in the mapping,
31+
* it returns the default Outlook calendar settings.
32+
* @param email - The user's email object, which may contain the address and verification status.
33+
* @returns The calendar settings for the user, including Outlook calendar settings if enabled.
34+
*/
35+
const getUserCalendar = (email: false | IUserEmail | undefined): IUserCalendar => {
36+
const calendarSettings: IUserCalendar = {};
37+
38+
const outlook = {
39+
Enabled: settings.get<boolean>('Outlook_Calendar_Enabled'),
40+
Exchange_Url: settings.get<string>('Outlook_Calendar_Exchange_Url'),
41+
Outlook_Url: settings.get<string>('Outlook_Calendar_Outlook_Url'),
42+
};
43+
44+
const domain = email ? email.address.split('@').pop() : undefined;
45+
const outlookCalendarUrlMapping = settings.get<string>('Outlook_Calendar_Url_Mapping');
46+
47+
if (domain && outlookCalendarUrlMapping && outlookCalendarUrlMapping.includes(domain)) {
48+
try {
49+
const mappingObject = JSON.parse(outlookCalendarUrlMapping);
50+
const mappedSettings = mappingObject[domain];
51+
if (mappedSettings) {
52+
outlook.Enabled = mappedSettings.Enabled ?? outlook.Enabled;
53+
outlook.Exchange_Url = mappedSettings.Exchange_Url ?? outlook.Exchange_Url;
54+
outlook.Outlook_Url = mappedSettings.Outlook_Url ?? outlook.Outlook_Url;
55+
}
56+
} catch (error) {
57+
console.error('Invalid Outlook Calendar URL Mapping JSON:', error);
58+
}
59+
}
60+
61+
if (outlook.Enabled) {
62+
calendarSettings.outlook = outlook;
63+
}
64+
65+
return calendarSettings;
66+
};
67+
2868
export async function getUserInfo(me: IUser): Promise<
2969
IUser & {
3070
email?: string;
31-
settings?: {
32-
profile: Record<string, unknown>;
33-
preferences: unknown;
34-
};
3571
avatarUrl: string;
3672
}
3773
> {
@@ -48,6 +84,7 @@ export async function getUserInfo(me: IUser): Promise<
4884
...(await getUserPreferences(me)),
4985
...userPreferences,
5086
},
87+
calendar: getUserCalendar(verifiedEmail),
5188
},
5289
avatarUrl: getURL(`/avatar/${me.username}`, { cdn: false, full: true }),
5390
isOAuthUser: isOAuthUser(me),

apps/meteor/app/api/server/v1/channels.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ API.v1.addRoute(
649649
if (access || joined) {
650650
msgs = room.msgs;
651651
latest = lm;
652-
members = room.usersCount;
652+
members = await Users.countActiveUsersInNonDMRoom(room._id);
653653
}
654654

655655
return API.v1.success({

0 commit comments

Comments
 (0)