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
@@ -0,0 +1,87 @@
package in.koreatech.koin.admin.article.controller;

import static in.koreatech.koin.domain.user.model.UserType.ADMIN;
import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import in.koreatech.koin.admin.article.dto.AdminArticleAiSummariesResponse;
import in.koreatech.koin.admin.article.dto.AdminArticleAiSummaryLogsResponse;
import in.koreatech.koin.admin.article.dto.AdminArticleAiSummaryOverviewResponse;
import in.koreatech.koin.admin.article.dto.AdminArticleAiSummaryResponse;
import in.koreatech.koin.domain.community.article.model.ArticleAiSummaryLogType;
import in.koreatech.koin.domain.community.article.model.ArticleAiSummaryStatus;
import in.koreatech.koin.domain.community.article.service.summary.ArticleSummaryFailureType;
import in.koreatech.koin.global.auth.Auth;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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;

@Tag(
name = "(Admin) ArticleAiSummary: 게시글 AI 요약",
description = "super_admin 여부와 관계없이 코인 어드민 로그인이 승인된 모든 관리자 계정이 게시글 AI 요약 작업 상태를 조회한다"
)
public interface AdminArticleAiSummaryApi {

@ApiResponses(value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true)))
})
@Operation(summary = "게시글 AI 요약 큐 상태 요약 조회")
@GetMapping("/admin/articles/ai-summaries/overview")
ResponseEntity<AdminArticleAiSummaryOverviewResponse> getOverview(
@Auth(permit = {ADMIN}) Integer adminId
);

@ApiResponses(value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true)))
})
@Operation(summary = "게시글 AI 요약 작업 목록 조회")
@GetMapping("/admin/articles/ai-summaries")
ResponseEntity<AdminArticleAiSummariesResponse> getSummaries(
@RequestParam(name = "page", defaultValue = "1") Integer page,
@RequestParam(name = "limit", defaultValue = "10", required = false) Integer limit,
@RequestParam(name = "status", required = false) ArticleAiSummaryStatus status,
@Auth(permit = {ADMIN}) Integer adminId
);

@ApiResponses(value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true)))
})
@Operation(summary = "게시글 AI 요약 작업 로그 조회")
@GetMapping("/admin/articles/ai-summaries/logs")
ResponseEntity<AdminArticleAiSummaryLogsResponse> getLogs(
@RequestParam(name = "page", defaultValue = "1") Integer page,
@RequestParam(name = "limit", defaultValue = "10", required = false) Integer limit,
@RequestParam(name = "summary_id", required = false) Integer summaryId,
@RequestParam(name = "article_id", required = false) Integer articleId,
@RequestParam(name = "event_type", required = false) ArticleAiSummaryLogType eventType,
@RequestParam(name = "failure_type", required = false) ArticleSummaryFailureType failureType,
@Auth(permit = {ADMIN}) Integer adminId
);

