From 7c9d6a807ff579d493d5050d5336d1a2cd688f7c Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 01:32:42 -0300 Subject: [PATCH 01/11] =?UTF-8?q?chore:=20adicionar=20arquivo=20package-lo?= =?UTF-8?q?ck.json=20e=20atualizar=20depend=C3=AAncias=20para=20peer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/microsservicos/aluguel/package-lock.json | 10 ++++++++++ back/microsservicos/catalogo/package-lock.json | 3 +++ back/microsservicos/disponibilidade/package-lock.json | 11 +++++------ back/microsservicos/property/package-lock.json | 2 ++ back/microsservicos/user/package-lock.json | 2 ++ 5 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 back/microsservicos/aluguel/package-lock.json diff --git a/back/microsservicos/aluguel/package-lock.json b/back/microsservicos/aluguel/package-lock.json new file mode 100644 index 00000000..8fae7811 --- /dev/null +++ b/back/microsservicos/aluguel/package-lock.json @@ -0,0 +1,10 @@ +{ + "name": "aluguel", + "lockfileVersion": 3, + "requires": true, + "packages": { + "../../common": { + "extraneous": true + } + } +} diff --git a/back/microsservicos/catalogo/package-lock.json b/back/microsservicos/catalogo/package-lock.json index 576f9d67..e409cb7e 100644 --- a/back/microsservicos/catalogo/package-lock.json +++ b/back/microsservicos/catalogo/package-lock.json @@ -239,6 +239,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.913.0.tgz", "integrity": "sha512-YdWHIXn+TltH1MbMkBrFl8Ocxj/PJXleacQ1U5AZRAt8EqxctYkeTNB/+XYS5x6ieYQ4uWnF7sF74sJx+KTpwg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", @@ -1809,6 +1810,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.11.tgz", "integrity": "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3590,6 +3592,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/back/microsservicos/disponibilidade/package-lock.json b/back/microsservicos/disponibilidade/package-lock.json index b23f6373..ced7176a 100644 --- a/back/microsservicos/disponibilidade/package-lock.json +++ b/back/microsservicos/disponibilidade/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "amqplib": "^0.10.8", "axios": "^1.9.0", - "common": "file:shared/clients/rabbitmq", "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^5.1.0", @@ -196,6 +195,7 @@ "integrity": "sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -472,10 +472,6 @@ "node": ">= 0.8" } }, - "node_modules/common": { - "resolved": "shared/clients/rabbitmq", - "link": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1661,6 +1657,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1747,6 +1744,8 @@ "node": ">=6" } }, - "shared/clients/rabbitmq": {} + "shared/clients/rabbitmq": { + "extraneous": true + } } } diff --git a/back/microsservicos/property/package-lock.json b/back/microsservicos/property/package-lock.json index a37edc5a..ec281fc5 100644 --- a/back/microsservicos/property/package-lock.json +++ b/back/microsservicos/property/package-lock.json @@ -178,6 +178,7 @@ "version": "22.15.29", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1592,6 +1593,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/back/microsservicos/user/package-lock.json b/back/microsservicos/user/package-lock.json index a3ad67ad..b33ceac1 100644 --- a/back/microsservicos/user/package-lock.json +++ b/back/microsservicos/user/package-lock.json @@ -191,6 +191,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1686,6 +1687,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From 49dafaff4b85155d7ec90717bc814c4f93de8618 Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 01:32:50 -0300 Subject: [PATCH 02/11] =?UTF-8?q?refactor:=20centralizar=20l=C3=B3gica=20d?= =?UTF-8?q?e=20encerramento=20do=20servidor=20em=20uma=20fun=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aluguel/bin/aluguel_dart.dart | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/back/microsservicos/aluguel/bin/aluguel_dart.dart b/back/microsservicos/aluguel/bin/aluguel_dart.dart index d6744236..f2dbcd9d 100644 --- a/back/microsservicos/aluguel/bin/aluguel_dart.dart +++ b/back/microsservicos/aluguel/bin/aluguel_dart.dart @@ -21,6 +21,20 @@ import 'package:aluguel_dart/presentation/http/controllers/create_aluguel_contro import 'package:aluguel_dart/presentation/http/controllers/update_aluguel_controller.dart'; import 'package:aluguel_dart/presentation/http/controllers/delete_aluguel_controller.dart'; +Future _shutdownServer(HttpServer server, String message) async { + stdout.writeln(message); + try { + await closeRabbitMQConnection(); + } catch (e) { + stderr.writeln('Erro ao fechar RabbitMQ: $e'); + } finally { + try { + await server.close(force: true); + } catch (_) {} + exit(0); + } +} + Future main() async { final AluguelRepository repo = Environments.getAluguelRepo(); @@ -57,30 +71,12 @@ Future main() async { final server = await io.serve(handler, InternetAddress.anyIPv4, port); print('Aluguel. Porta: $port'); await startQueue(); - ProcessSignal.sigint.watch().listen((_) async { - stdout.writeln('Service aluguel interrupted!'); - try { - await closeRabbitMQConnection(); - } catch (e) { - stderr.writeln('Erro ao fechar RabbitMQ: $e'); - } finally { - try { - await server.close(force: true); - } catch (_) {} - exit(0); - } - }); - ProcessSignal.sigterm.watch().listen((_) async { - stdout.writeln('Service aluguel terminated!'); - try { - await closeRabbitMQConnection(); - } catch (e) { - stderr.writeln('Erro ao fechar RabbitMQ: $e'); - } finally { - try { - await server.close(force: true); - } catch (_) {} - exit(0); - } - }); + ProcessSignal.sigint.watch().listen( + (_) async => _shutdownServer(server, 'Service aluguel interrupted!'), + ); + if (!Platform.isWindows) { + ProcessSignal.sigterm.watch().listen( + (_) async => _shutdownServer(server, 'Service aluguel terminated!'), + ); + } } From 75222706c24cff3d3232f8366819cda2af561803 Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 03:30:31 -0300 Subject: [PATCH 03/11] =?UTF-8?q?chore:=20adicionar=20a=20propriedade=20"p?= =?UTF-8?q?eer"=20em=20v=C3=A1rias=20depend=C3=AAncias=20no=20package-lock?= =?UTF-8?q?.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/package-lock.json | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/front/package-lock.json b/front/package-lock.json index 1f164c62..9616a0fc 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -80,6 +80,7 @@ "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1673,6 +1674,7 @@ "integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1723,6 +1725,7 @@ "integrity": "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.32.0", "@typescript-eslint/types": "8.32.0", @@ -1946,6 +1949,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2130,6 +2134,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -2601,6 +2606,7 @@ "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -2814,6 +2820,7 @@ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -4183,6 +4190,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -4318,6 +4326,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4327,6 +4336,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -4851,6 +4861,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4928,6 +4939,7 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5025,6 +5037,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -5113,6 +5126,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5160,20 +5174,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5193,6 +5193,7 @@ "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 28b04f06d8f8650f8bb9bff9ece7b01f9b723946 Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 03:30:44 -0300 Subject: [PATCH 04/11] =?UTF-8?q?chore:=20atualizar=20a=20depend=C3=AAncia?= =?UTF-8?q?=20"crypto"=20para=20ser=20direta=20no=20pubspec.yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/microsservicos/aluguel/pubspec.lock | 2 +- back/microsservicos/aluguel/pubspec.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/back/microsservicos/aluguel/pubspec.lock b/back/microsservicos/aluguel/pubspec.lock index 7909722f..418ac5a0 100644 --- a/back/microsservicos/aluguel/pubspec.lock +++ b/back/microsservicos/aluguel/pubspec.lock @@ -74,7 +74,7 @@ packages: source: hosted version: "1.15.0" crypto: - dependency: transitive + dependency: "direct main" description: name: crypto sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" diff --git a/back/microsservicos/aluguel/pubspec.yaml b/back/microsservicos/aluguel/pubspec.yaml index 6e553e37..39a3017b 100644 --- a/back/microsservicos/aluguel/pubspec.yaml +++ b/back/microsservicos/aluguel/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: shelf_router: ^1.1.4 uuid: ^4.5.1 dart_amqp: ^0.2.5 + crypto: ^3.0.6 dev_dependencies: lints: ^6.0.0 From 36a711460da996c217408e6bfc7dd622d516302d Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 03:39:08 -0300 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20adicionar=20suporte=20para=20c?= =?UTF-8?q?=C3=B3digo=20de=20porta=20nas=20opera=C3=A7=C3=B5es=20de=20alug?= =?UTF-8?q?uel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/create_aluguel_usecase.dart | 39 +++++++++++++------ .../application/update_aluguel_usecase.dart | 18 +++++++-- .../aluguel/lib/domain/entities/aluguel.dart | 3 ++ .../repositories/aluguel_repository.dart | 4 +- .../repositories/aluguel_repository_mock.dart | 4 ++ .../create_aluguel_controller.dart | 12 ++++-- .../update_aluguel_controller.dart | 11 ++++-- 7 files changed, 69 insertions(+), 22 deletions(-) diff --git a/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart b/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart index a29b3a57..23f624e1 100644 --- a/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart +++ b/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart @@ -2,6 +2,7 @@ import 'package:aluguel_dart/domain/entities/aluguel.dart'; import 'package:aluguel_dart/domain/repositories/aluguel_repository.dart'; import 'package:aluguel_dart/infrastructure/clients/rabbitmq/rabbitmq.dart'; import 'package:aluguel_dart/infrastructure/clients/rabbitmq/rabbitmq_event.dart'; +import 'package:aluguel_dart/shared/security/door_code_hasher.dart'; class CreateAluguelUsecase { final AluguelRepository repository; @@ -11,23 +12,36 @@ class CreateAluguelUsecase { Future call({ required String userId, required String workspaceId, - required int startDate, - required int endDate, + required int startDate, + required int endDate, required int people, required num finalPrice, + String? doorCode, }) async { - if (endDate < startDate) { throw StateError('endDate não pode ser menor que startDate.'); } if (people <= 0) { - throw StateError('O número de pessoas para a reserva deve ser diferente de null e maior que.'); + throw StateError( + 'O número de pessoas para a reserva deve ser diferente de null e maior que zero.', + ); } if (finalPrice < 0) { throw StateError('finalPrice não pode ser negativo.'); } + if (doorCode != null) { + if (doorCode.isEmpty) { + throw StateError('doorCode não pode ser vazio.'); + } + if (!RegExp(r'^\d{5}$').hasMatch(doorCode)) { + throw StateError('doorCode deve conter exatamente 5 dígitos.'); + } + } + + final String? hashedDoorCode = + doorCode != null ? hashDoorCode(doorCode) : null; - Aluguel createdAluguel = await repository.createAluguel( + final createdAluguel = await repository.createAluguel( userId: userId, workspaceId: workspaceId, startDate: startDate, @@ -35,20 +49,21 @@ class CreateAluguelUsecase { people: people, finalPrice: finalPrice.toDouble(), status: 'PENDING', + doorCode: hashedDoorCode, ); - RabbitMQEvent aluguelCreated = RabbitMQEvent(eventType: 'AluguelCreated', payload: createdAluguel.toJson()); + final aluguelCreated = RabbitMQEvent( + eventType: 'AluguelCreated', + payload: createdAluguel.toJson(), + ); - final published = await publishEvent('aluguel.created', aluguelCreated.toJson()); + final published = + await publishEvent('aluguel.created', aluguelCreated.toJson()); - if(published){ + if (published) { return createdAluguel; } else { throw StateError('Não foi possível criar o aluguel'); } - } } - - - diff --git a/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart b/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart index f3830733..0c2e7d31 100644 --- a/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart +++ b/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart @@ -1,5 +1,6 @@ import 'package:aluguel_dart/domain/entities/aluguel.dart'; import 'package:aluguel_dart/domain/repositories/aluguel_repository.dart'; +import 'package:aluguel_dart/shared/security/door_code_hasher.dart'; class UpdateAluguelUsecase { final AluguelRepository repository; @@ -8,13 +9,13 @@ class UpdateAluguelUsecase { Future call( String id, { - int? startDate, + int? startDate, int? endDate, int? people, double? finalPrice, String? status, + String? doorCode, }) async { - if (startDate != null && endDate != null && endDate < startDate) { throw StateError('endDate não pode ser menor que startDate.'); } @@ -24,6 +25,16 @@ class UpdateAluguelUsecase { if (finalPrice != null && finalPrice < 0) { throw StateError('finalPrice não pode ser negativo.'); } + String? hashedDoorCode; + if (doorCode != null) { + if (doorCode.isEmpty) { + throw StateError('doorCode não pode ser vazio.'); + } + if (!RegExp(r'^\d{5}$').hasMatch(doorCode)) { + throw StateError('doorCode deve conter exatamente 5 dígitos.'); + } + hashedDoorCode = hashDoorCode(doorCode); + } return repository.updateAluguel( id, @@ -32,6 +43,7 @@ class UpdateAluguelUsecase { people: people, finalPrice: finalPrice, status: status, + doorCode: hashedDoorCode, ); } -} +} \ No newline at end of file diff --git a/back/microsservicos/aluguel/lib/domain/entities/aluguel.dart b/back/microsservicos/aluguel/lib/domain/entities/aluguel.dart index 961072de..c9dd0700 100644 --- a/back/microsservicos/aluguel/lib/domain/entities/aluguel.dart +++ b/back/microsservicos/aluguel/lib/domain/entities/aluguel.dart @@ -7,6 +7,7 @@ class Aluguel { final int people; final double finalPrice; final String status; + final String? doorCode; final int createdAt; final int updatedAt; @@ -19,6 +20,7 @@ class Aluguel { required this.people, required this.finalPrice, required this.status, + this.doorCode, required this.createdAt, required this.updatedAt, }); @@ -33,6 +35,7 @@ class Aluguel { 'people': people, 'finalPrice': finalPrice, 'status': status, + 'doorCode': doorCode, 'createdAt': createdAt, 'updatedAt': updatedAt, }; diff --git a/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart b/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart index 233b5e7b..74fca1f7 100644 --- a/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart +++ b/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart @@ -9,6 +9,7 @@ abstract class AluguelRepository { required int people, required double finalPrice, required String status, + String? doorCode, }); Future getAluguel(String id); @@ -22,8 +23,9 @@ abstract class AluguelRepository { int? people, double? finalPrice, String? status, + String? doorCode, }); Future deleteAluguel(String id); -} \ No newline at end of file +} diff --git a/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart b/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart index 59a69d75..1629a480 100644 --- a/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart +++ b/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart @@ -19,6 +19,7 @@ class AluguelRepositoryMock implements AluguelRepository { required int people, required double finalPrice, required String status, + String? doorCode, }) async { @@ -34,6 +35,7 @@ class AluguelRepositoryMock implements AluguelRepository { people: people, finalPrice: finalPrice, status: status, + doorCode: doorCode, createdAt: now, updatedAt: now, ); @@ -60,6 +62,7 @@ class AluguelRepositoryMock implements AluguelRepository { int? people, double? finalPrice, String? status, + String? doorCode, }) async { final atual = store[id]; @@ -79,6 +82,7 @@ class AluguelRepositoryMock implements AluguelRepository { people: people ?? atual.people, finalPrice: finalPrice ?? atual.finalPrice, status: status ?? atual.status, + doorCode: doorCode ?? atual.doorCode, createdAt: atual.createdAt, updatedAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, ); diff --git a/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart b/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart index 80625b1f..f1c7fe23 100644 --- a/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart +++ b/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart @@ -1,8 +1,9 @@ import 'dart:convert'; -import 'package:shelf/shelf.dart'; + import 'package:aluguel_dart/application/create_aluguel_usecase.dart'; -import 'package:aluguel_dart/shared/http/json_response.dart'; import 'package:aluguel_dart/shared/http/app_failures.dart'; +import 'package:aluguel_dart/shared/http/json_response.dart'; +import 'package:shelf/shelf.dart'; class CreateAluguelController { final CreateAluguelUsecase createAluguelUsecase; @@ -24,7 +25,8 @@ class CreateAluguelController { final endDate = data['endDate']; final people = data['people']; final finalPrice = data['finalPrice']; - + final doorCode = data['doorCode']; + if (userId == null || userId.toString().isEmpty) { throw AppFailure('userId_required'); } @@ -43,6 +45,9 @@ class CreateAluguelController { if (finalPrice == null) { throw AppFailure('finalPrice_required'); } + if (doorCode != null && doorCode.toString().isEmpty) { + throw AppFailure('doorCode_cannot_be_empty'); + } final aluguel = await createAluguelUsecase.call( userId: userId, @@ -51,6 +56,7 @@ class CreateAluguelController { endDate: endDate, people: people, finalPrice: finalPrice, + doorCode: doorCode, ); return jsonCreated(aluguel.toJson()); diff --git a/back/microsservicos/aluguel/lib/presentation/http/controllers/update_aluguel_controller.dart b/back/microsservicos/aluguel/lib/presentation/http/controllers/update_aluguel_controller.dart index 8cc6deb8..1e6e667d 100644 --- a/back/microsservicos/aluguel/lib/presentation/http/controllers/update_aluguel_controller.dart +++ b/back/microsservicos/aluguel/lib/presentation/http/controllers/update_aluguel_controller.dart @@ -1,9 +1,9 @@ -// lib/src/presentation/http/controllers/update_aluguel_controller.dart import 'dart:convert'; -import 'package:shelf/shelf.dart'; + import 'package:aluguel_dart/application/update_aluguel_usecase.dart'; -import 'package:aluguel_dart/shared/http/json_response.dart'; import 'package:aluguel_dart/shared/http/app_failures.dart'; +import 'package:aluguel_dart/shared/http/json_response.dart'; +import 'package:shelf/shelf.dart'; class UpdateAluguelController { final UpdateAluguelUsecase updateAluguelUsecase; @@ -29,6 +29,7 @@ class UpdateAluguelController { final int? people = data['people']; final num? finalPrice = data['finalPrice']; final String? status = data['status']; + final String? doorCode = data['doorCode']; if (startDate != null && endDate != null && endDate <= startDate) { throw AppFailure('endDate_must_be_greater_than_startDate'); @@ -39,6 +40,9 @@ class UpdateAluguelController { if (finalPrice != null && finalPrice <= 0) { throw AppFailure('finalPrice_must_be_positive'); } + if (doorCode != null && doorCode.isEmpty) { + throw AppFailure('doorCode_cannot_be_empty'); + } final aluguelAtualizado = await updateAluguelUsecase.call( id.toString(), @@ -47,6 +51,7 @@ class UpdateAluguelController { people: people, finalPrice: finalPrice?.toDouble(), status: status, + doorCode: doorCode, ); return jsonOk(aluguelAtualizado.toJson()); From 1e9ca0c1cdfef1bde0f74ec7ee52c38693e3f56e Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 03:39:53 -0300 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20adicionar=20verifica=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20c=C3=B3digo=20de=20porta=20e=20controlador=20correspond?= =?UTF-8?q?ente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aluguel/bin/aluguel_dart.dart | 7 +- .../application/verify_door_code_usecase.dart | 37 ++++++++ .../clients/rabbitmq/rabbitmq.dart | 95 +++++++++++++++++++ .../controllers/get_door_hash_controller.dart | 56 +++++++++++ .../aluguel/lib/presentation/http/router.dart | 6 +- .../lib/shared/security/door_code_hasher.dart | 16 ++++ 6 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 back/microsservicos/aluguel/lib/application/verify_door_code_usecase.dart create mode 100644 back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart create mode 100644 back/microsservicos/aluguel/lib/shared/security/door_code_hasher.dart diff --git a/back/microsservicos/aluguel/bin/aluguel_dart.dart b/back/microsservicos/aluguel/bin/aluguel_dart.dart index f2dbcd9d..d09d1ea9 100644 --- a/back/microsservicos/aluguel/bin/aluguel_dart.dart +++ b/back/microsservicos/aluguel/bin/aluguel_dart.dart @@ -15,11 +15,13 @@ import 'package:aluguel_dart/application/get_aluguel_usecase.dart'; import 'package:aluguel_dart/application/create_aluguel_usecase.dart'; import 'package:aluguel_dart/application/update_aluguel_usecase.dart'; import 'package:aluguel_dart/application/delete_aluguel_usecase.dart'; +import 'package:aluguel_dart/application/verify_door_code_usecase.dart'; import 'package:aluguel_dart/domain/repositories/aluguel_repository.dart'; import 'package:aluguel_dart/presentation/http/controllers/get_aluguel_controller.dart'; import 'package:aluguel_dart/presentation/http/controllers/create_aluguel_controller.dart'; import 'package:aluguel_dart/presentation/http/controllers/update_aluguel_controller.dart'; import 'package:aluguel_dart/presentation/http/controllers/delete_aluguel_controller.dart'; +import 'package:aluguel_dart/presentation/http/controllers/get_door_hash_controller.dart'; Future _shutdownServer(HttpServer server, String message) async { stdout.writeln(message); @@ -43,12 +45,14 @@ Future main() async { final updateAluguelUsecase = UpdateAluguelUsecase(repository: repo); final deleteAluguelUsecase = DeleteAluguelUsecase(repository: repo); final getAllAluguelUsecase = GetAllAluguelUsecase(repository: repo); + final verifyDoorCodeUsecase = VerifyDoorCodeUsecase(repository: repo); final getController = GetAluguelController(getAluguelUsecase); final createController = CreateAluguelController(createAluguelUsecase); final updateController = UpdateAluguelController(updateAluguelUsecase); final deleteController = DeleteAluguelController(deleteAluguelUsecase); final getAllAluguelController = GetAllAluguelController(getAllAluguelUsecase); + final getDoorHashController = GetDoorHashController(verifyDoorCodeUsecase); final router = Router() ..mount( @@ -58,7 +62,8 @@ Future main() async { createController: createController, updateController: updateController, deleteController: deleteController, - getAllAluguelController: getAllAluguelController + getAllAluguelController: getAllAluguelController, + getDoorHashController: getDoorHashController, ).call, ); diff --git a/back/microsservicos/aluguel/lib/application/verify_door_code_usecase.dart b/back/microsservicos/aluguel/lib/application/verify_door_code_usecase.dart new file mode 100644 index 00000000..35319903 --- /dev/null +++ b/back/microsservicos/aluguel/lib/application/verify_door_code_usecase.dart @@ -0,0 +1,37 @@ +import 'package:aluguel_dart/domain/repositories/aluguel_repository.dart'; +import 'package:aluguel_dart/infrastructure/clients/rabbitmq/rabbitmq.dart'; + +class VerifyDoorCodeUsecase { + final AluguelRepository repository; + + VerifyDoorCodeUsecase({required this.repository}); + + Future call({ + required String aluguelId, + required String plainDoorCode, + }) async { + if (!RegExp(r'^\d{5}$').hasMatch(plainDoorCode)) { + throw StateError('doorCode deve conter exatamente 5 digitos.'); + } + + final aluguel = await repository.getAluguel(aluguelId); + + if (aluguel == null) { + throw StateError('aluguel_not_found'); + } + + if (aluguel.status.toUpperCase() != 'CONFIRMED') { + return false; + } + + final nowEpoch = DateTime.now().millisecondsSinceEpoch ~/ 1000; + if (nowEpoch < aluguel.startDate || nowEpoch > aluguel.endDate) { + return false; + } + + return await verifyDoorCodeWithCatalog( + workspaceId: aluguel.workspaceId, + doorCode: plainDoorCode, + ); + } +} diff --git a/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart b/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart index ccf43ed1..fab9659c 100644 --- a/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart +++ b/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:dart_amqp/dart_amqp.dart'; +import 'package:uuid/uuid.dart'; import 'package:aluguel_dart/shared/environments.dart'; const String EXCHANGE_NAME = 'global_events'; @@ -211,6 +212,100 @@ Future closeRabbitMQConnection() async { +Future verifyDoorCodeWithCatalog({ + required String workspaceId, + required String doorCode, + Duration timeout = const Duration(seconds: 5), +}) async { + final channel = await connectRabbitMQ(); + final exchange = + await channel.exchange(EXCHANGE_NAME, ExchangeType.TOPIC, durable: true); + final replyQueue = await channel.queue('', exclusive: true, autoDelete: true); + final correlationId = const Uuid().v4(); + final completer = Completer(); + final consumer = await replyQueue.consume(noAck: false); + StreamSubscription? subscription; + + subscription = consumer.listen( + (AmqpMessage message) { + try { + if (message.properties?.corellationId != correlationId) { + message.reject(false); + return; + } + + final decoded = jsonDecode(message.payloadAsString); + bool result = false; + if (decoded is Map) { + result = decoded['valid'] == true; + } else if (decoded is bool) { + result = decoded; + } + + message.ack(); + if (!completer.isCompleted) { + completer.complete(result); + } + subscription?.cancel(); + } catch (error) { + message.reject(false); + if (!completer.isCompleted) { + completer.completeError(error); + } + } + }, + onError: (error) { + if (!completer.isCompleted) { + completer.completeError(error); + } + }, + onDone: () { + if (!completer.isCompleted) { + completer.completeError( + StateError('reply queue closed before receiving response'), + ); + } + }, + cancelOnError: false, + ); + + final payload = { + 'eventType': 'CatalogoVerifyDoorCodeRequest', + 'payload': { + 'workspaceId': workspaceId, + 'doorCode': doorCode, + }, + }; + + final properties = MessageProperties() + ..replyTo = replyQueue.name + ..corellationId = correlationId + ..contentType = 'application/json' + ..deliveryMode = 1; + + exchange.publish( + utf8.encode(jsonEncode(payload)), + 'catalogo.verify-door-code.request', + properties: properties, + ); + + final timer = Timer(timeout, () { + if (!completer.isCompleted) { + completer.completeError( + TimeoutException('Door code verification timed out'), + ); + } + }); + + try { + return await completer.future; + } finally { + timer.cancel(); + await subscription.cancel(); + await replyQueue.delete(); + } +} + const String _DELAY_EXCHANGE = 'aluguel.delay.ex'; // exchange de delay (direct) const String _DELAY_QUEUE = 'aluguel.delay.q'; // fila de delay const String _DELAY_RK = 'aluguel.expire'; // routing key para entrar no delay diff --git a/back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart b/back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart new file mode 100644 index 00000000..c5fd3cce --- /dev/null +++ b/back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart @@ -0,0 +1,56 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:aluguel_dart/application/verify_door_code_usecase.dart'; +import 'package:aluguel_dart/shared/http/app_failures.dart'; +import 'package:aluguel_dart/shared/http/json_response.dart'; +import 'package:shelf/shelf.dart'; + +class GetDoorHashController { + final VerifyDoorCodeUsecase verifyDoorCodeUsecase; + + GetDoorHashController(this.verifyDoorCodeUsecase); + + Future handle(Request req) async { + try { + final body = await req.readAsString(); + if (body.isEmpty) { + throw AppFailure('body_required'); + } + + final data = jsonDecode(body); + + final aluguelId = (data['aluguelId'] ?? data['bookingId'])?.toString(); + final doorCode = data['doorCode']?.toString(); + + if (aluguelId == null || aluguelId.isEmpty) { + throw AppFailure('aluguelId_required'); + } + if (doorCode == null || doorCode.isEmpty) { + throw AppFailure('doorCode_required'); + } + + final isValid = await verifyDoorCodeUsecase.call( + aluguelId: aluguelId, + plainDoorCode: doorCode, + ); + + return jsonOk({'valid': isValid}); + } on AppFailure catch (e) { + return jsonBadRequest({'error': e.message}); + } on StateError catch (e) { + if (e.message == 'aluguel_not_found') { + return jsonNotFound({'error': e.message}); + } + return jsonBadRequest({'error': e.message}); + } on TimeoutException { + return Response( + 504, + body: jsonEncode({'error': 'catalogo_timeout'}), + headers: {'content-type': 'application/json'}, + ); + } catch (e) { + return jsonServerError({'error': 'internal_error', 'detail': e.toString()}); + } + } +} diff --git a/back/microsservicos/aluguel/lib/presentation/http/router.dart b/back/microsservicos/aluguel/lib/presentation/http/router.dart index 1f3f5260..d8244e89 100644 --- a/back/microsservicos/aluguel/lib/presentation/http/router.dart +++ b/back/microsservicos/aluguel/lib/presentation/http/router.dart @@ -2,6 +2,7 @@ import 'package:aluguel_dart/presentation/http/controllers/create_aluguel_contro import 'package:aluguel_dart/presentation/http/controllers/delete_aluguel_controller.dart'; import 'package:aluguel_dart/presentation/http/controllers/get_all_aluguel_controller.dart'; import 'package:aluguel_dart/presentation/http/controllers/get_aluguel_controller.dart'; +import 'package:aluguel_dart/presentation/http/controllers/get_door_hash_controller.dart'; import 'package:aluguel_dart/presentation/http/controllers/update_aluguel_controller.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf_router/shelf_router.dart'; @@ -12,7 +13,8 @@ Router buildAluguelRoutes({ required CreateAluguelController createController, required UpdateAluguelController updateController, required DeleteAluguelController deleteController, - required GetAllAluguelController getAllAluguelController + required GetAllAluguelController getAllAluguelController, + required GetDoorHashController getDoorHashController, }) { final r = Router(); @@ -29,5 +31,7 @@ Router buildAluguelRoutes({ r.delete('/aluguel', deleteController.handle); + r.post('/get-door-hash', getDoorHashController.handle); + return r; } diff --git a/back/microsservicos/aluguel/lib/shared/security/door_code_hasher.dart b/back/microsservicos/aluguel/lib/shared/security/door_code_hasher.dart new file mode 100644 index 00000000..25de5c40 --- /dev/null +++ b/back/microsservicos/aluguel/lib/shared/security/door_code_hasher.dart @@ -0,0 +1,16 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; + +String hashDoorCode(String code) { + final normalizedCode = code.trim(); + final bytes = utf8.encode(normalizedCode); + return sha256.convert(bytes).toString(); +} + +bool verifyDoorCodeHash({ + required String plainCode, + required String hashedCode, +}) { + return hashDoorCode(plainCode) == hashedCode; +} From f0779e206ef31e384fdeacd55970101b8ff6c5c1 Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 03:40:05 -0300 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20implementar=20verifica=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20c=C3=B3digo=20de=20porta=20com=20hash=20SHA-256?= =?UTF-8?q?=20e=20consumidor=20RabbitMQ?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../create_catalogo_controller.ts | 30 ++++- .../update_catalogo_controller.ts | 33 +++++- back/microsservicos/catalogo/index.ts | 13 ++- .../catalogo/shared/domain/interfaces.ts | 3 +- .../shared/domain/repo/catalogoRepository.ts | 2 +- .../rabbitmq/doorCodeVerificationConsumer.ts | 106 ++++++++++++++++++ .../shared/infra/clients/rabbitmq/rabbitmq.ts | 4 +- .../shared/infra/clients/rabbitmq/types.ts | 3 +- .../infra/repo/catalogoRepositoryMock.ts | 16 ++- 9 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts diff --git a/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts b/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts index d37f76c9..6f8e512b 100644 --- a/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts +++ b/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts @@ -1,3 +1,4 @@ +import crypto from "crypto"; import { CreateCatalogoUsecase } from "./create_catalogo_usecase"; import { Request, Response } from "express"; import { Catalogo } from "../../shared/domain/interfaces"; @@ -30,6 +31,12 @@ export class CreateCatalogoController { if (body.capacity === undefined) throw new Error("Missing catalogo capacity"); + const { + doorCode, + doorCodeHash: providedDoorCodeHash, + ...restBody + } = body; + const price = typeof body.price === "string" ? Number(body.price) : body.price; const capacity = @@ -41,12 +48,33 @@ export class CreateCatalogoController { ? [body.comodities] : []; + let doorCodeHash: string | undefined = providedDoorCodeHash; + const rawDoorInput: unknown = doorCode ?? providedDoorCodeHash; + + if (rawDoorInput !== undefined) { + if (typeof rawDoorInput !== "string") { + throw new Error("doorCode must be a string"); + } + const trimmed = rawDoorInput.trim(); + + if (/^\d{5}$/.test(trimmed)) { + doorCodeHash = crypto.createHash("sha256").update(trimmed).digest("hex"); + } else if (/^[a-f0-9]{64}$/i.test(trimmed)) { + doorCodeHash = trimmed.toLowerCase(); + } else { + throw new Error( + "Invalid doorCode format. Provide 5 digits or a SHA-256 hash.", + ); + } + } + const roomProps = { - ...body, + ...restBody, comodities, pictures: [], price, capacity, + doorCodeHash, } as Catalogo; const createdRoom = this.usecase.execute(roomProps); diff --git a/back/microsservicos/catalogo/app/update_catalogo/update_catalogo_controller.ts b/back/microsservicos/catalogo/app/update_catalogo/update_catalogo_controller.ts index c1374d74..ed218d72 100644 --- a/back/microsservicos/catalogo/app/update_catalogo/update_catalogo_controller.ts +++ b/back/microsservicos/catalogo/app/update_catalogo/update_catalogo_controller.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import crypto from "crypto"; import { Request, Response } from 'express' import { UpdateCatalogoUsecase } from "./update_catalogo_usecase"; import { updateCatalogoProps } from "../../shared/domain/types"; @@ -19,7 +20,35 @@ export class UpdateCatalogoController { if (id === undefined) throw new Error('ID não informado') if (id.length !== 36) throw new Error('ID inválido') - const props: updateCatalogoProps = { id, ...req.body }; + const { + doorCode, + doorCodeHash: providedDoorCodeHash, + ...restBody + } = req.body ?? {}; + + let doorCodeHash: string | undefined = providedDoorCodeHash; + const rawDoorInput: unknown = doorCode ?? providedDoorCodeHash; + + if (rawDoorInput !== undefined) { + if (typeof rawDoorInput !== "string") { + throw new Error("doorCode must be a string"); + } + const trimmed = rawDoorInput.trim(); + + if (/^\d{5}$/.test(trimmed)) { + doorCodeHash = crypto.createHash("sha256").update(trimmed).digest("hex"); + } else if (/^[a-f0-9]{64}$/i.test(trimmed)) { + doorCodeHash = trimmed.toLowerCase(); + } else { + throw new Error("Invalid doorCode format. Provide 5 digits or a SHA-256 hash."); + } + } + + const props: updateCatalogoProps = { + id, + ...restBody, + ...(doorCodeHash ? { doorCodeHash } : {}), + }; const room_updated = this.usecase.execute(props) @@ -50,4 +79,4 @@ export class UpdateCatalogoController { } -} \ No newline at end of file +} diff --git a/back/microsservicos/catalogo/index.ts b/back/microsservicos/catalogo/index.ts index 77a801c1..fcb75fbe 100644 --- a/back/microsservicos/catalogo/index.ts +++ b/back/microsservicos/catalogo/index.ts @@ -2,12 +2,19 @@ import { Environments } from './shared/environments' import { startQueue } from './shared/handlers/event/eventsHandler' import { App } from './shared/handlers/server/app' import { closeRabbitMQConnection } from './shared/infra/clients/rabbitmq/rabbitmq' +import { startDoorCodeVerificationConsumer } from './shared/infra/clients/rabbitmq/doorCodeVerificationConsumer' const port = Environments.getEnvs().port -new App().server.listen(port, () => { +new App().server.listen(port, async () => { console.log(`Catalogos. Porta: ${port}`) - startQueue() + try { + await startQueue() + await startDoorCodeVerificationConsumer() + } catch (error) { + console.error('Failed to start RabbitMQ consumers', error) + process.exit(1) + } }) // Desligamento seguro do RabbitMQ @@ -15,4 +22,4 @@ process.on('SIGINT', async () => { console.log('Service catalogo interrupted!') await closeRabbitMQConnection() process.exit(0) -}) \ No newline at end of file +}) diff --git a/back/microsservicos/catalogo/shared/domain/interfaces.ts b/back/microsservicos/catalogo/shared/domain/interfaces.ts index 98121253..6b61c9a1 100644 --- a/back/microsservicos/catalogo/shared/domain/interfaces.ts +++ b/back/microsservicos/catalogo/shared/domain/interfaces.ts @@ -7,4 +7,5 @@ export interface Catalogo { pictures: string[] price: number capacity: number -} \ No newline at end of file + doorCodeHash?: string +} diff --git a/back/microsservicos/catalogo/shared/domain/repo/catalogoRepository.ts b/back/microsservicos/catalogo/shared/domain/repo/catalogoRepository.ts index 7c1119e4..4913669a 100644 --- a/back/microsservicos/catalogo/shared/domain/repo/catalogoRepository.ts +++ b/back/microsservicos/catalogo/shared/domain/repo/catalogoRepository.ts @@ -4,7 +4,7 @@ import { updateCatalogoProps } from '../types'; export interface CatalogoRepository { getAllCatalogo(): { [key: string]: Catalogo }; - getCatalogo(id: string): Catalogo; + getCatalogo(id: string): Catalogo | undefined; createCatalogo(props: Catalogo): Catalogo; diff --git a/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts new file mode 100644 index 00000000..f7533f3a --- /dev/null +++ b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts @@ -0,0 +1,106 @@ +import crypto from 'crypto'; + +import type { ConsumeMessage } from 'amqplib'; +import { Environments } from '../../../environments'; +import { connectRabbitMQ, EXCHANGE_NAME } from './rabbitmq'; + +const DOOR_CODE_RPC_QUEUE = 'catalogo.verify-door-code.rpc'; +const DOOR_CODE_REQUEST_RK = 'catalogo.verify-door-code.request'; + +export const startDoorCodeVerificationConsumer = async (): Promise => { + const channel = await connectRabbitMQ(); + + await channel.assertExchange(EXCHANGE_NAME, 'topic', { durable: true }); + + const { queue } = await channel.assertQueue(DOOR_CODE_RPC_QUEUE, { + durable: false, + exclusive: false, + autoDelete: false, + }); + + await channel.bindQueue(queue, EXCHANGE_NAME, DOOR_CODE_REQUEST_RK); + + await channel.consume(queue, async (msg: ConsumeMessage | null) => { + if (!msg) { + return; + } + + try { + const { replyTo, correlationId } = msg.properties; + + if (!replyTo || !correlationId) { + console.warn( + '[DoorCodeVerification] Missing replyTo or correlationId. Ignoring message.', + ); + channel.ack(msg); + return; + } + + let parsedPayload: any; + try { + parsedPayload = JSON.parse(msg.content.toString()); + } catch (error) { + console.error( + '[DoorCodeVerification] Failed to parse message payload:', + error, + ); + parsedPayload = {}; + } + + const payload = parsedPayload?.payload ?? parsedPayload; + const workspaceId = payload?.workspaceId; + const doorCode: string | undefined = payload?.doorCode; + + let valid = false; + + if (workspaceId && typeof doorCode === 'string') { + try { + const repo = Environments.getCatalogoRepo(); + const catalogo = repo.getCatalogo(workspaceId); + const storedHash = catalogo?.doorCodeHash; + + if (storedHash) { + const computedHash = crypto + .createHash('sha256') + .update(doorCode.trim()) + .digest('hex'); + + valid = computedHash === storedHash; + } + } catch (error) { + console.error( + '[DoorCodeVerification] Error while validating door code:', + error, + ); + } + } + + channel.sendToQueue( + replyTo, + Buffer.from(JSON.stringify({ valid })), + { + correlationId, + contentType: 'application/json', + }, + ); + } catch (error) { + console.error( + '[DoorCodeVerification] Unexpected error handling request:', + error, + ); + const { replyTo, correlationId } = msg.properties; + if (replyTo && correlationId) { + channel.sendToQueue( + replyTo, + Buffer.from(JSON.stringify({ valid: false, error: 'internal_error' })), + { + correlationId, + contentType: 'application/json', + }, + ); + } + } finally { + channel.ack(msg); + } + }); +}; diff --git a/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/rabbitmq.ts b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/rabbitmq.ts index 2f44eef1..f7ee2b4a 100644 --- a/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/rabbitmq.ts +++ b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/rabbitmq.ts @@ -9,12 +9,12 @@ if (!RABBITMQ_URL) { process.exit(1); } -const EXCHANGE_NAME = 'global_events'; +export const EXCHANGE_NAME = 'global_events'; let connection: Connection | null = null; let channel: Channel | null = null; -const connectRabbitMQ = async (): Promise => { +export const connectRabbitMQ = async (): Promise => { if (channel) { return channel; diff --git a/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/types.ts b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/types.ts index f4c99aa4..fd5f27f7 100644 --- a/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/types.ts +++ b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/types.ts @@ -8,6 +8,7 @@ export type catalogo = { pictures: string[] price: number capacity: number + doorCodeHash?: string } export type userInformation = { @@ -17,4 +18,4 @@ export type userInformation = { cpf: string; birth: number; phone: string; -}; \ No newline at end of file +}; diff --git a/back/microsservicos/catalogo/shared/infra/repo/catalogoRepositoryMock.ts b/back/microsservicos/catalogo/shared/infra/repo/catalogoRepositoryMock.ts index b58cee75..cb9b60d0 100644 --- a/back/microsservicos/catalogo/shared/infra/repo/catalogoRepositoryMock.ts +++ b/back/microsservicos/catalogo/shared/infra/repo/catalogoRepositoryMock.ts @@ -16,7 +16,7 @@ export class CatalogoRepositoryMock implements CatalogoRepository { return this.baseCatalogo } - public getCatalogo(id: string): Catalogo { + public getCatalogo(id: string): Catalogo | undefined { return this.baseCatalogo[id] } @@ -32,7 +32,8 @@ export class CatalogoRepositoryMock implements CatalogoRepository { comodities: props.comodities, pictures: props.pictures, price: props.price, - capacity: props.capacity + capacity: props.capacity, + doorCodeHash: props.doorCodeHash } this.baseCatalogo[id] = room @@ -45,6 +46,10 @@ export class CatalogoRepositoryMock implements CatalogoRepository { const room_to_update = this.getCatalogo(props.id) + if (!room_to_update) { + throw new Error(`Catalogo with id "${props.id}" not found.`) + } + if(props.name) room_to_update.name = props.name if(props.description) room_to_update.description = props.description if(props.address) room_to_update.address = props.address @@ -52,6 +57,7 @@ export class CatalogoRepositoryMock implements CatalogoRepository { if(props.pictures && props.pictures.length > 0) room_to_update.pictures = props.pictures if(props.price) room_to_update.price = props.price if(props.capacity) room_to_update.capacity = props.capacity + if(props.doorCodeHash) room_to_update.doorCodeHash = props.doorCodeHash return room_to_update @@ -61,10 +67,14 @@ export class CatalogoRepositoryMock implements CatalogoRepository { const room_to_delete = this.getCatalogo(id) + if (!room_to_delete) { + throw new Error(`Catalogo with id "${id}" not found.`) + } + delete this.baseCatalogo[id] return room_to_delete } -} \ No newline at end of file +} From 75d5e0eac04025ba632647d54185976ce8ed7c80 Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 03:41:18 -0300 Subject: [PATCH 08/11] =?UTF-8?q?chore:=20comentar=20importa=C3=A7=C3=B5es?= =?UTF-8?q?=20e=20testes=20no=20arquivo=20aluguel=5Fdart=5Ftest.dart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aluguel/test/aluguel_dart_test.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/back/microsservicos/aluguel/test/aluguel_dart_test.dart b/back/microsservicos/aluguel/test/aluguel_dart_test.dart index 55f47a4d..2da1c1e8 100644 --- a/back/microsservicos/aluguel/test/aluguel_dart_test.dart +++ b/back/microsservicos/aluguel/test/aluguel_dart_test.dart @@ -1,8 +1,8 @@ -import 'package:aluguel_dart/aluguel_dart.dart'; -import 'package:test/test.dart'; +// import 'package:aluguel_dart/aluguel_dart.dart'; +// import 'package:test/test.dart'; -void main() { - test('calculate', () { - expect(calculate(), 42); - }); -} +// void main() { +// test('calculate', () { +// expect(calculate(), 42); +// }); +// } From 907726e92f77175355e699bf1c2b9d9bec7f3488 Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 03:50:26 -0300 Subject: [PATCH 09/11] =?UTF-8?q?feat:=20remover=20suporte=20a=20c=C3=B3di?= =?UTF-8?q?go=20de=20porta=20nas=20opera=C3=A7=C3=B5es=20de=20cria=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20aluguel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/application/create_aluguel_usecase.dart | 16 +--------------- .../domain/repositories/aluguel_repository.dart | 1 - .../repositories/aluguel_repository_mock.dart | 2 -- .../controllers/create_aluguel_controller.dart | 5 ----- 4 files changed, 1 insertion(+), 23 deletions(-) diff --git a/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart b/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart index 23f624e1..acd74cdf 100644 --- a/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart +++ b/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart @@ -2,7 +2,6 @@ import 'package:aluguel_dart/domain/entities/aluguel.dart'; import 'package:aluguel_dart/domain/repositories/aluguel_repository.dart'; import 'package:aluguel_dart/infrastructure/clients/rabbitmq/rabbitmq.dart'; import 'package:aluguel_dart/infrastructure/clients/rabbitmq/rabbitmq_event.dart'; -import 'package:aluguel_dart/shared/security/door_code_hasher.dart'; class CreateAluguelUsecase { final AluguelRepository repository; @@ -15,8 +14,7 @@ class CreateAluguelUsecase { required int startDate, required int endDate, required int people, - required num finalPrice, - String? doorCode, + required num finalPrice }) async { if (endDate < startDate) { throw StateError('endDate não pode ser menor que startDate.'); @@ -29,17 +27,6 @@ class CreateAluguelUsecase { if (finalPrice < 0) { throw StateError('finalPrice não pode ser negativo.'); } - if (doorCode != null) { - if (doorCode.isEmpty) { - throw StateError('doorCode não pode ser vazio.'); - } - if (!RegExp(r'^\d{5}$').hasMatch(doorCode)) { - throw StateError('doorCode deve conter exatamente 5 dígitos.'); - } - } - - final String? hashedDoorCode = - doorCode != null ? hashDoorCode(doorCode) : null; final createdAluguel = await repository.createAluguel( userId: userId, @@ -49,7 +36,6 @@ class CreateAluguelUsecase { people: people, finalPrice: finalPrice.toDouble(), status: 'PENDING', - doorCode: hashedDoorCode, ); final aluguelCreated = RabbitMQEvent( diff --git a/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart b/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart index 74fca1f7..653c2975 100644 --- a/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart +++ b/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart @@ -9,7 +9,6 @@ abstract class AluguelRepository { required int people, required double finalPrice, required String status, - String? doorCode, }); Future getAluguel(String id); diff --git a/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart b/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart index 1629a480..37151f58 100644 --- a/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart +++ b/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart @@ -19,7 +19,6 @@ class AluguelRepositoryMock implements AluguelRepository { required int people, required double finalPrice, required String status, - String? doorCode, }) async { @@ -35,7 +34,6 @@ class AluguelRepositoryMock implements AluguelRepository { people: people, finalPrice: finalPrice, status: status, - doorCode: doorCode, createdAt: now, updatedAt: now, ); diff --git a/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart b/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart index f1c7fe23..0c650e4c 100644 --- a/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart +++ b/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart @@ -25,7 +25,6 @@ class CreateAluguelController { final endDate = data['endDate']; final people = data['people']; final finalPrice = data['finalPrice']; - final doorCode = data['doorCode']; if (userId == null || userId.toString().isEmpty) { throw AppFailure('userId_required'); @@ -45,9 +44,6 @@ class CreateAluguelController { if (finalPrice == null) { throw AppFailure('finalPrice_required'); } - if (doorCode != null && doorCode.toString().isEmpty) { - throw AppFailure('doorCode_cannot_be_empty'); - } final aluguel = await createAluguelUsecase.call( userId: userId, @@ -56,7 +52,6 @@ class CreateAluguelController { endDate: endDate, people: people, finalPrice: finalPrice, - doorCode: doorCode, ); return jsonCreated(aluguel.toJson()); From 2f046a82c49ad05729f70ab28d1e15f66be04f5e Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Fri, 31 Oct 2025 18:14:28 -0300 Subject: [PATCH 10/11] =?UTF-8?q?feat:=20remover=20l=C3=B3gica=20de=20veri?= =?UTF-8?q?fica=C3=A7=C3=A3o=20e=20manipula=C3=A7=C3=A3o=20de=20doorCode?= =?UTF-8?q?=20nas=20opera=C3=A7=C3=B5es=20de=20cria=C3=A7=C3=A3o=20e=20atu?= =?UTF-8?q?aliza=C3=A7=C3=A3o=20de=20cat=C3=A1logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../create_catalogo_controller.ts | 56 ++++++++++--------- .../update_catalogo_controller.ts | 33 +---------- .../catalogo/shared/domain/interfaces.ts | 2 +- 3 files changed, 33 insertions(+), 58 deletions(-) diff --git a/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts b/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts index 6f8e512b..29142c3d 100644 --- a/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts +++ b/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts @@ -1,4 +1,4 @@ -import crypto from "crypto"; +import crypto, { randomInt } from "crypto"; import { CreateCatalogoUsecase } from "./create_catalogo_usecase"; import { Request, Response } from "express"; import { Catalogo } from "../../shared/domain/interfaces"; @@ -31,11 +31,11 @@ export class CreateCatalogoController { if (body.capacity === undefined) throw new Error("Missing catalogo capacity"); - const { - doorCode, - doorCodeHash: providedDoorCodeHash, - ...restBody - } = body; + // const { + // doorCode, + // doorCodeHash: providedDoorCodeHash, + // ...restBody + // } = body; const price = typeof body.price === "string" ? Number(body.price) : body.price; @@ -48,28 +48,32 @@ export class CreateCatalogoController { ? [body.comodities] : []; - let doorCodeHash: string | undefined = providedDoorCodeHash; - const rawDoorInput: unknown = doorCode ?? providedDoorCodeHash; - - if (rawDoorInput !== undefined) { - if (typeof rawDoorInput !== "string") { - throw new Error("doorCode must be a string"); - } - const trimmed = rawDoorInput.trim(); - - if (/^\d{5}$/.test(trimmed)) { - doorCodeHash = crypto.createHash("sha256").update(trimmed).digest("hex"); - } else if (/^[a-f0-9]{64}$/i.test(trimmed)) { - doorCodeHash = trimmed.toLowerCase(); - } else { - throw new Error( - "Invalid doorCode format. Provide 5 digits or a SHA-256 hash.", - ); - } - } + // let doorCodeHash: string | undefined = providedDoorCodeHash; + // const rawDoorInput: unknown = doorCode ?? providedDoorCodeHash; + + // if (rawDoorInput !== undefined) { + // if (typeof rawDoorInput !== "string") { + // throw new Error("doorCode must be a string"); + // } + // const trimmed = rawDoorInput.trim(); + + // if (trimmed.length === 0) { + // throw new Error("doorCode cannot be empty"); + // } + // if (trimmed.length !== 5) { + // throw new Error("doorCode must be exactly 5 characters"); + // } + // if (!/^[0-9]+$/.test(trimmed)) { + // throw new Error("doorCode must be numeric"); + // } + + // doorCodeHash = trimmed.toLowerCase(); + // } + + const doorCodeHash = String(Math.floor(Math.random() * 100000)).padStart(5, '0'); const roomProps = { - ...restBody, + ...body, comodities, pictures: [], price, diff --git a/back/microsservicos/catalogo/app/update_catalogo/update_catalogo_controller.ts b/back/microsservicos/catalogo/app/update_catalogo/update_catalogo_controller.ts index ed218d72..c1374d74 100644 --- a/back/microsservicos/catalogo/app/update_catalogo/update_catalogo_controller.ts +++ b/back/microsservicos/catalogo/app/update_catalogo/update_catalogo_controller.ts @@ -1,5 +1,4 @@ import axios from "axios"; -import crypto from "crypto"; import { Request, Response } from 'express' import { UpdateCatalogoUsecase } from "./update_catalogo_usecase"; import { updateCatalogoProps } from "../../shared/domain/types"; @@ -20,35 +19,7 @@ export class UpdateCatalogoController { if (id === undefined) throw new Error('ID não informado') if (id.length !== 36) throw new Error('ID inválido') - const { - doorCode, - doorCodeHash: providedDoorCodeHash, - ...restBody - } = req.body ?? {}; - - let doorCodeHash: string | undefined = providedDoorCodeHash; - const rawDoorInput: unknown = doorCode ?? providedDoorCodeHash; - - if (rawDoorInput !== undefined) { - if (typeof rawDoorInput !== "string") { - throw new Error("doorCode must be a string"); - } - const trimmed = rawDoorInput.trim(); - - if (/^\d{5}$/.test(trimmed)) { - doorCodeHash = crypto.createHash("sha256").update(trimmed).digest("hex"); - } else if (/^[a-f0-9]{64}$/i.test(trimmed)) { - doorCodeHash = trimmed.toLowerCase(); - } else { - throw new Error("Invalid doorCode format. Provide 5 digits or a SHA-256 hash."); - } - } - - const props: updateCatalogoProps = { - id, - ...restBody, - ...(doorCodeHash ? { doorCodeHash } : {}), - }; + const props: updateCatalogoProps = { id, ...req.body }; const room_updated = this.usecase.execute(props) @@ -79,4 +50,4 @@ export class UpdateCatalogoController { } -} +} \ No newline at end of file diff --git a/back/microsservicos/catalogo/shared/domain/interfaces.ts b/back/microsservicos/catalogo/shared/domain/interfaces.ts index 6b61c9a1..01c5658a 100644 --- a/back/microsservicos/catalogo/shared/domain/interfaces.ts +++ b/back/microsservicos/catalogo/shared/domain/interfaces.ts @@ -7,5 +7,5 @@ export interface Catalogo { pictures: string[] price: number capacity: number - doorCodeHash?: string + doorCodeHash: string } From 705996a0ea9dceafd7a23ae28f025b40c807e48a Mon Sep 17 00:00:00 2001 From: Lucas Galhardo Date: Sat, 1 Nov 2025 03:39:21 -0300 Subject: [PATCH 11/11] =?UTF-8?q?feat:=20implementar=20consumidor=20para?= =?UTF-8?q?=20busca=20de=20c=C3=B3digo=20de=20porta=20e=20refatorar=20veri?= =?UTF-8?q?fica=C3=A7=C3=A3o=20de=20c=C3=B3digo=20de=20porta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/application/get_aluguel_usecase.dart | 2 +- .../application/update_aluguel_usecase.dart | 41 +++- .../clients/rabbitmq/rabbitmq.dart | 229 ++++++++++++++---- .../presentation/events/event_handler.dart | 64 +++-- .../controllers/get_door_hash_controller.dart | 5 +- .../create_catalogo_controller.ts | 4 +- back/microsservicos/catalogo/index.ts | 3 +- .../rabbitmq/doorCodeVerificationConsumer.ts | 101 +++++++- 8 files changed, 367 insertions(+), 82 deletions(-) diff --git a/back/microsservicos/aluguel/lib/application/get_aluguel_usecase.dart b/back/microsservicos/aluguel/lib/application/get_aluguel_usecase.dart index 56433625..c8fdd435 100644 --- a/back/microsservicos/aluguel/lib/application/get_aluguel_usecase.dart +++ b/back/microsservicos/aluguel/lib/application/get_aluguel_usecase.dart @@ -5,7 +5,7 @@ class GetAluguelUsecase { final AluguelRepository repository; GetAluguelUsecase({required this.repository}); - + Future call(String id) async { if (id.isEmpty) { throw ArgumentError('ID do aluguel não pode ser vazio.'); diff --git a/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart b/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart index 0c2e7d31..9747463e 100644 --- a/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart +++ b/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart @@ -1,6 +1,6 @@ import 'package:aluguel_dart/domain/entities/aluguel.dart'; import 'package:aluguel_dart/domain/repositories/aluguel_repository.dart'; -import 'package:aluguel_dart/shared/security/door_code_hasher.dart'; +import 'package:aluguel_dart/infrastructure/clients/rabbitmq/rabbitmq.dart'; class UpdateAluguelUsecase { final AluguelRepository repository; @@ -25,15 +25,36 @@ class UpdateAluguelUsecase { if (finalPrice != null && finalPrice < 0) { throw StateError('finalPrice não pode ser negativo.'); } - String? hashedDoorCode; - if (doorCode != null) { - if (doorCode.isEmpty) { - throw StateError('doorCode não pode ser vazio.'); + + String? desiredDoorCode = doorCode; + + if (status != null && + status.toUpperCase() == 'CONFIRMED' && + doorCode == null) { + final current = await repository.getAluguel(id); + if (current == null) { + throw StateError('aluguel_not_found'); + } + + final fetchedDoorCode = await fetchDoorCodeFromCatalog( + current.workspaceId, + ); + if (fetchedDoorCode == null || fetchedDoorCode.isEmpty) { + throw StateError('door_code_not_found'); + } + desiredDoorCode = fetchedDoorCode; + } + + String? sanitizedDoorCode; + if (desiredDoorCode != null) { + final trimmedDoorCode = desiredDoorCode.trim(); + if (trimmedDoorCode.isEmpty) { + throw StateError('doorCode nǜo pode ser vazio.'); } - if (!RegExp(r'^\d{5}$').hasMatch(doorCode)) { - throw StateError('doorCode deve conter exatamente 5 dígitos.'); + if (!RegExp(r'^\d{5}$').hasMatch(trimmedDoorCode)) { + throw StateError('doorCode deve conter exatamente 5 d��gitos.'); } - hashedDoorCode = hashDoorCode(doorCode); + sanitizedDoorCode = trimmedDoorCode; } return repository.updateAluguel( @@ -43,7 +64,7 @@ class UpdateAluguelUsecase { people: people, finalPrice: finalPrice, status: status, - doorCode: hashedDoorCode, + doorCode: sanitizedDoorCode, ); } -} \ No newline at end of file +} diff --git a/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart b/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart index fab9659c..ddbc0b59 100644 --- a/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart +++ b/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart @@ -29,7 +29,9 @@ Future _invalidateChannel({String reason = 'unknown'}) async { // ignore } finally { _channel = null; - stdout.writeln('[RabbitMQ] Channel invalidated (reason: $reason). Will recreate on next use.'); + stdout.writeln( + '[RabbitMQ] Channel invalidated (reason: $reason). Will recreate on next use.', + ); } } @@ -40,7 +42,9 @@ Future _getConnection() async { _ensureRabbitUrlOrExit(); final uri = Uri.parse(RABBITMQ_URL); - final user = uri.userInfo.isNotEmpty ? Uri.decodeComponent(uri.userInfo.split(':').first) : 'guest'; + final user = uri.userInfo.isNotEmpty + ? Uri.decodeComponent(uri.userInfo.split(':').first) + : 'guest'; final pass = (uri.userInfo.contains(':')) ? Uri.decodeComponent(uri.userInfo.split(':').last) : 'guest'; @@ -67,7 +71,9 @@ Future connectRabbitMQ() async { // Espelha o TS: assert do exchange topic/durable await _channel!.exchange(EXCHANGE_NAME, ExchangeType.TOPIC, durable: true); - stdout.writeln('[RabbitMQ] Connected. Channel created and exchange asserted.'); + stdout.writeln( + '[RabbitMQ] Connected. Channel created and exchange asserted.', + ); return _channel!; } catch (e) { stderr.writeln('[RabbitMQ] Failed to connect/create channel: $e'); @@ -76,7 +82,10 @@ Future connectRabbitMQ() async { } /// Wrapper: executa ação no canal com retry 1x se o canal morrer -Future _withChannel(Future Function(Channel ch) action, {required String op}) async { +Future _withChannel( + Future Function(Channel ch) action, { + required String op, +}) async { Future attempt() async { final ch = await connectRabbitMQ(); return action(ch); @@ -85,19 +94,25 @@ Future _withChannel(Future Function(Channel ch) action, {required Strin try { return await attempt(); } on ChannelException catch (e) { - stderr.writeln('[RabbitMQ] ChannelException during $op: $e. Recreating channel and retrying once...'); + stderr.writeln( + '[RabbitMQ] ChannelException during $op: $e. Recreating channel and retrying once...', + ); await _invalidateChannel(reason: 'ChannelException:$e'); final result = await attempt(); stdout.writeln('[RabbitMQ] $op succeeded after channel recreation.'); return result; } on IOException catch (e) { - stderr.writeln('[RabbitMQ] IOException during $op: $e. Recreating channel and retrying once...'); + stderr.writeln( + '[RabbitMQ] IOException during $op: $e. Recreating channel and retrying once...', + ); await _invalidateChannel(reason: 'IOException:$e'); final result = await attempt(); stdout.writeln('[RabbitMQ] $op succeeded after channel recreation.'); return result; } on StateError catch (e) { - stderr.writeln('[RabbitMQ] StateError during $op: $e. Recreating channel and retrying once...'); + stderr.writeln( + '[RabbitMQ] StateError during $op: $e. Recreating channel and retrying once...', + ); await _invalidateChannel(reason: 'StateError:$e'); final result = await attempt(); stdout.writeln('[RabbitMQ] $op succeeded after channel recreation.'); @@ -112,14 +127,20 @@ Future publishEvent( T eventData, ) async { return _withChannel((ch) async { - final exchange = await ch.exchange(EXCHANGE_NAME, ExchangeType.TOPIC, durable: true); + final exchange = await ch.exchange( + EXCHANGE_NAME, + ExchangeType.TOPIC, + durable: true, + ); final body = utf8.encode(jsonEncode(eventData)); final props = MessageProperties()..deliveryMode = 2; // persistente exchange.publish(body, routingKey, properties: props); - stdout.writeln("[PUBLISHER] Topic '$routingKey' published to '$EXCHANGE_NAME': $eventData"); + stdout.writeln( + "[PUBLISHER] Topic '$routingKey' published to '$EXCHANGE_NAME': $eventData", + ); return true; }, op: 'publishEvent'); } @@ -133,7 +154,11 @@ Future consumeEvents( ) async { await _withChannel((ch) async { // Declarar exchange (assert) como no TS - final exchange = await ch.exchange(EXCHANGE_NAME, ExchangeType.TOPIC, durable: true); + final exchange = await ch.exchange( + EXCHANGE_NAME, + ExchangeType.TOPIC, + durable: true, + ); final queue = await ch.queue( queueName, @@ -147,7 +172,9 @@ Future consumeEvents( // ✅ Correção: bind usando o objeto Exchange, não a string await queue.bind(exchange, bindingKey); - stdout.writeln("[CONSUMER] Listening on queue '$queueName' with binding '$bindingKey'"); + stdout.writeln( + "[CONSUMER] Listening on queue '$queueName' with binding '$bindingKey'", + ); final consumer = await queue.consume(noAck: false); @@ -156,7 +183,9 @@ Future consumeEvents( bool ackedOrRejected = false; try { final payload = jsonDecode(msg.payloadAsString) as T; - stdout.writeln('[CONSUMER] Received (${msg.routingKey ?? bindingKey}): $payload'); + stdout.writeln( + '[CONSUMER] Received (${msg.routingKey ?? bindingKey}): $payload', + ); await callback(payload); @@ -167,13 +196,17 @@ Future consumeEvents( } catch (err) { stderr.writeln('[CONSUMER] Error processing message: $err'); if (!ackedOrRejected) { - msg.reject(false); // nack sem requeue (equivalente ao TS: nack(msg, false, false)) + msg.reject( + false, + ); // nack sem requeue (equivalente ao TS: nack(msg, false, false)) ackedOrRejected = true; } } }, onError: (err) async { - stderr.writeln('[CONSUMER] Stream error: $err. Marking channel as dead.'); + stderr.writeln( + '[CONSUMER] Stream error: $err. Marking channel as dead.', + ); await _invalidateChannel(reason: 'consumer.onError:$err'); }, onDone: () async { @@ -210,7 +243,100 @@ Future closeRabbitMQConnection() async { } } +Future fetchDoorCodeFromCatalog( + String workspaceId, { + Duration timeout = const Duration(seconds: 5), +}) async { + final channel = await connectRabbitMQ(); + final exchange = await channel.exchange( + EXCHANGE_NAME, + ExchangeType.TOPIC, + durable: true, + ); + final replyQueue = await channel.queue('', exclusive: true, autoDelete: true); + final correlationId = const Uuid().v4(); + final completer = Completer(); + final consumer = await replyQueue.consume(noAck: false); + late final StreamSubscription subscription; + + subscription = consumer.listen( + (AmqpMessage message) { + try { + if (message.properties?.corellationId != correlationId) { + message.reject(false); + return; + } + + final decoded = jsonDecode(message.payloadAsString); + String? doorCode; + if (decoded is Map) { + final rawDoorCode = + decoded['doorCodeHash'] ?? decoded['doorCode'] ?? decoded['code']; + if (rawDoorCode is String && rawDoorCode.isNotEmpty) { + doorCode = rawDoorCode; + } + } else if (decoded is String && decoded.isNotEmpty) { + doorCode = decoded; + } + + message.ack(); + if (!completer.isCompleted) { + completer.complete(doorCode); + } + subscription.cancel(); + } catch (error) { + message.reject(false); + if (!completer.isCompleted) { + completer.completeError(error); + } + } + }, + onError: (error) { + if (!completer.isCompleted) { + completer.completeError(error); + } + }, + onDone: () { + if (!completer.isCompleted) { + completer.completeError( + StateError('reply queue closed before receiving response'), + ); + } + }, + cancelOnError: false, + ); + + final payload = { + 'eventType': 'CatalogoDoorCodeRequest', + 'payload': {'workspaceId': workspaceId}, + }; + + final properties = MessageProperties() + ..replyTo = replyQueue.name + ..corellationId = correlationId + ..contentType = 'application/json' + ..deliveryMode = 1; + + exchange.publish( + utf8.encode(jsonEncode(payload)), + 'catalogo.door-code.request', + properties: properties, + ); + + final timer = Timer(timeout, () { + if (!completer.isCompleted) { + completer.completeError(TimeoutException('Door code fetch timed out')); + } + }); + try { + return await completer.future; + } finally { + timer.cancel(); + await subscription.cancel(); + await replyQueue.delete(); + } +} Future verifyDoorCodeWithCatalog({ required String workspaceId, @@ -218,13 +344,16 @@ Future verifyDoorCodeWithCatalog({ Duration timeout = const Duration(seconds: 5), }) async { final channel = await connectRabbitMQ(); - final exchange = - await channel.exchange(EXCHANGE_NAME, ExchangeType.TOPIC, durable: true); + final exchange = await channel.exchange( + EXCHANGE_NAME, + ExchangeType.TOPIC, + durable: true, + ); final replyQueue = await channel.queue('', exclusive: true, autoDelete: true); final correlationId = const Uuid().v4(); final completer = Completer(); final consumer = await replyQueue.consume(noAck: false); - StreamSubscription? subscription; + late final StreamSubscription subscription; subscription = consumer.listen( (AmqpMessage message) { @@ -237,7 +366,11 @@ Future verifyDoorCodeWithCatalog({ final decoded = jsonDecode(message.payloadAsString); bool result = false; if (decoded is Map) { - result = decoded['valid'] == true; + final dynamic candidate = + decoded['doorVerifiedHash'] ?? decoded['valid']; + if (candidate is bool) { + result = candidate; + } } else if (decoded is bool) { result = decoded; } @@ -246,7 +379,7 @@ Future verifyDoorCodeWithCatalog({ if (!completer.isCompleted) { completer.complete(result); } - subscription?.cancel(); + subscription.cancel(); } catch (error) { message.reject(false); if (!completer.isCompleted) { @@ -271,10 +404,7 @@ Future verifyDoorCodeWithCatalog({ final payload = { 'eventType': 'CatalogoVerifyDoorCodeRequest', - 'payload': { - 'workspaceId': workspaceId, - 'doorCode': doorCode, - }, + 'payload': {'workspaceId': workspaceId, 'doorCode': doorCode}, }; final properties = MessageProperties() @@ -307,13 +437,18 @@ Future verifyDoorCodeWithCatalog({ } const String _DELAY_EXCHANGE = 'aluguel.delay.ex'; // exchange de delay (direct) -const String _DELAY_QUEUE = 'aluguel.delay.q'; // fila de delay -const String _DELAY_RK = 'aluguel.expire'; // routing key para entrar no delay -const String _EXPIRED_RK = 'aluguel.expired'; // routing key de saída (vai para EXCHANGE_NAME) +const String _DELAY_QUEUE = 'aluguel.delay.q'; // fila de delay +const String _DELAY_RK = 'aluguel.expire'; // routing key para entrar no delay +const String _EXPIRED_RK = + 'aluguel.expired'; // routing key de saída (vai para EXCHANGE_NAME) Future _ensureDelayInfra(Channel ch) async { // Exchange/Fila de delay: quando a msg expira, vai para o exchange principal (EXCHANGE_NAME) - final delayEx = await ch.exchange(_DELAY_EXCHANGE, ExchangeType.DIRECT, durable: true); + final delayEx = await ch.exchange( + _DELAY_EXCHANGE, + ExchangeType.DIRECT, + durable: true, + ); final delayQ = await ch.queue( _DELAY_QUEUE, @@ -331,7 +466,7 @@ Future _ensureDelayInfra(Channel ch) async { /// Se `endDateMs` já passou, publica imediatamente em `EXCHANGE_NAME` com routing key `_EXPIRED_RK`. Future scheduleAluguelExpiration({ required String eventType, - required Map payload + required Map payload, }) async { final ch = await connectRabbitMQ(); final now = DateTime.now().toUtc().millisecondsSinceEpoch; @@ -339,28 +474,38 @@ Future scheduleAluguelExpiration({ if (delayMs <= 0) { // já venceu: publica direto no exchange principal - final mainEx = await ch.exchange(EXCHANGE_NAME, ExchangeType.TOPIC, durable: true); - final msg = { - 'eventType': eventType, - 'payload': payload - }; + final mainEx = await ch.exchange( + EXCHANGE_NAME, + ExchangeType.TOPIC, + durable: true, + ); + final msg = {'eventType': eventType, 'payload': payload}; final props = MessageProperties()..deliveryMode = 2; - mainEx.publish(utf8.encode(jsonEncode(msg)), _EXPIRED_RK, properties: props); - stdout.writeln("[SCHEDULER] Expired immediately -> $_EXPIRED_RK payload=$msg"); + mainEx.publish( + utf8.encode(jsonEncode(msg)), + _EXPIRED_RK, + properties: props, + ); + stdout.writeln( + "[SCHEDULER] Expired immediately -> $_EXPIRED_RK payload=$msg", + ); return; } await _ensureDelayInfra(ch); - final delayEx = await ch.exchange(_DELAY_EXCHANGE, ExchangeType.DIRECT, durable: true); + final delayEx = await ch.exchange( + _DELAY_EXCHANGE, + ExchangeType.DIRECT, + durable: true, + ); final props = MessageProperties() ..deliveryMode = 2 - ..expiration = delayMs.toString(); + ..expiration = delayMs.toString(); - final data = { - 'eventType': eventType, - 'payload': payload - }; + final data = {'eventType': eventType, 'payload': payload}; delayEx.publish(utf8.encode(jsonEncode(data)), _DELAY_RK, properties: props); - stdout.writeln("[SCHEDULER] Scheduled in ${delayMs}ms -> $_DELAY_EXCHANGE:$_DELAY_RK payload=$payload"); + stdout.writeln( + "[SCHEDULER] Scheduled in ${delayMs}ms -> $_DELAY_EXCHANGE:$_DELAY_RK payload=$payload", + ); } diff --git a/back/microsservicos/aluguel/lib/presentation/events/event_handler.dart b/back/microsservicos/aluguel/lib/presentation/events/event_handler.dart index 6f140cd2..47fb6dee 100644 --- a/back/microsservicos/aluguel/lib/presentation/events/event_handler.dart +++ b/back/microsservicos/aluguel/lib/presentation/events/event_handler.dart @@ -1,11 +1,9 @@ import 'dart:io'; import 'package:aluguel_dart/application/update_aluguel_usecase.dart'; -import 'package:aluguel_dart/domain/entities/aluguel.dart'; import 'package:aluguel_dart/infrastructure/clients/rabbitmq/rabbitmq.dart'; import 'package:aluguel_dart/infrastructure/clients/rabbitmq/rabbitmq_event.dart'; import 'package:aluguel_dart/shared/environments.dart'; - typedef EventHandlerFn = Future Function(dynamic payload); final Map eventsFunctions = { @@ -13,29 +11,61 @@ final Map eventsFunctions = { final aluguelID = payload["aluguel"]["id"]; final people = payload["aluguel"]['people']; final availableSpots = payload['availableSpots']; - Aluguel updatedAluguel; - - if(people <= availableSpots) { - updatedAluguel = await UpdateAluguelUsecase(repository: Environments.getAluguelRepo()).call(aluguelID, status: 'CONFIRMED'); - final aluguelConfirmed = RabbitMQEvent(eventType: 'AluguelConfirmed', payload: updatedAluguel.toJson()); - await publishEvent('aluguel.updated', aluguelConfirmed.toJson()); - await scheduleAluguelExpiration(eventType: 'AluguelExpired', payload: updatedAluguel.toJson()); - } else { - updatedAluguel = await UpdateAluguelUsecase(repository: Environments.getAluguelRepo()).call(aluguelID, status: 'CANCELED'); + try { + if (people <= availableSpots) { + final updatedAluguel = await UpdateAluguelUsecase( + repository: Environments.getAluguelRepo(), + ).call(aluguelID, status: 'CONFIRMED'); + final aluguelConfirmed = RabbitMQEvent( + eventType: 'AluguelConfirmed', + payload: updatedAluguel.toJson(), + ); + await publishEvent('aluguel.updated', aluguelConfirmed.toJson()); + await scheduleAluguelExpiration( + eventType: 'AluguelExpired', + payload: updatedAluguel.toJson(), + ); + } else { + await UpdateAluguelUsecase( + repository: Environments.getAluguelRepo(), + ).call(aluguelID, status: 'CANCELED'); + } + } catch (error) { + print('[AvaiabilityChecked] Failed to handle "$aluguelID": $error'); } }, 'AvaiabilityFree': (payload) async { final aluguelID = payload["aluguel"]["bookingID"]; - Aluguel updatedAluguel; - updatedAluguel = await UpdateAluguelUsecase(repository: Environments.getAluguelRepo()).call(aluguelID, status: 'COMPLETED'); - final aluguelCompleted = RabbitMQEvent(eventType: 'AluguelCompleted', payload: updatedAluguel.toJson()); - await publishEvent('aluguel.updated', aluguelCompleted.toJson()); - } + final repo = Environments.getAluguelRepo(); + try { + final existing = await repo.getAluguel(aluguelID); + if (existing == null) { + print( + '[AvaiabilityFree] Ignoring event. Aluguel "$aluguelID" not found.', + ); + return; + } + + final updatedAluguel = await UpdateAluguelUsecase( + repository: repo, + ).call(aluguelID, status: 'COMPLETED'); + final aluguelCompleted = RabbitMQEvent( + eventType: 'AluguelCompleted', + payload: updatedAluguel.toJson(), + ); + await publishEvent('aluguel.updated', aluguelCompleted.toJson()); + } catch (error) { + print('[AvaiabilityFree] Failed to handle "$aluguelID": $error'); + } + }, }; Future eventHandler(Map event) async { try { - RabbitMQEvent convertedEvent = RabbitMQEvent(eventType: event['eventType'], payload: event['payload']); + RabbitMQEvent convertedEvent = RabbitMQEvent( + eventType: event['eventType'], + payload: event['payload'], + ); final String eventType = convertedEvent.eventType; final dynamic payload = convertedEvent.payload; eventsFunctions[eventType]!(payload); diff --git a/back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart b/back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart index c5fd3cce..79cf1641 100644 --- a/back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart +++ b/back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart @@ -50,7 +50,10 @@ class GetDoorHashController { headers: {'content-type': 'application/json'}, ); } catch (e) { - return jsonServerError({'error': 'internal_error', 'detail': e.toString()}); + return jsonServerError({ + 'error': 'internal_error', + 'detail': e.toString(), + }); } } } diff --git a/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts b/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts index 29142c3d..82bfa636 100644 --- a/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts +++ b/back/microsservicos/catalogo/app/create_catalogo/create_catalogo_controller.ts @@ -1,4 +1,4 @@ -import crypto, { randomInt } from "crypto"; +import { randomInt } from "crypto"; import { CreateCatalogoUsecase } from "./create_catalogo_usecase"; import { Request, Response } from "express"; import { Catalogo } from "../../shared/domain/interfaces"; @@ -70,7 +70,7 @@ export class CreateCatalogoController { // doorCodeHash = trimmed.toLowerCase(); // } - const doorCodeHash = String(Math.floor(Math.random() * 100000)).padStart(5, '0'); + const doorCodeHash = randomInt(0, 100000).toString().padStart(5, '0'); const roomProps = { ...body, diff --git a/back/microsservicos/catalogo/index.ts b/back/microsservicos/catalogo/index.ts index fcb75fbe..80a02c07 100644 --- a/back/microsservicos/catalogo/index.ts +++ b/back/microsservicos/catalogo/index.ts @@ -2,7 +2,7 @@ import { Environments } from './shared/environments' import { startQueue } from './shared/handlers/event/eventsHandler' import { App } from './shared/handlers/server/app' import { closeRabbitMQConnection } from './shared/infra/clients/rabbitmq/rabbitmq' -import { startDoorCodeVerificationConsumer } from './shared/infra/clients/rabbitmq/doorCodeVerificationConsumer' +import { startDoorCodeFetchConsumer, startDoorCodeVerificationConsumer } from './shared/infra/clients/rabbitmq/doorCodeVerificationConsumer' const port = Environments.getEnvs().port @@ -11,6 +11,7 @@ new App().server.listen(port, async () => { try { await startQueue() await startDoorCodeVerificationConsumer() + await startDoorCodeFetchConsumer() } catch (error) { console.error('Failed to start RabbitMQ consumers', error) process.exit(1) diff --git a/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts index f7533f3a..b3246339 100644 --- a/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts +++ b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts @@ -1,11 +1,11 @@ -import crypto from 'crypto'; - import type { ConsumeMessage } from 'amqplib'; import { Environments } from '../../../environments'; import { connectRabbitMQ, EXCHANGE_NAME } from './rabbitmq'; const DOOR_CODE_RPC_QUEUE = 'catalogo.verify-door-code.rpc'; const DOOR_CODE_REQUEST_RK = 'catalogo.verify-door-code.request'; +const DOOR_CODE_FETCH_QUEUE = 'catalogo.door-code.rpc'; +const DOOR_CODE_FETCH_RK = 'catalogo.door-code.request'; export const startDoorCodeVerificationConsumer = async (): Promise => { const channel = await connectRabbitMQ(); @@ -60,12 +60,7 @@ export const startDoorCodeVerificationConsumer = async (): Promise => { const storedHash = catalogo?.doorCodeHash; if (storedHash) { - const computedHash = crypto - .createHash('sha256') - .update(doorCode.trim()) - .digest('hex'); - - valid = computedHash === storedHash; + valid = storedHash.trim() === doorCode.trim(); } } catch (error) { console.error( @@ -104,3 +99,93 @@ export const startDoorCodeVerificationConsumer = async (): Promise => { } }); }; + +export const startDoorCodeFetchConsumer = async (): Promise => { + const channel = await connectRabbitMQ(); + + await channel.assertExchange(EXCHANGE_NAME, 'topic', { durable: true }); + + const { queue } = await channel.assertQueue(DOOR_CODE_FETCH_QUEUE, { + durable: false, + exclusive: false, + autoDelete: false, + }); + + await channel.bindQueue(queue, EXCHANGE_NAME, DOOR_CODE_FETCH_RK); + + await channel.consume(queue, async (msg: ConsumeMessage | null) => { + if (!msg) { + return; + } + + try { + const { replyTo, correlationId } = msg.properties; + + if (!replyTo || !correlationId) { + console.warn( + '[DoorCodeFetch] Missing replyTo or correlationId. Ignoring message.', + ); + channel.ack(msg); + return; + } + + let parsedPayload: any; + try { + parsedPayload = JSON.parse(msg.content.toString()); + } catch (error) { + console.error( + '[DoorCodeFetch] Failed to parse message payload:', + error, + ); + parsedPayload = {}; + } + + const payload = parsedPayload?.payload ?? parsedPayload; + const workspaceId = payload?.workspaceId; + + let doorCodeHash: string | null = null; + + if (workspaceId) { + try { + const repo = Environments.getCatalogoRepo(); + const catalogo = repo.getCatalogo(workspaceId); + doorCodeHash = catalogo?.doorCodeHash ?? null; + } catch (error) { + console.error( + '[DoorCodeFetch] Error while retrieving door code:', + error, + ); + } + } + + channel.sendToQueue( + replyTo, + Buffer.from(JSON.stringify({ doorCodeHash })), + { + correlationId, + contentType: 'application/json', + }, + ); + } catch (error) { + console.error( + '[DoorCodeFetch] Unexpected error handling request:', + error, + ); + const { replyTo, correlationId } = msg.properties; + if (replyTo && correlationId) { + channel.sendToQueue( + replyTo, + Buffer.from( + JSON.stringify({ doorCodeHash: null, error: 'internal_error' }), + ), + { + correlationId, + contentType: 'application/json', + }, + ); + } + } finally { + channel.ack(msg); + } + }); +};