Skip to content

Commit

Permalink
feat(sandside): #193 notify admins users when user created
Browse files Browse the repository at this point in the history
close #193
  • Loading branch information
Marthym committed Jan 2, 2024
1 parent 7ed1587 commit cf487ec
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import fr.ght1pc9kc.baywatch.security.domain.exceptions.UnauthenticatedUser;
import fr.ght1pc9kc.baywatch.security.domain.exceptions.UnauthorizedOperation;
import fr.ght1pc9kc.baywatch.security.domain.ports.AuthorizationPersistencePort;
import fr.ght1pc9kc.baywatch.security.domain.ports.NotificationPort;
import fr.ght1pc9kc.baywatch.security.domain.ports.UserPersistencePort;
import fr.ght1pc9kc.baywatch.techwatch.domain.model.QueryContext;
import fr.ght1pc9kc.juery.api.Criteria;
Expand All @@ -33,7 +34,10 @@
import java.util.Objects;

import static fr.ght1pc9kc.baywatch.common.api.model.EntitiesProperties.ID;
import static fr.ght1pc9kc.baywatch.common.api.model.EntitiesProperties.ROLES;
import static fr.ght1pc9kc.baywatch.notify.api.model.EventType.USER_NOTIFICATION;
import static fr.ght1pc9kc.baywatch.security.api.model.RoleUtils.hasRole;
import static java.util.function.Predicate.not;

@Slf4j
@AllArgsConstructor
Expand All @@ -44,6 +48,7 @@ public final class UserServiceImpl implements UserService, AuthorizationService

private final UserPersistencePort userRepository;
private final AuthorizationPersistencePort authorizationRepository;
private final NotificationPort notificationPort;
private final AuthenticationFacade authFacade;
private final PasswordEncoder passwordEncoder;
private final Clock clock;
Expand Down Expand Up @@ -77,7 +82,16 @@ public Mono<Entity<User>> create(User user) {
Entity<User> entity = Entity.identify(userId, clock.instant(), withPassword);
return authorizeAllData()
.flatMap(u -> userRepository.persist(List.of(entity)).single())
.then(userRepository.persist(userId, user.roles.stream().map(Permission::toString).distinct().toList()));
.then(userRepository.persist(userId, user.roles.stream().map(Permission::toString).distinct().toList()))
.flatMap(this::notifyAdmins);
}

