Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
722444e
feat: 공지사항 엔티티 생성
boyekim Mar 28, 2026
9b482ab
feat: 공지사항 전체 조회 api 구현
boyekim Mar 28, 2026
4b79bca
feat: 공지사항 등록 api 구현
boyekim Mar 29, 2026
c440c35
refactor: 전체 조회 조건 추가
boyekim Mar 29, 2026
27ae010
refactor: 공지 생성 시 api 호출 레벨에서 검증 추가
boyekim Mar 29, 2026
979eea8
feat: 공지 수정 api 구현
boyekim Mar 29, 2026
a341e4c
feat: 공지 삭제 api 구현
boyekim Mar 29, 2026
ed71ea0
test: 공지 CRUD 단위 테스트 및 객체 협력 테스트 추가
boyekim Mar 29, 2026
fd83c52
feat: MethodArgumentNotValidException 핸들러 추가
boyekim Mar 30, 2026
cee4753
feat: OperationType 전체 카테고리 추가
boyekim Mar 30, 2026
60bb57b
feat: OperationType 리뷰 카테고리 추가
boyekim Apr 4, 2026
6c5affa
refactor: BaseEntity에 `@Getter` 적용
boyekim Apr 5, 2026
b445714
refactor: UserReview에 BaseEntity extends 추가
boyekim Apr 5, 2026
0b78d1a
refactor: Notice에 OperationType null 비허용
boyekim Apr 5, 2026
96d52ef
refactor: Test에 불필요한 entityManager 삭제
boyekim Apr 5, 2026
eb1dd87
feat: BaseEntity에 updatedAt 필드 추가
boyekim Apr 5, 2026
9d9f639
refactor: UpdatedNoticeResponse 필드 수정
boyekim Apr 5, 2026
66f736c
refactor: updatedAt 중간 반영 되도록 로직 추가
boyekim Apr 5, 2026
6cf169b
test: updatedAt 검증 가능하도록 테스트 수정
boyekim Apr 5, 2026
8e4b713
test: updatedAt 수정
boyekim Apr 5, 2026
55ec91b
refactor: 엔티티 생성 책임 DTO로 이관하여 캡슐화
boyekim Apr 7, 2026
a316c0c
test: 공지 수정 테스트 수정 및 BaseEntityListenerTest 추가
boyekim Apr 7, 2026
7f4f279
feat: 클라이언트용 공지 조회 api 구현
boyekim Apr 7, 2026
c3684b0
test: 테스트 추가
boyekim Apr 7, 2026
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 @@ -26,6 +26,7 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-mail'
// implementation 'io.micrometer:micrometer-registry-datadog'
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/kr/allcll/backend/admin/notice/AdminNoticeApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package kr.allcll.backend.admin.notice;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import kr.allcll.backend.admin.AdminRequestValidator;
import kr.allcll.backend.admin.notice.dto.CreateNoticeRequest;
import kr.allcll.backend.admin.notice.dto.CreateNoticeResponse;
import kr.allcll.backend.admin.notice.dto.NoticesResponse;
import kr.allcll.backend.admin.notice.dto.UpdateNoticeRequest;
import kr.allcll.backend.admin.notice.dto.UpdateNoticeResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
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.RestController;

