Files
twenty/packages/twenty-server/src/engine/middlewares/middleware.service.ts
T
Félix Malfait c3dd6b25a6 fix: use canonical oxlint rule id in lint-disable directives (#21253)
## What

Many `oxlint-disable` / `eslint-disable` directives across the repo
carry a corrupted rule id — `@typescripttypescript/<rule>` — most likely
a find-and-replace accident that mangled the eslint-era
`@typescript-eslint/` prefix.

oxlint matches disable directives **loosely by rule name**, so these
still suppress in practice (not a silent no-op), but the id is malformed
and misleading.

## Change

Replace them with the **canonical oxlint id** `typescript/<rule>` —
matching the plugin name and rule keys declared in `.oxlintrc.json` —
**127 files, 262 directives**:

| rule | count |
| --- | ----- |
| `typescript/no-explicit-any` | 250 |
| `typescript/ban-ts-comment` | 6 |
| `typescript/no-misused-promises` | 4 |
| `typescript/no-empty-object-type` | 2 |

- `twenty-server`: 122 files
- `twenty-front`: 5 files

Comment-only — no code or runtime changes.

## Verification

`oxlint --type-aware -c .oxlintrc.json` reports **0 warnings / 0
errors** for both `twenty-server` and `twenty-front`. Every changed line
is exactly the id correction inside a disable directive (262 insertions
/ 262 deletions, no collateral edits).

> Addresses the cubic review, which flagged that the canonical oxlint id
is `typescript/...` (no `@`). Worth noting the original
`@typescripttypescript/` was not actually a silent no-op — oxlint
matches these directives loosely by rule name — but `typescript/` is the
correct, config-aligned id.
2026-06-05 13:52:32 +02:00

155 lines
5.1 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { isNonEmptyString } from '@sniptt/guards';
import { type Request, type Response } from 'express';
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils';
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { getAuthExceptionRestStatus } from 'src/engine/core-modules/auth/utils/get-auth-exception-rest-status.util';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { INTERNAL_SERVER_ERROR } from 'src/engine/middlewares/constants/default-error-message.constant';
import { bindDataToRequestObject } from 'src/engine/utils/bind-data-to-request-object.util';
import {
handleException,
handleExceptionAndConvertToGraphQLError,
} from 'src/engine/utils/global-exception-handler.util';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { type CustomException } from 'src/utils/custom-exception';
@Injectable()
export class MiddlewareService {
constructor(
private readonly accessTokenService: AccessTokenService,
private readonly workspaceStorageCacheService: WorkspaceCacheStorageService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly exceptionHandlerService: ExceptionHandlerService,
private readonly jwtWrapperService: JwtWrapperService,
) {}
public isTokenPresent(request: Request): boolean {
const token = this.jwtWrapperService.extractJwtFromRequest()(request);
return !!token;
}
// oxlint-disable-next-line typescript/no-explicit-any
public writeRestResponseOnExceptionCaught(res: Response, error: any) {
const statusCode = this.getStatus(error);
// capture and handle custom exceptions
handleException({
exception: error as CustomException,
exceptionHandlerService: this.exceptionHandlerService,
statusCode,
});
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
res.write(
JSON.stringify({
statusCode,
messages: [error?.message || INTERNAL_SERVER_ERROR],
error: error?.code || ErrorCode.INTERNAL_SERVER_ERROR,
}),
);
res.end();
}
// oxlint-disable-next-line typescript/no-explicit-any
public writeGraphqlResponseOnExceptionCaught(res: Response, error: any) {
let errors;
if (error instanceof AuthException) {
try {
const authFilter = new AuthGraphqlApiExceptionFilter();
authFilter.catch(error);
} catch (transformedError) {
errors = [transformedError];
}
} else {
errors = [
handleExceptionAndConvertToGraphQLError(
error as Error,
this.exceptionHandlerService,
),
];
}
const statusCode = 200;
res.writeHead(statusCode, {
'Content-Type': 'application/json',
});
res.write(
JSON.stringify({
errors,
}),
);
res.end();
}
public async hydrateRestRequest(request: Request) {
const data = await this.accessTokenService.validateTokenByRequest(request);
const metadataVersion = data.workspace
? await this.workspaceStorageCacheService.getMetadataVersion(
data.workspace.id,
)
: undefined;
if (!data.workspace) {
throw new Error('No data sources found');
}
if (!isNonEmptyString(data.workspace.databaseSchema)) {
throw new Error('No data sources found');
}
bindDataToRequestObject(data, request, metadataVersion);
}
public async hydrateGraphqlRequest(request: Request) {
if (!this.isTokenPresent(request)) {
request.locale =
(request.headers['x-locale'] as keyof typeof APP_LOCALES) ??
SOURCE_LOCALE;
return;
}
const data = await this.accessTokenService.validateTokenByRequest(request);
const metadataVersion = data.workspace
? await this.workspaceStorageCacheService.getMetadataVersion(
data.workspace.id,
)
: undefined;
bindDataToRequestObject(data, request, metadataVersion);
}
private hasErrorStatus(error: unknown): error is { status: number } {
return isDefined((error as { status: number })?.status);
}
// oxlint-disable-next-line typescript/no-explicit-any
private getStatus(error: any): number {
if (this.hasErrorStatus(error)) {
return error.status;
}
if (error instanceof AuthException) {
return getAuthExceptionRestStatus(error);
}
return 500;
}
}