@ApiResponses(value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true)))
})
@Operation(summary = "게시글 AI 요약 작업 단건 조회")
@GetMapping("/admin/articles/ai-summaries/{summaryId}")
ResponseEntity<AdminArticleAiSummaryResponse> getSummary(
@Parameter(in = PATH) @PathVariable Integer summaryId,
@Auth(permit = {ADMIN}) Integer adminId
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package in.koreatech.koin.admin.article.controller;

import static in.koreatech.koin.domain.user.model.UserType.ADMIN;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import in.koreatech.koin.admin.article.dto.AdminArticleAiSummariesResponse;
import in.koreatech.koin.admin.article.dto.AdminArticleAiSummaryLogsResponse;
import in.koreatech.koin.admin.article.dto.AdminArticleAiSummaryOverviewResponse;
import in.koreatech.koin.admin.article.dto.AdminArticleAiSummaryResponse;
import in.koreatech.koin.admin.article.service.AdminArticleAiSummaryService;
import in.koreatech.koin.domain.community.article.model.ArticleAiSummaryLogType;
import in.koreatech.koin.domain.community.article.model.ArticleAiSummaryStatus;
import in.koreatech.koin.domain.community.article.service.summary.ArticleSummaryFailureType;
import in.koreatech.koin.global.auth.Auth;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class AdminArticleAiSummaryController implements AdminArticleAiSummaryApi {

private final AdminArticleAiSummaryService adminArticleAiSummaryService;

@Override
@GetMapping("/admin/articles/ai-summaries/overview")
public ResponseEntity<AdminArticleAiSummaryOverviewResponse> getOverview(
@Auth(permit = {ADMIN}) Integer adminId
) {
return ResponseEntity.ok(adminArticleAiSummaryService.getOverview());
}

@Override
@GetMapping("/admin/articles/ai-summaries")
public ResponseEntity<AdminArticleAiSummariesResponse> getSummaries(
@RequestParam(name = "page", defaultValue = "1") Integer page,
@RequestParam(name = "limit", defaultValue = "10", required = false) Integer limit,
@RequestParam(name = "status", required = false) ArticleAiSummaryStatus status,
@Auth(permit = {ADMIN}) Integer adminId
) {
return ResponseEntity.ok(adminArticleAiSummaryService.getSummaries(page, limit, status));
}

@Override
@GetMapping("/admin/articles/ai-summaries/logs")
public ResponseEntity<AdminArticleAiSummaryLogsResponse> getLogs(
@RequestParam(name = "page", defaultValue = "1") Integer page,
@RequestParam(name = "limit", defaultValue = "10", required = false) Integer limit,
@RequestParam(name = "summary_id", required = false) Integer summaryId,
@RequestParam(name = "article_id", required = false) Integer articleId,
@RequestParam(name = "event_type", required = false) ArticleAiSummaryLogType eventType,
@RequestParam(name = "failure_type", required = false) ArticleSummaryFailureType failureType,
@Auth(permit = {ADMIN}) Integer adminId
) {
return ResponseEntity.ok(adminArticleAiSummaryService.getLogs(
page,
limit,
summaryId,
articleId,
eventType,
failureType
));
}

@Override
@GetMapping("/admin/articles/ai-summaries/{summaryId}")
public ResponseEntity<AdminArticleAiSummaryResponse> getSummary(
@PathVariable Integer summaryId,
@Auth(permit = {ADMIN}) Integer adminId
) {
return ResponseEntity.ok(adminArticleAiSummaryService.getSummary(summaryId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package in.koreatech.koin.admin.article.dto;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import java.util.List;

import org.springframework.data.domain.Page;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import in.koreatech.koin.common.model.Criteria;
import io.swagger.v3.oas.annotations.media.Schema;

@JsonNaming(SnakeCaseStrategy.class)
public record AdminArticleAiSummariesResponse(
@Schema(description = "게시글 AI 요약 작업 목록", requiredMode = REQUIRED)
List<AdminArticleAiSummaryResponse> summaries,

@Schema(description = "총 작업 수", example = "57", requiredMode = REQUIRED)
Long totalCount,

@Schema(description = "현재 페이지에 포함된 작업 수", example = "10", requiredMode = REQUIRED)
Integer currentCount,

@Schema(description = "총 페이지 수", example = "6", requiredMode = REQUIRED)
Integer totalPage,

@Schema(description = "현재 페이지", example = "2", requiredMode = REQUIRED)
Integer currentPage
) {

public static AdminArticleAiSummariesResponse of(
Page<AdminArticleAiSummaryResponse> pagedResult,
Criteria criteria
) {
return new AdminArticleAiSummariesResponse(
pagedResult.getContent(),
pagedResult.getTotalElements(),
pagedResult.getContent().size(),
pagedResult.getTotalPages(),
criteria.getPage() + 1
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package in.koreatech.koin.admin.article.dto;

import java.time.LocalDateTime;

public interface AdminArticleAiSummaryLogProjection {

Integer getLogId();

Integer getSummaryId();

Integer getArticleId();

Integer getBoardId();

String getArticleTitle();

String getEventType();

String getStatus();

String getFailureType();

String getMessage();

Integer getRetryCount();

LocalDateTime getNextAttemptAt();

String getWorkerId();

LocalDateTime getCreatedAt();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package in.koreatech.koin.admin.article.dto;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import java.time.LocalDateTime;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import io.swagger.v3.oas.annotations.media.Schema;

@JsonNaming(SnakeCaseStrategy.class)
public record AdminArticleAiSummaryLogResponse(
@Schema(description = "로그 ID", example = "1", requiredMode = REQUIRED)
Integer logId,

@Schema(description = "요약 작업 ID", example = "12")
Integer summaryId,

@Schema(description = "게시글 ID", example = "17291")
Integer articleId,

@Schema(description = "게시판 ID", example = "5")
Integer boardId,

@Schema(description = "게시글 제목")
String articleTitle,

@Schema(description = "로그 유형", example = "FAILED", requiredMode = REQUIRED)
String eventType,

@Schema(description = "로그 발생 시점의 요약 상태", example = "FAILED")
String status,

@Schema(description = "실패 유형", example = "RATE_LIMIT")
String failureType,

@Schema(description = "안전하게 마스킹된 메시지")
String message,

@Schema(description = "로그 발생 시점의 재시도 횟수", example = "2")
Integer retryCount,

@Schema(description = "다음 시도 가능 시각")
LocalDateTime nextAttemptAt,

@Schema(description = "작업자 ID")
String workerId,

@Schema(description = "로그 생성 시각", requiredMode = REQUIRED)
LocalDateTime createdAt
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package in.koreatech.koin.admin.article.dto;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import java.util.List;

import org.springframework.data.domain.Page;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import in.koreatech.koin.common.model.Criteria;
import io.swagger.v3.oas.annotations.media.Schema;

@JsonNaming(SnakeCaseStrategy.class)
public record AdminArticleAiSummaryLogsResponse(
@Schema(description = "게시글 AI 요약 로그 목록", requiredMode = REQUIRED)
List<AdminArticleAiSummaryLogResponse> logs,

@Schema(description = "총 로그 수", example = "57", requiredMode = REQUIRED)
Long totalCount,

@Schema(description = "현재 페이지에 포함된 로그 수", example = "10", requiredMode = REQUIRED)
Integer currentCount,

@Schema(description = "총 페이지 수", example = "6", requiredMode = REQUIRED)
Integer totalPage,

@Schema(description = "현재 페이지", example = "2", requiredMode = REQUIRED)
Integer currentPage
) {

public static AdminArticleAiSummaryLogsResponse of(
Page<AdminArticleAiSummaryLogResponse> pagedResult,
Criteria criteria
) {
return new AdminArticleAiSummaryLogsResponse(
pagedResult.getContent(),
pagedResult.getTotalElements(),
pagedResult.getContent().size(),
pagedResult.getTotalPages(),
criteria.getPage() + 1
);
}
}
Loading
Loading