@RestController
@RequiredArgsConstructor
public class AdminNoticeApi {

private final AdminNoticeService adminNoticeService;
private final AdminRequestValidator validator;

@GetMapping("/api/admin/notices")
public ResponseEntity<NoticesResponse> getAllNotice(HttpServletRequest request) {
if (validator.isRateLimited(request) || validator.isUnauthorized(request)) {
return ResponseEntity.status(401).build();
}
NoticesResponse response = adminNoticeService.getAllNotice();
return ResponseEntity.ok(response);
}

@PostMapping("/api/admin/notices")
public ResponseEntity<CreateNoticeResponse> createNotice(
HttpServletRequest request,
@Valid @RequestBody CreateNoticeRequest createNoticeRequest
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Map bean validation failures to 4xx responses

Adding @Valid here makes invalid payloads throw MethodArgumentNotValidException, but GlobalExceptionHandler currently has no dedicated handler and its catch-all Exception path returns SERVER_ERROR (500). That means a bad notice create/update request is reported as a server fault instead of a client input error, which will break client-side error handling and monitoring by inflating 5xx rates for ordinary validation mistakes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅇㅈ ExceptionHandler 추가하겠습니다

) {
if (validator.isRateLimited(request) || validator.isUnauthorized(request)) {
return ResponseEntity.status(401).build();
}
CreateNoticeResponse response = adminNoticeService.createNewNotice(createNoticeRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@PatchMapping("/api/admin/notices/{id}")
public ResponseEntity<UpdateNoticeResponse> modifyNotice(
HttpServletRequest request,
@PathVariable Long id,
@Valid @RequestBody UpdateNoticeRequest updateNoticeRequest
) {
if (validator.isRateLimited(request) || validator.isUnauthorized(request)) {
return ResponseEntity.status(401).build();
}
UpdateNoticeResponse response = adminNoticeService.updateNotice(id, updateNoticeRequest);
return ResponseEntity.ok(response);
}

@DeleteMapping("/api/admin/notices/{id}")
public ResponseEntity<Void> deleteNotice(
HttpServletRequest request,
@PathVariable Long id
) {
if (validator.isRateLimited(request) || validator.isUnauthorized(request)) {
return ResponseEntity.status(401).build();
}
adminNoticeService.deleteNotice(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package kr.allcll.backend.admin.notice;

import java.util.List;
import kr.allcll.backend.admin.notice.dto.CreateNoticeRequest;
import kr.allcll.backend.admin.notice.dto.CreateNoticeResponse;
import kr.allcll.backend.admin.notice.dto.NoticesResponse;
import kr.allcll.backend.admin.notice.dto.UpdateNoticeRequest;
import kr.allcll.backend.admin.notice.dto.UpdateNoticeResponse;
import kr.allcll.backend.domain.notice.Notice;
import kr.allcll.backend.domain.notice.NoticeRepository;
import kr.allcll.backend.support.exception.AllcllErrorCode;
import kr.allcll.backend.support.exception.AllcllException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class AdminNoticeService {

private final NoticeRepository noticeRepository;

public NoticesResponse getAllNotice() {
List<Notice> allNotices = noticeRepository.findAllOrderedByCreatedAt();
return NoticesResponse.from(allNotices);
}

@Transactional
public CreateNoticeResponse createNewNotice(CreateNoticeRequest createNoticeRequest) {
Notice notice = noticeRepository.save(Notice.of(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DTO 내부에서 도메인으로 변환하는 로직을 캡슐화한다면, 같은 로직이 layer 층에서 반복되더라도 변경 지점이 줄 것 같아 제안해봅니다~!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분 이전에도 제안해주셨던 것으로 기억하는데요, DTO 내부에서 도메인을 생성하는게 과연 적절한지 쫌 의문입니다.
요청 DTO가 엔티티를 직접 생성하는 것은 계층이 흐려지는 것이라고 느껴집니다. DTO가 엔티티를 알고 있는 것이 변경 지점이 더 늘어나는 설계 아닌가요?!
엔티티 생성 시 요구사항 추가로 인해 로직이 추가된다거나,,의 경우에 DTO도 그 책임을 알게 되는 구조 아닌가요?
DTO마다 엔티티 생성 로직을 넣어두면 오히려 더 관리 리소스가 증가하는 것 아닌가 싶습니다. 저는 책임이 분산되는 것으로 느껴져서요.
결론은 DTO가 엔티티를 알게 되면 계층을 흐려지게 만드는 것 같은데, 진님의 제안에 대한 근거를 더 듣고싶습니다~

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 좀 끄적여봤는데, 상당히 길이가 길어서 읽기 리소스가 보통이 아니더라구요
AI 에게 정리 좀 해달라고 했습니다 ㅎ.ㅎ 슬쩍 한번 확인해주세여


DTO ↔ 도메인 변환 책임을 어디에 둘 것인가

배경

AdminNoticeService는 현재 Notice.of(title, content, operationType)처럼 원시 타입만 받아서 엔티티를 조립하고 있습니다. 도메인이 DTO를 import 하지 않는다는 점에서 의존성 관계는 보이지 않고 있습니다. 다만 한 가지 짚고 싶은 부분이 있어 코멘트로 남깁니다.

무엇이 신경 쓰이는가

이 코드에서 가장 자주 바뀌는 지점은 DTO의 필드, 그 다음이 엔티티의 필드라고 봅니다. 그런데 지금 구조에서는 그 변경이 다음과 같이 번집니다.

DTO에 필드가 하나 추가되면

  1. Notice.of(...)의 시그니처가 바뀌고
  2. Notice.update(...)의 시그니처가 바뀌고
  3. AdminNoticeService의 호출부가 같이 수정됩니다.

Service는 "저장한다 / 갱신한다"만 알면 충분한데, 엔티티의 조립 시그니처를 매번 따라다녀야 하는 셈입니다. 캡슐화 관점에서도 도메인 필드들이 Service 본문에 그대로 나열되어 노출됩니다.

제안

DTO가 자기 자신을 도메인으로 변환하는 책임을 가지면 좋겠습니다.

public record CreateNoticeRequest(...) {
    public Notice toEntity() {
        return Notice.of(title, content, operationType);
    }
}

public record UpdateNoticeRequest(...) {
    public void applyTo(Notice notice) {
        notice.update(title, content, operationType);
    }
}
public CreateNoticeResponse createNewNotice(CreateNoticeRequest request) {
    Notice notice = noticeRepository.save(request.toEntity());
    return CreateNoticeResponse.from(notice);
}

public UpdateNoticeResponse updateNotice(Long id, UpdateNoticeRequest request) {
    Notice notice = noticeRepository.findActiveById(id)
        .orElseThrow(() -> new AllcllException(AllcllErrorCode.NOTICE_NOT_FOUND, id));
    request.applyTo(notice);
    noticeRepository.flush();
    return UpdateNoticeResponse.from(notice);
}

이렇게 했을 때의 이점

  • 변경의 영향 반경이 좁아진다. DTO 필드가 추가되어도 수정 지점은 DTO와 엔티티 두 곳으로 닫히고, Service는 diff에 등장하지 않습니다.
  • Service가 조립 시그니처를 따라다니지 않는다. Service는 도메인에 어떤 필드가 있는지 알 필요가 없고, "저장 / 갱신"이라는 자기 책임만 표현하게 됩니다.
  • 엔티티 안에서는 도메인 정책 코드가 더 잘 보인다. of처럼 변환 로직 없이 필드를 그대로 받기만 하는 정적 팩토리가 계속 늘어나면 정작 중요한 정책 코드가 묻힙니다. 단순 매핑은 DTO 쪽에서 처리하는 편이 자연스럽습니다.
  • 도메인의 의존 방향은 그대로 단방향으로 유지된다. Notice.from(dto) 처럼 도메인이 DTO를 import 하게 만드는 방식과 달리, 의존은 여전히 dto → domain 한 방향입니다.

우려에 대한 답

엔티티 생성 시 요구사항 추가로 인해 로직이 추가된다면, DTO도 그 책임을 알게 되는 구조 아닌가?

지금은 Notice.of가 생성자만 호출해 그대로 반환하는 단순 매핑이라 이 우려는 발생하지 않습니다. 만약 이후에 도메인 정책(검증, 기본값 결정, 다른 객체 조회 등)이 필요해진다면 그 로직은 여전히 Notice 안의 정적 팩토리나 도메인 서비스로 들어가야 하고, DTO의 toEntity()는 그 정책 진입점을 호출만 하면 됩니다. 즉 단순 필드 매핑은 DTO가 책임지고, 도메인 정책은 도메인이 책임진다는 분리는 그대로 유지됩니다.

요약

  • 가장 자주 바뀌는 건 DTO/엔티티 필드인데, 지금 구조에서는 그 변경이 Service까지 따라온다.
  • DTO에 toEntity() / applyTo()를 두면 변경 지점이 DTO·엔티티 두 곳에 닫힌다.
  • 도메인은 여전히 DTO를 모르고, Service는 조립 절차를 모른다 — 각자의 책임만 표현하게 된다.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋네요 해당 부분 반영했습니다~

createNoticeRequest.title(),
createNoticeRequest.content(),
createNoticeRequest.operationType()
));
return CreateNoticeResponse.from(notice);
}

@Transactional
public UpdateNoticeResponse updateNotice(Long id, UpdateNoticeRequest updateNoticeRequest) {
Notice notice = noticeRepository.findActiveById(id)
.orElseThrow(() -> new AllcllException(AllcllErrorCode.NOTICE_NOT_FOUND, id));
notice.update(
updateNoticeRequest.title(),
updateNoticeRequest.content(),
updateNoticeRequest.operationType()
);
return UpdateNoticeResponse.from(notice);
}

@Transactional
public void deleteNotice(Long id) {
Notice notice = noticeRepository.findActiveById(id)
.orElseThrow(() -> new AllcllException(AllcllErrorCode.NOTICE_NOT_FOUND, id));
softDeleteNotice(notice);
}

private void softDeleteNotice(Notice notice) {
notice.delete();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package kr.allcll.backend.admin.notice.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import kr.allcll.backend.domain.operationPeriod.OperationType;

public record CreateNoticeRequest(
@NotBlank
@Size(max = 250)
String title,

@NotBlank
@Size(max = 1000)
String content,

@NotNull
OperationType operationType
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kr.allcll.backend.admin.notice.dto;

import java.time.LocalDateTime;
import kr.allcll.backend.domain.notice.Notice;
import kr.allcll.backend.domain.operationPeriod.OperationType;

public record CreateNoticeResponse(
long id,
String title,
String content,
OperationType operationType,
LocalDateTime createdAt
) {

public static CreateNoticeResponse from(Notice notice) {
return new CreateNoticeResponse(
notice.getId(),
notice.getTitle(),
notice.getContent(),
notice.getOperationType(),
notice.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kr.allcll.backend.admin.notice.dto;

import java.time.LocalDateTime;
import kr.allcll.backend.domain.notice.Notice;
import kr.allcll.backend.domain.operationPeriod.OperationType;

public record NoticeResponse(
long id,
String title,
String content,
OperationType operationType,
LocalDateTime createdAt
) {

public static NoticeResponse from(Notice notice) {
return new NoticeResponse(
notice.getId(),
notice.getTitle(),
notice.getContent(),
notice.getOperationType(),
notice.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kr.allcll.backend.admin.notice.dto;

import java.util.List;
import kr.allcll.backend.domain.notice.Notice;

public record NoticesResponse(
List<NoticeResponse> notices
) {

public static NoticesResponse from(List<Notice> allNotices) {
return new NoticesResponse(
allNotices.stream()
.map(NoticeResponse::from)
.toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kr.allcll.backend.admin.notice.dto;

import jakarta.validation.constraints.Size;
import kr.allcll.backend.domain.operationPeriod.OperationType;

public record UpdateNoticeRequest(
@Size(max = 250)
Copy link
Copy Markdown
Contributor

@2Jin1031 2Jin1031 Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateNoticeRequest에서는 @NotBlank로 DTO 측에서 한번 검증을 하고 들어온 것 같은데 update에서는 제외하신 이유가 있으실까요?? Notice.update 에서 null 검증을 하는 것보다 UpdateNoticeRequest에도 @NotBlank를 붙이면 DTO 단에서 검증이 끝나고, 도메인의 Notice.update에서는 null 체크를 뺄 수 있어 더 깔끔해질 것 같아서용

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PATCH 요청이라 수정하지 않은 필드는 보내지 않을 것으로 보고 있어서, 해당 값들은 null로 들어올 수 있다고 판단했습니다.
그래서 UpdateNoticeRequest에 @NotBlank를 적용하지 않았습니다~ 명세상 잘못된 요청이 아니기 때문에 Request에서 막지는 않고, update 시 유효한 값만 Notice에서 추가해주는 것이 자연스럽다고 생각했습니다.
어떻게 생각하시나요? 다른 방법이 좋아보이시나요?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아,, 맞네요
update 시 미 업데이트인 null 데이터 ,, 이부분저도 고민했던 부분이었던 것 같습니다
나중에 더 엔티티가 커지면 관리 지점이 늘어나긴 ㅎㅏ겟지만 가능성이 크지 않은 것 같아서 지금 방향성이 좋은 것 같아요!
감사합니다!!

String title,

@Size(max = 1000)
String content,
Comment on lines +7 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject blank notice fields on partial updates

The update DTO only enforces @Size, so values like "" or whitespace-only strings pass validation and are then persisted by Notice.update(...). This allows existing notices to end up with empty title/content even though creation explicitly requires non-blank fields, creating inconsistent data rules and potentially unreadable notices in admin/user views.

Useful? React with 👍 / 👎.


OperationType operationType
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kr.allcll.backend.admin.notice.dto;

import java.time.LocalDateTime;
import kr.allcll.backend.domain.notice.Notice;
import kr.allcll.backend.domain.operationPeriod.OperationType;

public record UpdateNoticeResponse(
long id,
String title,
String content,
OperationType operationType,
LocalDateTime createdAt
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 공지 작성 시각보다는,
수정 이후의 상태를 보여주는 의미에서 공지 수정 시각 같은 정보를 내려주는 쪽이 더 자연스럽지 않을까 생각했습니당!

Copy link
Copy Markdown
Member Author

@boyekim boyekim Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흠...그러네요 BaseEntity에 updatedAt을 추가했는데 어떠신가요?
AdminNoticeServiceupdateNotice메서드에서도 응답 반환 전 flush 호출로 중간 반영 시켜주어, response에 정확한 updatedAt이 담기도록 했습니다

(-> 추후 추가 작업사항: CrawlerBaseEntity에도 똑같이 만들어주기)

) {

public static UpdateNoticeResponse from(Notice notice) {
return new UpdateNoticeResponse(
notice.getId(),
notice.getTitle(),
notice.getContent(),
notice.getOperationType(),
notice.getCreatedAt()
);
}
}
65 changes: 65 additions & 0 deletions src/main/java/kr/allcll/backend/domain/notice/Notice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package kr.allcll.backend.domain.notice;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import kr.allcll.backend.domain.operationPeriod.OperationType;
import kr.allcll.backend.support.entity.BaseEntity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Table(name = "notices")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Notice extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 250)
private String title;

@Column(nullable = false, length = 1000)
private String content;

@Enumerated(EnumType.STRING)
private OperationType operationType;
Copy link
Copy Markdown
Contributor

@2Jin1031 2Jin1031 Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Column(nullable = false) 이것도 함께 추가하는 건 어떻게 생각하시나요??
enum으로 관리되고 있어서 null이 비지니스상 필요하진 않을 것 같아서요!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 그렇네요. 추가했습니다


private Notice(String title, String content, OperationType operationType) {
this.title = title;
this.content = content;
this.operationType = operationType;
}

public static Notice of(String title, String content, OperationType operationType) {
return new Notice(
title,
content,
operationType
);
}

public void update(String title, String content, OperationType operationType) {
if (title != null) {
this.title = title;
}
if (content != null) {
this.content = content;
}
if (operationType != null) {
this.operationType = operationType;
}
}

public void delete() {
super.delete();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kr.allcll.backend.domain.notice;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface NoticeRepository extends JpaRepository<Notice, Long> {

@Query("""
select n from Notice n
where n.isDeleted = false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 @SQLRestriction("deleted_at IS NULL") 이걸 도메인 위의 어노테이션으로 붙이면서 까묵지 않고 처리할 수 있게 될 것 같아 제안드려봅니당

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이러면 조건을 항상 추가해줄 수 있군요?! 엔티티 전체적으로 반영하면 좋을 것 같아서 @SQLDelete과 함께 pr로 따로 묶어서 올리는것 어떨까요?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다~!

order by n.createdAt desc
""")
List<Notice> findAllOrderedByCreatedAt();

@Query("""
select n from Notice n
where n.id = :id
and n.isDeleted = false
""")
Optional<Notice> findActiveById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

@Getter
public enum OperationType {
ALL("all"),
TIMETABLE("timetable"),
BASKETS("baskets"),
SIMULATION("simulation"),
LIVE("live"),
PRESEAT("preseat"),
GRADUATION("graduation");
GRADUATION("graduation"),
REVIEW("review"),
;

private final String value;

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/kr/allcll/backend/domain/review/UserReview.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kr.allcll.backend.domain.review;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
Expand Down Expand Up @@ -27,6 +28,7 @@ public class UserReview {
@Enumerated(EnumType.STRING)
private OperationType operationType;

@Column(nullable = false, length = 1000)
private String detail;

public UserReview(String studentId, Short rate, OperationType operationType, String detail) {
Expand Down
Loading
Loading