diff --git a/apps/client/src/features/editor/components/attachment/attachment-view.tsx b/apps/client/src/features/editor/components/attachment/attachment-view.tsx index f6c13c80..b72bb00a 100644 --- a/apps/client/src/features/editor/components/attachment/attachment-view.tsx +++ b/apps/client/src/features/editor/components/attachment/attachment-view.tsx @@ -10,7 +10,7 @@ import { useCallback } from "react"; export default function AttachmentView(props: NodeViewProps) { const { t } = useTranslation(); const { editor, node, getPos, selected } = props; - const { url, name, size, mime, attachmentId } = node.attrs; + const { url, name, size, mime, attachmentId, placeholder } = node.attrs; const { hovered, ref } = useHover(); const isPdf = mime === "application/pdf" || name?.toLowerCase().endsWith(".pdf"); @@ -49,14 +49,14 @@ export default function AttachmentView(props: NodeViewProps) { h={25} > - {url ? ( - - ) : ( + {!url && placeholder ? ( + ) : ( + )} - {url ? name : t("Uploading {{name}}", { name })} + {!url && placeholder ? t("Uploading {{name}}", { name }) : name} diff --git a/apps/client/src/features/editor/components/audio/audio-view.tsx b/apps/client/src/features/editor/components/audio/audio-view.tsx index a353ce45..9e5f619f 100644 --- a/apps/client/src/features/editor/components/audio/audio-view.tsx +++ b/apps/client/src/features/editor/components/audio/audio-view.tsx @@ -29,7 +29,7 @@ export default function AudioView(props: NodeViewProps) { return ( -
+
{safeSrc && (
); diff --git a/apps/client/src/features/editor/components/image/image-view.tsx b/apps/client/src/features/editor/components/image/image-view.tsx index 7ec3e26f..1f874694 100644 --- a/apps/client/src/features/editor/components/image/image-view.tsx +++ b/apps/client/src/features/editor/components/image/image-view.tsx @@ -33,7 +33,7 @@ export default function ImageView(props: NodeViewProps) { className={clsx( selected && "ProseMirror-selectednode", classes.imageWrapper, - !src && classes.skeleton, + !src && placeholder && classes.skeleton, alignClass, )} style={{ @@ -55,7 +55,7 @@ export default function ImageView(props: NodeViewProps) { )} - {!src && !previewSrc && ( + {!src && !previewSrc && placeholder && ( diff --git a/apps/client/src/features/editor/components/pdf/pdf-view.tsx b/apps/client/src/features/editor/components/pdf/pdf-view.tsx index 6207da9f..4d06402b 100644 --- a/apps/client/src/features/editor/components/pdf/pdf-view.tsx +++ b/apps/client/src/features/editor/components/pdf/pdf-view.tsx @@ -73,15 +73,17 @@ export default function PdfView(props: NodeViewProps) { if (!src || !safeSrc) { return ( -
- - - - {placeholder?.name - ? t("Uploading {{name}}", { name: placeholder.name }) - : t("Uploading file")} - - +
+ {placeholder && ( + + + + {placeholder?.name + ? t("Uploading {{name}}", { name: placeholder.name }) + : t("Uploading file")} + + + )}
); diff --git a/apps/client/src/features/editor/components/video/video-view.tsx b/apps/client/src/features/editor/components/video/video-view.tsx index 1e662640..46ff7908 100644 --- a/apps/client/src/features/editor/components/video/video-view.tsx +++ b/apps/client/src/features/editor/components/video/video-view.tsx @@ -33,7 +33,7 @@ export default function VideoView(props: NodeViewProps) { className={clsx( selected && "ProseMirror-selectednode", classes.videoWrapper, - !src && classes.skeleton, + !src && placeholder && classes.skeleton, alignClass, )} style={{ @@ -60,7 +60,7 @@ export default function VideoView(props: NodeViewProps) { )} - {!src && !previewSrc && ( + {!src && !previewSrc && placeholder && ( @@ -70,6 +70,9 @@ export default function VideoView(props: NodeViewProps) { )} + {!src && !previewSrc && !placeholder && ( +
); diff --git a/apps/client/src/features/editor/extensions/extensions.ts b/apps/client/src/features/editor/extensions/extensions.ts index c5ca4cd1..8f6e6cdb 100644 --- a/apps/client/src/features/editor/extensions/extensions.ts +++ b/apps/client/src/features/editor/extensions/extensions.ts @@ -253,8 +253,8 @@ export const mainExtensions = [ resize: { enabled: true, directions: ["left", "right"], - minWidth: 80, - minHeight: 40, + minWidth: 24, + minHeight: 16, alwaysPreserveAspectRatio: true, //@ts-ignore createCustomHandle: createImageHandle, @@ -266,8 +266,8 @@ export const mainExtensions = [ resize: { enabled: true, directions: ["left", "right"], - minWidth: 80, - minHeight: 40, + minWidth: 24, + minHeight: 16, alwaysPreserveAspectRatio: true, //@ts-ignore createCustomHandle: createResizeHandle, @@ -297,8 +297,8 @@ export const mainExtensions = [ resize: { enabled: true, directions: ["left", "right"], - minWidth: 80, - minHeight: 40, + minWidth: 24, + minHeight: 16, alwaysPreserveAspectRatio: true, //@ts-ignore createCustomHandle: createResizeHandle, @@ -310,8 +310,8 @@ export const mainExtensions = [ resize: { enabled: true, directions: ["left", "right"], - minWidth: 80, - minHeight: 40, + minWidth: 24, + minHeight: 16, alwaysPreserveAspectRatio: true, //@ts-ignore createCustomHandle: createResizeHandle, diff --git a/apps/server/package.json b/apps/server/package.json index d8bab08c..a8869302 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -44,6 +44,7 @@ "@langchain/core": "1.1.34", "@langchain/textsplitters": "1.0.1", "@modelcontextprotocol/sdk": "^1.27.1", + "@nest-lab/throttler-storage-redis": "^1.2.0", "@nestjs-labs/nestjs-ioredis": "^11.0.4", "@nestjs/bullmq": "^11.0.4", "@nestjs/cache-manager": "^3.1.0", @@ -58,6 +59,7 @@ "@nestjs/platform-socket.io": "^11.1.17", "@nestjs/schedule": "^6.1.1", "@nestjs/terminus": "^11.1.1", + "@nestjs/throttler": "^6.5.0", "@nestjs/websockets": "^11.1.17", "@node-saml/passport-saml": "^5.1.0", "@react-email/components": "1.0.10", @@ -73,6 +75,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.15.1", "cookie": "^1.1.1", + "fastify-ip": "^2.0.0", "fs-extra": "^11.3.4", "happy-dom": "20.8.9", "ioredis": "^5.10.1", diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index 6280ee09..b8cfc587 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -26,6 +26,7 @@ import KeyvRedis from '@keyv/redis'; import { LoggerModule } from './common/logger/logger.module'; import { ClsModule } from 'nestjs-cls'; import { NoopAuditModule } from './integrations/audit/audit.module'; +import { ThrottleModule } from './integrations/throttle/throttle.module'; const enterpriseModules = []; try { @@ -83,6 +84,7 @@ try { EventEmitterModule.forRoot(), SecurityModule, TelemetryModule, + ThrottleModule, ...enterpriseModules, ], controllers: [AppController], diff --git a/apps/server/src/common/logger/pino.config.ts b/apps/server/src/common/logger/pino.config.ts index 7299a8e9..0b8cd11a 100644 --- a/apps/server/src/common/logger/pino.config.ts +++ b/apps/server/src/common/logger/pino.config.ts @@ -50,20 +50,12 @@ export function createPinoConfig(): Params { }, }, serializers: { - req: (req) => { - const forwardedFor = req.headers?.['x-forwarded-for']; - const ip = - req.headers?.['cf-connecting-ip'] || - (typeof forwardedFor === 'string' ? forwardedFor.split(',')[0]?.trim() : undefined) || - req.remoteAddress; - - return { - method: req.method, - url: req.url, - ip, - userAgent: req.headers?.['user-agent'], - }; - }, + req: (req) => ({ + method: req.method, + url: req.url, + ip: req.ip || req.remoteAddress, + userAgent: req.headers?.['user-agent'], + }), res: (res) => ({ statusCode: res.statusCode, }), diff --git a/apps/server/src/common/middlewares/audit-context.middleware.ts b/apps/server/src/common/middlewares/audit-context.middleware.ts index f5066535..52956219 100644 --- a/apps/server/src/common/middlewares/audit-context.middleware.ts +++ b/apps/server/src/common/middlewares/audit-context.middleware.ts @@ -18,7 +18,8 @@ export class AuditContextMiddleware implements NestMiddleware { use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) { const workspaceId = (req as any).workspaceId ?? null; - const ipAddress = this.extractIpAddress(req); + + const ipAddress = (req as any).ip ?? (req as any).socket?.remoteAddress ?? null; const userAgent = (req.headers['user-agent'] as string) ?? null; @@ -35,21 +36,4 @@ export class AuditContextMiddleware implements NestMiddleware { next(); } - - private extractIpAddress(req: FastifyRequest['raw']): string | null { - const xForwardedFor = req.headers['x-forwarded-for']; - if (xForwardedFor) { - const ips = Array.isArray(xForwardedFor) - ? xForwardedFor[0] - : xForwardedFor.split(',')[0]; - return ips?.trim() ?? null; - } - - const xRealIp = req.headers['x-real-ip']; - if (xRealIp) { - return Array.isArray(xRealIp) ? xRealIp[0] : xRealIp; - } - - return (req as any).socket?.remoteAddress ?? null; - } } diff --git a/apps/server/src/core/auth/auth.controller.ts b/apps/server/src/core/auth/auth.controller.ts index 6eab6539..441bfc1c 100644 --- a/apps/server/src/core/auth/auth.controller.ts +++ b/apps/server/src/core/auth/auth.controller.ts @@ -10,6 +10,7 @@ import { UseGuards, Logger, } from '@nestjs/common'; +import { SkipThrottle, ThrottlerGuard } from '@nestjs/throttler'; import { LoginDto } from './dto/login.dto'; import { AuthService } from './services/auth.service'; import { SessionService } from '../session/session.service'; @@ -33,6 +34,7 @@ import { IAuditService, } from '../../integrations/audit/audit.service'; +@UseGuards(ThrottlerGuard) @Controller('auth') export class AuthController { private readonly logger = new Logger(AuthController.name); @@ -111,6 +113,7 @@ export class AuthController { return workspace; } + @SkipThrottle() @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @Post('change-password') @@ -173,6 +176,7 @@ export class AuthController { return this.authService.verifyUserToken(verifyUserTokenDto, workspace.id); } + @SkipThrottle() @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @Post('collab-token') @@ -183,6 +187,7 @@ export class AuthController { return this.authService.getCollabToken(user, workspace.id); } + @SkipThrottle() @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @Post('logout') diff --git a/apps/server/src/ee b/apps/server/src/ee index 05f1c816..350ef574 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 05f1c816a839072efc1143cce71322a9ed6b4a0a +Subproject commit 350ef574e398c318aa57ce5f79ab12e9d8329dcb diff --git a/apps/server/src/integrations/import/services/import-attachment.service.ts b/apps/server/src/integrations/import/services/import-attachment.service.ts index 3c14d854..9100149b 100644 --- a/apps/server/src/integrations/import/services/import-attachment.service.ts +++ b/apps/server/src/integrations/import/services/import-attachment.service.ts @@ -193,6 +193,8 @@ export class ImportAttachmentService { // Build a map from resolved archive path → real filename from Confluence // metadata. Confluence Server archives often store files under numeric IDs // (e.g. "attachments/65601/65602") instead of the original filename. + // Also register aliases so HTML references using the original filename + // (e.g. "attachments/pageId/original.mp3") resolve to the numeric path. const pageDir = path.dirname(pageRelativePath); const attachmentNameByRelPath = new Map(); for (const attachment of pageAttachments) { @@ -203,6 +205,13 @@ export class ImportAttachmentService { ); if (relPath && attachment.fileName) { attachmentNameByRelPath.set(relPath, attachment.fileName); + + const dir = path.posix.dirname(relPath); + const aliasKey = `${dir}/${attachment.fileName}`; + if (!attachmentCandidates.has(aliasKey)) { + attachmentCandidates.set(aliasKey, attachmentCandidates.get(relPath)!); + attachmentNameByRelPath.set(aliasKey, attachment.fileName); + } } } @@ -562,18 +571,31 @@ export class ImportAttachmentService { continue; } - // Check if already processed (was referenced in HTML) - if (processed.has(href)) { - continue; - } + // Resolve the metadata href to the actual archive path + const resolvedHref = resolveRelativeAttachmentPath( + href, + pageDir, + attachmentCandidates, + ); + if (!resolvedHref) continue; - // Skip if the file doesn't exist - if (!attachmentCandidates.has(href)) { + // Check if already processed (was referenced in HTML). + // Inline elements may have been processed under an alias key (original + // filename) rather than the numeric archive path, so also check whether + // the underlying absolute file path has already been uploaded. + const absPath = attachmentCandidates.get(resolvedHref); + const alreadyProcessed = + processed.has(resolvedHref) || + (absPath && + Array.from(processed.values()).some( + (entry) => entry.abs === absPath, + )); + if (alreadyProcessed) { continue; } // This attachment was in the list but not referenced in HTML - add it - const { attachmentId, apiFilePath, abs } = processFile(href); + const { attachmentId, apiFilePath, abs } = processFile(resolvedHref); const mime = mimeType || getMimeType(abs); // Add as attachment node at the end diff --git a/apps/server/src/integrations/throttle/throttle.module.ts b/apps/server/src/integrations/throttle/throttle.module.ts new file mode 100644 index 00000000..8f080e1d --- /dev/null +++ b/apps/server/src/integrations/throttle/throttle.module.ts @@ -0,0 +1,35 @@ +import { Module } from '@nestjs/common'; +import { ThrottlerModule } from '@nestjs/throttler'; +import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis'; +import { EnvironmentService } from '../environment/environment.service'; +import { EnvironmentModule } from '../environment/environment.module'; +import { parseRedisUrl } from '../../common/helpers'; +import Redis from 'ioredis'; + +@Module({ + imports: [ + ThrottlerModule.forRootAsync({ + imports: [EnvironmentModule], + useFactory: (environmentService: EnvironmentService) => { + const redisConfig = parseRedisUrl(environmentService.getRedisUrl()); + + return { + throttlers: [{ name: 'auth', ttl: 60_000, limit: 10 }], + errorMessage: 'Too many requests', + storage: new ThrottlerStorageRedisService( + new Redis({ + host: redisConfig.host, + port: redisConfig.port, + password: redisConfig.password, + db: redisConfig.db, + family: redisConfig.family, + keyPrefix: 'throttle:', + }), + ), + }; + }, + inject: [EnvironmentService], + }), + ], +}) +export class ThrottleModule {} diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 0f2a82a1..d47bf547 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -10,6 +10,7 @@ import { TransformHttpResponseInterceptor } from './common/interceptors/http-res import { WsRedisIoAdapter } from './ws/adapter/ws-redis.adapter'; import fastifyMultipart from '@fastify/multipart'; import fastifyCookie from '@fastify/cookie'; +import fastifyIp from 'fastify-ip'; import { InternalLogFilter } from './common/logger/internal-log-filter'; async function bootstrap() { @@ -45,6 +46,7 @@ async function bootstrap() { app.useWebSocketAdapter(redisIoAdapter); + await app.register(fastifyIp); await app.register(fastifyMultipart); await app.register(fastifyCookie); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0389d23..6599b07a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -493,6 +493,9 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.27.1 version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) + '@nest-lab/throttler-storage-redis': + specifier: ^1.2.0 + version: 1.2.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/throttler@6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2))(ioredis@5.10.1)(reflect-metadata@0.2.2) '@nestjs-labs/nestjs-ioredis': specifier: ^11.0.4 version: 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(ioredis@5.10.1) @@ -535,6 +538,9 @@ importers: '@nestjs/terminus': specifier: ^11.1.1 version: 11.1.1(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/throttler': + specifier: ^6.5.0 + version: 6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2) '@nestjs/websockets': specifier: ^11.1.17 version: 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/platform-socket.io@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -580,6 +586,9 @@ importers: cookie: specifier: ^1.1.1 version: 1.1.1 + fastify-ip: + specifier: ^2.0.0 + version: 2.0.0 fs-extra: specifier: ^11.3.4 version: 11.3.4 @@ -2925,6 +2934,15 @@ packages: '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@nest-lab/throttler-storage-redis@1.2.0': + resolution: {integrity: sha512-tMkUyo68NCKTR+zILk+EC35SMYBtDPZY2mCj7ZaCietWGVTnuP4zwq9ERYfvU6kJv6h8teNZrC6MJCmY6/dljw==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/throttler': '>=6.0.0' + ioredis: '>=5.0.0' + reflect-metadata: ^0.2.1 + '@nestjs-labs/nestjs-ioredis@11.0.4': resolution: {integrity: sha512-4jPNOrxDiwNMIN5OLmsMWhA782kxv/ZBxkySX9l8n6sr55acHX/BciaFsOXVa/ILsm+Y7893y98/6WNhmEoiNQ==} engines: {node: '>=16'} @@ -3127,6 +3145,13 @@ packages: '@nestjs/platform-express': optional: true + '@nestjs/throttler@6.5.0': + resolution: {integrity: sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + '@nestjs/websockets@11.1.17': resolution: {integrity: sha512-YbwQ0QfVj0lxkKQhdIIgk14ZSVWDqGk1J8nNSN6SLjf36sVv58Ma5ro+dtQua8wj3l2Ub7JJCVFixEhKtYc/rQ==} peerDependencies: @@ -7016,6 +7041,10 @@ packages: resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} hasBin: true + fastify-ip@2.0.0: + resolution: {integrity: sha512-7mQyAc7sapawpiriEFoJyQIs41nNIO42UCzgMKrjNGsIegnevj2VhOlXLLTa+q7cxXfJ5fDGmOAdQpaIgA9ObA==} + engines: {node: '>=20.x'} + fastify-plugin@5.0.1: resolution: {integrity: sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==} @@ -13463,6 +13492,15 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@nest-lab/throttler-storage-redis@1.2.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/throttler@6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2))(ioredis@5.10.1)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/throttler': 6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2) + ioredis: 5.10.1 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + '@nestjs-labs/nestjs-ioredis@11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(ioredis@5.10.1)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -13643,6 +13681,12 @@ snapshots: '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 + '@nestjs/throttler@6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + '@nestjs/websockets@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/platform-socket.io@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -18072,6 +18116,10 @@ snapshots: path-expression-matcher: 1.2.0 strnum: 2.2.1 + fastify-ip@2.0.0: + dependencies: + fastify-plugin: 5.1.0 + fastify-plugin@5.0.1: {} fastify-plugin@5.1.0: {}