Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] notice 관련 기능 구현 #16

Merged
merged 33 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b8c9315
notice entity 정의
k-kbk Sep 26, 2023
df82249
notice CRUD 구현
k-kbk Sep 27, 2023
0c5a53d
ktlint
k-kbk Sep 27, 2023
ed7e910
[etc] restdocs와 openapi3를 이용한 swagger 생성 (#15)
dojinyou Sep 27, 2023
9f7406b
[etc] sycn sql & update final id (#17)
dojinyou Sep 27, 2023
bdc2121
[etc] springmockk 의존성 추가 및 mockito-core 제외 (#19)
dojinyou Sep 29, 2023
e0f9cc9
[etc] codecov 불필요한 동작 제거 (#21)
dojinyou Sep 29, 2023
9505601
[feature] Category Domain 정의 및 CRUD 개발 (#18)
dojinyou Sep 29, 2023
e0d4ef4
[etc] codecov 설정 파일 추가 (#22)
dojinyou Sep 29, 2023
4c03247
[etc] timezone 제거 및 Instant 사용 (#20)
dojinyou Sep 29, 2023
64cd199
아키텍쳐 리팩토링
k-kbk Sep 29, 2023
179c5d8
Merge branch 'main' into notice
k-kbk Sep 29, 2023
fdab756
formatting
k-kbk Sep 29, 2023
0960413
리뷰 반영
k-kbk Sep 30, 2023
dbb6a46
string validate 메서드 변경
k-kbk Sep 30, 2023
91a0689
[etc] jacoco 설정 및 테스트 수정 (#23)
dojinyou Sep 29, 2023
fd259ba
[feature] store 도메인 정의 및 sql 추가 및 test 작성 (#24)
dojinyou Sep 30, 2023
6733e9f
[feature] storehours domain 정의 및 테스트 작성 (#25)
dojinyou Oct 1, 2023
c635573
[etc] restdocs와 openapi3를 이용한 swagger 생성 (#15)
dojinyou Sep 27, 2023
05a9ccd
[etc] sycn sql & update final id (#17)
dojinyou Sep 27, 2023
10c0f8d
[etc] springmockk 의존성 추가 및 mockito-core 제외 (#19)
dojinyou Sep 29, 2023
5736a6b
[etc] codecov 불필요한 동작 제거 (#21)
dojinyou Sep 29, 2023
93d176a
[feature] Category Domain 정의 및 CRUD 개발 (#18)
dojinyou Sep 29, 2023
be37e69
[etc] timezone 제거 및 Instant 사용 (#20)
dojinyou Sep 29, 2023
ded30da
테스트 코드 작성
k-kbk Oct 8, 2023
88866ea
충돌 해결
k-kbk Oct 8, 2023
6b862de
줄바꿈
k-kbk Oct 8, 2023
92d3852
Update src/main/kotlin/com/mjucow/eatda/domain/notice/service/command…
k-kbk Oct 12, 2023
6b5ece3
Update src/main/kotlin/com/mjucow/eatda/domain/notice/service/query/N…
k-kbk Oct 12, 2023
eb8a540
리뷰 반영
k-kbk Oct 12, 2023
d9f94df
리뷰 반영
k-kbk Oct 15, 2023
dd036d5
테스트 수정
k-kbk Oct 15, 2023
7603c51
NoticeMother 추가
k-kbk Oct 15, 2023
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
36 changes: 36 additions & 0 deletions src/main/kotlin/com/mjucow/eatda/domain/notice/entity/Notice.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.mjucow.eatda.domain.notice.entity

import com.mjucow.eatda.domain.common.BaseEntity
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Table

@Entity
@Table(name = "notice")
class Notice() : BaseEntity() {
constructor(
title: String,
content: String,
) : this() {
this.title = title
this.content = content
}

@Column(nullable = false)
var title: String = ""
set(value) {
validateValue(value)
Copy link
Member

Choose a reason for hiding this comment

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

title은 길이 제한에 대해서도 검증이 되어야 할 것 같음!

field = value
}

@Column(nullable = false)
var content: String = ""
set(value) {
validateValue(value)
field = value
}

private fun validateValue(value: String) {
require(value.isNotBlank())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.mjucow.eatda.domain.notice.service.command

import com.mjucow.eatda.domain.notice.entity.Notice
import com.mjucow.eatda.domain.notice.service.command.dto.CreateNoticeCommand
import com.mjucow.eatda.domain.notice.service.command.dto.UpdateNoticeCommand
import com.mjucow.eatda.persistence.notice.NoticeRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional
class NoticeCommandService(
private val repository: NoticeRepository,
) {
fun create(request: CreateNoticeCommand): Long {
return repository.save(Notice(request.title, request.content)).id
}

fun update(noticeId: Long, command: UpdateNoticeCommand) {
val (newTitle, newContent) = command
val notice = repository.findByIdOrNull(noticeId)?.apply {
title = newTitle
content = newContent
} ?: throw IllegalArgumentException()

repository.save(notice)
k-kbk marked this conversation as resolved.
Show resolved Hide resolved
}

fun deleteById(noticeId: Long) {
val notice = repository.findByIdOrNull(noticeId) ?: return

repository.delete(notice)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.mjucow.eatda.domain.notice.service.command.dto

data class CreateNoticeCommand(
val title: String,
val content: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.mjucow.eatda.domain.notice.service.command.dto

data class UpdateNoticeCommand(
val title: String,
val content: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mjucow.eatda.domain.notice.service.query

import com.mjucow.eatda.domain.notice.service.query.dto.NoticeDto
import com.mjucow.eatda.domain.notice.service.query.dto.Notices
import com.mjucow.eatda.persistence.notice.NoticeRepository
import jakarta.persistence.EntityNotFoundException
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional


@Service
@Transactional(readOnly = true)
class NoticeQueryService(
private val repository: NoticeRepository,
) {
fun findAll(): Notices {
return Notices(repository.findAllByOrderByIdDesc().map(NoticeDto::from))
k-kbk marked this conversation as resolved.
Show resolved Hide resolved
}

fun findById(noticeId: Long): NoticeDto {
val notice = repository.findByIdOrNull(noticeId)
?: throw EntityNotFoundException("공지사항이 존재하지 않습니다.")

return NoticeDto.from(notice)
k-kbk marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mjucow.eatda.domain.notice.service.query.dto

import com.mjucow.eatda.domain.notice.entity.Notice
import java.time.Instant

data class NoticeDto(
val id: Long,
val title: String,
val content: String,
val createdAt: Instant,
) {
companion object {
fun from(domain: Notice): NoticeDto {
return NoticeDto(
domain.id,
domain.title,
domain.content,
domain.createdAt
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mjucow.eatda.domain.notice.service.query.dto

data class Notices(
val noticeList: List<NoticeDto>,
) : ArrayList<NoticeDto>(noticeList)
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Category() : BaseEntity() {
private fun validateName(name: String) {
require(name.isNotBlank() && name.trim().length <= MAX_NAME_LENGTH)
}

companion object {
const val MAX_NAME_LENGTH = 31
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mjucow.eatda.persistence.notice

import com.mjucow.eatda.domain.notice.entity.Notice
import org.springframework.data.jpa.repository.JpaRepository

interface NoticeRepository : JpaRepository<Notice, Long> {
fun findAllByOrderByIdDesc(): List<Notice>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.mjucow.eatda.presentation.notice

import com.mjucow.eatda.domain.notice.service.command.NoticeCommandService
import com.mjucow.eatda.domain.notice.service.command.dto.CreateNoticeCommand
import com.mjucow.eatda.domain.notice.service.command.dto.UpdateNoticeCommand
import com.mjucow.eatda.domain.notice.service.query.NoticeQueryService
import com.mjucow.eatda.domain.notice.service.query.dto.NoticeDto
import com.mjucow.eatda.domain.notice.service.query.dto.Notices
import com.mjucow.eatda.presentation.common.ApiResponse
import org.springframework.http.HttpStatus
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.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController

@RequestMapping("/api/v1/notices")
@RestController
class NoticeController(
private val noticeQueryService: NoticeQueryService,
private val noticeCommandService: NoticeCommandService,
) {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun create(
@RequestBody command: CreateNoticeCommand,
): ApiResponse<Long> {
return ApiResponse.success(noticeCommandService.create(command))
}

@GetMapping
@ResponseStatus(HttpStatus.OK)
fun findAll(): ApiResponse<Notices> {
return ApiResponse.success(noticeQueryService.findAll())
}

@GetMapping("/{noticeId}")
@ResponseStatus(HttpStatus.OK)
fun findById(
@PathVariable("noticeId") noticeId: Long,
): ApiResponse<NoticeDto> {
return ApiResponse.success(noticeQueryService.findById(noticeId))
}

@PatchMapping("/{noticeId}")
@ResponseStatus(HttpStatus.OK)
fun update(
@PathVariable("noticeId") noticeId: Long,
@RequestBody command: UpdateNoticeCommand,
) {
noticeCommandService.update(noticeId, command)
}

@DeleteMapping("/{noticeId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteById(
@PathVariable("noticeId") noticeId: Long,
) {
noticeCommandService.deleteById(noticeId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class CategoryController(
@ResponseStatus(HttpStatus.CREATED)
fun create(@RequestBody command: CreateCommand): ApiResponse<Long> {
val id = categoryCommandService.create(command)

return ApiResponse.success(id)
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ spring:
connection-timeout: 1100
keepalive-time: 30000
validation-timeout: 1000
max-lifetime: 600000
max-lifetime: 600000
2 changes: 1 addition & 1 deletion src/main/resources/db/changelog/230925-init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ CREATE TABLE category (
);

ALTER TABLE category ADD CONSTRAINT PK_CATEGORY PRIMARY KEY (id);
CREATE UNIQUE INDEX idx_category_name ON category(name);
CREATE UNIQUE INDEX idx_category_name ON category(name);
11 changes: 11 additions & 0 deletions src/main/resources/db/changelog/231004-notioce.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- liquibase formatted sql

-- changeset liquibase:2
CREATE TABLE notice
(
id bigserial NOT NULL PRIMARY KEY,
title varchar(31) NOT NULL,
content text NOT NULL,
created_at timestamp NOT NULL DEFAULT NOW(),
updated_at timestamp NOT NULL
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.mjucow.eatda.domain.notice.entity

import org.assertj.core.api.Assertions
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EmptySource

class NoticeTest {
@DisplayName("제목이 빈 값일 경우 예외를 반환한다")
@ParameterizedTest
@EmptySource
fun task1(title: String) {
// given
val content = "content"

// when
val throwable = Assertions.catchThrowable { Notice(title, content) }

// then
Assertions.assertThat(throwable).isInstanceOf(RuntimeException::class.java)
}

@DisplayName("내용이 빈 값일 경우 예외를 반환한다")
@ParameterizedTest
@EmptySource
fun task2(content: String) {
// given
val title = "title"

// when
val throwable = Assertions.catchThrowable { Notice(title, content) }

// then
Assertions.assertThat(throwable).isInstanceOf(RuntimeException::class.java)
}

@DisplayName("새로운 제목이 빈 값일 경우 예외를 반환한다")
@ParameterizedTest
@EmptySource
fun task3(newTitle: String) {
// given
val notice = Notice("title", "content")

// when
val throwable = Assertions.catchThrowable { notice.title = newTitle }

// then
Assertions.assertThat(throwable).isInstanceOf(RuntimeException::class.java)
}

@DisplayName("새로운 내용이 빈 값일 경우 예외를 반환한다")
@ParameterizedTest
@EmptySource
fun task4(newContent: String) {
// given
val notice = Notice("title", "content")

// when
val throwable = Assertions.catchThrowable { notice.content = newContent }

// then
Assertions.assertThat(throwable).isInstanceOf(RuntimeException::class.java)
}

@DisplayName("정상적인 경우 객체가 생성된다")
@Test
fun task5() {
// given
val title = "title"
val content = "content"

// when
val notice = Notice(title, content)

// then
Assertions.assertThat(notice).isNotNull
}
}
Loading
Loading