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,12 +1,17 @@
package io.theurl.framework.domain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

public class AggregateRoot<ID extends Comparable<ID>> extends Entity<ID> implements IAggregateRoot<ID>, IHasDomainEvents {

private final List<IDomainEvent> events;

private final Map<Class<? extends IDomainEvent>, Consumer<IDomainEvent>> eventHandlers = new HashMap<>();

/**
* Initializes the aggregate with the given id.
*
Expand All @@ -29,16 +34,26 @@ public void clearEvents() {

@Override
public <E extends IDomainEvent> void raiseEvent(E event) {

applyEvent(event);
this.events.add(event);
}

@Override
public <E extends IDomainEvent> void applyEvent(E event) {
var handler = eventHandlers.get(event.getClass());
if (handler != null) {
handler.accept(event);
}
}

public <E extends IDomainEvent> void registerEvent(Class<E> eventType, Consumer<E> handler) {
eventHandlers.put(eventType, event -> handler.accept(eventType.cast(event)));
}

@Override
public void attachEvent() {

for (var event : events) {
event.attach(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public EventAggregate getEventAggregate() {
return aggregate;
}


public <V> V getAggregate(Class<V> targetType) {
var aggregate = getAggregatePayload();
if (aggregate == null) {
Expand All @@ -47,4 +46,11 @@ public <V> V getAggregate(Class<V> targetType) {
}
return (V) TypeHelper.coerceValue(targetType, aggregate.getClass(), aggregate);
}

@Override
public <ID extends Comparable<ID>> void attach(IAggregateRoot<ID> aggregateRoot) {
setOriginatorId(aggregateRoot.getId().toString());
setOriginatorType(aggregateRoot.getClass().getName());
setAggregatePayload(aggregateRoot);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@
*/
@SuppressWarnings("unused")
public class AccountException extends RuntimeException {
private final String identity;
private final Object identity;

private final Map<String, Object> details = Collections.emptyMap();

public AccountException(String identity) {
public AccountException(Object identity) {
this.identity = identity;
}

public AccountException(String identity, String message) {
public AccountException(Object identity, String message) {
super(message);
this.identity = identity;
}

public AccountException(String identity, String message, Throwable cause) {
public AccountException(Object identity, String message, Throwable cause) {
super(message, cause);
this.identity = identity;
}

public String getIdentity() {
public Object getIdentity() {
return identity;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
*/
@SuppressWarnings("unused")
public class AccountExpiredException extends AccountException {
public AccountExpiredException(String identity) {
public AccountExpiredException(Object identity) {
super(identity);
}

public AccountExpiredException(String identity, String message) {
public AccountExpiredException(Object identity, String message) {
super(identity, message);
}

public AccountExpiredException(String identity, String message, Throwable cause) {
public AccountExpiredException(Object identity, String message, Throwable cause) {
super(identity, message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
*/
@SuppressWarnings("unused")
public class AccountLockedException extends AccountException {
public AccountLockedException(String identity) {
public AccountLockedException(Object identity) {
super(identity);
}

public AccountLockedException(String identity, String message) {
public AccountLockedException(Object identity, String message) {
super(identity, message);
}

public AccountLockedException(String identity, String message, Throwable cause) {
public AccountLockedException(Object identity, String message, Throwable cause) {
super(identity, message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
*/
@SuppressWarnings("unused")
public class AccountNotFoundException extends AccountException {
public AccountNotFoundException(String identity) {
public AccountNotFoundException(Object identity) {
super(identity);
}

public AccountNotFoundException(String identity, String message) {
public AccountNotFoundException(Object identity, String message) {
super(identity, message);
}

public AccountNotFoundException(String identity, String message, Throwable cause) {
public AccountNotFoundException(Object identity, String message, Throwable cause) {
super(identity, message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.theurl.framework.utility;

public class RegexUtility {
public static boolean isEmail(String email) {
return email != null && email.matches("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
}

public static boolean isPhone(String phone) {
return phone != null && phone.matches("^\\+?[0-9]{7,15}$");
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.theurl.framework.utility;

import lombok.Getter;
import org.jspecify.annotations.NonNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
Expand All @@ -10,7 +9,7 @@
@SuppressWarnings("unused")
@Component
public class SpringUtil implements ApplicationContextAware {
@Getter

private static ApplicationContext applicationContext;

@Override
Expand All @@ -20,6 +19,10 @@ public void setApplicationContext(@NonNull ApplicationContext context) throws Be
}
}

public static ApplicationContext getApplicationContext() {
return applicationContext;
}

public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
Expand Down
2 changes: 1 addition & 1 deletion identity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<dependency>
<groupId>io.theurl</groupId>
<artifactId>framework</artifactId>
<version>1.0</version>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
package io.theurl.identity.application.command;

import com.neroyun.mediator.Command;
import lombok.Getter;

/**
* Command to update the access failure count of a user.
* This command is typically used when a user authentication attempt fails, and we want to increment the failure count for that user.
* The command contains the user ID and the new failure count to be set.
*/
@Getter
public class UserAccessFailureCountCommand implements Command {
public record UserAccessFailureCountCommand(Long userId, String action) implements Command {

private final Long userId;
private final String action;

public UserAccessFailureCountCommand(Long userId, String action) {
this.userId = userId;
this.action = action;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
package io.theurl.identity.application.command;

public class UserCreateCommand {
import com.neroyun.mediator.Command;
import lombok.Data;

@Data
public class UserCreateCommand implements Command {
private String username;
private String password;
private String nickname;
private String email;
private String phone;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.theurl.identity.application.command;

import com.neroyun.mediator.Command;

/**
* Command for changing user password. The changeType can be "reset" for resetting password or "update" for updating password.
*
* @param userId the ID of the user whose password is to be changed
* @param password the new password for the user
* @param changeType the type of password change, either "reset" or "update"
*/
public record UserPasswordChangeCommand(Long userId, String password, String changeType) implements Command {
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ public UserAccessFailureCountCommandHandler(UserRepository repository) {

@Override
public CompletableFuture<Void> handleAsync(UserAccessFailureCountCommand message) {
return null;
var user = repository.findById(message.userId());
if (user == null) {
return CompletableFuture.completedFuture(null);
}

switch (message.action()) {
case "increase" -> user.increaseAccessFailedCount();
case "reset" -> user.resetAccessFailedCount();
}

repository.save(user);
return CompletableFuture.completedFuture(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.theurl.identity.application.handler;

import com.neroyun.mediator.Handler;
import io.theurl.identity.application.command.UserCreateCommand;
import io.theurl.identity.domain.aggregate.User;
import io.theurl.identity.domain.repository.UserRepository;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

import java.util.concurrent.CompletableFuture;

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserCreateCommandHandler implements Handler<UserCreateCommand, Void> {
private final UserRepository repository;

public UserCreateCommandHandler(UserRepository repository) {
this.repository = repository;
}

@Async
@Transactional
@Override
public CompletableFuture<Void> handleAsync(UserCreateCommand message) {
var exists = repository.findByAnyOf(message.getUsername(), message.getEmail(), message.getPhone());

if (exists != null) {
throw new IllegalArgumentException("User with the same username, email or phone already exists.");
}

var user = User.create(message.getUsername());
user.setPassword(message.getPassword(), "init");
user.setNickname(message.getNickname());
if (message.getEmail() != null) {
user.setEmail(message.getEmail());
}
if (message.getPhone() != null) {
user.setPhone(message.getPhone());
}
repository.save(user);
return CompletableFuture.completedFuture(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.theurl.identity.application.handler;

import com.neroyun.mediator.Handler;
import io.theurl.identity.application.command.UserPasswordChangeCommand;
import io.theurl.identity.domain.repository.UserRepository;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

import java.util.concurrent.CompletableFuture;

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserPasswordChangeCommandHandler implements Handler<UserPasswordChangeCommand, Void> {

private final UserRepository repository;

public UserPasswordChangeCommandHandler(UserRepository repository) {
this.repository = repository;
}

@Override
public CompletableFuture<Void> handleAsync(UserPasswordChangeCommand message) {
var user = repository.findById(message.userId());
if (user == null) {
return CompletableFuture.completedFuture(null);
}

user.setPassword(message.password(), message.changeType());
repository.save(user);
return CompletableFuture.completedFuture(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.CompletableFuture;

@Slf4j
Expand Down Expand Up @@ -110,7 +107,7 @@ public CompletableFuture<TokenGrantResponseDto> grant(TokenGrantRequestDto reque
handleException(e, ex -> {
switch (ex) {
case AccountLockedException exception:
event.setUserId(exception.getIdentity());
event.setUserId((Long) exception.getIdentity());
event.setGrantType(request.grantType());
event.setGrantTime(LocalDateTime.now());
event.setError(exception.getLocalizedMessage());
Expand Down
Loading
Loading