Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:math';

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 UpdateAluguelUsecase {
final AluguelRepository repository;
Expand All @@ -14,7 +15,6 @@ class UpdateAluguelUsecase {
int? people,
double? finalPrice,
String? status,
String? doorCode,
}) async {
if (startDate != null && endDate != null && endDate < startDate) {
throw StateError('endDate não pode ser menor que startDate.');
Expand All @@ -26,35 +26,14 @@ class UpdateAluguelUsecase {
throw StateError('finalPrice não pode ser negativo.');
}

String? desiredDoorCode = doorCode;
String? desiredDoorCode = Random().nextInt(100000).toString().padLeft(5, '0');

if (status != null &&
status.toUpperCase() == 'CONFIRMED' &&
doorCode == null) {
status.toUpperCase() == 'CONFIRMED') {
final current = await repository.getAluguel(id);
if (current == null) {
throw StateError('aluguel_not_found');
}

final fetchedDoorCode = await fetchDoorCodeFromCatalog(
current.workspaceId,
);
if (fetchedDoorCode == null || fetchedDoorCode.isEmpty) {
throw StateError('door_code_not_found');
}
desiredDoorCode = fetchedDoorCode;
}

String? sanitizedDoorCode;
if (desiredDoorCode != null) {
final trimmedDoorCode = desiredDoorCode.trim();
if (trimmedDoorCode.isEmpty) {
throw StateError('doorCode nǜo pode ser vazio.');
}
if (!RegExp(r'^\d{5}$').hasMatch(trimmedDoorCode)) {
throw StateError('doorCode deve conter exatamente 5 d��gitos.');
}
sanitizedDoorCode = trimmedDoorCode;
}

return repository.updateAluguel(
Expand All @@ -64,7 +43,7 @@ class UpdateAluguelUsecase {
people: people,
finalPrice: finalPrice,
status: status,
doorCode: sanitizedDoorCode,
doorCode: desiredDoorCode,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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';

Expand All @@ -6,32 +7,55 @@ class VerifyDoorCodeUsecase {

VerifyDoorCodeUsecase({required this.repository});

Future<bool> call({
required String aluguelId,
required String plainDoorCode,
Future<String> call({
required String doorCode,
}) async {
if (!RegExp(r'^\d{5}$').hasMatch(plainDoorCode)) {
throw StateError('doorCode deve conter exatamente 5 digitos.');
final normalizedDoorCode = doorCode.trim();

if (!RegExp(r'^\d{5}$').hasMatch(normalizedDoorCode)) {
throw StateError('door code invalid');
}

final aluguel = await repository.getAluguel(aluguelId);
final allAlugueis = await repository.getAllAluguel();

if (aluguel == null) {
throw StateError('aluguel_not_found');
if (allAlugueis == null || allAlugueis.isEmpty) {
throw StateError('door code not found');
}

if (aluguel.status.toUpperCase() != 'CONFIRMED') {
return false;
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;
}

final nowEpoch = DateTime.now().millisecondsSinceEpoch ~/ 1000;
if (nowEpoch < aluguel.startDate || nowEpoch > aluguel.endDate) {
return false;
if (matchingAluguel == null) {
throw StateError('door code not found');
}

return await verifyDoorCodeWithCatalog(
workspaceId: aluguel.workspaceId,
doorCode: plainDoorCode,
final doorSerial = await fetchDoorCodeFromCatalog(
matchingAluguel.workspaceId,
);

if (doorSerial == null || doorSerial.trim().isEmpty) {
throw StateError('door serial not found');
}

return doorSerial.trim();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ abstract class AluguelRepository {


Future<void> deleteAluguel(String id);

Future<Aluguel?> getDoorAluguel(String doorCode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,10 @@ Future<String?> fetchDoorCodeFromCatalog(
final decoded = jsonDecode(message.payloadAsString);
String? doorCode;
if (decoded is Map<String, dynamic>) {
final rawDoorCode =
decoded['doorCodeHash'] ?? decoded['doorCode'] ?? decoded['code'];
final rawDoorCode = decoded['doorSerial'] ??
decoded['doorCodeHash'] ??
decoded['doorCode'] ??
decoded['code'];
if (rawDoorCode is String && rawDoorCode.isNotEmpty) {
doorCode = rawDoorCode;
}
Expand Down Expand Up @@ -338,104 +340,6 @@ Future<String?> fetchDoorCodeFromCatalog(
}
}

Future<bool> verifyDoorCodeWithCatalog({
required String workspaceId,
required String doorCode,
Duration timeout = const Duration(seconds: 5),
}) async {
final channel = await connectRabbitMQ();
final exchange = await channel.exchange(
EXCHANGE_NAME,
ExchangeType.TOPIC,
durable: true,
);
final replyQueue = await channel.queue('', exclusive: true, autoDelete: true);
final correlationId = const Uuid().v4();
final completer = Completer<bool>();
final consumer = await replyQueue.consume(noAck: false);
late final StreamSubscription<AmqpMessage> subscription;

subscription = consumer.listen(
(AmqpMessage message) {
try {
if (message.properties?.corellationId != correlationId) {
message.reject(false);
return;
}

final decoded = jsonDecode(message.payloadAsString);
bool result = false;
if (decoded is Map<String, dynamic>) {
final dynamic candidate =
decoded['doorVerifiedHash'] ?? decoded['valid'];
if (candidate is bool) {
result = candidate;
}
} else if (decoded is bool) {
result = decoded;
}

message.ack();
if (!completer.isCompleted) {
completer.complete(result);
}
subscription.cancel();
} catch (error) {
message.reject(false);
if (!completer.isCompleted) {
completer.completeError(error);
}
}
},
onError: (error) {
if (!completer.isCompleted) {
completer.completeError(error);
}
},
onDone: () {
if (!completer.isCompleted) {
completer.completeError(
StateError('reply queue closed before receiving response'),
);
}
},
cancelOnError: false,
);

final payload = {
'eventType': 'CatalogoVerifyDoorCodeRequest',
'payload': {'workspaceId': workspaceId, 'doorCode': doorCode},
};

final properties = MessageProperties()
..replyTo = replyQueue.name
..corellationId = correlationId
..contentType = 'application/json'
..deliveryMode = 1;

exchange.publish(
utf8.encode(jsonEncode(payload)),
'catalogo.verify-door-code.request',
properties: properties,
);

final timer = Timer(timeout, () {
if (!completer.isCompleted) {
completer.completeError(
TimeoutException('Door code verification timed out'),
);
}
});

try {
return await completer.future;
} finally {
timer.cancel();
await subscription.cancel();
await replyQueue.delete();
}
}

const String _DELAY_EXCHANGE = 'aluguel.delay.ex'; // exchange de delay (direct)
const String _DELAY_QUEUE = 'aluguel.delay.q'; // fila de delay
const String _DELAY_RK = 'aluguel.expire'; // routing key para entrar no delay
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,17 @@ class AluguelRepositoryMock implements AluguelRepository {
throw StateError('Aluguel com id "$id" não encontrado para exclusão.');
}
}

@override
Future<Aluguel?> getDoorAluguel(String doorCode) async {
for (var aluguel in store.values) {

if (aluguel.doorCode == doorCode) {
return aluguel;
}

}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,30 @@ class GetDoorHashController {

final data = jsonDecode(body);

final aluguelId = (data['aluguelId'] ?? data['bookingId'])?.toString();
final doorCode = data['doorCode']?.toString();

if (aluguelId == null || aluguelId.isEmpty) {
throw AppFailure('aluguelId_required');
}
if (doorCode == null || doorCode.isEmpty) {
throw AppFailure('doorCode_required');
}

final isValid = await verifyDoorCodeUsecase.call(
aluguelId: aluguelId,
plainDoorCode: doorCode,
final doorSerial = await verifyDoorCodeUsecase.call(
doorCode: doorCode,
);

return jsonOk({'valid': isValid});
return jsonOk({'doorSerial': doorSerial});
} on AppFailure catch (e) {
return jsonBadRequest({'error': e.message});
} on StateError catch (e) {
if (e.message == 'aluguel_not_found') {
return jsonNotFound({'error': e.message});
final message = e.message;
if (message == 'door code not found') {
return jsonNotFound({'error': message});
}
return jsonBadRequest({'error': e.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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class UpdateAluguelController {
final int? people = data['people'];
final num? finalPrice = data['finalPrice'];
final String? status = data['status'];
final String? doorCode = data['doorCode'];

if (startDate != null && endDate != null && endDate <= startDate) {
throw AppFailure('endDate_must_be_greater_than_startDate');
Expand All @@ -40,9 +39,6 @@ class UpdateAluguelController {
if (finalPrice != null && finalPrice <= 0) {
throw AppFailure('finalPrice_must_be_positive');
}
if (doorCode != null && doorCode.isEmpty) {
throw AppFailure('doorCode_cannot_be_empty');
}

final aluguelAtualizado = await updateAluguelUsecase.call(
id.toString(),
Expand All @@ -51,7 +47,6 @@ class UpdateAluguelController {
people: people,
finalPrice: finalPrice?.toDouble(),
status: status,
doorCode: doorCode,
);

return jsonOk(aluguelAtualizado.toJson());
Expand Down
Loading