diff --git a/package.json b/package.json index 98070799..215c46c9 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@types/jsonwebtoken": "^9.0.2", "@types/node": "^20.2.3", "@types/pg": "^8.10.2", + "@types/ws": "^8.5.14", "@vitest/coverage-v8": "^0.34.5", "eslint": "^9.2.0", "eslint-config-codex": "^2.0.0", @@ -49,6 +50,7 @@ "@fastify/swagger-ui": "^1.9.3", "@testcontainers/postgresql": "^10.2.1", "arg": "^5.0.2", + "ctproto": "^0.0.13", "fastify": "^4.17.0", "http-status-codes": "^2.2.0", "jsonwebtoken": "^9.0.0", @@ -60,6 +62,7 @@ "prom-client": "^14.2.0", "sequelize": "^6.31.1", "testcontainers": "^10.2.1", + "ws": "^8.18.0", "zod": "^3.21.4" }, "packageManager": "yarn@3.6.4" diff --git a/src/index.ts b/src/index.ts index 5a0cffac..e74f325c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import runMetricsServer from '@infrastructure/metrics/index.js'; import { init as initDomainServices } from '@domain/index.js'; import { initORM, init as initRepositories } from '@repository/index.js'; import process from 'process'; +import SocketApi from '@presentation/sockets/socket-api.js'; /** * Application entry point @@ -14,11 +15,11 @@ const start = async (): Promise => { const orm = await initORM(config.database); const repositories = await initRepositories(orm, config.s3); const domainServices = initDomainServices(repositories, config); - const api = new API(config.httpApi); - - await api.init(domainServices); - await api.run(); + const httpApi = new API(config.httpApi); + const socketApi = new SocketApi(config.socketApi); + await httpApi.init(domainServices); + await httpApi.run(); if (config.metrics.enabled) { await runMetricsServer(); } diff --git a/src/infrastructure/config/index.ts b/src/infrastructure/config/index.ts index cfd565f6..6608d7d8 100644 --- a/src/infrastructure/config/index.ts +++ b/src/infrastructure/config/index.ts @@ -111,6 +111,16 @@ const HttpApiConfig = z.object({ export type HttpApiConfig = z.infer; +/** + * Socket API configuration + */ +const SocketApiConfig = z.object({ + host: z.string(), + port: z.number(), +}); + +export type SocketApiConfig = z.infer; + /** * Application configuration */ @@ -118,6 +128,7 @@ const AppConfig = z.object({ httpApi: HttpApiConfig, metrics: MetricsConfig, logging: LoggingConfig, + socketApi: SocketApiConfig, database: DatabaseConfig, auth: AuthConfig, openai: OpenAIConfig, @@ -162,6 +173,10 @@ const defaultConfig: AppConfig = { database: 'info', s3Storage: 'info', }, + socketApi: { + host: 'localhost', + port: 8080, + }, database: { dsn: 'postgres://user:pass@postgres/codex-notes', }, diff --git a/src/presentation/sockets/router/index.ts b/src/presentation/sockets/router/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/presentation/sockets/socket-api.ts b/src/presentation/sockets/socket-api.ts new file mode 100644 index 00000000..8a7f2036 --- /dev/null +++ b/src/presentation/sockets/socket-api.ts @@ -0,0 +1,83 @@ +import { CTProtoServer } from "ctproto"; + +/** + * Интерфейсы для CTProto + */ +interface AuthRequestPayload { + token: string; +} + +interface AuthResponsePayload { + userId: string; + username?: string; +} + +interface ApiRequest { + messageId: string; + type: string; + payload: any; +} + +interface ApiResponse { + messageId: string; + type: string; + payload: any; +} + +interface ApiUpdate { + messageId: string; + type: string; + payload: any; +} + +export default class SocketApi { + private ctproto: CTProtoServer< + AuthRequestPayload, + AuthResponsePayload, + ApiRequest, + ApiResponse, + ApiUpdate + >; + + private config: { host: string; port: number }; + + constructor(config: { host: string; port: number }) { + this.config = config; + this.ctproto = new CTProtoServer({ + port: this.config.port, + + async onAuth(authRequestPayload: AuthRequestPayload): Promise { + console.log("🔑 Проверка токена:", authRequestPayload.token); + + if (!authRequestPayload.token || authRequestPayload.token !== "valid-token") { + throw new Error("❌ Неверный токен"); + } + + return { + userId: "111", + username: "TestUser", + }; + }, + + async onMessage(message: ApiRequest): Promise { + console.log("📩 Получено сообщение от клиента:", message); + + if (message.type === "note:join") { + return { + messageId: message.messageId, + type: "response", + payload: { status: "joined", noteId: message.payload.noteId }, + }; + } + + return { + messageId: message.messageId, + type: "error", + payload: "❌ Неизвестная команда", + }; + }, + }); + + console.log(`✅ CTProto-сервер запущен на порту ${this.config.port}`); + } +} diff --git a/yarn.lock b/yarn.lock index 48967d84..af2c245e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2351,6 +2351,15 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:^8.5.14": + version: 8.5.14 + resolution: "@types/ws@npm:8.5.14" + dependencies: + "@types/node": "*" + checksum: b63d25146a0d2ebb9cb35e4b68c5a01d81b8f4657d62b0a0d9470df6a357798349a44064b2f84b8f98a553ba84d9a5ee302d3053feef02c7771010c55d290ca6 + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:7.9.0": version: 7.9.0 resolution: "@typescript-eslint/eslint-plugin@npm:7.9.0" @@ -3623,6 +3632,16 @@ __metadata: languageName: node linkType: hard +"ctproto@npm:^0.0.13": + version: 0.0.13 + resolution: "ctproto@npm:0.0.13" + dependencies: + nanoid: ^3.1.20 + ws: ^7.4.1 + checksum: 48adbd6c3fa69c1fa11a00105422c7b8563aadfb55d70908eba3988666177525775be7d0a64c2ec73a2c688b42aa63124b1f9c2ac9a2ad523401478fa9e6c6db + languageName: node + linkType: hard + "data-view-buffer@npm:^1.0.1": version: 1.0.1 resolution: "data-view-buffer@npm:1.0.1" @@ -6422,6 +6441,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.1.20": + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" + bin: + nanoid: bin/nanoid.cjs + checksum: dfe0adbc0c77e9655b550c333075f51bb28cfc7568afbf3237249904f9c86c9aaaed1f113f0fddddba75673ee31c758c30c43d4414f014a52a7a626efc5958c9 + languageName: node + linkType: hard + "nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" @@ -6555,8 +6583,10 @@ __metadata: "@types/jsonwebtoken": ^9.0.2 "@types/node": ^20.2.3 "@types/pg": ^8.10.2 + "@types/ws": ^8.5.14 "@vitest/coverage-v8": ^0.34.5 arg: ^5.0.2 + ctproto: ^0.0.13 eslint: ^9.2.0 eslint-config-codex: ^2.0.0 eslint-import-resolver-alias: 1.1.2 @@ -6580,6 +6610,7 @@ __metadata: typescript: ^5.0.4 typescript-eslint: ^7.6.0 vitest: ^1.4.0 + ws: ^8.18.0 zod: ^3.21.4 languageName: unknown linkType: soft @@ -9229,6 +9260,36 @@ __metadata: languageName: node linkType: hard +"ws@npm:^7.4.1": + version: 7.5.10 + resolution: "ws@npm:7.5.10" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: f9bb062abf54cc8f02d94ca86dcd349c3945d63851f5d07a3a61c2fcb755b15a88e943a63cf580cbdb5b74436d67ef6b67f745b8f7c0814e411379138e1863cb + languageName: node + linkType: hard + +"ws@npm:^8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 91d4d35bc99ff6df483bdf029b9ea4bfd7af1f16fc91231a96777a63d263e1eabf486e13a2353970efc534f9faa43bdbf9ee76525af22f4752cbc5ebda333975 + languageName: node + linkType: hard + "xml-name-validator@npm:^4.0.0": version: 4.0.0 resolution: "xml-name-validator@npm:4.0.0"