From 379111a94af9afccd0a9af07c5ef81805a8acc45 Mon Sep 17 00:00:00 2001 From: AlfredoMate Date: Fri, 30 May 2025 18:03:46 +0200 Subject: [PATCH 1/2] endpoint update user --- .../UpdateUserUseCase/UpdateUserCommand.java | 7 +++ .../UpdateUserCommandHandler.java | 52 +++++++++++++++++++ .../UpdateUserUseCase/UserDoesntExist.java | 13 +++++ .../application/UsersReadModel.java | 1 + .../io/autoinvestor/domain/events/Event.java | 4 ++ .../io/autoinvestor/domain/model/User.java | 14 +++++ .../autoinvestor/domain/model/UserState.java | 12 +++++ .../domain/model/UserWasUpdatedEvent.java | 39 ++++++++++++++ .../model/UserWasUpdatedEventPayload.java | 16 ++++++ .../read_models/InMemoryUsersReadModel.java | 5 ++ .../read_models/MongoUsersReadModel.java | 9 ++++ .../ui/GlobalExceptionHandler.java | 4 +- .../autoinvestor/ui/UpdateUserController.java | 27 ++++++++++ .../io/autoinvestor/ui/UpdateUserDTO.java | 5 ++ 14 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommand.java create mode 100644 src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java create mode 100644 src/main/java/io/autoinvestor/application/UpdateUserUseCase/UserDoesntExist.java create mode 100644 src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEvent.java create mode 100644 src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEventPayload.java create mode 100644 src/main/java/io/autoinvestor/ui/UpdateUserController.java create mode 100644 src/main/java/io/autoinvestor/ui/UpdateUserDTO.java diff --git a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommand.java b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommand.java new file mode 100644 index 0000000..37f587f --- /dev/null +++ b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommand.java @@ -0,0 +1,7 @@ +package io.autoinvestor.application.UpdateUserUseCase; + +public record UpdateUserCommand( + String userId, + int riskLevel +) { +} diff --git a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java new file mode 100644 index 0000000..ee3d4b6 --- /dev/null +++ b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java @@ -0,0 +1,52 @@ +package io.autoinvestor.application.UpdateUserUseCase; + +import io.autoinvestor.application.UserDTO; +import io.autoinvestor.application.UserNotFound; +import io.autoinvestor.application.UsersReadModel; +import io.autoinvestor.domain.EventStore; +import io.autoinvestor.domain.events.Event; +import io.autoinvestor.domain.events.EventPublisher; +import io.autoinvestor.domain.model.User; +import io.autoinvestor.domain.model.UserId; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class UpdateUserCommandHandler { + + private final EventStore eventStore; + private final EventPublisher eventPublisher; + private final UsersReadModel readModel; + + + public void handle (UpdateUserCommand command) { + + String userIdToUpdate = String.valueOf(readModel.getById(command.userId()) + .map(UserDTO::riskLevel) + .orElseThrow(() -> UserNotFound.with(command.userId()))); + User user = this.eventStore.get(UserId.from(userIdToUpdate)); + user.update(userIdToUpdate, command.riskLevel()); + + List> events = user.getUncommittedEvents(); + + this.eventStore.save(user); + + UserDTO dto = new UserDTO( + user.getState().userId().value(), + command.getState().userEmail().value, + command.getState().firstName().value, + command.getState().lastName().value, + command.riskLevel() + ); + this.readModel.update(dto); + + this.eventPublisher.publish(events); + + user.markEventsAsCommitted(); + + } +} diff --git a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UserDoesntExist.java b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UserDoesntExist.java new file mode 100644 index 0000000..c6aea32 --- /dev/null +++ b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UserDoesntExist.java @@ -0,0 +1,13 @@ +package io.autoinvestor.application.UpdateUserUseCase; + +public class UserDoesntExist extends RuntimeException { + private UserDoesntExist(String message) { + super(message); + } + + public static UserDoesntExist with(String userId) { + String message = "User with id " + userId + "doesn't exist"; + return new UserDoesntExist(message); + } + +} diff --git a/src/main/java/io/autoinvestor/application/UsersReadModel.java b/src/main/java/io/autoinvestor/application/UsersReadModel.java index 2b025bd..28a70b6 100644 --- a/src/main/java/io/autoinvestor/application/UsersReadModel.java +++ b/src/main/java/io/autoinvestor/application/UsersReadModel.java @@ -7,4 +7,5 @@ public interface UsersReadModel { void save(UserDTO user); Optional get(String email); Optional getById(String userId); + void update(UserDTO dto); } diff --git a/src/main/java/io/autoinvestor/domain/events/Event.java b/src/main/java/io/autoinvestor/domain/events/Event.java index ea579ff..940207c 100644 --- a/src/main/java/io/autoinvestor/domain/events/Event.java +++ b/src/main/java/io/autoinvestor/domain/events/Event.java @@ -14,6 +14,10 @@ public abstract class Event

