diff --git a/api-server/src/main/java/kusitms/duduk/apiserver/user/event/LoginEventListener.java b/api-server/src/main/java/kusitms/duduk/apiserver/user/event/LoginEventListener.java new file mode 100644 index 0000000..a9e0940 --- /dev/null +++ b/api-server/src/main/java/kusitms/duduk/apiserver/user/event/LoginEventListener.java @@ -0,0 +1,19 @@ +package kusitms.duduk.apiserver.user.event; + +import kusitms.duduk.application.user.event.LoginUserEvent; +import kusitms.duduk.core.user.port.input.AttendUserUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class LoginEventListener { + + private final AttendUserUseCase attendUserUseCase; + + @EventListener + public void attendUser(LoginUserEvent event) { + attendUserUseCase.attend(event.getEmail()); + } +} diff --git a/api-server/src/test/java/kusitms/duduk/apiserver/user/event/LoginEventListenerTest.java b/api-server/src/test/java/kusitms/duduk/apiserver/user/event/LoginEventListenerTest.java new file mode 100644 index 0000000..a019b1f --- /dev/null +++ b/api-server/src/test/java/kusitms/duduk/apiserver/user/event/LoginEventListenerTest.java @@ -0,0 +1,46 @@ +package kusitms.duduk.apiserver.user.event; + +import static org.codehaus.groovy.runtime.DefaultGroovyMethods.any; + +import kusitms.duduk.application.attendence.persistence.AttendantRepository; +import kusitms.duduk.application.attendence.persistence.entity.AttendantJpaEntity; +import kusitms.duduk.application.user.event.LoginUserEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.ApplicationEventPublisher; + +@SpringBootTest +public class LoginEventListenerTest { + + @MockBean + private LoginEventListener loginEventListener; + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + @BeforeEach + public void setup() { + MockitoAnnotations.initMocks(this); + // Ensure correct initialization + } + + @Test + public void 로그인_이벤트가_발생하면_리스너가_호출된다() { + // given + // Event 인스턴스를 생성한다 + LoginUserEvent event = new LoginUserEvent(this, "test@test,com"); + + // when + // 이벤트를 발행한다 + applicationEventPublisher.publishEvent(event); + + // then + // verify 메소드를 활요하여 리스너의 attendUser 메소드가 호출되었는지 확인한다 + Mockito.verify(loginEventListener).attendUser(event); + } +} \ No newline at end of file diff --git a/application/src/main/java/kusitms/duduk/application/attendence/persistence/AttendantPersistenceAdapter.java b/application/src/main/java/kusitms/duduk/application/attendence/persistence/AttendantPersistenceAdapter.java new file mode 100644 index 0000000..29ae154 --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendence/persistence/AttendantPersistenceAdapter.java @@ -0,0 +1,30 @@ +package kusitms.duduk.application.attendence.persistence; + +import java.time.LocalDate; +import kusitms.duduk.application.attendence.persistence.entity.AttendantJpaEntity; +import kusitms.duduk.core.annotation.Adapter; +import kusitms.duduk.core.attendant.port.output.LoadAttendantPort; +import kusitms.duduk.core.attendant.port.output.SaveAttendantPort; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +@Adapter +public class AttendantPersistenceAdapter implements SaveAttendantPort, LoadAttendantPort { + + private final AttendantRepository attendantRepository; + + @Override + public void save(LocalDate today, String email) { + attendantRepository.save(AttendantJpaEntity.builder() + .date(today) + .email(email) + .build()); + } + + @Override + public boolean isAttendedToday(String email) { + return attendantRepository.existsByEmailAndDate(email, LocalDate.now()); + } +} diff --git a/application/src/main/java/kusitms/duduk/application/attendence/persistence/AttendantRepository.java b/application/src/main/java/kusitms/duduk/application/attendence/persistence/AttendantRepository.java new file mode 100644 index 0000000..0c8a7a1 --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendence/persistence/AttendantRepository.java @@ -0,0 +1,10 @@ +package kusitms.duduk.application.attendence.persistence; + +import java.time.LocalDate; +import kusitms.duduk.application.attendence.persistence.entity.AttendantJpaEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AttendantRepository extends JpaRepository { + + boolean existsByEmailAndDate(String email, LocalDate date); +} diff --git a/application/src/main/java/kusitms/duduk/application/attendence/persistence/entity/AttendantJpaEntity.java b/application/src/main/java/kusitms/duduk/application/attendence/persistence/entity/AttendantJpaEntity.java new file mode 100644 index 0000000..4a4638f --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendence/persistence/entity/AttendantJpaEntity.java @@ -0,0 +1,31 @@ +package kusitms.duduk.application.attendence.persistence.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Entity +@Table(name = "attendences", uniqueConstraints = { + @UniqueConstraint(columnNames = {"date", "email"}) +}) +@Builder(toBuilder = true) +public class AttendantJpaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "attendence_id") + private Long id; + private LocalDate date; + private String email; +} diff --git a/application/src/main/java/kusitms/duduk/application/attendence/service/AttendUserCommand.java b/application/src/main/java/kusitms/duduk/application/attendence/service/AttendUserCommand.java new file mode 100644 index 0000000..47b007d --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendence/service/AttendUserCommand.java @@ -0,0 +1,26 @@ +package kusitms.duduk.application.attendence.service; + +import java.time.LocalDate; +import kusitms.duduk.core.attendant.port.output.LoadAttendantPort; +import kusitms.duduk.core.attendant.port.output.SaveAttendantPort; +import kusitms.duduk.core.user.port.input.AttendUserUseCase; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class AttendUserCommand implements AttendUserUseCase { + + private final SaveAttendantPort saveAttendantPort; + private final LoadAttendantPort loadAttendantPort; + + @Override + public void attend(String email) { + if (!loadAttendantPort.isAttendedToday(email)) { + log.info("attend user: {}", email); + saveAttendantPort.save(LocalDate.now(), email); + } + } +} diff --git a/application/src/main/java/kusitms/duduk/application/security/service/LoginOAuthCommand.java b/application/src/main/java/kusitms/duduk/application/security/service/LoginOAuthCommand.java index 4d209e2..6cf30e9 100644 --- a/application/src/main/java/kusitms/duduk/application/security/service/LoginOAuthCommand.java +++ b/application/src/main/java/kusitms/duduk/application/security/service/LoginOAuthCommand.java @@ -2,6 +2,7 @@ import java.util.Map; +import kusitms.duduk.application.user.event.LoginUserEvent; import kusitms.duduk.core.security.dto.response.JwtTokenResponse; import kusitms.duduk.core.security.dto.response.OAuthDetailResponse; import kusitms.duduk.core.security.dto.response.OAuthLoginResponse; @@ -11,6 +12,7 @@ import kusitms.duduk.core.user.port.input.RetrieveUserQuery; import kusitms.duduk.core.user.port.input.UpdateUserUseCase; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @RequiredArgsConstructor @@ -20,14 +22,15 @@ public class LoginOAuthCommand implements LoginOAuthUseCase { private final RetrieveUserQuery retrieveUserQuery; private final UpdateUserUseCase updateUserUseCase; private final JwtTokenProvider jwtTokenProvider; + private final ApplicationEventPublisher applicationEventPublisher; private final Map oAuthClientPortMap; public OAuthLoginResponse process(Provider provider, String accessToken) { - OAuthDetailResponse response = oAuthClientPortMap.get(provider).retrieveOAuthDetail(accessToken); + OAuthDetailResponse response = oAuthClientPortMap.get(provider) + .retrieveOAuthDetail(accessToken); JwtTokenResponse jwtTokenInfo = jwtTokenProvider.createTokenInfo(response.email()); - boolean isRegistered = retrieveUserQuery.isUserRegisteredByEmail(response.email()); - updateRefreshTokenIfRegistered(isRegistered, response.email(), jwtTokenInfo.refreshToken()); + boolean isRegistered = isRegistered(response, jwtTokenInfo); return new OAuthLoginResponse( jwtTokenInfo.accessToken(), @@ -36,6 +39,16 @@ public OAuthLoginResponse process(Provider provider, String accessToken) { isRegistered); } + private boolean isRegistered(OAuthDetailResponse response, JwtTokenResponse jwtTokenInfo) { + boolean isRegistered = retrieveUserQuery.isUserRegisteredByEmail(response.email()); + updateRefreshTokenIfRegistered(isRegistered, response.email(), jwtTokenInfo.refreshToken()); + + if (isRegistered) { + applicationEventPublisher.publishEvent(new LoginUserEvent(this, response.email())); + } + return isRegistered; + } + private void updateRefreshTokenIfRegistered(boolean isRegistered, String email, String refreshToken) { if (isRegistered) { diff --git a/application/src/main/java/kusitms/duduk/application/user/event/LoginUserEvent.java b/application/src/main/java/kusitms/duduk/application/user/event/LoginUserEvent.java new file mode 100644 index 0000000..d471133 --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/user/event/LoginUserEvent.java @@ -0,0 +1,15 @@ +package kusitms.duduk.application.user.event; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class LoginUserEvent extends ApplicationEvent { + + private final String email; + + public LoginUserEvent(Object source, String email) { + super(source); + this.email = email; + } +} diff --git a/core/src/main/java/kusitms/duduk/core/attendant/port/output/LoadAttendantPort.java b/core/src/main/java/kusitms/duduk/core/attendant/port/output/LoadAttendantPort.java new file mode 100644 index 0000000..59e8673 --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/attendant/port/output/LoadAttendantPort.java @@ -0,0 +1,9 @@ +package kusitms.duduk.core.attendant.port.output; + +import java.time.LocalDate; + +public interface LoadAttendantPort { + boolean isAttendedToday(String email); + + // todo : 한 주에 몇번 출석 체크 했는지도 검증 가능 +} diff --git a/core/src/main/java/kusitms/duduk/core/attendant/port/output/SaveAttendantPort.java b/core/src/main/java/kusitms/duduk/core/attendant/port/output/SaveAttendantPort.java new file mode 100644 index 0000000..d6dde5f --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/attendant/port/output/SaveAttendantPort.java @@ -0,0 +1,7 @@ +package kusitms.duduk.core.attendant.port.output; + +import java.time.LocalDate; + +public interface SaveAttendantPort { + void save(LocalDate date, String email); +} diff --git a/core/src/main/java/kusitms/duduk/core/user/port/input/AttendUserUseCase.java b/core/src/main/java/kusitms/duduk/core/user/port/input/AttendUserUseCase.java new file mode 100644 index 0000000..f396e2d --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/user/port/input/AttendUserUseCase.java @@ -0,0 +1,6 @@ +package kusitms.duduk.core.user.port.input; + +public interface AttendUserUseCase { + + void attend(String email); +}