Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,44 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.ject.support.admin.mail.domain.MailDispatchJobStatus;
import org.ject.support.admin.mail.domain.MailDispatchTargetStatus;
import org.ject.support.admin.mail.dto.MailDispatchJobResponse;
import org.ject.support.admin.mail.dto.MailDispatchResponse;
import org.ject.support.admin.mail.dto.MailDispatchTargetResponse;
import org.ject.support.admin.mail.dto.SendMailDispatchRequest;
import org.ject.support.common.security.AuthPrincipal;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;

@Tag(name = "AdminMailDispatch", description = "단체 메일 발송 API (어드민 전용)")
public interface AdminMailDispatchApiSpec {

@Operation(summary = "단체 메일 발송 작업 목록 조회", description = "관리자 본인의 발송 작업을 최신순으로 조회합니다.")
Page<MailDispatchJobResponse> searchJobs(
@Parameter(hidden = true) @AuthPrincipal Long requestedByAdminId,
@RequestParam(required = false) Long recruitId,
@RequestParam(required = false) MailDispatchJobStatus status,
@PageableDefault(size = 10, sort = "requestedAt", direction = Direction.DESC) Pageable pageable);

@Operation(summary = "단체 메일 발송 작업 상세 조회", description = "관리자 본인의 발송 작업 요약을 조회합니다.")
MailDispatchJobResponse getJob(
@Parameter(hidden = true) @AuthPrincipal Long requestedByAdminId,
@PathVariable Long dispatchJobId);

@Operation(summary = "단체 메일 수신자별 결과 조회", description = "발송 작업의 수신자별 결과를 상태로 필터링해 조회합니다.")
Page<MailDispatchTargetResponse> searchTargets(
@Parameter(hidden = true) @AuthPrincipal Long requestedByAdminId,
@PathVariable Long dispatchJobId,
@RequestParam(required = false) MailDispatchTargetStatus status,
@PageableDefault(size = 10, sort = "id", direction = Direction.ASC) Pageable pageable);

@Operation(summary = "단체 메일 발송", description = "선택한 제출 지원자에게 메일을 발송하고 대상별 결과를 기록합니다.")
MailDispatchResponse sendMail(
@Parameter(hidden = true) @AuthPrincipal Long requestedByAdminId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.ject.support.admin.mail.domain.MailDispatchJobStatus;
import org.ject.support.admin.mail.domain.MailDispatchTargetStatus;
import org.ject.support.admin.mail.dto.MailDispatchJobResponse;
import org.ject.support.admin.mail.dto.MailDispatchJobSearchCondition;
import org.ject.support.admin.mail.dto.MailDispatchResponse;
import org.ject.support.admin.mail.dto.MailDispatchTargetResponse;
import org.ject.support.admin.mail.dto.SendMailDispatchRequest;
import org.ject.support.admin.mail.service.MailDispatchQueryService;
import org.ject.support.admin.mail.service.MailDispatchUseCase;
import org.ject.support.common.security.AuthPrincipal;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -20,6 +33,36 @@
public class AdminMailDispatchController implements AdminMailDispatchApiSpec {

private final MailDispatchUseCase mailDispatchUseCase;
private final MailDispatchQueryService mailDispatchQueryService;

@Override
@GetMapping
public Page<MailDispatchJobResponse> searchJobs(
@AuthPrincipal Long requestedByAdminId,
@RequestParam(required = false) Long recruitId,
@RequestParam(required = false) MailDispatchJobStatus status,
@PageableDefault(size = 10, sort = "requestedAt", direction = Direction.DESC) Pageable pageable) {
return mailDispatchQueryService.searchJobs(
requestedByAdminId, new MailDispatchJobSearchCondition(recruitId, status), pageable);
}

@Override
@GetMapping("/{dispatchJobId}")
public MailDispatchJobResponse getJob(
@AuthPrincipal Long requestedByAdminId,
@PathVariable Long dispatchJobId) {
return mailDispatchQueryService.getJob(requestedByAdminId, dispatchJobId);
}

@Override
@GetMapping("/{dispatchJobId}/targets")
public Page<MailDispatchTargetResponse> searchTargets(
@AuthPrincipal Long requestedByAdminId,
@PathVariable Long dispatchJobId,
@RequestParam(required = false) MailDispatchTargetStatus status,
@PageableDefault(size = 10, sort = "id", direction = Direction.ASC) Pageable pageable) {
return mailDispatchQueryService.searchTargets(requestedByAdminId, dispatchJobId, status, pageable);
}

@Override
@PostMapping
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.ject.support.admin.mail.dto;

import java.time.LocalDateTime;
import org.ject.support.admin.mail.domain.MailDispatchJob;
import org.ject.support.admin.mail.domain.MailDispatchJobStatus;

public record MailDispatchJobResponse(
Long dispatchJobId,
Long scenarioId,
Long recruitId,
Long requestedByAdminId,
MailDispatchJobStatus status,
int targetCount,
int processingCount,
int successCount,
int failedCount,
LocalDateTime requestedAt,
LocalDateTime startedAt,
LocalDateTime finishedAt
) {

public static MailDispatchJobResponse from(MailDispatchJob job) {
return new MailDispatchJobResponse(
job.getId(),
job.getScenarioId(),
job.getRecruitId(),
job.getRequestedByAdminId(),
job.getStatus(),
job.getTargetCount(),
job.getProcessingCount(),
job.getSuccessCount(),
job.getFailedCount(),
job.getRequestedAt(),
job.getStartedAt(),
job.getFinishedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.ject.support.admin.mail.dto;

import org.ject.support.admin.mail.domain.MailDispatchJobStatus;

public record MailDispatchJobSearchCondition(
Long recruitId,
MailDispatchJobStatus status
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.ject.support.admin.mail.dto;

import java.time.LocalDateTime;
import org.ject.support.admin.mail.domain.MailDispatchTarget;
import org.ject.support.admin.mail.domain.MailDispatchTargetStatus;

public record MailDispatchTargetResponse(
Long targetId,
Long applyId,
String email,
MailDispatchTargetStatus status,
LocalDateTime sentAt,
String failureReason
) {

public static MailDispatchTargetResponse from(MailDispatchTarget target) {
return new MailDispatchTargetResponse(
target.getId(),
target.getApplyId(),
target.getEmail(),
target.getStatus(),
target.getSentAt(),
target.getFailureReason()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ject.support.admin.mail.repository;

import org.ject.support.admin.mail.domain.MailDispatchJob;
import org.ject.support.admin.mail.dto.MailDispatchJobSearchCondition;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface MailDispatchJobQueryRepository {

Page<MailDispatchJob> findJobs(Long requestedByAdminId,
MailDispatchJobSearchCondition condition,
Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.ject.support.admin.mail.repository;

import static org.ject.support.admin.mail.domain.QMailDispatchJob.mailDispatchJob;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.ject.support.admin.mail.domain.MailDispatchJob;
import org.ject.support.admin.mail.domain.MailDispatchJobStatus;
import org.ject.support.admin.mail.dto.MailDispatchJobSearchCondition;
import org.ject.support.common.data.PageResponse;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class MailDispatchJobQueryRepositoryImpl implements MailDispatchJobQueryRepository {

private final JPAQueryFactory queryFactory;

@Override
public Page<MailDispatchJob> findJobs(Long requestedByAdminId,
MailDispatchJobSearchCondition condition,
Pageable pageable) {
List<MailDispatchJob> content = queryFactory
.selectFrom(mailDispatchJob)
.where(
mailDispatchJob.requestedByAdminId.eq(requestedByAdminId),
eqRecruitId(condition.recruitId()),
eqStatus(condition.status())
)
.orderBy(mailDispatchJob.requestedAt.desc(), mailDispatchJob.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

Long total = queryFactory
.select(mailDispatchJob.count())
.from(mailDispatchJob)
.where(
mailDispatchJob.requestedByAdminId.eq(requestedByAdminId),
eqRecruitId(condition.recruitId()),
eqStatus(condition.status())
)
.fetchOne();

return PageResponse.from(content, pageable, total != null ? total : 0L);
}

private BooleanExpression eqRecruitId(Long recruitId) {
return Optional.ofNullable(recruitId)
.map(mailDispatchJob.recruitId::eq)
.orElse(null);
}

private BooleanExpression eqStatus(MailDispatchJobStatus status) {
return Optional.ofNullable(status)
.map(mailDispatchJob.status::eq)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import org.ject.support.admin.mail.domain.MailDispatchJob;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MailDispatchJobRepository extends JpaRepository<MailDispatchJob, Long> {
public interface MailDispatchJobRepository extends JpaRepository<MailDispatchJob, Long>, MailDispatchJobQueryRepository {

Optional<MailDispatchJob> findByRequestedByAdminIdAndIdempotencyKey(
Long requestedByAdminId, String idempotencyKey);

Optional<MailDispatchJob> findByIdAndRequestedByAdminId(Long dispatchJobId, Long requestedByAdminId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ject.support.admin.mail.repository;

import org.ject.support.admin.mail.domain.MailDispatchTarget;
import org.ject.support.admin.mail.domain.MailDispatchTargetStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface MailDispatchTargetQueryRepository {

Page<MailDispatchTarget> findTargets(Long dispatchJobId,
MailDispatchTargetStatus status,
Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.ject.support.admin.mail.repository;

import static org.ject.support.admin.mail.domain.QMailDispatchTarget.mailDispatchTarget;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.ject.support.admin.mail.domain.MailDispatchTarget;
import org.ject.support.admin.mail.domain.MailDispatchTargetStatus;
import org.ject.support.common.data.PageResponse;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class MailDispatchTargetQueryRepositoryImpl implements MailDispatchTargetQueryRepository {

private final JPAQueryFactory queryFactory;

@Override
public Page<MailDispatchTarget> findTargets(Long dispatchJobId,
MailDispatchTargetStatus status,
Pageable pageable) {
List<MailDispatchTarget> content = queryFactory
.selectFrom(mailDispatchTarget)
.where(
mailDispatchTarget.dispatchJob.id.eq(dispatchJobId),
eqStatus(status)
)
.orderBy(mailDispatchTarget.id.asc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

Long total = queryFactory
.select(mailDispatchTarget.count())
.from(mailDispatchTarget)
.where(
mailDispatchTarget.dispatchJob.id.eq(dispatchJobId),
eqStatus(status)
)
.fetchOne();

return PageResponse.from(content, pageable, total != null ? total : 0L);
}

private BooleanExpression eqStatus(MailDispatchTargetStatus status) {
return Optional.ofNullable(status)
.map(mailDispatchTarget.status::eq)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.ject.support.admin.mail.domain.MailDispatchTarget;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MailDispatchTargetRepository extends JpaRepository<MailDispatchTarget, Long> {
public interface MailDispatchTargetRepository extends JpaRepository<MailDispatchTarget, Long>, MailDispatchTargetQueryRepository {

Optional<MailDispatchTarget> findByDispatchJobIdAndApplyId(Long dispatchJobId, Long applyId);

Expand Down
Loading
Loading