diff --git a/README.md b/README.md index 41438fbc..93236800 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,20 @@ Além de seu propósito funcional, o sistema serve como aplicação prática de --- +## 🗺️ Diagramas + +
+ +
Figura 1: Arquitetura do projeto
+
+ +
+ +
Figura 2: Fluxo de deploy
+
+ +--- + ## 📚 Documentação da API A coleção completa de endpoints, exemplos de requisições e esquemas de resposta está disponível no Postman: diff --git a/back/microsservicos/aluguel/bin/aluguel_dart.dart b/back/microsservicos/aluguel/bin/aluguel_dart.dart index d6744236..d09d1ea9 100644 --- a/back/microsservicos/aluguel/bin/aluguel_dart.dart +++ b/back/microsservicos/aluguel/bin/aluguel_dart.dart @@ -15,11 +15,27 @@ 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); + 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(); @@ -29,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( @@ -44,7 +62,8 @@ Future main() async { createController: createController, updateController: updateController, deleteController: deleteController, - getAllAluguelController: getAllAluguelController + getAllAluguelController: getAllAluguelController, + getDoorHashController: getDoorHashController, ).call, ); @@ -57,30 +76,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!'), + ); + } } diff --git a/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart b/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart index a29b3a57..acd74cdf 100644 --- a/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart +++ b/back/microsservicos/aluguel/lib/application/create_aluguel_usecase.dart @@ -11,23 +11,24 @@ 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, + required num finalPrice }) 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.'); } - Aluguel createdAluguel = await repository.createAluguel( + final createdAluguel = await repository.createAluguel( userId: userId, workspaceId: workspaceId, startDate: startDate, @@ -37,18 +38,18 @@ class CreateAluguelUsecase { status: 'PENDING', ); - 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/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 f3830733..cece5950 100644 --- a/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart +++ b/back/microsservicos/aluguel/lib/application/update_aluguel_usecase.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:aluguel_dart/domain/entities/aluguel.dart'; import 'package:aluguel_dart/domain/repositories/aluguel_repository.dart'; @@ -8,13 +10,12 @@ class UpdateAluguelUsecase { Future call( String id, { - int? startDate, + int? startDate, int? endDate, int? people, double? finalPrice, String? status, }) async { - if (startDate != null && endDate != null && endDate < startDate) { throw StateError('endDate não pode ser menor que startDate.'); } @@ -25,6 +26,16 @@ class UpdateAluguelUsecase { throw StateError('finalPrice não pode ser negativo.'); } + String? desiredDoorCode = Random().nextInt(100000).toString().padLeft(5, '0'); + + if (status != null && + status.toUpperCase() == 'CONFIRMED') { + final current = await repository.getAluguel(id); + if (current == null) { + throw StateError('aluguel_not_found'); + } + } + return repository.updateAluguel( id, startDate: startDate, @@ -32,6 +43,7 @@ class UpdateAluguelUsecase { people: people, finalPrice: finalPrice, status: status, + doorCode: desiredDoorCode, ); } } 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..214d0992 --- /dev/null +++ b/back/microsservicos/aluguel/lib/application/verify_door_code_usecase.dart @@ -0,0 +1,61 @@ +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'; + +class VerifyDoorCodeUsecase { + final AluguelRepository repository; + + VerifyDoorCodeUsecase({required this.repository}); + + Future call({ + required String doorCode, + }) async { + final normalizedDoorCode = doorCode.trim(); + + if (!RegExp(r'^\d{5}$').hasMatch(normalizedDoorCode)) { + throw StateError('door code invalid'); + } + + final allAlugueis = await repository.getAllAluguel(); + + if (allAlugueis == null || allAlugueis.isEmpty) { + throw StateError('door code not found'); + } + + final nowEpoch = DateTime.now().millisecondsSinceEpoch ~/ 1000; + + Aluguel? matchingAluguel; + + for (final aluguel in allAlugueis.values) { + final storedDoor = aluguel.doorCode?.trim(); + if (storedDoor == null || storedDoor.isEmpty) { + continue; + } + if (storedDoor != normalizedDoorCode) { + continue; + } + if (aluguel.status.toUpperCase() != 'CONFIRMED') { + continue; + } + if (nowEpoch < aluguel.startDate || nowEpoch > aluguel.endDate) { + continue; + } + matchingAluguel = aluguel; + break; + } + + if (matchingAluguel == null) { + throw StateError('door code not found'); + } + + final doorSerial = await fetchDoorCodeFromCatalog( + matchingAluguel.workspaceId, + ); + + if (doorSerial == null || doorSerial.trim().isEmpty) { + throw StateError('door serial not found'); + } + + return doorSerial.trim(); + } +} 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..2d740916 100644 --- a/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart +++ b/back/microsservicos/aluguel/lib/domain/repositories/aluguel_repository.dart @@ -22,8 +22,11 @@ abstract class AluguelRepository { int? people, double? finalPrice, String? status, + String? doorCode, }); Future deleteAluguel(String id); -} \ No newline at end of file + + Future getDoorAluguel(String doorCode); +} diff --git a/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart b/back/microsservicos/aluguel/lib/infrastructure/clients/rabbitmq/rabbitmq.dart index ccf43ed1..1437645b 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'; @@ -28,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.', + ); } } @@ -39,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'; @@ -66,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'); @@ -75,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); @@ -84,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.'); @@ -111,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'); } @@ -132,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, @@ -146,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); @@ -155,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); @@ -166,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 { @@ -209,16 +243,116 @@ 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['doorSerial'] ?? + 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(); + } +} 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, @@ -236,7 +370,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; @@ -244,28 +378,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/infrastructure/repositories/aluguel_repository_mock.dart b/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart index 59a69d75..8ff6b3b2 100644 --- a/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart +++ b/back/microsservicos/aluguel/lib/infrastructure/repositories/aluguel_repository_mock.dart @@ -60,6 +60,7 @@ class AluguelRepositoryMock implements AluguelRepository { int? people, double? finalPrice, String? status, + String? doorCode, }) async { final atual = store[id]; @@ -79,6 +80,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, ); @@ -94,4 +96,17 @@ class AluguelRepositoryMock implements AluguelRepository { throw StateError('Aluguel com id "$id" não encontrado para exclusão.'); } } + + @override + Future getDoorAluguel(String doorCode) async { + for (var aluguel in store.values) { + + if (aluguel.doorCode == doorCode) { + return aluguel; + } + + } + return null; + } + } 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/create_aluguel_controller.dart b/back/microsservicos/aluguel/lib/presentation/http/controllers/create_aluguel_controller.dart index 80625b1f..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 @@ -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,7 @@ class CreateAluguelController { final endDate = data['endDate']; final people = data['people']; final finalPrice = data['finalPrice']; - + if (userId == null || userId.toString().isEmpty) { throw AppFailure('userId_required'); } 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..bdf24473 --- /dev/null +++ b/back/microsservicos/aluguel/lib/presentation/http/controllers/get_door_hash_controller.dart @@ -0,0 +1,60 @@ +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 doorCode = data['doorCode']?.toString(); + if (doorCode == null || doorCode.isEmpty) { + throw AppFailure('doorCode_required'); + } + + final doorSerial = await verifyDoorCodeUsecase.call( + doorCode: doorCode, + ); + + return jsonOk({'doorSerial': doorSerial}); + } on AppFailure catch (e) { + return jsonBadRequest({'error': e.message}); + } on StateError catch (e) { + final message = e.message; + if (message == 'door code not found') { + return jsonNotFound({'error': message}); + } + if (message == 'door serial not found') { + return jsonBadRequest({'error': message}); + } + if (message == 'door code invalid') { + return jsonBadRequest({'error': message}); + } + return jsonBadRequest({'error': 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/controllers/update_aluguel_controller.dart b/back/microsservicos/aluguel/lib/presentation/http/controllers/update_aluguel_controller.dart index 8cc6deb8..12851823 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; 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; +} 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/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 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); +// }); +// } 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..6b9a86aa 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 { randomInt } from "crypto"; import { CreateCatalogoUsecase } from "./create_catalogo_usecase"; import { Request, Response } from "express"; import { Catalogo } from "../../shared/domain/interfaces"; @@ -29,6 +30,8 @@ export class CreateCatalogoController { throw new Error("Missing catalogo price"); if (body.capacity === undefined) throw new Error("Missing catalogo capacity"); + if (body.doorSerial === undefined) + throw new Error("Missing door serial") const price = typeof body.price === "string" ? Number(body.price) : body.price; @@ -41,12 +44,15 @@ export class CreateCatalogoController { ? [body.comodities] : []; + const doorSerial = body.doorSerial + const roomProps = { ...body, comodities, pictures: [], price, capacity, + doorSerial, } as Catalogo; const createdRoom = this.usecase.execute(roomProps); diff --git a/back/microsservicos/catalogo/index.ts b/back/microsservicos/catalogo/index.ts index 77a801c1..80a02c07 100644 --- a/back/microsservicos/catalogo/index.ts +++ b/back/microsservicos/catalogo/index.ts @@ -2,12 +2,20 @@ 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 { startDoorCodeFetchConsumer, 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() + await startDoorCodeFetchConsumer() + } catch (error) { + console.error('Failed to start RabbitMQ consumers', error) + process.exit(1) + } }) // Desligamento seguro do RabbitMQ @@ -15,4 +23,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/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/catalogo/shared/domain/interfaces.ts b/back/microsservicos/catalogo/shared/domain/interfaces.ts index 98121253..0251fd3d 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 + doorSerial: 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..92c4dd53 --- /dev/null +++ b/back/microsservicos/catalogo/shared/infra/clients/rabbitmq/doorCodeVerificationConsumer.ts @@ -0,0 +1,195 @@ +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(); + + 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 responseBody: any = { valid: false }; + + if (workspaceId && typeof doorCode === 'string') { + try { + const repo = Environments.getCatalogoRepo(); + const catalogo = repo.getCatalogo(workspaceId); + const storedSerial = catalogo?.doorSerial; + + if (storedSerial) { + const isValid = storedSerial.trim() === doorCode.trim(); + responseBody = { + valid: isValid, + doorSerial: storedSerial.trim(), + }; + } + } catch (error) { + console.error( + '[DoorCodeVerification] Error while validating door code:', + error, + ); + } + } + + channel.sendToQueue( + replyTo, + Buffer.from(JSON.stringify(responseBody)), + { + 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); + } + }); +}; + +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 doorSerial: string | null = null; + + if (workspaceId) { + try { + const repo = Environments.getCatalogoRepo(); + const catalogo = repo.getCatalogo(workspaceId); + doorSerial = catalogo?.doorSerial ?? null; + } catch (error) { + console.error( + '[DoorCodeFetch] Error while retrieving door code:', + error, + ); + } + } + + channel.sendToQueue( + replyTo, + Buffer.from(JSON.stringify({ doorSerial })), + { + 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({ doorSerial: null, 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..951031d2 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, + doorSerial: props.doorSerial } 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.doorSerial) room_to_update.doorSerial = props.doorSerial 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 +} 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" diff --git a/docs/deploy_to_aws.png b/docs/deploy_to_aws.png new file mode 100644 index 00000000..de115724 Binary files /dev/null and b/docs/deploy_to_aws.png differ diff --git a/docs/fluxo.png b/docs/fluxo.png new file mode 100644 index 00000000..46960514 Binary files /dev/null and b/docs/fluxo.png differ 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" }