private Mono<Entity<User>> notifyAdmins(Entity<User> newUser) {
return this.list(PageRequest.all(Criteria.property(ROLES).eq(Role.ADMIN.toString())))
.filter(not(admin -> admin.id.equals(newUser.createdBy)))
.map(admin -> notificationPort.send(admin.id, USER_NOTIFICATION,
String.format("New user %s created by %s.", newUser.self.login, newUser.createdBy)))
.then(Mono.just(newUser));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package fr.ght1pc9kc.baywatch.security.domain.ports;

import fr.ght1pc9kc.baywatch.notify.api.model.BasicEvent;
import fr.ght1pc9kc.baywatch.notify.api.model.EventType;

public interface NotificationPort {
<T> BasicEvent<T> send(String userId, EventType type, T data);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package fr.ght1pc9kc.baywatch.security.infra.adapters;

import fr.ght1pc9kc.baywatch.notify.api.NotifyService;
import fr.ght1pc9kc.baywatch.security.domain.ports.NotificationPort;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Delegate;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class NotificationServiceAdapter implements NotificationPort {
@Delegate
private final NotifyService delegate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import fr.ght1pc9kc.baywatch.security.api.UserService;
import fr.ght1pc9kc.baywatch.security.domain.UserServiceImpl;
import fr.ght1pc9kc.baywatch.security.domain.ports.AuthorizationPersistencePort;
import fr.ght1pc9kc.baywatch.security.domain.ports.NotificationPort;
import fr.ght1pc9kc.baywatch.security.domain.ports.UserPersistencePort;
import fr.ght1pc9kc.baywatch.security.infra.model.BaywatchUserDetails;
import fr.ght1pc9kc.juery.api.Criteria;
Expand Down Expand Up @@ -34,11 +35,12 @@ public class UserServiceAdapter implements AuthorizationService, UserService, Re
@Autowired
public UserServiceAdapter(UserPersistencePort userPersistencePort,
AuthorizationPersistencePort authorizationRepository,
NotificationPort notificationPort,
AuthenticationFacade authFacade,
PasswordEncoder passwordEncoder) {
this.delegate = new UserServiceImpl(
userPersistencePort, authorizationRepository, authFacade, passwordEncoder, Clock.systemUTC(),
UlidFactory.newInstance());
userPersistencePort, authorizationRepository, notificationPort, authFacade, passwordEncoder,
Clock.systemUTC(), UlidFactory.newInstance());
this.delegateA = (UserServiceImpl) this.delegate;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import com.github.f4b6a3.ulid.UlidFactory;
import fr.ght1pc9kc.baywatch.common.api.model.EntitiesProperties;
import fr.ght1pc9kc.baywatch.common.api.model.Entity;
import fr.ght1pc9kc.baywatch.notify.api.model.BasicEvent;
import fr.ght1pc9kc.baywatch.notify.api.model.EventType;
import fr.ght1pc9kc.baywatch.security.api.AuthenticationFacade;
import fr.ght1pc9kc.baywatch.security.api.model.Permission;
import fr.ght1pc9kc.baywatch.security.api.model.Role;
import fr.ght1pc9kc.baywatch.security.api.model.User;
import fr.ght1pc9kc.baywatch.security.domain.exceptions.UnauthenticatedUser;
import fr.ght1pc9kc.baywatch.security.domain.exceptions.UnauthorizedOperation;
import fr.ght1pc9kc.baywatch.security.domain.ports.AuthorizationPersistencePort;
import fr.ght1pc9kc.baywatch.security.domain.ports.NotificationPort;
import fr.ght1pc9kc.baywatch.security.domain.ports.UserPersistencePort;
import fr.ght1pc9kc.baywatch.techwatch.domain.model.QueryContext;
import fr.ght1pc9kc.baywatch.tests.samples.UserSamples;
Expand Down Expand Up @@ -43,6 +46,7 @@ class UserServiceImplTest {
private final AuthorizationPersistencePort mockAuthorizationRepository = mock(AuthorizationPersistencePort.class);
private final AuthenticationFacade mockAuthFacade = mock(AuthenticationFacade.class);
private final UlidFactory mockUlidFactory = mock(UlidFactory.class);
private final NotificationPort mockNotificationPort = mock(NotificationPort.class);

private UserServiceImpl tested;

Expand All @@ -57,8 +61,10 @@ void setUp() {
when(mockUserRepository.delete(anyString(), anyCollection())).thenReturn(Mono.empty().then());
when(mockUserRepository.count(any())).thenReturn(Mono.just(3));
when(mockAuthorizationRepository.count(any())).thenReturn(Mono.just(0));
when(mockNotificationPort.send(anyString(), any(EventType.class), any()))
.thenReturn(new BasicEvent<>(Ulid.fast().toString(), EventType.USER_NOTIFICATION, "New jedi in the force"));

tested = new UserServiceImpl(mockUserRepository, mockAuthorizationRepository,
tested = new UserServiceImpl(mockUserRepository, mockAuthorizationRepository, mockNotificationPort,
mockAuthFacade, NoOpPasswordEncoder.getInstance(),
Clock.fixed(CURRENT, ZoneOffset.UTC), mockUlidFactory);
}
Expand Down Expand Up @@ -117,7 +123,7 @@ void should_create_user_with_admin() {
ArgumentCaptor<List<Entity<User>>> users = ArgumentCaptor.forClass(List.class);
verify(mockUserRepository).persist(users.capture());

Entity<User> actual = users.getValue().get(0);
Entity<User> actual = users.getValue().getFirst();
Assertions.assertThat(actual).isEqualTo(Entity.identify(UserSamples.OBIWAN.id, CURRENT, UserSamples.OBIWAN.self));
}

Expand Down

0 comments on commit cf487ec

Please sign in to comment.