{ private final Date occurredAt; private final int version; + protected Event(Id aggregateId, String type, P payload) { + this(aggregateId, type, payload, 1); + } + protected Event(Id aggregateId, String type, P payload, int version) { this.id = EventId.generate(); this.aggregateId = aggregateId; diff --git a/src/main/java/io/autoinvestor/domain/model/User.java b/src/main/java/io/autoinvestor/domain/model/User.java index e5ed88b..96bc5e7 100644 --- a/src/main/java/io/autoinvestor/domain/model/User.java +++ b/src/main/java/io/autoinvestor/domain/model/User.java @@ -43,12 +43,22 @@ public static User create(String firstName, String lastName, String email, int r return user; } + public void update(String userId, int riskLevel) { + this.apply(UserWasUpdatedEvent.with( + UserId.from(userId), + RiskLevel.from(riskLevel) + )); + } + @Override protected void when(Event event) { switch (event.getType()) { case UserWasRegisteredEvent.TYPE : whenUserCreated((UserWasRegisteredEvent) event); break; + case UserWasUpdatedEvent.TYPE: + whenUserUpdated((UserWasUpdatedEvent) event); + break; default: throw new IllegalArgumentException("Unknown event type"); } @@ -61,4 +71,8 @@ private void whenUserCreated(UserWasRegisteredEvent event) { assert this.state != null; this.state = this.state.withUserCreated(event); } + + private void whenUserUpdated(UserWasUpdatedEvent event) { + this.state = this.state.withUserDeleted(event); + } } diff --git a/src/main/java/io/autoinvestor/domain/model/UserState.java b/src/main/java/io/autoinvestor/domain/model/UserState.java index 972ac52..c8f65c7 100644 --- a/src/main/java/io/autoinvestor/domain/model/UserState.java +++ b/src/main/java/io/autoinvestor/domain/model/UserState.java @@ -25,4 +25,16 @@ public UserState withUserCreated(UserWasRegisteredEvent event) { event.getOccurredAt(), new Date()); } + + public UserState withUserDeleted(UserWasUpdatedEvent event) { + UserWasUpdatedEventPayload payload = event.getPayload(); + return new UserState( + UserId.from(event.getAggregateId().value()), + this.firstName, + this.lastName, + this.userEmail, + RiskLevel.from(payload.riskLevel()), + event.getOccurredAt(), + new Date()); + } } diff --git a/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEvent.java b/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEvent.java new file mode 100644 index 0000000..db65c1a --- /dev/null +++ b/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEvent.java @@ -0,0 +1,39 @@ +package io.autoinvestor.domain.model; + +import io.autoinvestor.domain.Id; +import io.autoinvestor.domain.events.Event; +import io.autoinvestor.domain.events.EventId; + +import java.util.Date; + +public class UserWasUpdatedEvent extends Event{ + public static final String TYPE = "SUBSCRIPTION_UPDATED"; + + private UserWasUpdatedEvent(Id aggregateId, UserWasUpdatedEventPayload payload) { + super(aggregateId, TYPE, payload); + } + + protected UserWasUpdatedEvent( + EventId id, + Id aggregateId, + UserWasUpdatedEventPayload payload, + Date occurredAt, + int version) { + super(id, aggregateId, TYPE, payload, occurredAt, version); + } + + public static UserWasUpdatedEvent with (UserId userId, + RiskLevel riskLevel) { + UserWasUpdatedEventPayload payload = new UserWasUpdatedEventPayload( + riskLevel.value()); + return new UserWasUpdatedEvent(userId, payload); + } + + public static UserWasUpdatedEvent hydrate (EventId id, + Id aggregateId, + UserWasUpdatedEventPayload payload, + Date occurredAt, + int version){ + return new UserWasUpdatedEvent(id, aggregateId, payload, occurredAt, version); + } +} diff --git a/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEventPayload.java b/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEventPayload.java new file mode 100644 index 0000000..8b70d03 --- /dev/null +++ b/src/main/java/io/autoinvestor/domain/model/UserWasUpdatedEventPayload.java @@ -0,0 +1,16 @@ +package io.autoinvestor.domain.model; + +import io.autoinvestor.domain.events.EventPayload; + +import java.util.Map; + +public record UserWasUpdatedEventPayload( + int riskLevel +) implements EventPayload { + @Override + public Map asMap() { + return Map.of( + "riskLevel", riskLevel + ); + } +} diff --git a/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java b/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java index 199268d..6d4d93b 100644 --- a/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java +++ b/src/main/java/io/autoinvestor/infrastructure/read_models/InMemoryUsersReadModel.java @@ -31,4 +31,9 @@ public Optional getById(String userId) { .filter(user -> user.userId().equals(userId)) .findFirst(); } + + @Override + public void update(UserDTO dto) { + + } } diff --git a/src/main/java/io/autoinvestor/infrastructure/read_models/MongoUsersReadModel.java b/src/main/java/io/autoinvestor/infrastructure/read_models/MongoUsersReadModel.java index 4931158..af1d447 100644 --- a/src/main/java/io/autoinvestor/infrastructure/read_models/MongoUsersReadModel.java +++ b/src/main/java/io/autoinvestor/infrastructure/read_models/MongoUsersReadModel.java @@ -7,6 +7,7 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -46,4 +47,12 @@ public Optional getById(String userId) { return Optional.ofNullable(doc) .map(mapper::toDTO); } + + @Override + public void update(UserDTO dto) { + Query query = new Query(Criteria.where("userId").is(dto.userId())); + Update update = new Update() + .set("riskLevel", dto.riskLevel()); + this.template.updateFirst(query, update, UserDocument.class, COLLECTION); + } } diff --git a/src/main/java/io/autoinvestor/ui/GlobalExceptionHandler.java b/src/main/java/io/autoinvestor/ui/GlobalExceptionHandler.java index d51d5c9..ff44298 100644 --- a/src/main/java/io/autoinvestor/ui/GlobalExceptionHandler.java +++ b/src/main/java/io/autoinvestor/ui/GlobalExceptionHandler.java @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -@RestControllerAdvice(assignableTypes = {RegisterUserController.class, GetUserController.class}) +@RestControllerAdvice(assignableTypes = {RegisterUserController.class, GetUserController.class, UpdateUserController.class}) public class GlobalExceptionHandler { @ExceptionHandler(DuplicatedException.class) @@ -61,4 +61,6 @@ public ResponseEntity handleRiskLevelNotValid(RiskLevelNotValid e public ResponseEntity handleUserNotFound(UserNotFound ex) { return ErrorResponse.builder().status(HttpStatus.valueOf(404)).message(ex.getMessage()).build(); } + + } diff --git a/src/main/java/io/autoinvestor/ui/UpdateUserController.java b/src/main/java/io/autoinvestor/ui/UpdateUserController.java new file mode 100644 index 0000000..b9f42f0 --- /dev/null +++ b/src/main/java/io/autoinvestor/ui/UpdateUserController.java @@ -0,0 +1,27 @@ +package io.autoinvestor.ui; + +import io.autoinvestor.application.UpdateUserUseCase.UpdateUserCommand; +import io.autoinvestor.application.UpdateUserUseCase.UpdateUserCommandHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/user") +@RequiredArgsConstructor +public class UpdateUserController { + + private final UpdateUserCommandHandler updateUserCommandHandler; + + public ResponseEntity handle ( + @RequestHeader(value = "X-User-Id", required = false) String userId, + @RequestBody UpdateUserDTO dto + ){ + updateUserCommandHandler.handle(new UpdateUserCommand(userId, dto.riskLevel())); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } +} diff --git a/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java b/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java new file mode 100644 index 0000000..8303736 --- /dev/null +++ b/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java @@ -0,0 +1,5 @@ +package io.autoinvestor.ui; + +public record UpdateUserDTO( + String riskLevel +) {} From 55812e5cb9648905e65eebdccb189c2e23faab25 Mon Sep 17 00:00:00 2001 From: AlfredoMate Date: Fri, 30 May 2025 18:12:06 +0200 Subject: [PATCH 2/2] smallFix --- .../UpdateUserUseCase/UpdateUserCommandHandler.java | 6 +++--- src/main/java/io/autoinvestor/ui/UpdateUserDTO.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java index ee3d4b6..e7f6e1f 100644 --- a/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java +++ b/src/main/java/io/autoinvestor/application/UpdateUserUseCase/UpdateUserCommandHandler.java @@ -37,9 +37,9 @@ public void handle (UpdateUserCommand command) { UserDTO dto = new UserDTO( user.getState().userId().value(), - command.getState().userEmail().value, - command.getState().firstName().value, - command.getState().lastName().value, + user.getState().userEmail().value(), + user.getState().firstName().value(), + user.getState().lastName().value(), command.riskLevel() ); this.readModel.update(dto); diff --git a/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java b/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java index 8303736..b36ee7c 100644 --- a/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java +++ b/src/main/java/io/autoinvestor/ui/UpdateUserDTO.java @@ -1,5 +1,5 @@ package io.autoinvestor.ui; public record UpdateUserDTO( - String riskLevel + int riskLevel ) {}