Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions apps/meteor/.mocharc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
spec: [
'server/lib/callbacks.spec.ts',
'server/lib/cas/*.spec.ts',
'server/lib/messages/**/*.spec.ts',
'server/lib/ldap/*.spec.ts',
'server/lib/ldap/**/*.spec.ts',
'server/lib/dataExport/**/*.spec.ts',
Expand Down
15 changes: 15 additions & 0 deletions apps/meteor/MIGRATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ Each phase produces a manifest file (the tables below), feeds it to `move-batch.

**Primary validation command**: `yarn lint --quiet` is the authoritative check for broken imports after a move. The `--quiet` flag suppresses warnings so unresolved-import errors stand out. Run it from the repo root after every batch. `tsc --noEmit` may additionally be used to catch type regressions, but lint is the first line of defense for import integrity.

**Lint has a blind spot — run `yarn typecheck` per phase too.** ESLint ignores some directories that DO import server code (notably `apps/meteor/imports/`), so a moved module can leave broken specifiers there that lint never reports — Phase 4 left three such imports in `imports/personal-access-tokens/` that only `tsc` caught. The migration scripts' search dirs must include every code directory (`app`, `server`, `ee`, `lib`, `tests`, `imports`, `client`, `definition`, `packages`), and each phase's verification must include a full `yarn typecheck` (from `apps/meteor/`), not just lint.

**Deliverable**: Scripts written and tested on a small dry-run (e.g., move one slash command file, verify, revert).

---
Expand Down Expand Up @@ -711,6 +713,18 @@ These files don't fit neatly into `lib/<domain>/` because they're hooks, utiliti

This file is the main import aggregator for the app/lib module. As files move out, update this file to remove the corresponding imports. Once all files are moved, delete `app/lib/server/index.ts` entirely.

### Mirrored Test Trees (`tests/unit/**`) — move the specs with their sources

Not every spec is co-located with its source: `tests/unit/` (and `ee/tests/unit/`) is a **mirror of the source tree** (`tests/unit/app/lib/server/functions/setUsername.spec.ts` tests `app/lib/server/functions/setUsername.ts`). Moving a source file without moving its mirrored spec leaves the spec **orphaned in a stale mirror** — it keeps running (the `tests/unit/**` globs still match it), so nothing fails, but the tree layout silently rots and the next person can't find the test for a module.

**Procedure for every module move:**

1. Move the mirrored spec so it mirrors the **new** source location: source `app/lib/server/functions/X.ts` → `server/lib/users/X.ts` means spec `tests/unit/app/lib/server/functions/X.spec.ts` → `tests/unit/server/lib/users/X.spec.ts`.
2. Recompute the spec's relative `import`s **and its proxyquire target path** (`proxyquire(...)` / `.load(...)` — string literals the move script does not rewrite) for the new depth. Mock **keys** are unaffected by moving the spec (they match the loaded module's own specifiers, not the spec's location).
3. Check runner globs both ways: the new location must be matched by an existing glob (`tests/unit/server/**/*.{spec,tests}.ts` already covers the server mirror), and the old glob must not be left matching nothing.

**Orphan detector** (run after each phase): for every `*.spec.ts`/`*.tests.ts` under `tests/unit/app/**`, resolve its relative imports and proxyquire targets; any spec resolving into `server/**` (or `ee/server/**`) belongs in the corresponding mirror under `tests/unit/server/**` (or `ee/tests/unit/**`). Phases 3 and 4 left 15 such orphans (fixed in the Phase 4 follow-up); phases 1–2 had no unit-test mirrors.

### Test Mocks and Runner Globs (proxyquire / jest.mock) — lint & tsc do NOT catch these

Moving a module silently breaks two things that `yarn lint --quiet` and `tsc --noEmit` **cannot** see, because both are driven by **string literals**, not statically-resolved imports:
Expand All @@ -725,6 +739,7 @@ Moving a module silently breaks two things that `yarn lint --quiet` and `tsc --n

**Procedure after every module move (do this in the same PR):**

- **Relocate mirrored specs.** Move the module's specs under `tests/unit/**` to mirror the new source location — see [Mirrored Test Trees](#mirrored-test-trees-testsunit--move-the-specs-with-their-sources).
- **Re-wire discovery globs.** For each moved `*.spec.ts`, update `jest.config.ts` `testMatch` (jest specs) or `.mocharc.js` `spec` (mocha specs) to its new location; remove the now-empty old glob (it prints a `Cannot find any files matching pattern` warning).
- **Find dependent specs.** Grep for specs that mock the moved module **or any module the moved module transitively imports**: `grep -rlE "proxyquire|jest\.mock" --include='*.spec.ts'`, then check each mock/`jest.mock` key against the moved module's new import specifiers. A quick detector: for every relative mock key, resolve it against the loaded module's directory and flag the ones that no longer resolve.
- **Rewrite stale keys** to exactly match the moved module's new relative import specifiers (update the reused property-key strings too).
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/2fa/server/code/checkCodeForUser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const { checkCodeForUser, getFingerprintFromConnection } = proxyquire.noCallThru
'./TOTPCheck': { TOTPCheck },
'./EmailCheck': { EmailCheck },
'./PasswordCheckFallback': { PasswordCheckFallback },
'../../../lib/server/functions/getModifiedHttpHeaders': {
'../../../../server/lib/shared/getModifiedHttpHeaders': {
normalizeHeaders: (headers: unknown) => headers,
},
'../../../settings/server': {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/2fa/server/code/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const { checkCodeForUser } = proxyquire.noCallThru().load('./index', {
'./TOTPCheck': { TOTPCheck: TOTPCheckMock },
'./EmailCheck': { EmailCheck: DisabledCheckMock },
'./PasswordCheckFallback': { PasswordCheckFallback: class extends DisabledCheckMock {} },
'../../../lib/server/functions/getModifiedHttpHeaders': { normalizeHeaders: (headers: unknown) => headers },
'../../../../server/lib/shared/getModifiedHttpHeaders': { normalizeHeaders: (headers: unknown) => headers },
'../../../settings/server': { settings: { get: settingsMock } },
'@rocket.chat/models': {
Users: {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/2fa/server/code/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { EmailCheck } from './EmailCheck';
import type { ICodeCheck } from './ICodeCheck';
import { PasswordCheckFallback } from './PasswordCheckFallback';
import { TOTPCheck } from './TOTPCheck';
import { normalizeHeaders } from '../../../lib/server/functions/getModifiedHttpHeaders';
import { normalizeHeaders } from '../../../../server/lib/shared/getModifiedHttpHeaders';
import { settings } from '../../../settings/server';

export interface ITwoFactorOptions {
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/apps/server/bridges/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { api } from '@rocket.chat/core-services';
import type { IMessage } from '@rocket.chat/core-typings';
import { Users, Subscriptions } from '@rocket.chat/models';

import { deleteMessage } from '../../../lib/server/functions/deleteMessage';
import { updateMessage } from '../../../lib/server/functions/updateMessage';
import { deleteMessage } from '../../../../server/lib/messages/deleteMessage';
import { updateMessage } from '../../../../server/lib/messages/updateMessage';
import { executeSendMessage } from '../../../lib/server/methods/sendMessage';
import notifications from '../../../notifications/server/lib/Notifications';
import { executeSetReaction } from '../../../reactions/server/setReaction';
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/app/apps/server/bridges/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import type { ISubscription, IUser as ICoreUser, IRoom as ICoreRoom, IMessage as
import { Subscriptions, Users, Rooms, Messages } from '@rocket.chat/models';
import type { FindOptions, Sort } from 'mongodb';

import { addUserToRoom } from '../../../../server/lib/rooms/addUserToRoom';
import { deleteRoom } from '../../../../server/lib/rooms/deleteRoom';
import { removeUserFromRoom } from '../../../../server/lib/rooms/removeUserFromRoom';
import { createDirectMessage } from '../../../../server/methods/createDirectMessage';
import { createDiscussion } from '../../../discussion/server/methods/createDiscussion';
import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom';
import { deleteRoom } from '../../../lib/server/functions/deleteRoom';
import { removeUserFromRoom } from '../../../lib/server/functions/removeUserFromRoom';
import { createChannelMethod } from '../../../lib/server/methods/createChannel';
import { createPrivateGroupMethod } from '../../../lib/server/methods/createPrivateGroup';

Expand Down
12 changes: 6 additions & 6 deletions apps/meteor/app/apps/server/bridges/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import type { PresenceSource, UserStatus } from '@rocket.chat/core-typings';
import { Subscriptions, Users } from '@rocket.chat/models';
import { Random } from '@rocket.chat/random';

import { checkUsernameAvailability } from '../../../lib/server/functions/checkUsernameAvailability';
import { deleteUser } from '../../../lib/server/functions/deleteUser';
import { getUserCreatedByApp } from '../../../lib/server/functions/getUserCreatedByApp';
import { setStatusText } from '../../../lib/server/functions/setStatusText';
import { setUserActiveStatus } from '../../../lib/server/functions/setUserActiveStatus';
import { setUserAvatar } from '../../../lib/server/functions/setUserAvatar';
import { checkUsernameAvailability } from '../../../../server/lib/users/checkUsernameAvailability';
import { deleteUser } from '../../../../server/lib/users/deleteUser';
import { getUserCreatedByApp } from '../../../../server/lib/users/getUserCreatedByApp';
import { setStatusText } from '../../../../server/lib/users/setStatusText';
import { setUserActiveStatus } from '../../../../server/lib/users/setUserActiveStatus';
import { setUserAvatar } from '../../../../server/lib/users/setUserAvatar';
import { notifyOnUserChange, notifyOnUserChangeById } from '../../../lib/server/lib/notifyListener';

export class AppUserBridge extends UserBridge {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/assets/server/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Meteor } from 'meteor/meteor';
import { WebApp, WebAppInternals } from 'meteor/webapp';
import sharp from 'sharp';

import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission';
import { hasPermissionAsync } from '../../../server/lib/authorization/hasPermission';
import { RocketChatFile } from '../../file/server';
import { notifyOnSettingChangedById } from '../../lib/server/lib/notifyListener';
import { settings, settingsRegistry } from '../../settings/server';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Rooms, ServerEvents, Users } from '@rocket.chat/models';

import { addMinutesToADate } from '../../../../lib/utils/addMinutesToADate';
import { getClientAddress } from '../../../../server/lib/getClientAddress';
import { sendMessage } from '../../../lib/server/functions/sendMessage';
import { sendMessage } from '../../../../server/lib/messages/sendMessage';
import { settings } from '../../../settings/server';
import type { ILoginAttempt } from '../ILoginAttempt';

Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/app/authentication/server/startup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { getClientAddress } from '../../../../server/lib/getClientAddress';
import { getMaxLoginTokens } from '../../../../server/lib/getMaxLoginTokens';
import { i18n } from '../../../../server/lib/i18n';
import { addUserRolesAsync } from '../../../../server/lib/roles/addUserRoles';
import { joinDefaultChannels } from '../../../../server/lib/rooms/joinDefaultChannels';
import { getAvatarSuggestionForUser } from '../../../../server/lib/users/getAvatarSuggestionForUser';
import { setAvatarFromServiceWithValidation } from '../../../../server/lib/users/setUserAvatar';
import { getNewUserRoles } from '../../../../server/services/user/lib/getNewUserRoles';
import { getAvatarSuggestionForUser } from '../../../lib/server/functions/getAvatarSuggestionForUser';
import { joinDefaultChannels } from '../../../lib/server/functions/joinDefaultChannels';
import { setAvatarFromServiceWithValidation } from '../../../lib/server/functions/setUserAvatar';
import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener';
import * as Mailer from '../../../mailer/server/api';
import { settings } from '../../../settings/server';
Expand Down
8 changes: 4 additions & 4 deletions apps/meteor/app/authorization/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { roomAccessAttributes, canAccessRoomAsync } from './functions/canAccessRoom';
import { getRoles } from './functions/getRoles';
import { getUsersInRole } from './functions/getUsersInRole';
import { subscriptionHasRole } from './functions/hasRole';
import { roomAccessAttributes, canAccessRoomAsync } from '../../../server/lib/authorization/canAccessRoom';
import { getRoles } from '../../../server/lib/authorization/getRoles';
import { getUsersInRole } from '../../../server/lib/authorization/getUsersInRole';
import { subscriptionHasRole } from '../../../server/lib/authorization/hasRole';
import './methods/addPermissionToRole';
import './methods/addUserToRole';
import './methods/removeRoleFromPermission';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Permissions } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';
import { notifyOnPermissionChangedById } from '../../../lib/server/lib/notifyListener';
import { CONSTANTS, AuthorizationUtils } from '../../lib';
import { hasPermissionAsync } from '../functions/hasPermission';

declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { IRole, IUser } from '@rocket.chat/core-typings';
import { Roles, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';
import { addUserRolesAsync } from '../../../../server/lib/roles/addUserRoles';
import { settings } from '../../../settings/server';
import { hasPermissionAsync } from '../functions/hasPermission';

export const addUserToRole = async (userId: string, roleId: string, username: IUser['username'], scope?: string): Promise<boolean> => {
if (!(await hasPermissionAsync(userId, 'access-permissions'))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Permissions } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';
import { notifyOnPermissionChangedById } from '../../../lib/server/lib/notifyListener';
import { CONSTANTS } from '../../lib';
import { hasPermissionAsync } from '../functions/hasPermission';

declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { IRole, IUser } from '@rocket.chat/core-typings';
import { Roles, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';
import { removeUserFromRolesAsync } from '../../../../server/lib/roles/removeUserFromRoles';
import { settings } from '../../../settings/server';
import { hasPermissionAsync } from '../functions/hasPermission';

export const removeUserFromRole = async (userId: string, roleId: string, username: IUser['username'], scope?: string): Promise<boolean> => {
if (!(await hasPermissionAsync(userId, 'access-permissions'))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';

import { TranslationProviderRegistry } from '..';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';
import { settings } from '../../../settings/server';

export const getSupportedLanguages = async (userId: string, targetLanguage: string) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Subscriptions, Rooms } from '@rocket.chat/models';
import { check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';

import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';
import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener';

export const saveAutoTranslateSettings = async (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { Document, UpdateResult } from 'mongodb';

import { callbacks } from '../../../../server/lib/callbacks';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
import { checkUsernameAvailability } from '../../../lib/server/functions/checkUsernameAvailability';
import { checkUsernameAvailability } from '../../../../server/lib/users/checkUsernameAvailability';
import { notifyOnIntegrationChangedByChannels, notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener';
import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';

import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig';
import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { setRoomAvatar } from '../../../../server/lib/rooms/setRoomAvatar';
import { isABACManagedRoom } from '../../../authorization/server/lib/isABACManagedRoom';
import { setRoomAvatar } from '../../../lib/server/functions/setRoomAvatar';
import { notifyOnRoomChangedById } from '../../../lib/server/lib/notifyListener';
import { settings } from '../../../settings/server';
import { saveReactWhenReadOnly } from '../functions/saveReactWhenReadOnly';
Expand Down
10 changes: 5 additions & 5 deletions apps/meteor/app/cloud/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { cronJobs } from '@rocket.chat/cron';
import { Meteor } from 'meteor/meteor';

import { connectWorkspace } from './functions/connectWorkspace';
import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from './functions/getWorkspaceAccessToken';
import { getWorkspaceAccessTokenWithScope } from './functions/getWorkspaceAccessTokenWithScope';
import { retrieveRegistrationStatus } from './functions/retrieveRegistrationStatus';
import { syncWorkspace } from './functions/syncWorkspace';
import { connectWorkspace } from '../../../server/lib/cloud/connectWorkspace';
import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from '../../../server/lib/cloud/getWorkspaceAccessToken';
import { getWorkspaceAccessTokenWithScope } from '../../../server/lib/cloud/getWorkspaceAccessTokenWithScope';
import { retrieveRegistrationStatus } from '../../../server/lib/cloud/retrieveRegistrationStatus';
import { syncWorkspace } from '../../../server/lib/cloud/syncWorkspace';
import { SystemLogger } from '../../../server/lib/logger/system';
import './methods';

Expand Down
Loading
Loading