diff --git a/framework/src/main/java/io/theurl/framework/configure/MediatorConfiguration.java b/framework/src/main/java/io/theurl/framework/configure/MediatorConfiguration.java index 421a0d6..40199da 100644 --- a/framework/src/main/java/io/theurl/framework/configure/MediatorConfiguration.java +++ b/framework/src/main/java/io/theurl/framework/configure/MediatorConfiguration.java @@ -2,22 +2,31 @@ import com.neroyun.mediator.*; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; +import java.util.concurrent.CompletableFuture; + @SuppressWarnings("rawtypes") @Configuration public class MediatorConfiguration { @Bean - public Mediator mediator(ObjectProvider handlers, ObjectProvider middlewares, ObjectProvider validators) { + public Mediator mediator(ObjectProvider handlers, ObjectProvider middlewares, ObjectProvider validators, ApplicationEventPublisher publisher) { return new PipelinedMediator() .use(() -> handlers.stream()) .use(() -> middlewares.stream()) - .use(() -> validators.stream()); + .use(() -> validators.stream()) + .use(event -> CompletableFuture.runAsync(() -> { + publisher.publishEvent(event); + }, taskExecutor().getThreadPoolExecutor())); } @Bean(name = "taskExecutor") @@ -47,4 +56,11 @@ private TaskDecorator copyRequestContextDecorator() { }; }; } + + @Bean(name = "applicationEventMulticaster") + public ApplicationEventMulticaster simpleApplicationEventMulticaster() { + SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(); + multicaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); + return multicaster; + } } diff --git a/identity/src/main/java/io/theurl/identity/SpringUtil.java b/framework/src/main/java/io/theurl/framework/utility/SpringUtil.java similarity index 96% rename from identity/src/main/java/io/theurl/identity/SpringUtil.java rename to framework/src/main/java/io/theurl/framework/utility/SpringUtil.java index d0147c2..a1afca1 100644 --- a/identity/src/main/java/io/theurl/identity/SpringUtil.java +++ b/framework/src/main/java/io/theurl/framework/utility/SpringUtil.java @@ -1,4 +1,4 @@ -package io.theurl.identity; +package io.theurl.framework.utility; import lombok.Getter; import org.jspecify.annotations.NonNull; diff --git a/identity/pom.xml b/identity/pom.xml index 7762455..1985f53 100644 --- a/identity/pom.xml +++ b/identity/pom.xml @@ -33,18 +33,18 @@ jjwt-api 0.13.0 - - org.springframework.boot - spring-boot-starter-amqp - + + + + org.springframework.boot spring-boot-starter-data-jpa - - org.springframework.boot - spring-boot-starter-data-redis - + + + + org.springframework.boot spring-boot-starter-web @@ -74,20 +74,25 @@ lombok true - - org.springframework.boot - spring-boot-starter-amqp-test - test - + + + + + org.springframework.boot spring-boot-starter-data-jpa-test test + + + + + - org.springframework.boot - spring-boot-starter-data-redis-test - test + org.modelmapper + modelmapper + ${modelmapper.version} org.springframework.boot @@ -113,6 +118,9 @@ org.apache.maven.plugins maven-compiler-plugin + + true + default-compile diff --git a/identity/src/main/java/io/theurl/identity/application/command/TokenCreateCommand.java b/identity/src/main/java/io/theurl/identity/application/command/TokenCreateCommand.java new file mode 100644 index 0000000..7a2dccd --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/command/TokenCreateCommand.java @@ -0,0 +1,15 @@ +package io.theurl.identity.application.command; + +import com.neroyun.mediator.Command; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class TokenCreateCommand implements Command { + private String jti; + private String content; + private Long subject; + private LocalDateTime issuedAt; + private LocalDateTime expiresAt; +} diff --git a/identity/src/main/java/io/theurl/identity/application/command/UserAccessFailureCountCommand.java b/identity/src/main/java/io/theurl/identity/application/command/UserAccessFailureCountCommand.java new file mode 100644 index 0000000..c82a235 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/command/UserAccessFailureCountCommand.java @@ -0,0 +1,21 @@ +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 { + + private final Long userId; + private final String action; + + public UserAccessFailureCountCommand(Long userId, String action) { + this.userId = userId; + this.action = action; + } +} diff --git a/identity/src/main/java/io/theurl/identity/application/command/UserCreateCommand.java b/identity/src/main/java/io/theurl/identity/application/command/UserCreateCommand.java new file mode 100644 index 0000000..a296625 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/command/UserCreateCommand.java @@ -0,0 +1,4 @@ +package io.theurl.identity.application.command; + +public class UserCreateCommand { +} diff --git a/identity/src/main/java/io/theurl/identity/application/command/UserUpdateCommand.java b/identity/src/main/java/io/theurl/identity/application/command/UserUpdateCommand.java new file mode 100644 index 0000000..41ab369 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/command/UserUpdateCommand.java @@ -0,0 +1,4 @@ +package io.theurl.identity.application.command; + +public class UserUpdateCommand { +} diff --git a/identity/src/main/java/io/theurl/identity/application/contract/UserApplicationService.java b/identity/src/main/java/io/theurl/identity/application/contract/UserApplicationService.java new file mode 100644 index 0000000..43115d4 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/contract/UserApplicationService.java @@ -0,0 +1,6 @@ +package io.theurl.identity.application.contract; + +import io.theurl.framework.application.ApplicationService; + +public interface UserApplicationService extends ApplicationService { +} diff --git a/identity/src/main/java/io/theurl/identity/application/event/TokenGrantedEvent.java b/identity/src/main/java/io/theurl/identity/application/event/TokenGrantedEvent.java new file mode 100644 index 0000000..2f1b02b --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/event/TokenGrantedEvent.java @@ -0,0 +1,6 @@ +package io.theurl.identity.application.event; + +import io.theurl.framework.domain.ApplicationEvent; + +public class TokenGrantedEvent extends ApplicationEvent { +} diff --git a/identity/src/main/java/io/theurl/identity/application/event/TokenRefreshedEvent.java b/identity/src/main/java/io/theurl/identity/application/event/TokenRefreshedEvent.java new file mode 100644 index 0000000..2393351 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/event/TokenRefreshedEvent.java @@ -0,0 +1,6 @@ +package io.theurl.identity.application.event; + +import io.theurl.framework.domain.ApplicationEvent; + +public class TokenRefreshedEvent extends ApplicationEvent { +} diff --git a/identity/src/main/java/io/theurl/identity/application/event/UserAuthFailureEvent.java b/identity/src/main/java/io/theurl/identity/application/event/UserAuthFailureEvent.java index 6604bed..38f12e7 100644 --- a/identity/src/main/java/io/theurl/identity/application/event/UserAuthFailureEvent.java +++ b/identity/src/main/java/io/theurl/identity/application/event/UserAuthFailureEvent.java @@ -11,7 +11,7 @@ @EqualsAndHashCode(callSuper = true) @Data public class UserAuthFailureEvent extends ApplicationEvent implements Event { - private String userId; + private Long userId; private String username; private String grantType; private LocalDateTime grantTime; diff --git a/identity/src/main/java/io/theurl/identity/application/handler/TokenCreateCommandHandler.java b/identity/src/main/java/io/theurl/identity/application/handler/TokenCreateCommandHandler.java new file mode 100644 index 0000000..37f27c1 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/handler/TokenCreateCommandHandler.java @@ -0,0 +1,30 @@ +package io.theurl.identity.application.handler; + +import com.neroyun.mediator.Handler; +import io.theurl.identity.application.command.TokenCreateCommand; +import io.theurl.identity.domain.aggregate.Token; +import io.theurl.identity.domain.repository.TokenRepository; +import jakarta.annotation.Resource; +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 TokenCreateCommandHandler implements Handler { + + @Resource + private TokenRepository tokenRepository; + + @Override + public CompletableFuture handleAsync(TokenCreateCommand message) { + var token = Token.create(message.getJti(), message.getContent(), message.getSubject()); + token.setExpiresAt(message.getExpiresAt()); + token.setIssuedAt(message.getIssuedAt()); + tokenRepository.save(token); + return CompletableFuture.completedFuture(null); + } +} diff --git a/identity/src/main/java/io/theurl/identity/application/handler/UserAccessFailureCountCommandHandler.java b/identity/src/main/java/io/theurl/identity/application/handler/UserAccessFailureCountCommandHandler.java new file mode 100644 index 0000000..238dc43 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/handler/UserAccessFailureCountCommandHandler.java @@ -0,0 +1,27 @@ +package io.theurl.identity.application.handler; + +import com.neroyun.mediator.Handler; +import io.theurl.identity.application.command.UserAccessFailureCountCommand; +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 UserAccessFailureCountCommandHandler implements Handler { + + private final UserRepository repository; + + public UserAccessFailureCountCommandHandler(UserRepository repository) { + this.repository = repository; + } + + @Override + public CompletableFuture handleAsync(UserAccessFailureCountCommand message) { + return null; + } +} diff --git a/identity/src/main/java/io/theurl/identity/application/implement/UserApplicationServiceImpl.java b/identity/src/main/java/io/theurl/identity/application/implement/UserApplicationServiceImpl.java new file mode 100644 index 0000000..a8d2e16 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/implement/UserApplicationServiceImpl.java @@ -0,0 +1,16 @@ +package io.theurl.identity.application.implement; + +import io.theurl.framework.application.BaseApplicationService; +import io.theurl.identity.application.contract.UserApplicationService; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; +import org.springframework.web.context.annotation.RequestScope; + +@Service +@RequestScope +public class UserApplicationServiceImpl extends BaseApplicationService implements UserApplicationService { + + public UserApplicationServiceImpl(ApplicationContext applicationContext) { + super(applicationContext); + } +} diff --git a/identity/src/main/java/io/theurl/identity/application/subscriber/LoggingEventSubscriber.java b/identity/src/main/java/io/theurl/identity/application/subscriber/LoggingEventSubscriber.java new file mode 100644 index 0000000..cc150e6 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/subscriber/LoggingEventSubscriber.java @@ -0,0 +1,4 @@ +package io.theurl.identity.application.subscriber; + +public class LoggingEventSubscriber { +} diff --git a/identity/src/main/java/io/theurl/identity/application/subscriber/TokenEventSubscriber.java b/identity/src/main/java/io/theurl/identity/application/subscriber/TokenEventSubscriber.java new file mode 100644 index 0000000..0c34575 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/subscriber/TokenEventSubscriber.java @@ -0,0 +1,30 @@ +package io.theurl.identity.application.subscriber; + +import com.neroyun.mediator.Mediator; +import io.theurl.identity.application.command.TokenCreateCommand; +import io.theurl.identity.application.event.UserAuthSuccessEvent; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Component +public class TokenEventSubscriber { + private final Mediator mediator; + + public TokenEventSubscriber(Mediator mediator) { + this.mediator = mediator; + } + + @Async + @EventListener + public void handleUserAuthSucceedEvent(UserAuthSuccessEvent event) { + var command = new TokenCreateCommand() {{ + setJti(event.getData().get("jti")); + setContent(event.getData().get("content")); + setSubject(event.getUserId()); + }}; + + mediator.sendAsync(command) + .join(); + } +} diff --git a/identity/src/main/java/io/theurl/identity/application/subscriber/UserAccessFailureCountEventSubscriber.java b/identity/src/main/java/io/theurl/identity/application/subscriber/UserAccessFailureCountEventSubscriber.java new file mode 100644 index 0000000..6520c7c --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/application/subscriber/UserAccessFailureCountEventSubscriber.java @@ -0,0 +1,40 @@ +package io.theurl.identity.application.subscriber; + +import com.neroyun.mediator.Mediator; +import io.theurl.identity.application.command.UserAccessFailureCountCommand; +import io.theurl.identity.application.event.UserAuthFailureEvent; +import io.theurl.identity.application.event.UserAuthSuccessEvent; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Scope; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +@AllArgsConstructor +public class UserAccessFailureCountEventSubscriber { + + private final Mediator mediator; + + @Async + @EventListener + public void listen(UserAuthFailureEvent event) { + if (event.getUserId() == null || event.getUserId() <= 0) { + return; + } + + mediator.sendAsync(new UserAccessFailureCountCommand(event.getUserId(), "increase")) + .join(); + } + + @Async + @EventListener + public void listen(UserAuthSuccessEvent event) { + if (event.getUserId() == null || event.getUserId() <= 0) { + return; + } + mediator.sendAsync(new UserAccessFailureCountCommand(event.getUserId(), "reset")) + .join(); + } +} diff --git a/identity/src/main/java/io/theurl/identity/configure/ModelMapperConfiguration.java b/identity/src/main/java/io/theurl/identity/configure/ModelMapperConfiguration.java new file mode 100644 index 0000000..e20e14f --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/configure/ModelMapperConfiguration.java @@ -0,0 +1,24 @@ +package io.theurl.identity.configure; + +import org.modelmapper.ModelMapper; +import org.modelmapper.config.Configuration.AccessLevel; +import org.modelmapper.convention.MatchingStrategies; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ModelMapperConfiguration { + @Bean + public ModelMapper modelMapper() { + ModelMapper mapper = new ModelMapper(); + + mapper.getConfiguration() + .setMatchingStrategy(MatchingStrategies.STRICT) + .setFieldMatchingEnabled(true) + .setFieldAccessLevel(AccessLevel.PRIVATE) + .setCollectionsMergeEnabled(true) + .setSkipNullEnabled(true); + + return mapper; + } +} diff --git a/identity/src/main/java/io/theurl/identity/domain/aggregate/Token.java b/identity/src/main/java/io/theurl/identity/domain/aggregate/Token.java new file mode 100644 index 0000000..4ff26d4 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/domain/aggregate/Token.java @@ -0,0 +1,51 @@ +package io.theurl.identity.domain.aggregate; + +import io.theurl.framework.domain.AggregateRoot; +import io.theurl.framework.utility.SnowflakeId; + +import java.time.LocalDateTime; + +@SuppressWarnings({"LombokGetterMayBeUsed"}) +public class Token extends AggregateRoot { + private String jti; + private String content; + private Long subject; + private LocalDateTime issuedAt; + private LocalDateTime expiresAt; + + /** + * Initializes the aggregate with the given id. + * + * @param id the identifier of the aggregate + */ + public Token(Long id, String jti, String content, Long subject) { + super(id); + } + + public LocalDateTime getIssuedAt() { + return issuedAt; + } + + public LocalDateTime getExpiresAt() { + return expiresAt; + } + + public void setIssuedAt(LocalDateTime issuedAt) { + if (issuedAt.isBefore(LocalDateTime.now())) { + throw new IllegalArgumentException("issuedAt must be in the future"); + } + this.issuedAt = issuedAt; + } + + public void setExpiresAt(LocalDateTime expiresAt) { + if (expiresAt.isBefore(LocalDateTime.now())) { + throw new IllegalArgumentException("expiresAt must be in the future"); + } + this.expiresAt = expiresAt; + } + + public static Token create(String jti, String content, Long subject) { + var id = SnowflakeId.getInstance().nextId(); + return new Token(id, jti, content, subject); + } +} diff --git a/identity/src/main/java/io/theurl/identity/domain/aggregate/User.java b/identity/src/main/java/io/theurl/identity/domain/aggregate/User.java new file mode 100644 index 0000000..4d44339 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/domain/aggregate/User.java @@ -0,0 +1,14 @@ +package io.theurl.identity.domain.aggregate; + +import io.theurl.framework.domain.AggregateRoot; + +public class User extends AggregateRoot { + /** + * Initializes the aggregate with the given id. + * + * @param id the identifier of the aggregate + */ + public User(Long id) { + super(id); + } +} diff --git a/identity/src/main/java/io/theurl/identity/domain/repository/TokenRepository.java b/identity/src/main/java/io/theurl/identity/domain/repository/TokenRepository.java new file mode 100644 index 0000000..7a6579a --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/domain/repository/TokenRepository.java @@ -0,0 +1,11 @@ +package io.theurl.identity.domain.repository; + +import io.theurl.identity.domain.aggregate.Token; + +public interface TokenRepository { + void save(Token token); + + Token findById(Long id); + + Token findByJti(String jti); +} diff --git a/identity/src/main/java/io/theurl/identity/domain/repository/UserRepository.java b/identity/src/main/java/io/theurl/identity/domain/repository/UserRepository.java new file mode 100644 index 0000000..68a8432 --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/domain/repository/UserRepository.java @@ -0,0 +1,17 @@ +package io.theurl.identity.domain.repository; + +import io.theurl.identity.domain.aggregate.User; + +public interface UserRepository { + void save(User user); + + User findById(Long id); + + User findByUsername(String username); + + User findByEmail(String email); + + User findByPhone(String phone); + + User findByAnyOf(String username, String email, String phone); +} diff --git a/identity/src/main/java/io/theurl/identity/persistence/entity/Token.java b/identity/src/main/java/io/theurl/identity/persistence/entity/Token.java index 4480d7a..c3687b3 100644 --- a/identity/src/main/java/io/theurl/identity/persistence/entity/Token.java +++ b/identity/src/main/java/io/theurl/identity/persistence/entity/Token.java @@ -16,11 +16,11 @@ public class Token implements Persistable { @Id private Long id; - @Column(length = 32) - private String type; + @Column(length = 36) + private String jti; - @Column(length = 256) - private String key; + @Column(length = 1000) + private String content; @Column private Long subject; diff --git a/identity/src/main/java/io/theurl/identity/persistence/handler/OnetimePasswordDetailQueryHandler.java b/identity/src/main/java/io/theurl/identity/persistence/handler/OnetimePasswordDetailQueryHandler.java index 6685c79..f77bbb7 100644 --- a/identity/src/main/java/io/theurl/identity/persistence/handler/OnetimePasswordDetailQueryHandler.java +++ b/identity/src/main/java/io/theurl/identity/persistence/handler/OnetimePasswordDetailQueryHandler.java @@ -4,6 +4,7 @@ import io.theurl.identity.persistence.model.OnetimePasswordDetail; import io.theurl.identity.persistence.query.OnetimePasswordDetailQuery; import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.scheduling.annotation.Async; @@ -16,11 +17,8 @@ @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public class OnetimePasswordDetailQueryHandler implements Handler { - private final EntityManager context; - - public OnetimePasswordDetailQueryHandler(EntityManager context) { - this.context = context; - } + @PersistenceContext + private EntityManager context; @Override @Async diff --git a/identity/src/main/java/io/theurl/identity/persistence/handler/UserAuthInfoQueryHandler.java b/identity/src/main/java/io/theurl/identity/persistence/handler/UserAuthInfoQueryHandler.java index 51abaad..6a4ff49 100644 --- a/identity/src/main/java/io/theurl/identity/persistence/handler/UserAuthInfoQueryHandler.java +++ b/identity/src/main/java/io/theurl/identity/persistence/handler/UserAuthInfoQueryHandler.java @@ -8,6 +8,7 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.NoResultException; +import jakarta.persistence.PersistenceContext; import org.jspecify.annotations.NonNull; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; @@ -24,11 +25,8 @@ @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public class UserAuthInfoQueryHandler implements Handler { - private final EntityManager entityManager; - - public UserAuthInfoQueryHandler(EntityManager entityManager) { - this.entityManager = entityManager; - } + @PersistenceContext + private EntityManager context; /** * Handles the given query and returns the corresponding UserAuthInfo. @@ -42,7 +40,7 @@ public CompletableFuture handleAsync(UserAuthInfoQuery query) { return supplyAsync(() -> { String sql = getSql(query); - var typedQuery = entityManager.createQuery(sql, User.class); + var typedQuery = context.createQuery(sql, User.class); switch (query.provider()) { case "id" -> typedQuery.setParameter("id", Long.parseLong(query.name())); @@ -67,7 +65,7 @@ public CompletableFuture handleAsync(UserAuthInfoQuery query) { return null; } - List roles = entityManager.createQuery("SELECT u from UserRole u where u.userId = :userId", UserRole.class).setParameter("userId", user.getId()).getResultList(); + List roles = context.createQuery("SELECT u from UserRole u where u.userId = :userId", UserRole.class).setParameter("userId", user.getId()).getResultList(); return new UserAuthInfo() {{ setId(user.getId()); diff --git a/identity/src/main/java/io/theurl/identity/persistence/repository/JpaTokenRepository.java b/identity/src/main/java/io/theurl/identity/persistence/repository/JpaTokenRepository.java new file mode 100644 index 0000000..61d4a1f --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/persistence/repository/JpaTokenRepository.java @@ -0,0 +1,12 @@ +package io.theurl.identity.persistence.repository; + +import io.theurl.identity.persistence.entity.Token; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface JpaTokenRepository extends JpaRepository { + Optional findByJti(String jti); +} diff --git a/identity/src/main/java/io/theurl/identity/persistence/repository/TokenRepositoryImpl.java b/identity/src/main/java/io/theurl/identity/persistence/repository/TokenRepositoryImpl.java new file mode 100644 index 0000000..0c87cbe --- /dev/null +++ b/identity/src/main/java/io/theurl/identity/persistence/repository/TokenRepositoryImpl.java @@ -0,0 +1,35 @@ +package io.theurl.identity.persistence.repository; + +import io.theurl.identity.domain.repository.TokenRepository; +import io.theurl.identity.persistence.entity.Token; +import lombok.AllArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Repository; + +@Repository +@AllArgsConstructor +public class TokenRepositoryImpl implements TokenRepository { + + private final JpaTokenRepository repository; + private final ModelMapper mapper; + + @Override + public void save(io.theurl.identity.domain.aggregate.Token token) { + Token tokenEntity = mapper.map(token, Token.class); + repository.save(tokenEntity); + } + + @Override + public io.theurl.identity.domain.aggregate.Token findById(Long id) { + return repository.findById(id) + .map(tokenEntity -> mapper.map(tokenEntity, io.theurl.identity.domain.aggregate.Token.class)) + .orElse(null); + } + + @Override + public io.theurl.identity.domain.aggregate.Token findByJti(String jti) { + return repository.findByJti(jti) + .map(tokenEntity -> mapper.map(tokenEntity, io.theurl.identity.domain.aggregate.Token.class)) + .orElse(null); + } +} diff --git a/identity/src/main/resources/application.yaml b/identity/src/main/resources/application.yaml index 00d39a6..46bae52 100644 --- a/identity/src/main/resources/application.yaml +++ b/identity/src/main/resources/application.yaml @@ -24,6 +24,7 @@ spring: show-sql: true properties: hibernate: + multiTenancy: SCHEMA format_sql: true data: redis: diff --git a/identity/src/main/resources/logback-spring.xml b/identity/src/main/resources/logback-spring.xml index 4e7ae83..fa04d6c 100644 --- a/identity/src/main/resources/logback-spring.xml +++ b/identity/src/main/resources/logback-spring.xml @@ -10,19 +10,19 @@ - ${LOG_PATH}/info/current.log + ${LOG_PATH}/identity/info/current.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - ${LOG_PATH}/info/%d{yyyy-MM-dd}.log + ${LOG_PATH}/identity/info/%d{yyyy-MM-dd}.log 100MB 30 - ${LOG_PATH}/error/current.log + ${LOG_PATH}/identity/error/current.log ERROR ACCEPT @@ -32,14 +32,14 @@ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - ${LOG_PATH}/error/%d{yyyy-MM-dd}.log + ${LOG_PATH}/identity/error/%d{yyyy-MM-dd}.log 100MB 30 - ${LOG_PATH}/warn/current.log + ${LOG_PATH}/identity/warn/current.log WARN ACCEPT @@ -49,14 +49,14 @@ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - ${LOG_PATH}/warn/%d{yyyy-MM-dd}.log + ${LOG_PATH}/identity/warn/%d{yyyy-MM-dd}.log 100MB 30 - ${LOG_PATH}/debug/current.log + ${LOG_PATH}/identity/debug/current.log DEBUG ACCEPT @@ -66,14 +66,14 @@ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - ${LOG_PATH}/debug/%d{yyyy-MM-dd}.log + ${LOG_PATH}/identity/debug/%d{yyyy-MM-dd}.log 100MB 30 - ${LOG_PATH}/sql/current.log + ${LOG_PATH}/identity/sql/current.log DEBUG ACCEPT @@ -83,7 +83,7 @@ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - ${LOG_PATH}/sql/%d{yyyy-MM-dd}.log + ${LOG_PATH}/identity/sql/%d{yyyy-MM-dd}.log 100MB 30 diff --git a/message/src/main/java/io/theurl/message/MessageApplication.java b/message/src/main/java/io/theurl/message/MessageApplication.java index 151e252..76917d8 100644 --- a/message/src/main/java/io/theurl/message/MessageApplication.java +++ b/message/src/main/java/io/theurl/message/MessageApplication.java @@ -2,10 +2,16 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScans; import org.springframework.scheduling.annotation.EnableAsync; @EnableAsync @SpringBootApplication +@ComponentScans({ + @ComponentScan("io.theurl.framework"), + @ComponentScan("io.theurl.message") +}) public class MessageApplication { public static void main(String[] args) { diff --git a/message/src/main/java/io/theurl/message/configure/MediatorConfiguration.java b/message/src/main/java/io/theurl/message/configure/MediatorConfiguration.java deleted file mode 100644 index 0365789..0000000 --- a/message/src/main/java/io/theurl/message/configure/MediatorConfiguration.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.theurl.message.configure; - -import com.neroyun.mediator.*; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.concurrent.Executors; - -@Configuration -public class MediatorConfiguration { - @Bean - public Mediator mediator(ObjectProvider handlers, ObjectProvider middlewares, ObjectProvider validators) { - return new PipelinedMediator() - .use(() -> handlers.stream()) - .use(() -> middlewares.stream()) - .use(() -> validators.stream()) - .use(Executors::newVirtualThreadPerTaskExecutor); - } - -} diff --git a/pom.xml b/pom.xml index f5e24e6..39780c0 100644 --- a/pom.xml +++ b/pom.xml @@ -50,8 +50,9 @@ 2025.1.0.0 4.0.6 true - 1.1.1 + 1.1.2 3.2.0 + 3.2.6 @@ -111,6 +112,13 @@ pom import + + org.modelmapper + modelmapper + ${modelmapper.version} + pom + import +