Skip to content

Commit 4342713

Browse files
authored
Merge pull request #140 from Dugout-Developers/feature/#136
feat : 공지사항 CRUD API 기능 구현
2 parents 460825e + cd5a475 commit 4342713

File tree

9 files changed

+393
-1
lines changed

9 files changed

+393
-1
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.back.catchmate.domain.admin.notice.controller;
2+
3+
import com.back.catchmate.domain.admin.notice.dto.NoticeRequest;
4+
import com.back.catchmate.domain.admin.notice.dto.NoticeResponse;
5+
import com.back.catchmate.domain.admin.notice.service.NoticeService;
6+
import com.back.catchmate.global.jwt.JwtValidation;
7+
import com.fasterxml.jackson.annotation.JsonFormat;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import io.swagger.v3.oas.annotations.Parameter;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import jakarta.validation.Valid;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.data.domain.Pageable;
14+
import org.springframework.data.domain.Sort;
15+
import org.springframework.data.web.PageableDefault;
16+
import org.springframework.web.bind.annotation.*;
17+
18+
import java.time.LocalDate;
19+
20+
@Tag(name = "공지사항 API")
21+
@RestController
22+
@RequestMapping("/admin/notice")
23+
@RequiredArgsConstructor
24+
public class NoticeController {
25+
private final NoticeService noticeService;
26+
27+
@PostMapping
28+
@Operation(summary = "공지글 등록 API", description = "공지글을 등록하는 API 입니다.")
29+
public NoticeResponse.NoticeInfo create(@JwtValidation Long userId,
30+
@Valid @RequestBody NoticeRequest.CreateNoticeRequest request) {
31+
return noticeService.create(userId, request);
32+
}
33+
34+
@PutMapping("/{noticeId}")
35+
@Operation(summary = "공지글 수정 API", description = "공지글을 수정하는 API 입니다.")
36+
public NoticeResponse.NoticeInfo update(@JwtValidation Long userId,
37+
@PathVariable Long noticeId,
38+
@Valid @RequestBody NoticeRequest.UpdateNoticeRequest request) {
39+
return noticeService.update(userId, noticeId, request);
40+
}
41+
42+
@DeleteMapping("/{noticeId}")
43+
@Operation(summary = "공지글 삭제 API", description = "공지글을 삭제하는 API 입니다.")
44+
public void delete(@JwtValidation Long userId, @PathVariable Long noticeId) {
45+
noticeService.delete(userId, noticeId);
46+
}
47+
48+
@GetMapping("/list")
49+
@Operation(summary = "공지사항 목록 조회 API", description = "공지사항 목록을 페이징하여 조회합니다.")
50+
public NoticeResponse.PagedNoticeInfo getNoticeList(
51+
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") LocalDate startDate,
52+
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") LocalDate endDate,
53+
@PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC)
54+
@Parameter(hidden = true) Pageable pageable) {
55+
return noticeService.getNoticeList(startDate, endDate, pageable);
56+
}
57+
58+
@GetMapping("/{noticeId}")
59+
@Operation(summary = "공지사항 단일 조회 API", description = "특정 공지사항을 조회합니다.")
60+
public NoticeResponse.NoticeInfo getNotice(@PathVariable Long noticeId) {
61+
return noticeService.getNotice(noticeId);
62+
}
63+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.back.catchmate.domain.admin.notice.converter;
2+
3+
import com.back.catchmate.domain.admin.notice.dto.NoticeRequest;
4+
import com.back.catchmate.domain.admin.notice.dto.NoticeResponse;
5+
import com.back.catchmate.domain.admin.notice.entity.Notice;
6+
import com.back.catchmate.domain.admin.notice.repository.NoticeRepository;
7+
import com.back.catchmate.domain.user.converter.UserConverter;
8+
import com.back.catchmate.domain.user.entity.User;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.data.domain.Page;
11+
import org.springframework.stereotype.Component;
12+
13+
import java.util.List;
14+
import java.util.stream.Collectors;
15+
16+
@Component
17+
@RequiredArgsConstructor
18+
public class NoticeConverter {
19+
private final NoticeRepository noticeRepository;
20+
private final UserConverter userConverter;
21+
22+
public Notice toEntity(User user, NoticeRequest.CreateNoticeRequest request) {
23+
return Notice.builder()
24+
.user(user)
25+
.title(request.getTitle())
26+
.content(request.getContent())
27+
.build();
28+
}
29+
30+
public Notice toUpdatedEntity(Notice notice, NoticeRequest.UpdateNoticeRequest request) {
31+
return Notice.builder()
32+
.id(notice.getId()) // 기존 ID 유지
33+
.user(notice.getUser()) // 기존 유저 유지
34+
.title(request.getTitle())
35+
.content(request.getContent())
36+
.build();
37+
}
38+
39+
public NoticeResponse.NoticeInfo toNoticeInfo(Notice notice) {
40+
return NoticeResponse.NoticeInfo.builder()
41+
.noticeId(notice.getId())
42+
.title(notice.getTitle())
43+
.content(notice.getContent())
44+
.createdAt(notice.getCreatedAt())
45+
.updatedAt(notice.getUpdatedAt())
46+
.build();
47+
}
48+
49+
public NoticeResponse.PagedNoticeInfo toPagedNoticeInfo(Page<Notice> noticePage) {
50+
List<NoticeResponse.NoticeInfo> noticeInfos = noticePage.getContent().stream()
51+
.map(this::toNoticeInfo)
52+
.collect(Collectors.toList());
53+
54+
return NoticeResponse.PagedNoticeInfo.builder()
55+
.notices(noticeInfos)
56+
.totalPages(noticePage.getTotalPages())
57+
.totalElements(noticePage.getTotalElements())
58+
.currentPage(noticePage.getNumber())
59+
.size(noticePage.getSize())
60+
.build();
61+
}
62+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.back.catchmate.domain.admin.notice.dto;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
public abstract class NoticeRequest {
10+
@Getter
11+
@Builder
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
public static class CreateNoticeRequest {
15+
@NotNull
16+
private String title;
17+
@NotNull
18+
private String content;
19+
}
20+
21+
@Getter
22+
@Builder
23+
@NoArgsConstructor
24+
@AllArgsConstructor
25+
public static class UpdateNoticeRequest {
26+
@NotNull
27+
private String title;
28+
@NotNull
29+
private String content;
30+
}
31+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.back.catchmate.domain.admin.notice.dto;
2+
3+
import com.back.catchmate.domain.admin.notice.entity.Notice;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
import org.springframework.data.domain.Page;
9+
10+
import java.time.LocalDateTime;
11+
import java.util.List;
12+
import java.util.stream.Collectors;
13+
14+
public abstract class NoticeResponse {
15+
16+
@Getter
17+
@Builder
18+
@NoArgsConstructor
19+
@AllArgsConstructor
20+
public static class NoticeInfo {
21+
private Long noticeId;
22+
private String title;
23+
private String content;
24+
private LocalDateTime createdAt;
25+
private LocalDateTime updatedAt;
26+
27+
public static NoticeInfo from(Notice notice) {
28+
return NoticeInfo.builder()
29+
.noticeId(notice.getId())
30+
.title(notice.getTitle())
31+
.content(notice.getContent())
32+
.createdAt(notice.getCreatedAt())
33+
.updatedAt(notice.getUpdatedAt())
34+
.build();
35+
}
36+
}
37+
38+
@Getter
39+
@Builder
40+
@AllArgsConstructor
41+
public static class PagedNoticeInfo {
42+
private List<NoticeInfo> notices;
43+
private int totalPages;
44+
private long totalElements;
45+
private int currentPage;
46+
private int size;
47+
48+
public static PagedNoticeInfo from(Page<Notice> noticePage) {
49+
return PagedNoticeInfo.builder()
50+
.notices(noticePage.getContent().stream()
51+
.map(NoticeInfo::from)
52+
.collect(Collectors.toList()))
53+
.totalPages(noticePage.getTotalPages())
54+
.totalElements(noticePage.getTotalElements())
55+
.currentPage(noticePage.getNumber())
56+
.size(noticePage.getSize())
57+
.build();
58+
}
59+
}
60+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.back.catchmate.domain.admin.notice.entity;
2+
3+
import com.back.catchmate.domain.user.entity.User;
4+
import com.back.catchmate.global.entity.BaseTimeEntity;
5+
import jakarta.persistence.*;
6+
import lombok.*;
7+
8+
import java.time.LocalDateTime;
9+
10+
@Entity
11+
@Getter
12+
@Builder
13+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
14+
@AllArgsConstructor
15+
@Table(name = "notices")
16+
public class Notice extends BaseTimeEntity {
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.IDENTITY)
19+
@Column(name = "board_id")
20+
private Long id;
21+
22+
@Column(nullable = false)
23+
private String title;
24+
25+
@Column(nullable = false)
26+
private String content;
27+
28+
@ManyToOne(fetch = FetchType.LAZY)
29+
@JoinColumn(name = "user_id", nullable = false)
30+
private User user;
31+
32+
@Column(name = "deleted_at")
33+
private LocalDateTime deletedAt;
34+
35+
// 💡 공지사항 삭제 (Soft Delete)
36+
public void softDelete() {
37+
this.deletedAt = LocalDateTime.now();
38+
}
39+
40+
// 💡 삭제 여부 확인
41+
public boolean isDeleted() {
42+
return this.deletedAt != null;
43+
}
44+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.back.catchmate.domain.admin.notice.repository;
2+
3+
import com.back.catchmate.domain.admin.notice.entity.Notice;
4+
import org.springframework.data.domain.Page;
5+
import org.springframework.data.domain.Pageable;
6+
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Query;
8+
import org.springframework.data.repository.query.Param;
9+
10+
import java.time.LocalDateTime;
11+
import java.util.Optional;
12+
13+
public interface NoticeRepository extends JpaRepository<Notice, Long> {
14+
15+
@Query("SELECT n FROM Notice n " +
16+
"WHERE (:startDateTime IS NULL OR n.createdAt >= :startDateTime) " +
17+
"AND (:endDateTime IS NULL OR n.createdAt <= :endDateTime) " +
18+
"AND n.deletedAt IS NULL") // Soft Delete 적용
19+
Page<Notice> findNoticesWithinDateRange(@Param("startDateTime") LocalDateTime startDateTime,
20+
@Param("endDateTime") LocalDateTime endDateTime,
21+
Pageable pageable);
22+
23+
Optional<Notice> findByIdAndDeletedAtIsNull(Long id);
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.back.catchmate.domain.admin.notice.service;
2+
3+
import com.back.catchmate.domain.admin.notice.dto.NoticeRequest;
4+
import com.back.catchmate.domain.admin.notice.dto.NoticeResponse;
5+
import org.springframework.data.domain.Pageable;
6+
7+
import java.time.LocalDate;
8+
9+
public interface NoticeService {
10+
NoticeResponse.NoticeInfo create(Long userId, NoticeRequest.CreateNoticeRequest noticeRequest);
11+
12+
NoticeResponse.NoticeInfo update(Long userId, Long noticeId, NoticeRequest.UpdateNoticeRequest request);
13+
14+
void delete(Long userId, Long noticeId);
15+
16+
NoticeResponse.NoticeInfo getNotice(Long noticeId);
17+
18+
NoticeResponse.PagedNoticeInfo getNoticeList(LocalDate startDate, LocalDate endDate, Pageable pageable);
19+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.back.catchmate.domain.admin.notice.service;
2+
3+
import com.back.catchmate.domain.admin.notice.converter.NoticeConverter;
4+
import com.back.catchmate.domain.admin.notice.dto.NoticeRequest;
5+
import com.back.catchmate.domain.admin.notice.dto.NoticeResponse;
6+
import com.back.catchmate.domain.admin.notice.entity.Notice;
7+
import com.back.catchmate.domain.admin.notice.repository.NoticeRepository;
8+
import com.back.catchmate.domain.user.entity.User;
9+
import com.back.catchmate.domain.user.repository.UserRepository;
10+
import com.back.catchmate.global.error.ErrorCode;
11+
import com.back.catchmate.global.error.exception.BaseException;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.data.domain.Page;
14+
import org.springframework.data.domain.Pageable;
15+
import org.springframework.stereotype.Service;
16+
import org.springframework.transaction.annotation.Transactional;
17+
18+
import java.time.LocalDate;
19+
import java.time.LocalDateTime;
20+
import java.time.LocalTime;
21+
22+
@Service
23+
@RequiredArgsConstructor
24+
public class NoticeServiceImpl implements NoticeService {
25+
private final UserRepository userRepository;
26+
private final NoticeConverter noticeConverter;
27+
private final NoticeRepository noticeRepository;
28+
29+
@Override
30+
public NoticeResponse.NoticeInfo create(Long userId, NoticeRequest.CreateNoticeRequest noticeRequest) {
31+
User user = userRepository.findById(userId)
32+
.orElseThrow(() -> new BaseException(ErrorCode.USER_NOT_FOUND));
33+
34+
Notice notice = noticeConverter.toEntity(user, noticeRequest);
35+
notice = noticeRepository.save(notice);
36+
37+
return noticeConverter.toNoticeInfo(notice);
38+
}
39+
40+
@Override
41+
public NoticeResponse.NoticeInfo update(Long userId, Long noticeId, NoticeRequest.UpdateNoticeRequest request) {
42+
Notice notice = noticeRepository.findById(noticeId)
43+
.orElseThrow(() -> new BaseException(ErrorCode.NOTICE_NOT_FOUND));
44+
45+
// if (!notice.getUser().getId().equals(userId)) {
46+
// throw new BaseException(ErrorCode.FORBIDDEN);
47+
// }
48+
49+
Notice updatedNotice = noticeConverter.toUpdatedEntity(notice, request);
50+
noticeRepository.save(updatedNotice);
51+
52+
return noticeConverter.toNoticeInfo(updatedNotice);
53+
}
54+
55+
@Override
56+
public void delete(Long userId, Long noticeId) {
57+
Notice notice = noticeRepository.findById(noticeId)
58+
.orElseThrow(() -> new BaseException(ErrorCode.NOTICE_NOT_FOUND));
59+
60+
// if (!notice.getUser().getId().equals(userId)) {
61+
// throw new BaseException(ErrorCode.FORBIDDEN);
62+
// }
63+
64+
notice.softDelete();
65+
noticeRepository.save(notice);
66+
}
67+
68+
@Override
69+
@Transactional(readOnly = true)
70+
public NoticeResponse.NoticeInfo getNotice(Long noticeId) {
71+
Notice notice = noticeRepository.findByIdAndDeletedAtIsNull(noticeId)
72+
.orElseThrow(() -> new BaseException(ErrorCode.NOTICE_NOT_FOUND));
73+
74+
return noticeConverter.toNoticeInfo(notice);
75+
}
76+
77+
@Override
78+
@Transactional(readOnly = true)
79+
public NoticeResponse.PagedNoticeInfo getNoticeList(LocalDate startDate, LocalDate endDate, Pageable pageable) {
80+
LocalDateTime startDateTime = (startDate != null) ? startDate.atStartOfDay() : null;
81+
LocalDateTime endDateTime = (endDate != null) ? endDate.atTime(LocalTime.MAX) : null;
82+
83+
Page<Notice> notices = noticeRepository.findNoticesWithinDateRange(startDateTime, endDateTime, pageable);
84+
return noticeConverter.toPagedNoticeInfo(notices);
85+
}
86+
}

0 commit comments

Comments
 (0)