Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'

//ElasticSearch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import clap.server.adapter.inbound.security.SecurityUserDetails;
import clap.server.adapter.inbound.web.dto.auth.LoginRequest;
import clap.server.adapter.inbound.web.dto.auth.LoginResponse;
import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
import clap.server.application.port.inbound.auth.LoginUsecase;
import clap.server.application.port.inbound.auth.LogoutUsecase;
import clap.server.common.annotation.architecture.WebAdapter;
import clap.server.config.annotation.LogType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -27,6 +29,7 @@ public class AuthController {
private final LoginUsecase loginUsecase;
private final LogoutUsecase logoutUsecase;

@LogType(LogStatus.LOGIN)
@Operation(summary = "로그인 API")
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@RequestHeader(name = "sessionId") String sessionId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package clap.server.adapter.inbound.web.dto.common;

import org.springframework.data.domain.Page;

import java.util.List;

public record PageResponse<T>(
List<T> content,
long totalElements,
int totalPages,
int pageNumber,
int pageSize,
boolean isFirst,
boolean isLast
) {
public static <T> PageResponse<T> from(Page<T> page) {
return new PageResponse<>(
page.getContent(),
page.getTotalElements(),
page.getTotalPages(),
page.getNumber() + 1,
page.getSize(),
page.isFirst(),
page.isLast()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package clap.server.adapter.inbound.web.dto.log;

import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;

public record AnonymousLogResponse(
@NotBlank
Long logId,
LogStatus logStatus,
@NotBlank
LocalDateTime requestAt,
@NotBlank
String nickName,
String clientIp,
@NotBlank
Integer statusCode,
@NotNull
String customStatusCode,
int failedAttempts
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package clap.server.adapter.inbound.web.dto.log;

import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

import java.util.List;

public record FilterLogRequest(
@Schema(description = "검색 기간 (단위: 시간)", example = "1, 24, 168, 730, 2190 (1시간, 24시간, 1주일, 1개월, 3개월)")
Integer term,
@NotNull
List<LogStatus> logStatus,
@NotNull
String nickName,
@NotNull
String clientIp
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package clap.server.adapter.inbound.web.dto.log;

import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;

public record MemberLogResponse(
@NotBlank
Long logId,
LogStatus logStatus,
@NotBlank
LocalDateTime responseAt,
String nickName,
String clientIp,
@NotBlank
Integer statusCode
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package clap.server.adapter.inbound.web.log;

import clap.server.adapter.inbound.security.SecurityUserDetails;
import clap.server.adapter.inbound.web.dto.common.PageResponse;
import clap.server.adapter.inbound.web.dto.log.AnonymousLogResponse;
import clap.server.adapter.inbound.web.dto.log.FilterLogRequest;
import clap.server.adapter.inbound.web.dto.log.MemberLogResponse;
import clap.server.application.port.inbound.log.FindApiLogsUsecase;
import clap.server.common.annotation.architecture.WebAdapter;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@WebAdapter
@RestController
@RequestMapping("/api/logs")
@RequiredArgsConstructor
public class LogController {

private final FindApiLogsUsecase findApiLogsUsecase;

@Secured({"ROLE_ADMIN"})
@GetMapping("/login")
public ResponseEntity<PageResponse<AnonymousLogResponse>> getLoginAttempts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int pageSize,
@ModelAttribute FilterLogRequest anonymousLogRequest,
@AuthenticationPrincipal SecurityUserDetails userInfo) {
Pageable pageable = PageRequest.of(page, pageSize);
return ResponseEntity.ok(findApiLogsUsecase.filterAnonymousLogs(anonymousLogRequest, pageable));
}

@Secured({"ROLE_ADMIN"})
@GetMapping("/general")
public ResponseEntity<PageResponse<MemberLogResponse>> getApiCalls(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int pageSize,
@ModelAttribute FilterLogRequest memberLogRequest,
@AuthenticationPrincipal SecurityUserDetails userInfo) {
Pageable pageable = PageRequest.of(page, pageSize);
return ResponseEntity.ok(findApiLogsUsecase.filterMemberLogs(memberLogRequest, pageable));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import clap.server.adapter.inbound.security.SecurityUserDetails;
import clap.server.adapter.inbound.web.dto.task.FindTaskDetailsForManagerResponse;
import clap.server.adapter.inbound.web.dto.task.response.FindTaskHistoryResponse;
import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
import clap.server.application.port.inbound.task.FindTaskHistoriesUsecase;
import clap.server.common.annotation.architecture.WebAdapter;
import clap.server.config.annotation.LogType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
Expand All @@ -26,6 +28,7 @@ public class FindTaskHistoryController {

private final FindTaskHistoriesUsecase findTaskHistoriesUsecase;

@LogType(LogStatus.TASK_VIEWED)
@Operation(summary = "작업 히스토리 조회")
@Secured({"ROLE_MANAGER","ROLE_USER"})
@GetMapping("/{taskId}/histories")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package clap.server.adapter.outbound.persistense;

import clap.server.adapter.inbound.web.dto.log.AnonymousLogResponse;
import clap.server.adapter.inbound.web.dto.log.FilterLogRequest;

import clap.server.adapter.inbound.web.dto.log.MemberLogResponse;
import clap.server.adapter.outbound.persistense.mapper.ApiLogPersistenceMapper;
import clap.server.adapter.outbound.persistense.mapper.MemberPersistenceMapper;
import clap.server.adapter.outbound.persistense.repository.log.AnonymousLogRepository;
import clap.server.adapter.outbound.persistense.repository.log.ApiLogRepository;
import clap.server.adapter.outbound.persistense.repository.log.MemberLogRepository;
import clap.server.application.mapper.response.LogMapper;
import clap.server.application.port.outbound.log.CommandLogPort;
import clap.server.application.port.outbound.log.LoadLogPort;
import clap.server.common.annotation.architecture.PersistenceAdapter;
import clap.server.domain.model.log.AnonymousLog;
import clap.server.domain.model.log.ApiLog;
import clap.server.domain.model.log.MemberLog;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;

@PersistenceAdapter
@RequiredArgsConstructor
public class ApiLogPersistenceAdapter implements CommandLogPort, LoadLogPort {

private final ApiLogRepository apiLogRepository;
private final AnonymousLogRepository anonymousLogRepository;
private final MemberLogRepository memberLogRepository;
private final ApiLogPersistenceMapper apiLogPersistenceMapper;
private final MemberPersistenceMapper memberPersistenceMapper;

@Override
public void saveMemberLog(MemberLog memberLog) {
apiLogRepository.save(apiLogPersistenceMapper.mapMemberLogToEntity(memberLog, memberPersistenceMapper.toEntity(memberLog.getMember())));

}

@Override
public void saveAnonymousLog(AnonymousLog anonymousLog) {
apiLogRepository.save(apiLogPersistenceMapper.mapAnonymousLogToEntity(anonymousLog, anonymousLog.getLoginNickname()));
}

@Override
public List<ApiLog> findAllLogs() {
return apiLogRepository.findAll().stream()
.map(apiLogPersistenceMapper::mapLogEntityToDomain)
.toList();
}

@Override
public Page<MemberLog> filterMemberLogs(FilterLogRequest memberLogRequest, Pageable pageable) {
return memberLogRepository.filterMemberLogs(memberLogRequest, pageable)
.map(apiLogPersistenceMapper::mapMemberLogEntityToDomain);
}

@Override
public Page<AnonymousLog> filterAnonymousLogs(FilterLogRequest anonymousLogRequest, Pageable pageable) {
return anonymousLogRepository.filterAnonymousLogs(anonymousLogRequest, pageable)
.map(apiLogPersistenceMapper::mapAnonymousLogEntityToDomain);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,4 @@ public Comment saveComment(Comment comment) {
CommentEntity commentEntity = commentRepository.save(commentPersistenceMapper.toEntity(comment));
return commentPersistenceMapper.toDomain(commentEntity);
}

@Override
public Comment saveComment(Comment comment) {
CommentEntity commentEntity = commentRepository.save(commentPersistenceMapper.toEntity(comment));
return commentPersistenceMapper.toDomain(commentEntity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DiscriminatorValue("ANONYMOUS")
@SuperBuilder
public class AnonymousLogEntity extends ApiLogEntity{
public class AnonymousLogEntity extends ApiLogEntity {

@Column
private String loginNickname;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import clap.server.adapter.outbound.persistense.entity.common.BaseTimeEntity;
import clap.server.adapter.outbound.persistense.entity.log.constant.ApiHttpMethod;
import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
Expand All @@ -16,15 +17,12 @@
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public class ApiLogEntity extends BaseTimeEntity {
@DiscriminatorColumn
public abstract class ApiLogEntity extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long logId;

@Column(nullable = false)
private String serverIp;

@Column(nullable = false)
private String clientIp;

Expand All @@ -42,15 +40,18 @@ public class ApiLogEntity extends BaseTimeEntity {
private String customStatusCode;

@Column(length = 4096, nullable = false)
private String request;
private String requestBody;

@Column(length = 4096, nullable = false)
private String response;
private String responseBody;

@Column(nullable = false)
private LocalDateTime requestAt;

@Column(nullable = false)
private LocalDateTime responseAt;
@Enumerated(EnumType.STRING)
private LogStatus logStatus;

}
@Version
private Long version;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package clap.server.adapter.outbound.persistense.entity.log;

import clap.server.adapter.outbound.persistense.entity.member.MemberEntity;
import clap.server.domain.model.log.ApiLog;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
Expand All @@ -12,8 +13,9 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DiscriminatorValue("MEMBER")
@SuperBuilder
public class MemberLogEntity extends ApiLogEntity{
@ManyToOne(fetch = FetchType.LAZY)
public class MemberLogEntity extends ApiLogEntity {

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "member_id")
private MemberEntity member;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ public enum ApiHttpMethod {
GET,
PATCH,
PUT,
DELETE;
DELETE,
UNKNOWN; // Logging
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package clap.server.adapter.outbound.persistense.entity.log.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum LogStatus {
LOGIN("로그인"),
REQUEST_CREATED("요청 생성"),
REQUEST_UPDATED("요청 수정"),
REQUEST_CANCELLED("요청 취소"),
REQUEST_APPROVED("요청 승인"),
ASSIGNER_CHANGED("처리자 변경"),
COMMENT_ADDED("댓글 추가"),
COMMENT_UPDATED("댓글 수정"),
STATUS_CHANGED("작업 상태 변경"),
TASK_COMPLETED("작업 완료"),
TASK_FAILED("작업 실패"),
TASK_VIEWED("작업 조회");

private final String description;
}
Loading