diff --git a/api-server/src/main/java/kusitms/duduk/apiserver/archive/presentation/ArchiveController.java b/api-server/src/main/java/kusitms/duduk/apiserver/archive/presentation/ArchiveController.java new file mode 100644 index 0000000..04dfab3 --- /dev/null +++ b/api-server/src/main/java/kusitms/duduk/apiserver/archive/presentation/ArchiveController.java @@ -0,0 +1,40 @@ +package kusitms.duduk.apiserver.archive.presentation; + +import kusitms.duduk.apiserver.security.infrastructure.CustomUserDetails; +import kusitms.duduk.core.archive.dto.response.ArchiveResponse; +import kusitms.duduk.core.archive.port.input.RetrieveArchiveUseCase; +import kusitms.duduk.domain.global.Category; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/archies") +public class ArchiveController implements ArchiveControllerDocs { + + private final RetrieveArchiveUseCase retrieveArchiveUseCase; + + @GetMapping("/newsletters/{category}") + public ResponseEntity retrieveArchivedNewsLetters( + @AuthenticationPrincipal final CustomUserDetails customUserDetails, + @PathVariable(name = "category") String category) { + + return new ResponseEntity<>( + retrieveArchiveUseCase.retrieveNewsLetters(customUserDetails.getEmail(), + Category.from(category)), HttpStatus.OK); + } + + @GetMapping("/terms") + public ResponseEntity retrieveArchivedTerms( + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + return new ResponseEntity<>( + retrieveArchiveUseCase.retrieveTerms(customUserDetails.getEmail()), + HttpStatus.OK); + } +} diff --git a/api-server/src/main/java/kusitms/duduk/apiserver/archive/presentation/ArchiveControllerDocs.java b/api-server/src/main/java/kusitms/duduk/apiserver/archive/presentation/ArchiveControllerDocs.java new file mode 100644 index 0000000..6f05a44 --- /dev/null +++ b/api-server/src/main/java/kusitms/duduk/apiserver/archive/presentation/ArchiveControllerDocs.java @@ -0,0 +1,42 @@ +package kusitms.duduk.apiserver.archive.presentation; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import kusitms.duduk.apiserver.security.infrastructure.CustomUserDetails; +import kusitms.duduk.common.exception.ErrorResponse; +import kusitms.duduk.core.archive.dto.response.ArchiveResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; + +@Tag(name = "Archive", description = "아카이브 API") +public interface ArchiveControllerDocs { + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "BAD REQUEST", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}) + }) + @Operation(summary = "아카이브 뉴스레터 조회", description = "아카이브에 저장되어 있는 뉴스레터를 조회합니다.") + ResponseEntity retrieveArchivedNewsLetters( + @AuthenticationPrincipal final CustomUserDetails customUserDetails, + @PathVariable final String category + ); + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "BAD REQUEST", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}) + }) + @Operation(summary = "아카이브 단어 조회", description = "아카이브에 저장되어 있는 단어를 조회합니다.") + ResponseEntity retrieveArchivedTerms( + @AuthenticationPrincipal final CustomUserDetails customUserDetails); +} diff --git a/api-server/src/main/java/kusitms/duduk/apiserver/healthcheck/presentation/HealthCheckControllerDocs.java b/api-server/src/main/java/kusitms/duduk/apiserver/healthcheck/presentation/HealthCheckControllerDocs.java index db903f8..2dd16f5 100644 --- a/api-server/src/main/java/kusitms/duduk/apiserver/healthcheck/presentation/HealthCheckControllerDocs.java +++ b/api-server/src/main/java/kusitms/duduk/apiserver/healthcheck/presentation/HealthCheckControllerDocs.java @@ -5,10 +5,12 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import kusitms.duduk.common.exception.ErrorResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +@Tag(name = "HealthCheck", description = "헬스체크 API") public interface HealthCheckControllerDocs { @GetMapping("/api/healthcheck") diff --git a/api-server/src/main/java/kusitms/duduk/apiserver/newsletter/presentation/NewsLetterControllerDocs.java b/api-server/src/main/java/kusitms/duduk/apiserver/newsletter/presentation/NewsLetterControllerDocs.java index e632f2f..7fdfeb8 100644 --- a/api-server/src/main/java/kusitms/duduk/apiserver/newsletter/presentation/NewsLetterControllerDocs.java +++ b/api-server/src/main/java/kusitms/duduk/apiserver/newsletter/presentation/NewsLetterControllerDocs.java @@ -1,5 +1,8 @@ package kusitms.duduk.apiserver.newsletter.presentation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "NewsLetter", description = "뉴스레터 API") public interface NewsLetterControllerDocs { } diff --git a/api-server/src/main/java/kusitms/duduk/apiserver/user/presentation/UserController.java b/api-server/src/main/java/kusitms/duduk/apiserver/user/presentation/UserController.java index d0df951..ef5ab03 100644 --- a/api-server/src/main/java/kusitms/duduk/apiserver/user/presentation/UserController.java +++ b/api-server/src/main/java/kusitms/duduk/apiserver/user/presentation/UserController.java @@ -3,12 +3,17 @@ import java.time.LocalDateTime; import java.util.List; import kusitms.duduk.apiserver.security.infrastructure.CustomUserDetails; +import kusitms.duduk.core.newsletter.dto.response.ArchiveNewsLetterResponse; import kusitms.duduk.core.newsletter.dto.response.NewsLetterThumbnailResponse; +import kusitms.duduk.core.newsletter.port.input.ArchiveNewsLetterUseCase; +import kusitms.duduk.core.term.dto.response.ArchiveTermResponse; import kusitms.duduk.core.term.dto.response.RetrieveTermResponse; +import kusitms.duduk.core.term.port.input.ArchiveTermUseCase; import kusitms.duduk.core.user.dto.request.CreateUserRequest; import kusitms.duduk.core.user.dto.request.ValidateUserEmailRequest; import kusitms.duduk.core.user.dto.request.ValidateUserNicknameRequest; import kusitms.duduk.core.user.dto.response.RetrieveHomeResponse; +import kusitms.duduk.core.user.dto.response.RetrieveMypageResponse; import kusitms.duduk.core.user.dto.response.UserResponse; import kusitms.duduk.core.user.port.input.RegisterUserUseCase; import kusitms.duduk.core.user.port.input.RetrieveUserQuery; @@ -18,6 +23,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -31,6 +37,8 @@ public class UserController implements UserControllerDocs { private final RegisterUserUseCase registerUserUseCase; private final RetrieveUserQuery retrieveUserQuery; private final ValidateDuplicatedUserQuery validateDuplicatedUserQuery; + private final ArchiveNewsLetterUseCase archiveNewsLetterUseCase; + private final ArchiveTermUseCase archiveTermUseCase; @PostMapping public ResponseEntity register(@RequestBody CreateUserRequest createUserRequest) { @@ -54,80 +62,83 @@ public ResponseEntity validateNickname(@RequestBody ValidateUserNicknameRe public ResponseEntity home(CustomUserDetails customUserDetails) { RetrieveHomeResponse response = RetrieveHomeResponse.builder() .todayNewsLetter(NewsLetterThumbnailResponse.builder() - .id(1L) - .title("오늘의 주목할 뉴스") - .type("editor") - .keywords("테슬라, 주가 변동, 시장 분석") - .thumbnail("https://image.shutterstock.com/image-photo/latest-stock-market-charts.jpg") - .isScrapped(false) - .createdAt(LocalDateTime.now()) - .build()) + .id(1L) + .title("오늘의 주목할 뉴스") + .type("editor") + .keywords("테슬라, 주가 변동, 시장 분석") + .thumbnail( + "https://image.shutterstock.com/image-photo/latest-stock-market-charts.jpg") + .isScrapped(false) + .createdAt(LocalDateTime.now()) + .build()) .realtimeTrendNewsLetters(List.of( - NewsLetterThumbnailResponse.builder() - .id(2L) - .title("기술 섹터 동향") - .type("analysis") - .keywords("애플, 삼성, 최신 기술") - .thumbnail("https://image.shutterstock.com/image-photo/technology-trends.jpg") - .isScrapped(true) - .createdAt(LocalDateTime.now().minusHours(3)) - .build(), - NewsLetterThumbnailResponse.builder() - .id(3L) - .title("유럽 시장 개요") - .type("global") - .keywords("유로존, 경제 성장") - .thumbnail("https://image.shutterstock.com/image-photo/european-markets.jpg") - .isScrapped(false) - .createdAt(LocalDateTime.now().minusHours(2)) - .build(), - NewsLetterThumbnailResponse.builder() - .id(4L) - .title("투자 조언: 금융 포트폴리오 관리") - .type("financial") - .keywords("투자, 자산 관리") - .thumbnail("https://image.shutterstock.com/image-photo/financial-management.jpg") - .isScrapped(true) - .createdAt(LocalDateTime.now().minusHours(1)) - .build() + NewsLetterThumbnailResponse.builder() + .id(2L) + .title("기술 섹터 동향") + .type("analysis") + .keywords("애플, 삼성, 최신 기술") + .thumbnail("https://image.shutterstock.com/image-photo/technology-trends.jpg") + .isScrapped(true) + .createdAt(LocalDateTime.now().minusHours(3)) + .build(), + NewsLetterThumbnailResponse.builder() + .id(3L) + .title("유럽 시장 개요") + .type("global") + .keywords("유로존, 경제 성장") + .thumbnail("https://image.shutterstock.com/image-photo/european-markets.jpg") + .isScrapped(false) + .createdAt(LocalDateTime.now().minusHours(2)) + .build(), + NewsLetterThumbnailResponse.builder() + .id(4L) + .title("투자 조언: 금융 포트폴리오 관리") + .type("financial") + .keywords("투자, 자산 관리") + .thumbnail( + "https://image.shutterstock.com/image-photo/financial-management.jpg") + .isScrapped(true) + .createdAt(LocalDateTime.now().minusHours(1)) + .build() )) .customizeNewsLetters(List.of( - NewsLetterThumbnailResponse.builder() - .id(5L) - .title("지속 가능한 투자 동향") - .type("sustainable") - .keywords("그린 에너지, 친환경 정책") - .thumbnail("https://image.shutterstock.com/image-photo/sustainable-investments.jpg") - .isScrapped(false) - .createdAt(LocalDateTime.now().minusDays(1)) - .build(), - NewsLetterThumbnailResponse.builder() - .id(6L) - .title("신흥 시장 뉴스 업데이트") - .type("emerging") - .keywords("신흥 시장, 경제 발전") - .thumbnail("https://image.shutterstock.com/image-photo/emerging-markets.jpg") - .isScrapped(true) - .createdAt(LocalDateTime.now().minusDays(1).minusHours(2)) - .build(), - NewsLetterThumbnailResponse.builder() - .id(7L) - .title("실리콘 밸리 최신 소식") - .type("tech") - .keywords("스타트업, 혁신, 기술") - .thumbnail("https://image.shutterstock.com/image-photo/silicon-valley.jpg") - .isScrapped(false) - .createdAt(LocalDateTime.now().minusDays(1).minusHours(4)) - .build() + NewsLetterThumbnailResponse.builder() + .id(5L) + .title("지속 가능한 투자 동향") + .type("sustainable") + .keywords("그린 에너지, 친환경 정책") + .thumbnail( + "https://image.shutterstock.com/image-photo/sustainable-investments.jpg") + .isScrapped(false) + .createdAt(LocalDateTime.now().minusDays(1)) + .build(), + NewsLetterThumbnailResponse.builder() + .id(6L) + .title("신흥 시장 뉴스 업데이트") + .type("emerging") + .keywords("신흥 시장, 경제 발전") + .thumbnail("https://image.shutterstock.com/image-photo/emerging-markets.jpg") + .isScrapped(true) + .createdAt(LocalDateTime.now().minusDays(1).minusHours(2)) + .build(), + NewsLetterThumbnailResponse.builder() + .id(7L) + .title("실리콘 밸리 최신 소식") + .type("tech") + .keywords("스타트업, 혁신, 기술") + .thumbnail("https://image.shutterstock.com/image-photo/silicon-valley.jpg") + .isScrapped(false) + .createdAt(LocalDateTime.now().minusDays(1).minusHours(4)) + .build() )) .todayTerm(RetrieveTermResponse.builder() - .termId(1L) - .koreanName("지속 가능성") - .englishName("Sustainability") - .description("지속 가능성은 환경적, 경제적, 사회적 측면에서 장기적으로 견딜 수 있는 발전을 의미합니다.") - .category("ENVIRONMENT") - .isScrapped(true) - .build()) + .termId(1L) + .koreanName("지속 가능성") + .englishName("Sustainability") + .description("지속 가능성은 환경적, 경제적, 사회적 측면에서 장기적으로 견딜 수 있는 발전을 의미합니다.") + .category("ENVIRONMENT") + .isScrapped(true) + .build()) .build(); return new ResponseEntity<>(response, HttpStatus.OK); @@ -135,6 +146,29 @@ public ResponseEntity home(CustomUserDetails customUserDet // HttpStatus.OK); } + @GetMapping("/mypage") + public ResponseEntity mypage(CustomUserDetails customUserDetails) { + return new ResponseEntity<>(retrieveUserQuery.mypage(customUserDetails.getEmail()), + HttpStatus.OK); + } + + @GetMapping("/archive/{newsLetterId}") + public ResponseEntity archiveNewsLetter( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable(name = "newsLetterId") Long newsLetterId) { + return new ResponseEntity<>( + archiveNewsLetterUseCase.archive(customUserDetails.getEmail(), newsLetterId), + HttpStatus.OK); + } + + @GetMapping("/archive/{termId}") + public ResponseEntity archiveTerm( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable(name = "termId") Long termId) { + return new ResponseEntity<>( + archiveTermUseCase.archive(customUserDetails.getEmail(), termId), HttpStatus.OK); + } + @GetMapping("/test") public ResponseEntity test(@AuthenticationPrincipal CustomUserDetails customUserDetails) { diff --git a/api-server/src/main/java/kusitms/duduk/apiserver/user/presentation/UserControllerDocs.java b/api-server/src/main/java/kusitms/duduk/apiserver/user/presentation/UserControllerDocs.java index 48f4102..357ebdf 100644 --- a/api-server/src/main/java/kusitms/duduk/apiserver/user/presentation/UserControllerDocs.java +++ b/api-server/src/main/java/kusitms/duduk/apiserver/user/presentation/UserControllerDocs.java @@ -8,6 +8,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import kusitms.duduk.apiserver.security.infrastructure.CustomUserDetails; import kusitms.duduk.common.exception.ErrorResponse; +import kusitms.duduk.core.newsletter.dto.response.ArchiveNewsLetterResponse; +import kusitms.duduk.core.term.dto.response.ArchiveTermResponse; import kusitms.duduk.core.user.dto.request.CreateUserRequest; import kusitms.duduk.core.user.dto.request.ValidateUserEmailRequest; import kusitms.duduk.core.user.dto.request.ValidateUserNicknameRequest; @@ -15,6 +17,7 @@ import kusitms.duduk.core.user.dto.response.UserResponse; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @Tag(name = "User", description = "유저 API") @@ -75,4 +78,30 @@ ResponseEntity validateNickname( ResponseEntity home( @AuthenticationPrincipal final CustomUserDetails customUserDetails ); + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "BAD REQUEST", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}) + }) + @Operation(summary = "뉴스레터 아카이브", description = "뉴스레터를 아카이브에 저장합니다.") + ResponseEntity archiveNewsLetter( + @AuthenticationPrincipal final CustomUserDetails customUserDetails, + @PathVariable final Long newsLetterId + ); + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "BAD REQUEST", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), + @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}) + }) + @Operation(summary = "단어 아카이브", description = "단어를 아카이브에 저장합니다.") + ResponseEntity archiveTerm( + @AuthenticationPrincipal final CustomUserDetails customUserDetails, + @PathVariable final Long termId + ); } diff --git a/application/src/main/generated/kusitms/duduk/application/attendance/persistence/entity/QAttendanceJpaEntity.java b/application/src/main/generated/kusitms/duduk/application/attendance/persistence/entity/QAttendanceJpaEntity.java new file mode 100644 index 0000000..b250ba0 --- /dev/null +++ b/application/src/main/generated/kusitms/duduk/application/attendance/persistence/entity/QAttendanceJpaEntity.java @@ -0,0 +1,41 @@ +package kusitms.duduk.application.attendance.persistence.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QAttendanceJpaEntity is a Querydsl query type for AttendanceJpaEntity + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QAttendanceJpaEntity extends EntityPathBase { + + private static final long serialVersionUID = -17130809L; + + public static final QAttendanceJpaEntity attendanceJpaEntity = new QAttendanceJpaEntity("attendanceJpaEntity"); + + public final DatePath date = createDate("date", java.time.LocalDate.class); + + public final StringPath email = createString("email"); + + public final NumberPath id = createNumber("id", Long.class); + + public QAttendanceJpaEntity(String variable) { + super(AttendanceJpaEntity.class, forVariable(variable)); + } + + public QAttendanceJpaEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QAttendanceJpaEntity(PathMetadata metadata) { + super(AttendanceJpaEntity.class, metadata); + } + +} + diff --git a/application/src/main/generated/kusitms/duduk/application/attendant/persistence/entity/QAttendantJpaEntity.java b/application/src/main/generated/kusitms/duduk/application/attendant/persistence/entity/QAttendantJpaEntity.java deleted file mode 100644 index 548d0a6..0000000 --- a/application/src/main/generated/kusitms/duduk/application/attendant/persistence/entity/QAttendantJpaEntity.java +++ /dev/null @@ -1,41 +0,0 @@ -package kusitms.duduk.application.attendant.persistence.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QAttendantJpaEntity is a Querydsl query type for AttendantJpaEntity - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QAttendantJpaEntity extends EntityPathBase { - - private static final long serialVersionUID = 1188764693L; - - public static final QAttendantJpaEntity attendantJpaEntity = new QAttendantJpaEntity("attendantJpaEntity"); - - public final DatePath date = createDate("date", java.time.LocalDate.class); - - public final StringPath email = createString("email"); - - public final NumberPath id = createNumber("id", Long.class); - - public QAttendantJpaEntity(String variable) { - super(AttendantJpaEntity.class, forVariable(variable)); - } - - public QAttendantJpaEntity(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QAttendantJpaEntity(PathMetadata metadata) { - super(AttendantJpaEntity.class, metadata); - } - -} - diff --git a/application/src/main/java/kusitms/duduk/application/archive/service/RetrieveArchiveCommand.java b/application/src/main/java/kusitms/duduk/application/archive/service/RetrieveArchiveCommand.java new file mode 100644 index 0000000..bfb42fb --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/archive/service/RetrieveArchiveCommand.java @@ -0,0 +1,38 @@ +package kusitms.duduk.application.archive.service; + +import java.util.List; +import kusitms.duduk.common.exception.custom.NotExistsException; +import kusitms.duduk.core.archive.dto.response.ArchiveResponse; +import kusitms.duduk.core.archive.port.input.RetrieveArchiveUseCase; +import kusitms.duduk.core.user.port.output.LoadUserPort; +import kusitms.duduk.domain.global.Category; +import kusitms.duduk.domain.user.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class RetrieveArchiveCommand implements RetrieveArchiveUseCase { + + private final LoadUserPort loadUserPort; + + @Override + public ArchiveResponse retrieveNewsLetters(String email, Category category) { + User user = loadUserPort.findByEmail(email) + .orElseThrow(() -> new NotExistsException("User not found")); + + List newsLetterIds = user.getNewsLettersFromArchive(category); + return new ArchiveResponse(newsLetterIds); + } + + @Override + public ArchiveResponse retrieveTerms(String email) { + User user = loadUserPort.findByEmail(email) + .orElseThrow(() -> new NotExistsException("User not found")); + + List termIds = user.getTermsFromArchive(); + return new ArchiveResponse(termIds); + } +} diff --git a/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendancePersistenceAdapter.java b/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendancePersistenceAdapter.java new file mode 100644 index 0000000..6961aac --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendancePersistenceAdapter.java @@ -0,0 +1,38 @@ +package kusitms.duduk.application.attendance.persistence; + +import java.time.LocalDate; +import java.util.List; +import kusitms.duduk.application.attendance.persistence.entity.AttendanceJpaEntity; +import kusitms.duduk.common.annotation.Adapter; +import kusitms.duduk.core.attendance.port.output.LoadAttendancePort; +import kusitms.duduk.core.attendance.port.output.SaveAttendancePort; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +@Adapter +public class AttendancePersistenceAdapter implements SaveAttendancePort, LoadAttendancePort { + + private final AttendanceRepository attendantRepository; + private final AttendanceRepositoryCustom attendanceRepositoryCustom; + + @Override + public void save(LocalDate today, String email) { + attendantRepository.save(AttendanceJpaEntity.builder() + .date(today) + .email(email) + .build()); + } + + @Override + public boolean isAttendedToday(String email) { + return attendantRepository.existsByEmailAndDate(email, LocalDate.now()); + } + + @Override + public List findAttendantDatesByEmailAndDateBetween(String email, LocalDate startDate, + LocalDate endDate) { + return attendanceRepositoryCustom.findAttendanceBetweenStartDateAndEndDate(email, startDate, endDate); + } +} diff --git a/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepository.java b/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepository.java new file mode 100644 index 0000000..3a9c056 --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepository.java @@ -0,0 +1,10 @@ +package kusitms.duduk.application.attendance.persistence; + +import java.time.LocalDate; +import kusitms.duduk.application.attendance.persistence.entity.AttendanceJpaEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AttendanceRepository extends JpaRepository { + + boolean existsByEmailAndDate(String email, LocalDate date); +} diff --git a/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepositoryCustom.java b/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepositoryCustom.java new file mode 100644 index 0000000..156a568 --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepositoryCustom.java @@ -0,0 +1,9 @@ +package kusitms.duduk.application.attendance.persistence; + +import java.time.LocalDate; +import java.util.List; + +public interface AttendanceRepositoryCustom { + + List findAttendanceBetweenStartDateAndEndDate(String email, LocalDate startDate, LocalDate endDate); +} diff --git a/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepositoryImpl.java b/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepositoryImpl.java new file mode 100644 index 0000000..b36fbc3 --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendance/persistence/AttendanceRepositoryImpl.java @@ -0,0 +1,30 @@ +package kusitms.duduk.application.attendance.persistence; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import java.time.LocalDate; +import java.util.List; +import kusitms.duduk.application.attendance.persistence.entity.QAttendanceJpaEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class AttendanceRepositoryImpl implements AttendanceRepositoryCustom { + + private final EntityManager entityManager; + + @Override + public List findAttendanceBetweenStartDateAndEndDate(String email, LocalDate startDate, + LocalDate endDate) { + QAttendanceJpaEntity attendant = QAttendanceJpaEntity.attendanceJpaEntity; + + // QueryDSL로 쿼리 구성하여 LocalDate 리스트 반환 + return new JPAQueryFactory(entityManager) + .select(attendant.date) + .from(attendant) + .where(attendant.email.eq(email) + .and(attendant.date.between(startDate, endDate))) + .fetch(); + } +} diff --git a/application/src/main/java/kusitms/duduk/application/attendant/persistence/entity/AttendantJpaEntity.java b/application/src/main/java/kusitms/duduk/application/attendance/persistence/entity/AttendanceJpaEntity.java similarity index 79% rename from application/src/main/java/kusitms/duduk/application/attendant/persistence/entity/AttendantJpaEntity.java rename to application/src/main/java/kusitms/duduk/application/attendance/persistence/entity/AttendanceJpaEntity.java index b965b34..0c5e837 100644 --- a/application/src/main/java/kusitms/duduk/application/attendant/persistence/entity/AttendantJpaEntity.java +++ b/application/src/main/java/kusitms/duduk/application/attendance/persistence/entity/AttendanceJpaEntity.java @@ -1,4 +1,4 @@ -package kusitms.duduk.application.attendant.persistence.entity; +package kusitms.duduk.application.attendance.persistence.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -16,15 +16,15 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Entity -@Table(name = "attendants", uniqueConstraints = { +@Table(name = "attendances", uniqueConstraints = { @UniqueConstraint(columnNames = {"date", "email"}) }) @Builder(toBuilder = true) -public class AttendantJpaEntity { +public class AttendanceJpaEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "attendant_id") + @Column(name = "attendance_id") private Long id; private LocalDate date; private String email; diff --git a/application/src/main/java/kusitms/duduk/application/attendance/service/AttendUserCommand.java b/application/src/main/java/kusitms/duduk/application/attendance/service/AttendUserCommand.java new file mode 100644 index 0000000..b92ecb8 --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendance/service/AttendUserCommand.java @@ -0,0 +1,45 @@ +package kusitms.duduk.application.attendance.service; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.List; +import kusitms.duduk.core.attendance.dto.WeeklyAttendanceResponse; +import kusitms.duduk.core.attendance.port.input.DateProvider; +import kusitms.duduk.core.attendance.port.output.LoadAttendancePort; +import kusitms.duduk.core.attendance.port.output.SaveAttendancePort; +import kusitms.duduk.core.user.port.input.AttendUserUseCase; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class AttendUserCommand implements AttendUserUseCase { + + private final SaveAttendancePort saveAttendantPort; + private final LoadAttendancePort loadAttendantPort; + + @Qualifier("systemDateProvider") + private final DateProvider dateProvider; + + @Override + public void attend(final String email) { + if (!loadAttendantPort.isAttendedToday(email)) { + log.info("attend user: {}", email); + saveAttendantPort.save(dateProvider.now(), email); + } + } + + @Override + public WeeklyAttendanceResponse calculateAttendance(final String email) { + LocalDate startDate = LocalDate.now().with(DayOfWeek.MONDAY); + LocalDate endDate = LocalDate.now().with(DayOfWeek.SUNDAY); + + List attendanceDates = loadAttendantPort.findAttendantDatesByEmailAndDateBetween( + email, startDate, endDate); + + return WeeklyAttendanceResponse.of(attendanceDates); + } +} diff --git a/application/src/main/java/kusitms/duduk/application/attendance/service/SystemDateProvider.java b/application/src/main/java/kusitms/duduk/application/attendance/service/SystemDateProvider.java new file mode 100644 index 0000000..02cd68c --- /dev/null +++ b/application/src/main/java/kusitms/duduk/application/attendance/service/SystemDateProvider.java @@ -0,0 +1,14 @@ +package kusitms.duduk.application.attendance.service; + +import java.time.LocalDate; +import kusitms.duduk.core.attendance.port.input.DateProvider; +import org.springframework.stereotype.Component; + +@Component +public class SystemDateProvider implements DateProvider { + + @Override + public LocalDate now() { + return LocalDate.now(); + } +} diff --git a/application/src/main/java/kusitms/duduk/application/attendant/persistence/AttendantPersistenceAdapter.java b/application/src/main/java/kusitms/duduk/application/attendant/persistence/AttendantPersistenceAdapter.java deleted file mode 100644 index a0f03c4..0000000 --- a/application/src/main/java/kusitms/duduk/application/attendant/persistence/AttendantPersistenceAdapter.java +++ /dev/null @@ -1,30 +0,0 @@ -package kusitms.duduk.application.attendant.persistence; - -import java.time.LocalDate; -import kusitms.duduk.application.attendant.persistence.entity.AttendantJpaEntity; -import kusitms.duduk.common.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/attendant/persistence/AttendantRepository.java b/application/src/main/java/kusitms/duduk/application/attendant/persistence/AttendantRepository.java deleted file mode 100644 index b4e46e3..0000000 --- a/application/src/main/java/kusitms/duduk/application/attendant/persistence/AttendantRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package kusitms.duduk.application.attendant.persistence; - -import java.time.LocalDate; -import kusitms.duduk.application.attendant.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/attendant/service/AttendUserCommand.java b/application/src/main/java/kusitms/duduk/application/attendant/service/AttendUserCommand.java deleted file mode 100644 index bbb8c85..0000000 --- a/application/src/main/java/kusitms/duduk/application/attendant/service/AttendUserCommand.java +++ /dev/null @@ -1,26 +0,0 @@ -package kusitms.duduk.application.attendant.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/newsletter/persistence/NewsLetterRepositoryImpl.java b/application/src/main/java/kusitms/duduk/application/newsletter/persistence/NewsLetterRepositoryImpl.java index 5f59a91..dea1574 100644 --- a/application/src/main/java/kusitms/duduk/application/newsletter/persistence/NewsLetterRepositoryImpl.java +++ b/application/src/main/java/kusitms/duduk/application/newsletter/persistence/NewsLetterRepositoryImpl.java @@ -1,6 +1,5 @@ package kusitms.duduk.application.newsletter.persistence; -import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; import java.util.Optional; diff --git a/application/src/main/java/kusitms/duduk/application/newsletter/service/ArchiveNewsLetterCommand.java b/application/src/main/java/kusitms/duduk/application/newsletter/service/ArchiveNewsLetterCommand.java index f067c01..668a0be 100644 --- a/application/src/main/java/kusitms/duduk/application/newsletter/service/ArchiveNewsLetterCommand.java +++ b/application/src/main/java/kusitms/duduk/application/newsletter/service/ArchiveNewsLetterCommand.java @@ -2,6 +2,7 @@ import kusitms.duduk.application.newsletter.event.ArchiveNewsLetterEvent; import kusitms.duduk.common.exception.custom.NotExistsException; +import kusitms.duduk.core.newsletter.dto.response.ArchiveNewsLetterResponse; import kusitms.duduk.core.newsletter.port.input.ArchiveNewsLetterUseCase; import kusitms.duduk.core.newsletter.port.output.LoadNewsLetterPort; import kusitms.duduk.core.user.port.output.LoadUserPort; @@ -24,7 +25,7 @@ public class ArchiveNewsLetterCommand implements ArchiveNewsLetterUseCase { private final ApplicationEventPublisher applicationEventPublisher; @Override - public void archiveNewsLetter(String email, Long newsLetterId) { + public ArchiveNewsLetterResponse archive(String email, Long newsLetterId) { // 유저를 찾는다 User user = loadUserPort.findByEmail(email) .orElseThrow(() -> new NotExistsException("User not found")); @@ -37,5 +38,6 @@ public void archiveNewsLetter(String email, Long newsLetterId) { applicationEventPublisher.publishEvent(new ArchiveNewsLetterEvent(this, newsLetterId)); user.archiveNewsLetter(newsLetter); updateUserPort.update(user); + return new ArchiveNewsLetterResponse(newsLetterId); } } diff --git a/application/src/main/java/kusitms/duduk/application/term/persistence/TermPersistenceAdapter.java b/application/src/main/java/kusitms/duduk/application/term/persistence/TermPersistenceAdapter.java index c3e4ab5..9b1890d 100644 --- a/application/src/main/java/kusitms/duduk/application/term/persistence/TermPersistenceAdapter.java +++ b/application/src/main/java/kusitms/duduk/application/term/persistence/TermPersistenceAdapter.java @@ -3,6 +3,7 @@ import java.util.Optional; import kusitms.duduk.application.term.persistence.entity.TermJpaEntity; import kusitms.duduk.common.annotation.Adapter; +import kusitms.duduk.core.term.port.output.DeleteTermPort; import kusitms.duduk.core.term.port.output.LoadTermPort; import kusitms.duduk.core.term.port.output.SaveTermPort; import kusitms.duduk.domain.term.Term; @@ -12,7 +13,7 @@ @Slf4j @RequiredArgsConstructor @Adapter -public class TermPersistenceAdapter implements SaveTermPort, LoadTermPort { +public class TermPersistenceAdapter implements SaveTermPort, LoadTermPort, DeleteTermPort { private final TermRepository termRepository; private final TermJpaMapper termJpaEntityMapper; @@ -41,4 +42,15 @@ public Optional findLatestTerm() { return termRepository.findLatestTerm() .map(termJpaEntityMapper::toDomain); } + + @Override + public void deleteById(Long termId) { + termRepository.deleteById(termId); + } + + + @Override + public void deleteAll() { + termRepository.deleteAll(); + } } diff --git a/application/src/main/java/kusitms/duduk/application/term/service/ArchiveTermCommand.java b/application/src/main/java/kusitms/duduk/application/term/service/ArchiveTermCommand.java index fb2d406..4a57463 100644 --- a/application/src/main/java/kusitms/duduk/application/term/service/ArchiveTermCommand.java +++ b/application/src/main/java/kusitms/duduk/application/term/service/ArchiveTermCommand.java @@ -1,6 +1,7 @@ package kusitms.duduk.application.term.service; import kusitms.duduk.common.exception.custom.NotExistsException; +import kusitms.duduk.core.term.dto.response.ArchiveTermResponse; import kusitms.duduk.core.term.port.input.ArchiveTermUseCase; import kusitms.duduk.core.term.port.output.LoadTermPort; import kusitms.duduk.core.user.port.output.LoadUserPort; @@ -21,7 +22,7 @@ public class ArchiveTermCommand implements ArchiveTermUseCase { private final LoadTermPort loadTermPort; @Override - public void archive(String email, Long termId) { + public ArchiveTermResponse archive(String email, Long termId) { User user = loadUserPort.findByEmail(email) .orElseThrow(() -> new NotExistsException("User not found")); @@ -30,5 +31,6 @@ public void archive(String email, Long termId) { user.archiveTerm(term); updateUserPort.update(user); + return new ArchiveTermResponse(termId); } } diff --git a/application/src/main/java/kusitms/duduk/application/term/service/RetrieveTermCommand.java b/application/src/main/java/kusitms/duduk/application/term/service/RetrieveTermCommand.java index 7ea5d10..1c0804e 100644 --- a/application/src/main/java/kusitms/duduk/application/term/service/RetrieveTermCommand.java +++ b/application/src/main/java/kusitms/duduk/application/term/service/RetrieveTermCommand.java @@ -34,6 +34,4 @@ public RetrieveTermResponse retrieveLatestTerm(User user) { return termDtoMapper.toResponse(term, isScrapped); } - - } diff --git a/application/src/main/java/kusitms/duduk/application/user/service/RetrieveUserCommand.java b/application/src/main/java/kusitms/duduk/application/user/service/RetrieveUserCommand.java index 8d57ba3..1c3e7b8 100644 --- a/application/src/main/java/kusitms/duduk/application/user/service/RetrieveUserCommand.java +++ b/application/src/main/java/kusitms/duduk/application/user/service/RetrieveUserCommand.java @@ -5,6 +5,8 @@ import kusitms.duduk.core.term.dto.response.RetrieveTermResponse; import kusitms.duduk.core.term.port.input.RetrieveTermQuery; import kusitms.duduk.core.user.dto.response.RetrieveHomeResponse; +import kusitms.duduk.core.user.dto.response.RetrieveMypageResponse; +import kusitms.duduk.core.user.port.input.AttendUserUseCase; import kusitms.duduk.core.user.port.input.RetrieveUserQuery; import kusitms.duduk.core.user.port.output.LoadUserPort; import kusitms.duduk.domain.user.User; @@ -18,6 +20,7 @@ public class RetrieveUserCommand implements RetrieveUserQuery { private final LoadUserPort loadUserPort; private final RetrieveNewsLetterQuery retrieveNewsLetterQuery; private final RetrieveTermQuery retrieveTermQuery; + private final AttendUserUseCase attendUserUseCase; @Override public boolean isUserRegisteredByEmail(String email) { @@ -42,4 +45,17 @@ public RetrieveHomeResponse home(String email) { .todayTerm(todayTerm) .build(); } + + @Override + public RetrieveMypageResponse mypage(String email) { + // 유저 정보를 가져온다 + User user = loadUserPort.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("해당 유저를 찾을 수 없습니다.")); + + return RetrieveMypageResponse.builder() + .nickname(user.getNickname().getValue()) + .reward(5) + .attendants(attendUserUseCase.calculateAttendance(email)) + .build(); + } } diff --git a/application/src/test/java/kusitms/duduk/application/archive/service/RetrieveArchiveCommandTest.java b/application/src/test/java/kusitms/duduk/application/archive/service/RetrieveArchiveCommandTest.java new file mode 100644 index 0000000..0e9ef12 --- /dev/null +++ b/application/src/test/java/kusitms/duduk/application/archive/service/RetrieveArchiveCommandTest.java @@ -0,0 +1,120 @@ +package kusitms.duduk.application.archive.service; + +import kusitms.duduk.application.newsletter.service.NewsLetterSteps; +import kusitms.duduk.application.term.service.TermSteps; +import kusitms.duduk.core.archive.port.input.RetrieveArchiveUseCase; +import kusitms.duduk.core.newsletter.port.input.ArchiveNewsLetterUseCase; +import kusitms.duduk.core.newsletter.port.output.DeleteNewsLetterPort; +import kusitms.duduk.core.newsletter.port.output.SaveNewsLetterPort; +import kusitms.duduk.core.term.dto.response.ArchiveTermResponse; +import kusitms.duduk.core.term.port.input.ArchiveTermUseCase; +import kusitms.duduk.core.term.port.output.DeleteTermPort; +import kusitms.duduk.core.term.port.output.SaveTermPort; +import kusitms.duduk.domain.newsletter.NewsLetter; +import kusitms.duduk.domain.term.Term; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import kusitms.duduk.application.user.service.UserSteps; +import kusitms.duduk.core.archive.dto.response.ArchiveResponse; +import kusitms.duduk.core.user.port.output.DeleteUserPort; +import kusitms.duduk.core.user.port.output.SaveUserPort; +import kusitms.duduk.domain.global.Category; +import kusitms.duduk.domain.user.User; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@DisplayName("RetrieveArchiveCommand 테스트") +public class RetrieveArchiveCommandTest { + + @Autowired + private SaveUserPort saveUserPort; + + @Autowired + private DeleteUserPort deleteUserPort; + + @Autowired + private SaveNewsLetterPort saveNewsLetterPort; + + @Autowired + private DeleteNewsLetterPort deleteNewsLetterPort; + + @Autowired + private SaveTermPort saveTermPort; + + @Autowired + private DeleteTermPort deleteTermPort; + + @Autowired + private RetrieveArchiveUseCase retrieveArchiveUseCase; + + @Autowired + private ArchiveNewsLetterUseCase archiveNewsLetterUseCase; + + @Autowired + private ArchiveTermUseCase archiveTermUseCase; + + private User savedUser; + + @BeforeEach + void setup() { + // 사용자와 뉴스레터, 약관 데이터 생성 및 저장 + User user = UserSteps.ROLE_USER_생성_요청(); + savedUser = saveUserPort.create(user); + } + + @AfterEach + void cleanUp() { + deleteUserPort.deleteAll(); + deleteNewsLetterPort.deleteAll(); + deleteTermPort.deleteAll(); + } + + @Test + @Transactional + void 뉴스레터_카테고리별_아카이브를_조회한다() { + // given + // 뉴스레터 생성 + NewsLetter newsLetter = NewsLetterSteps.AI_FINANCE_뉴스_레터_생성(); + NewsLetter savedNewsLetter = saveNewsLetterPort.create(newsLetter); + + String email = savedUser.getEmail().getValue(); + Long newsLetterId = savedNewsLetter.getNewsLetterId().getValue(); + + archiveNewsLetterUseCase.archive(savedUser.getEmail().getValue(), + savedNewsLetter.getNewsLetterId().getValue()); + + // when + ArchiveResponse newsLettersResponse = retrieveArchiveUseCase.retrieveNewsLetters(email, + Category.FINANCE); + + // then + Assertions.assertThat(newsLettersResponse.ids()).contains(newsLetterId); + } + + @Test + @Transactional + void 단어_아카이브를_조회한다() { + // given + // 단어 생성 + Term term = TermSteps.COMPANY_단어_생성(); + Term savedTerm = saveTermPort.save(term); + + String email = savedUser.getEmail().getValue(); + Long termId = savedTerm.getId().getValue(); + + archiveTermUseCase.archive(savedUser.getEmail().getValue(), + savedTerm.getId().getValue()); + + // when + ArchiveResponse response = retrieveArchiveUseCase.retrieveTerms(email); + + // then + Assertions.assertThat(response.ids()).contains(termId); + } +} diff --git a/application/src/test/java/kusitms/duduk/application/attendance/service/AttendUserCommandTest.java b/application/src/test/java/kusitms/duduk/application/attendance/service/AttendUserCommandTest.java new file mode 100644 index 0000000..5cff8c6 --- /dev/null +++ b/application/src/test/java/kusitms/duduk/application/attendance/service/AttendUserCommandTest.java @@ -0,0 +1,48 @@ +package kusitms.duduk.application.attendance.service; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDate; +import kusitms.duduk.core.attendance.dto.WeeklyAttendanceResponse; +import kusitms.duduk.core.attendance.port.output.SaveAttendancePort; +import kusitms.duduk.core.user.port.input.AttendUserUseCase; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class AttendanceServiceTest { + + @Autowired + private AttendUserUseCase attendUserUseCase; + + @Autowired + private SaveAttendancePort saveAttendancePort; + + private final String EMAIL = "test@example.com"; + + @BeforeEach + void setup() { + // 일주일간 날짜 데이터 설정 + LocalDate today = LocalDate.now(); + // 어제 + saveAttendancePort.save(today, EMAIL); + } + + @Test + void 일주일_간의_출석_률을_계산_한다() { + // when + WeeklyAttendanceResponse response = attendUserUseCase.calculateAttendance(EMAIL); + + // then + Assertions.assertNotNull(response); + assertEquals(7, response.attendants().size()); + assertTrue(response.attendants().stream().anyMatch(a-> a.isAttendant() && a.getDayOfWeek() == LocalDate.now().getDayOfWeek())); + } +} \ No newline at end of file diff --git a/application/src/test/java/kusitms/duduk/application/newsletter/service/ArchiveNewsLetterCommandTest.java b/application/src/test/java/kusitms/duduk/application/newsletter/service/ArchiveNewsLetterCommandTest.java index 78b7ab6..ed7f572 100644 --- a/application/src/test/java/kusitms/duduk/application/newsletter/service/ArchiveNewsLetterCommandTest.java +++ b/application/src/test/java/kusitms/duduk/application/newsletter/service/ArchiveNewsLetterCommandTest.java @@ -44,13 +44,13 @@ void cleanUp() { void 뉴스레터를_아카이브한다() { // given User user = saveUserPort.create(UserSteps.ROLE_EDITOR_생성_요청()); - NewsLetter newsLetter = saveNewsLetterPort.create(NewsLetterSteps.AI_뉴스_레터_생성()); + NewsLetter newsLetter = saveNewsLetterPort.create(NewsLetterSteps.AI_FINANCE_뉴스_레터_생성()); String email = user.getEmail().getValue(); Long newsLetterId = newsLetter.getNewsLetterId().getValue(); // when - archiveNewsLetterUseCase.archiveNewsLetter(email, newsLetterId); + archiveNewsLetterUseCase.archive(email, newsLetterId); // then User loadUser = loadUserPort.findByEmail(email).get(); diff --git a/application/src/test/java/kusitms/duduk/application/newsletter/service/NewsLetterSteps.java b/application/src/test/java/kusitms/duduk/application/newsletter/service/NewsLetterSteps.java index 4231fce..e9fc8d8 100644 --- a/application/src/test/java/kusitms/duduk/application/newsletter/service/NewsLetterSteps.java +++ b/application/src/test/java/kusitms/duduk/application/newsletter/service/NewsLetterSteps.java @@ -55,7 +55,7 @@ public class NewsLetterSteps { return request; } - public static NewsLetter AI_뉴스_레터_생성() { + public static NewsLetter AI_FINANCE_뉴스_레터_생성() { CreateNewsLetterRequest request = AI_뉴스_레터_생성_요청(); NewsLetter newsLetter = NewsLetter.builder() .thumbnail(Thumbnail.from(request.thumbnail())) diff --git a/application/src/test/java/kusitms/duduk/application/term/service/ArchiveTermCommandTest.java b/application/src/test/java/kusitms/duduk/application/term/service/ArchiveTermCommandTest.java index d6a69ba..c4fea91 100644 --- a/application/src/test/java/kusitms/duduk/application/term/service/ArchiveTermCommandTest.java +++ b/application/src/test/java/kusitms/duduk/application/term/service/ArchiveTermCommandTest.java @@ -47,7 +47,7 @@ void cleanUp() { void 단어를_아카이브한다() { // given User user = saveUserPort.create(UserSteps.ROLE_EDITOR_생성_요청()); - Term term = saveTermPort.save(TermSteps.단어_생성()); + Term term = saveTermPort.save(TermSteps.COMPANY_단어_생성()); String email = user.getEmail().getValue(); Long termId = term.getId().getValue(); diff --git a/application/src/test/java/kusitms/duduk/application/term/service/RetrieveTermCommandTest.java b/application/src/test/java/kusitms/duduk/application/term/service/RetrieveTermCommandTest.java index da13298..af8182b 100644 --- a/application/src/test/java/kusitms/duduk/application/term/service/RetrieveTermCommandTest.java +++ b/application/src/test/java/kusitms/duduk/application/term/service/RetrieveTermCommandTest.java @@ -3,8 +3,6 @@ import static org.junit.jupiter.api.Assertions.*; import kusitms.duduk.application.user.service.UserSteps; -import kusitms.duduk.core.term.dto.TermDtoMapper; -import kusitms.duduk.core.term.dto.request.CreateTermRequest; import kusitms.duduk.core.term.dto.request.RetrieveTermRequest; import kusitms.duduk.core.term.dto.response.RetrieveTermResponse; import kusitms.duduk.core.term.port.input.RetrieveTermQuery; @@ -19,7 +17,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; @SpringBootTest @DisplayName("RetrieveTermCommand 테스트") @@ -54,7 +51,7 @@ void cleanUp() { @Test void 단어를_조회한다() { // given - Term term = TermSteps.단어_생성(); + Term term = TermSteps.COMPANY_단어_생성(); Term savedTerm = saveTermPort.save(term); // when @@ -72,7 +69,7 @@ void cleanUp() { @Test void 여러_개의_단어가_있을_경우_가장_최근의_단어를_조회한다() { // given - Term term1 = TermSteps.단어_생성(); + Term term1 = TermSteps.COMPANY_단어_생성(); Term term2 = TermSteps.단어_생성_2(); Term savedTerm1 = saveTermPort.save(term1); diff --git a/application/src/test/java/kusitms/duduk/application/term/service/TermSteps.java b/application/src/test/java/kusitms/duduk/application/term/service/TermSteps.java index b1f855b..49c733a 100644 --- a/application/src/test/java/kusitms/duduk/application/term/service/TermSteps.java +++ b/application/src/test/java/kusitms/duduk/application/term/service/TermSteps.java @@ -1,7 +1,6 @@ package kusitms.duduk.application.term.service; import kusitms.duduk.core.term.dto.request.CreateTermRequest; -import kusitms.duduk.domain.global.Category; import kusitms.duduk.domain.term.Term; import kusitms.duduk.domain.term.vo.Description; import kusitms.duduk.domain.term.vo.Name; @@ -19,7 +18,7 @@ public class TermSteps { termCategory); } - public static Term 단어_생성() { + public static Term COMPANY_단어_생성() { CreateTermRequest request = 단어_생성_요청(); return Term.builder() .koreanName(Name.from(request.koreanName())) diff --git a/application/src/test/java/kusitms/duduk/application/user/service/UserSteps.java b/application/src/test/java/kusitms/duduk/application/user/service/UserSteps.java index d9aa987..9e73f46 100644 --- a/application/src/test/java/kusitms/duduk/application/user/service/UserSteps.java +++ b/application/src/test/java/kusitms/duduk/application/user/service/UserSteps.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import kusitms.duduk.core.user.dto.request.CreateUserRequest; import kusitms.duduk.domain.global.Category; -import kusitms.duduk.domain.global.Id; import kusitms.duduk.domain.user.User; import kusitms.duduk.domain.user.vo.Email; import kusitms.duduk.domain.user.vo.Gender; diff --git a/core/src/main/java/kusitms/duduk/core/archive/dto/response/ArchiveResponse.java b/core/src/main/java/kusitms/duduk/core/archive/dto/response/ArchiveResponse.java new file mode 100644 index 0000000..43c8cad --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/archive/dto/response/ArchiveResponse.java @@ -0,0 +1,7 @@ +package kusitms.duduk.core.archive.dto.response; + +import java.util.List; + +public record ArchiveResponse(List ids) { + +} diff --git a/core/src/main/java/kusitms/duduk/core/archive/port/input/RetrieveArchiveUseCase.java b/core/src/main/java/kusitms/duduk/core/archive/port/input/RetrieveArchiveUseCase.java new file mode 100644 index 0000000..db8a7df --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/archive/port/input/RetrieveArchiveUseCase.java @@ -0,0 +1,11 @@ +package kusitms.duduk.core.archive.port.input; + +import kusitms.duduk.core.archive.dto.response.ArchiveResponse; +import kusitms.duduk.domain.global.Category; + +public interface RetrieveArchiveUseCase { + + ArchiveResponse retrieveNewsLetters(String email, Category category); + + ArchiveResponse retrieveTerms(String email); +} diff --git a/core/src/main/java/kusitms/duduk/core/attendance/dto/WeeklyAttendanceResponse.java b/core/src/main/java/kusitms/duduk/core/attendance/dto/WeeklyAttendanceResponse.java new file mode 100644 index 0000000..be87950 --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/attendance/dto/WeeklyAttendanceResponse.java @@ -0,0 +1,41 @@ +package kusitms.duduk.core.attendance.dto; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +@Builder(toBuilder = true) +public record WeeklyAttendanceResponse(List attendants) { + + public static WeeklyAttendanceResponse of(List attendanceDates) { + Map attendanceMap = Stream.of(DayOfWeek.values()) + .collect(Collectors.toMap(day -> day, day -> false)); // 모든 요일을 false로 초기화 + + attendanceDates.forEach( + date -> attendanceMap.put(date.getDayOfWeek(), true)); // 출석한 날짜 업데이트 + + List dailyAttendants = attendanceMap.entrySet().stream() + .map(entry -> new DailyAttendant(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + + return WeeklyAttendanceResponse.builder() + .attendants(dailyAttendants) + .build(); + } + + @Data + @AllArgsConstructor + @Getter + public static class DailyAttendant { + + public DayOfWeek dayOfWeek; + public boolean isAttendant; + } +} diff --git a/core/src/main/java/kusitms/duduk/core/attendance/port/input/DateProvider.java b/core/src/main/java/kusitms/duduk/core/attendance/port/input/DateProvider.java new file mode 100644 index 0000000..553fad5 --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/attendance/port/input/DateProvider.java @@ -0,0 +1,8 @@ +package kusitms.duduk.core.attendance.port.input; + +import java.time.LocalDate; + +public interface DateProvider { + + LocalDate now(); +} diff --git a/core/src/main/java/kusitms/duduk/core/attendance/port/output/LoadAttendancePort.java b/core/src/main/java/kusitms/duduk/core/attendance/port/output/LoadAttendancePort.java new file mode 100644 index 0000000..ed58f7f --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/attendance/port/output/LoadAttendancePort.java @@ -0,0 +1,12 @@ +package kusitms.duduk.core.attendance.port.output; + +import java.time.LocalDate; +import java.util.List; + +public interface LoadAttendancePort { + + boolean isAttendedToday(String email); + + List findAttendantDatesByEmailAndDateBetween(String email, LocalDate startDate, + LocalDate endDate); +} diff --git a/core/src/main/java/kusitms/duduk/core/attendance/port/output/SaveAttendancePort.java b/core/src/main/java/kusitms/duduk/core/attendance/port/output/SaveAttendancePort.java new file mode 100644 index 0000000..1e81fe8 --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/attendance/port/output/SaveAttendancePort.java @@ -0,0 +1,7 @@ +package kusitms.duduk.core.attendance.port.output; + +import java.time.LocalDate; + +public interface SaveAttendancePort { + void save(LocalDate date, String 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 deleted file mode 100644 index 59e8673..0000000 --- a/core/src/main/java/kusitms/duduk/core/attendant/port/output/LoadAttendantPort.java +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index d6dde5f..0000000 --- a/core/src/main/java/kusitms/duduk/core/attendant/port/output/SaveAttendantPort.java +++ /dev/null @@ -1,7 +0,0 @@ -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/newsletter/dto/response/ArchiveNewsLetterResponse.java b/core/src/main/java/kusitms/duduk/core/newsletter/dto/response/ArchiveNewsLetterResponse.java new file mode 100644 index 0000000..cf21e3e --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/newsletter/dto/response/ArchiveNewsLetterResponse.java @@ -0,0 +1,5 @@ +package kusitms.duduk.core.newsletter.dto.response; + +public record ArchiveNewsLetterResponse(Long newsLetterId) { + +} diff --git a/core/src/main/java/kusitms/duduk/core/newsletter/port/input/ArchiveNewsLetterUseCase.java b/core/src/main/java/kusitms/duduk/core/newsletter/port/input/ArchiveNewsLetterUseCase.java index 8a6e344..81c56fa 100644 --- a/core/src/main/java/kusitms/duduk/core/newsletter/port/input/ArchiveNewsLetterUseCase.java +++ b/core/src/main/java/kusitms/duduk/core/newsletter/port/input/ArchiveNewsLetterUseCase.java @@ -1,7 +1,9 @@ package kusitms.duduk.core.newsletter.port.input; +import kusitms.duduk.core.newsletter.dto.response.ArchiveNewsLetterResponse; + public interface ArchiveNewsLetterUseCase { // todo: 후에 응답 결과를 처리하는 DTO를 만들 예정 - void archiveNewsLetter(String email, Long newsLetterId); + ArchiveNewsLetterResponse archive(String email, Long newsLetterId); } diff --git a/core/src/main/java/kusitms/duduk/core/term/dto/response/ArchiveTermResponse.java b/core/src/main/java/kusitms/duduk/core/term/dto/response/ArchiveTermResponse.java new file mode 100644 index 0000000..99bc98c --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/term/dto/response/ArchiveTermResponse.java @@ -0,0 +1,5 @@ +package kusitms.duduk.core.term.dto.response; + +public record ArchiveTermResponse(Long termId) { + +} diff --git a/core/src/main/java/kusitms/duduk/core/term/port/input/ArchiveTermUseCase.java b/core/src/main/java/kusitms/duduk/core/term/port/input/ArchiveTermUseCase.java index eaf4d50..6e0a488 100644 --- a/core/src/main/java/kusitms/duduk/core/term/port/input/ArchiveTermUseCase.java +++ b/core/src/main/java/kusitms/duduk/core/term/port/input/ArchiveTermUseCase.java @@ -1,5 +1,7 @@ package kusitms.duduk.core.term.port.input; +import kusitms.duduk.core.term.dto.response.ArchiveTermResponse; + public interface ArchiveTermUseCase { - void archive(String email, Long termId); + ArchiveTermResponse archive(String email, Long termId); } diff --git a/core/src/main/java/kusitms/duduk/core/term/port/output/DeleteTermPort.java b/core/src/main/java/kusitms/duduk/core/term/port/output/DeleteTermPort.java new file mode 100644 index 0000000..a37043b --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/term/port/output/DeleteTermPort.java @@ -0,0 +1,8 @@ +package kusitms.duduk.core.term.port.output; + +public interface DeleteTermPort { + + void deleteById(Long termId); + + void deleteAll(); +} diff --git a/core/src/main/java/kusitms/duduk/core/user/dto/response/RetrieveMypageResponse.java b/core/src/main/java/kusitms/duduk/core/user/dto/response/RetrieveMypageResponse.java new file mode 100644 index 0000000..9980b2c --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/user/dto/response/RetrieveMypageResponse.java @@ -0,0 +1,10 @@ +package kusitms.duduk.core.user.dto.response; + +import kusitms.duduk.core.attendance.dto.WeeklyAttendanceResponse; +import lombok.Builder; + +@Builder(toBuilder = true) +public record RetrieveMypageResponse(String nickname, int reward, + WeeklyAttendanceResponse attendants) { + +} diff --git a/core/src/main/java/kusitms/duduk/core/user/dto/response/UserAttendantResponse.java b/core/src/main/java/kusitms/duduk/core/user/dto/response/UserAttendantResponse.java new file mode 100644 index 0000000..94be5d7 --- /dev/null +++ b/core/src/main/java/kusitms/duduk/core/user/dto/response/UserAttendantResponse.java @@ -0,0 +1,5 @@ +package kusitms.duduk.core.user.dto.response; + +public record UserAttendantResponse() { + +} 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 index f396e2d..9a668d2 100644 --- 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 @@ -1,6 +1,9 @@ package kusitms.duduk.core.user.port.input; +import kusitms.duduk.core.attendance.dto.WeeklyAttendanceResponse; + public interface AttendUserUseCase { - void attend(String email); + void attend(final String email); + WeeklyAttendanceResponse calculateAttendance(final String email); } diff --git a/core/src/main/java/kusitms/duduk/core/user/port/input/RetrieveUserQuery.java b/core/src/main/java/kusitms/duduk/core/user/port/input/RetrieveUserQuery.java index 8d16da4..6315f70 100644 --- a/core/src/main/java/kusitms/duduk/core/user/port/input/RetrieveUserQuery.java +++ b/core/src/main/java/kusitms/duduk/core/user/port/input/RetrieveUserQuery.java @@ -1,9 +1,13 @@ package kusitms.duduk.core.user.port.input; import kusitms.duduk.core.user.dto.response.RetrieveHomeResponse; +import kusitms.duduk.core.user.dto.response.RetrieveMypageResponse; +import kusitms.duduk.core.user.dto.response.UserResponse; public interface RetrieveUserQuery { boolean isUserRegisteredByEmail(String email); RetrieveHomeResponse home(String email); + + RetrieveMypageResponse mypage(String email); } diff --git a/domain/src/main/java/kusitms/duduk/domain/user/User.java b/domain/src/main/java/kusitms/duduk/domain/user/User.java index 515ef79..b9a6219 100644 --- a/domain/src/main/java/kusitms/duduk/domain/user/User.java +++ b/domain/src/main/java/kusitms/duduk/domain/user/User.java @@ -27,6 +27,7 @@ public class User { private Id id; private Email email; + // todo : Profile 추가해야 할까 private Nickname nickname; private RefreshToken refreshToken; private Gender gender; @@ -35,6 +36,7 @@ public class User { private Provider provider; private Category category; private Goal goal; + // todo : Reward Count 추가 해야될까 private List archives; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -75,4 +77,20 @@ public boolean isScrapped(Term term) { .findFirst() .isPresent(); } + + public List getNewsLettersFromArchive(Category category) { + return this.archives.stream() + .filter(archive -> archive.getCategory().equals(category)) + .findFirst() + .map(Archive::getNewsLetterIds) + .orElseThrow(() -> new IllegalArgumentException("Category not found")); + } + + public List getTermsFromArchive() { + return this.archives.stream() + .filter(archive -> archive.getCategory().equals(Category.WORD)) + .findFirst() + .map(Archive::getTermIds) + .orElseThrow(() -> new IllegalArgumentException("Category not found")); + } } \ No newline at end of file