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

feat: 공지사항 수정, 삭제, 태그 기능 추가 #3

Merged
merged 9 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
runtimeOnly("com.mysql:mysql-connector-j")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.wafflestudio.csereal.common.config

import com.wafflestudio.csereal.common.CserealException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.validation.BindingResult
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import java.sql.SQLIntegrityConstraintViolationException

@RestControllerAdvice
class CserealExceptionHandler {

// @Valid로 인해 오류 떴을 때 메시지 전송
@ExceptionHandler(value = [MethodArgumentNotValidException::class])
fun handle(e: MethodArgumentNotValidException): ResponseEntity<Any> {
val bindingResult: BindingResult = e.bindingResult
return ResponseEntity(bindingResult.fieldError?.defaultMessage, HttpStatus.BAD_REQUEST)
}

// csereal 내부 규정 오류
@ExceptionHandler(value = [CserealException::class])
fun handle(e: CserealException): ResponseEntity<Any> {
return ResponseEntity(e.message, e.status)
}

// db에서 중복된 값 있을 때
@ExceptionHandler(value = [SQLIntegrityConstraintViolationException::class])
fun handle(e: SQLIntegrityConstraintViolationException): ResponseEntity<Any> {
return ResponseEntity("중복된 값이 있습니다.", HttpStatus.CONFLICT)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package com.wafflestudio.csereal.core.notice.api

import com.wafflestudio.csereal.core.notice.dto.CreateNoticeRequest
import com.wafflestudio.csereal.core.notice.dto.NoticeDto
import com.wafflestudio.csereal.core.notice.dto.UpdateNoticeRequest
import com.wafflestudio.csereal.core.notice.service.NoticeService
import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RequestMapping("/notice")
Expand All @@ -19,8 +23,31 @@ class NoticeController(

@PostMapping
fun createNotice(
@RequestBody request: CreateNoticeRequest
@Valid @RequestBody request: CreateNoticeRequest
) : NoticeDto {
return noticeService.createNotice(request)
}

@PatchMapping("/{noticeId}")
fun updateNotice(
@PathVariable noticeId: Long,
@Valid @RequestBody request: UpdateNoticeRequest,
) : NoticeDto {
return noticeService.updateNotice(noticeId, request)
}

@DeleteMapping("/{noticeId}")
fun deleteNotice(
@PathVariable noticeId: Long
) {
noticeService.deleteNotice(noticeId)
}

@PostMapping("/tag")
fun enrollTag(
@RequestBody tagName: Map<String, String>
) : ResponseEntity<String> {
noticeService.enrollTag(tagName["name"]!!)
return ResponseEntity<String>("등록되었습니다. (tagName: ${tagName["name"]})", HttpStatus.OK)
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
package com.wafflestudio.csereal.core.notice.database

import com.wafflestudio.csereal.common.config.BaseTimeEntity
import jakarta.persistence.CascadeType
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.OneToMany


@Entity(name = "notice")
class NoticeEntity(

@Column
var isDeleted: Boolean = false,

@Column
var title: String,

@Column(columnDefinition = "text")
var description: String
var description: String,

// var postType: String,
//
// var isPublic: Boolean,
//
// var isSlide: Boolean,
//
// var isPinned: Boolean,

@OneToMany(mappedBy = "notice", cascade = [CascadeType.ALL])
var noticeTag: MutableSet<NoticeTagEntity> = mutableSetOf()
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
): BaseTimeEntity() {

fun setNoticeTag(noticeTag: NoticeTagEntity) {

this.noticeTag.add(noticeTag)
noticeTag.notice = this
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.wafflestudio.csereal.core.notice.database

import com.wafflestudio.csereal.common.config.BaseTimeEntity
import jakarta.persistence.*

@Entity(name="noticeTag")
class NoticeTagEntity(
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "notice_id")
var notice: NoticeEntity,

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tag_id")
var tag: TagEntity,

) : BaseTimeEntity() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wafflestudio.csereal.core.notice.database

import org.springframework.data.jpa.repository.JpaRepository

interface NoticeTagRepository : JpaRepository<NoticeTagEntity, Long> {
fun findAllByNoticeId(noticeId: Long)
fun deleteAllByNoticeId(noticeId: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.wafflestudio.csereal.core.notice.database

import com.wafflestudio.csereal.common.config.BaseTimeEntity
import jakarta.persistence.CascadeType
import jakarta.persistence.Entity
import jakarta.persistence.OneToMany

@Entity(name = "tag")
class TagEntity(
var name: String,

@OneToMany(mappedBy = "tag")
val noticeTag: MutableSet<NoticeTagEntity> = mutableSetOf()
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
) : BaseTimeEntity() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wafflestudio.csereal.core.notice.database

import org.springframework.data.jpa.repository.JpaRepository

interface TagRepository : JpaRepository<TagEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.wafflestudio.csereal.core.notice.dto

import jakarta.validation.constraints.NotBlank

data class CreateNoticeRequest(
@field:NotBlank(message = "제목은 비어있을 수 없습니다")
val title: String,
val description: String

@field:NotBlank(message = "내용은 비어있을 수 없습니다")
val description: String,
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved

val tag: List<Long> = emptyList()
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.time.LocalDateTime

data class NoticeDto(
val id: Long,
val isDeleted: Boolean,
val title: String,
val description: String,
// val postType: String,
Expand All @@ -20,10 +21,15 @@ data class NoticeDto(
fun of(entity: NoticeEntity): NoticeDto = entity.run {
NoticeDto(
id = this.id,
isDeleted = false,
title = this.title,
description = this.description,
// postType = this.postType,
createdAt = this.createdAt,
modifiedAt = this.modifiedAt
modifiedAt = this.modifiedAt,
// isPublic = this.isPublic,
// isSlide = this.isSlide,
// isPinned = this.isPinned,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wafflestudio.csereal.core.notice.dto

data class UpdateNoticeRequest(
val title: String?,
val description: String?,
val tag: List<Long>?
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
) {
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,103 @@
package com.wafflestudio.csereal.core.notice.service

import com.wafflestudio.csereal.common.CserealException
import com.wafflestudio.csereal.core.notice.database.NoticeEntity
import com.wafflestudio.csereal.core.notice.database.NoticeRepository
import com.wafflestudio.csereal.core.notice.database.*
import com.wafflestudio.csereal.core.notice.dto.CreateNoticeRequest
import com.wafflestudio.csereal.core.notice.dto.NoticeDto
import com.wafflestudio.csereal.core.notice.dto.UpdateNoticeRequest
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

interface NoticeService {
fun readNotice(noticeId: Long): NoticeDto
fun createNotice(request: CreateNoticeRequest): NoticeDto
fun updateNotice(noticeId: Long, request: UpdateNoticeRequest): NoticeDto
fun deleteNotice(noticeId: Long)
fun enrollTag(tagName: String)
}

@Service
class NoticeServiceImpl(
private val noticeRepository: NoticeRepository,
private val tagRepository: TagRepository,
private val noticeTagRepository: NoticeTagRepository
) : NoticeService {

@Transactional(readOnly = true)
override fun readNotice(noticeId: Long): NoticeDto {
val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId)
?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId)")
if (notice.isDeleted) throw CserealException.Csereal400("삭제된 공지사항입니다.(noticeId: $noticeId)")
return NoticeDto.of(notice)
}

@Transactional
override fun createNotice(request: CreateNoticeRequest): NoticeDto {
// TODO:"아직 날짜가 제대로 안 뜸"
val newNotice = NoticeEntity(
title = request.title,
description = request.description,
)

for (i: Int in 0 until request.tag.size) {
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
newNotice.setNoticeTag(
NoticeTagEntity(
notice = newNotice,
tag = tagRepository.findByIdOrNull(request.tag[i])
?: throw CserealException.Csereal400("해당하는 태그가 없습니다")
)
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
)
}

noticeRepository.save(newNotice)

return NoticeDto.of(newNotice)

}

@Transactional
override fun updateNotice(noticeId: Long, request: UpdateNoticeRequest): NoticeDto {
val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId)
?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId")
if (notice.isDeleted) throw CserealException.Csereal400("삭제된 공지사항입니다.(noticeId: $noticeId")

notice.title = request.title ?: notice.title
notice.description = request.description ?: notice.description

if (request.tag != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

request.tag 가 null 인 경우는 어떤 경우인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

프론트에서 수정사항을 보낼때 tag에서 변화가 없는 상황이면 null을 보내는 상황을 생각했습니다. 일반적으로 tag 변화 없어도 수정때 null로 보내는 게 아니라면 말해주면 수정하겠습니다. 케바케면 프론트랑 논의하고 거기에 맞출게요

Copy link
Contributor

Choose a reason for hiding this comment

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

네 이 부분은 프론트랑 소통이 필요해보이네요

noticeTagRepository.deleteAllByNoticeId(noticeId)
notice.noticeTag = mutableSetOf()
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
for (i: Int in 0 until request.tag.size) {
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
notice.setNoticeTag(
NoticeTagEntity(
notice = notice,
tag = tagRepository.findByIdOrNull(request.tag[i])
?: throw CserealException.Csereal400("해당하는 태그가 없습니다")
)
leeeryboy marked this conversation as resolved.
Show resolved Hide resolved
)
}
}



return NoticeDto.of(notice)
}

@Transactional
override fun deleteNotice(noticeId: Long) {
val notice: NoticeEntity = noticeRepository.findByIdOrNull(noticeId)
?: throw CserealException.Csereal400("존재하지 않는 공지사항입니다.(noticeId: $noticeId)")

notice.isDeleted = true

}

override fun enrollTag(tagName: String) {
val newTag = TagEntity(
name = tagName
)
tagRepository.save(newTag)
}
Comment on lines +85 to +90
Copy link
Contributor

Choose a reason for hiding this comment

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

@transactional 이 필요해보이는데, 어제 회의에서 태그는 그냥 고정으로 가져가자 하면 enrollTag 기능 자체가 딱히 필요없을것 같기도 합니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아.. 이거는 가끔 db 갈아엎고 태그 만들 때 쓰고 있는 애라서 나중에 없애도록 할게요.


//TODO: 이미지 등록, 페이지네이션, 검색
}
7 changes: 6 additions & 1 deletion src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ spring:
---
spring:
config.activate.on-profile: local
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/csereal_local?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul
username: root
password: toor
jpa:
hibernate:
ddl-auto: create
ddl-auto: update
show-sql: true
logging.level:
default: INFO
Expand Down