diff --git a/build.gradle b/build.gradle index 77b3104..4224428 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,11 @@ dependencies { // Goolge OAuth2 implementation 'com.google.api-client:google-api-client:2.8.0' + + // jsch 라이브러리, for ssh tunneling + implementation 'com.github.mwiede:jsch:0.2.15' + // application.yml의 환경 변수를 class에서 사용하기 위한 라이브러리 + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' // Apache Commons Lang // implementation 'org.apache.commons:commons-lang3:3.12.0' diff --git a/src/main/java/com/ikdaman/domain/notice/controller/NoticeController.java b/src/main/java/com/ikdaman/domain/notice/controller/NoticeController.java new file mode 100644 index 0000000..448071a --- /dev/null +++ b/src/main/java/com/ikdaman/domain/notice/controller/NoticeController.java @@ -0,0 +1,45 @@ +package com.ikdaman.domain.notice.controller; + +import com.ikdaman.domain.notice.model.NoticeListRes; +import com.ikdaman.domain.notice.model.NoticeReq; +import com.ikdaman.domain.notice.model.NoticeRes; +import com.ikdaman.domain.notice.service.NoticeService; +import com.ikdaman.global.auth.model.AuthMember; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/notices") +@RequiredArgsConstructor +public class NoticeController { + + private final NoticeService noticeService; + + // 공지사항 등록 + @PostMapping + public ResponseEntity addNotice( + @AuthenticationPrincipal AuthMember authMember, + @RequestBody NoticeReq request) { + noticeService.addNotice(request); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @GetMapping + public ResponseEntity getNotices( + @AuthenticationPrincipal AuthMember authMember, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "9") Integer limit) { + return ResponseEntity.ok(noticeService.getNotices(page, limit)); + } + + // 공지사항 상세 조회 + @GetMapping("/{notice_id}") + public ResponseEntity getNotice( + @AuthenticationPrincipal AuthMember authMember, + @PathVariable("notice_id") Long noticeId) { + return ResponseEntity.ok(noticeService.getNotice(noticeId)); + } +} diff --git a/src/main/java/com/ikdaman/domain/notice/entity/Notice.java b/src/main/java/com/ikdaman/domain/notice/entity/Notice.java new file mode 100644 index 0000000..d6ab14c --- /dev/null +++ b/src/main/java/com/ikdaman/domain/notice/entity/Notice.java @@ -0,0 +1,49 @@ +package com.ikdaman.domain.notice.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DynamicInsert +@DynamicUpdate +public class Notice { + @Id + @GeneratedValue + @Column(name = "notice_id", nullable = false, updatable = false) + private int noticeId; + + @Column(length = 100) + @Size(min = 1, max = 100) + private String title; + + @Column(name = "notice_writer", length = 10) + @Size(min = 1, max = 10) + private String noticeWriter; + + @Column(columnDefinition = "TEXT") + private String content; + + @Column(updatable = false, name = "uploaded_at") + private LocalDateTime uploadedAt; + + @Builder + public Notice(String title, String noticeWriter, String content, LocalDateTime uploadedAt) { + this.title = title; + this.noticeWriter = noticeWriter; + this.content = content; + this.uploadedAt = uploadedAt; + } +} \ No newline at end of file diff --git a/src/main/java/com/ikdaman/domain/notice/model/NoticeListRes.java b/src/main/java/com/ikdaman/domain/notice/model/NoticeListRes.java new file mode 100644 index 0000000..602e18b --- /dev/null +++ b/src/main/java/com/ikdaman/domain/notice/model/NoticeListRes.java @@ -0,0 +1,24 @@ +package com.ikdaman.domain.notice.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@AllArgsConstructor +public class NoticeListRes { + private List notices; + private boolean hasNext; + private Integer currentPage; + private Integer totalPages; + + @Getter + @AllArgsConstructor + public static class NoticeListDTO { + private int noticeId; + private String title; + private LocalDateTime uploadedAt; + } +} diff --git a/src/main/java/com/ikdaman/domain/notice/model/NoticeReq.java b/src/main/java/com/ikdaman/domain/notice/model/NoticeReq.java new file mode 100644 index 0000000..76e0c1c --- /dev/null +++ b/src/main/java/com/ikdaman/domain/notice/model/NoticeReq.java @@ -0,0 +1,21 @@ +package com.ikdaman.domain.notice.model; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class NoticeReq { + private String title; + private String noticeWriter; + private String content; + + @Builder + public NoticeReq(String title, String noticeWriter, String content) { + this.title = title; + this.noticeWriter = noticeWriter; + this.content = content; + } +} diff --git a/src/main/java/com/ikdaman/domain/notice/model/NoticeRes.java b/src/main/java/com/ikdaman/domain/notice/model/NoticeRes.java new file mode 100644 index 0000000..7cea3c0 --- /dev/null +++ b/src/main/java/com/ikdaman/domain/notice/model/NoticeRes.java @@ -0,0 +1,26 @@ +package com.ikdaman.domain.notice.model; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor +public class NoticeRes { + private int noticeId; + private String title; + private String content; + private LocalDateTime uploadedAt; + private String noticeWriter; + + @Builder + public NoticeRes(int noticeId, String title, String content, LocalDateTime uploadedAt, String noticeWriter) { + this.noticeId = noticeId; + this.title = title; + this.content = content; + this.uploadedAt = uploadedAt; + this.noticeWriter = noticeWriter; + } +} diff --git a/src/main/java/com/ikdaman/domain/notice/notice/Notice.java b/src/main/java/com/ikdaman/domain/notice/notice/Notice.java deleted file mode 100644 index f49ba26..0000000 --- a/src/main/java/com/ikdaman/domain/notice/notice/Notice.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.ikdaman.domain.notice.notice; - -public class Notice { -} diff --git a/src/main/java/com/ikdaman/domain/notice/repository/NoticeRepository.java b/src/main/java/com/ikdaman/domain/notice/repository/NoticeRepository.java new file mode 100644 index 0000000..7194a8c --- /dev/null +++ b/src/main/java/com/ikdaman/domain/notice/repository/NoticeRepository.java @@ -0,0 +1,8 @@ +package com.ikdaman.domain.notice.repository; + +import com.ikdaman.domain.notice.entity.Notice; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NoticeRepository extends JpaRepository { + +} diff --git a/src/main/java/com/ikdaman/domain/notice/service/NoticeService.java b/src/main/java/com/ikdaman/domain/notice/service/NoticeService.java new file mode 100644 index 0000000..c9d1467 --- /dev/null +++ b/src/main/java/com/ikdaman/domain/notice/service/NoticeService.java @@ -0,0 +1,11 @@ +package com.ikdaman.domain.notice.service; + +import com.ikdaman.domain.notice.model.NoticeListRes; +import com.ikdaman.domain.notice.model.NoticeReq; +import com.ikdaman.domain.notice.model.NoticeRes; + +public interface NoticeService { + NoticeRes addNotice(NoticeReq noticeReq); + NoticeListRes getNotices(Integer page, Integer limit); + NoticeRes getNotice(Long noticeId); +} diff --git a/src/main/java/com/ikdaman/domain/notice/service/NoticeServiceImpl.java b/src/main/java/com/ikdaman/domain/notice/service/NoticeServiceImpl.java new file mode 100644 index 0000000..d924118 --- /dev/null +++ b/src/main/java/com/ikdaman/domain/notice/service/NoticeServiceImpl.java @@ -0,0 +1,90 @@ +package com.ikdaman.domain.notice.service; + +import com.ikdaman.domain.notice.entity.Notice; +import com.ikdaman.domain.notice.model.NoticeListRes; +import com.ikdaman.domain.notice.model.NoticeReq; +import com.ikdaman.domain.notice.model.NoticeRes; +import com.ikdaman.domain.notice.repository.NoticeRepository; +import com.ikdaman.global.exception.BaseException; +import com.ikdaman.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor +public class NoticeServiceImpl implements NoticeService { + + private final NoticeRepository noticeRepository; + + @Override + @Transactional + public NoticeRes addNotice(NoticeReq request) { + Notice notice = Notice.builder() + .title(request.getTitle()) + .noticeWriter( + request.getNoticeWriter() == null || request.getNoticeWriter().isBlank() + ? "관리자" + : request.getNoticeWriter()) + .content(request.getContent()) + .uploadedAt(LocalDateTime.now()) + .build(); + + Notice savedNotice = noticeRepository.save(notice); + + return NoticeRes.builder() + .noticeId(savedNotice.getNoticeId()) + .title(savedNotice.getTitle()) + .content(savedNotice.getContent()) + .uploadedAt(savedNotice.getUploadedAt()) + .noticeWriter(savedNotice.getNoticeWriter()) + .build(); + } + + @Override + @Transactional(readOnly = true) + public NoticeListRes getNotices(Integer page, Integer limit) { + Pageable pageable = PageRequest.of(page - 1, limit, Sort.by(Sort.Direction.DESC, "uploadedAt")); + + Page noticePage = noticeRepository.findAll(pageable); + + List noticeDTOs = noticePage.stream() + .map(notice -> new NoticeListRes.NoticeListDTO( + notice.getNoticeId(), + notice.getTitle(), + notice.getUploadedAt() + )) + .toList(); + + return new NoticeListRes( + noticeDTOs, + noticePage.hasNext(), + noticePage.getNumber() + 1, + noticePage.getTotalPages() + 1 + ); + } + + @Override + @Transactional(readOnly = true) + public NoticeRes getNotice(Long id) { + Notice notice = noticeRepository.findById(id) + .orElseThrow(() -> new BaseException(ErrorCode.NOT_FOUND_NOTICE)); + + return new NoticeRes( + notice.getNoticeId(), + notice.getTitle(), + notice.getContent(), + notice.getUploadedAt(), + notice.getNoticeWriter() + ); + } +} diff --git a/src/main/java/com/ikdaman/global/exception/ErrorCode.java b/src/main/java/com/ikdaman/global/exception/ErrorCode.java index ffc0c7f..1fbfead 100644 --- a/src/main/java/com/ikdaman/global/exception/ErrorCode.java +++ b/src/main/java/com/ikdaman/global/exception/ErrorCode.java @@ -64,6 +64,7 @@ public enum ErrorCode { EMPTY_IMPRESSION(HttpStatus.BAD_REQUEST.value(), 4040303, "첫인상을 입력해주세요."), // Notice(04) + NOT_FOUND_NOTICE(HttpStatus.NOT_FOUND.value(), 4040301, "해당하는 공지사항이 존재하지 않습니다."), /** * 409 Conflict