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"
}