From 0355391a4b3ff45270de06a436e6a6e6ace9f852 Mon Sep 17 00:00:00 2001 From: Leafguyk <81000589+Leafguyk@users.noreply.github.com> Date: Sun, 5 Jan 2025 20:33:02 +0900 Subject: [PATCH 1/7] Auth User --- build.gradle.kts | 2 + .../toyproject/memoWithTags/user/AuthUser.kt | 5 ++ .../toyproject/memoWithTags/user/JwtUtil.kt | 63 +++++++++++++++++++ .../memoWithTags/user/UserArgumentResolver.kt | 39 ++++++++++++ .../memoWithTags/user/UserException.kt | 6 ++ .../memoWithTags/user/contoller/User.kt | 19 ++++++ .../user/contoller/UserController.kt | 5 -- .../user/persistence/UserEntity.kt | 19 +++++- .../user/persistence/UserRepository.kt | 9 +++ .../memoWithTags/user/service/UserService.kt | 50 ++++++++++++++- src/main/resources/application.properties | 1 + src/main/resources/application.yml | 3 + .../MemoWithTagsApplicationTests.kt | 13 ++++ 13 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/AuthUser.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/JwtUtil.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/UserArgumentResolver.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/UserException.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/contoller/User.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/persistence/UserRepository.kt create mode 100644 src/main/resources/application.properties create mode 100644 src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt diff --git a/build.gradle.kts b/build.gradle.kts index e21f718..b738b3f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,8 +31,10 @@ dependencies { implementation("io.jsonwebtoken:jjwt-api:0.11.5") implementation("io.jsonwebtoken:jjwt-impl:0.11.5") implementation("io.jsonwebtoken:jjwt-jackson:0.11.5") + implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-oauth2-client") testImplementation("org.springframework.boot:spring-boot-starter-test") + annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") } kotlin { diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/AuthUser.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/AuthUser.kt new file mode 100644 index 0000000..52b0aa3 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/AuthUser.kt @@ -0,0 +1,5 @@ +package com.wafflestudio.toyproject.memoWithTags.user + +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.RUNTIME) +annotation class AuthUser diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/JwtUtil.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/JwtUtil.kt new file mode 100644 index 0000000..ab65e70 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/JwtUtil.kt @@ -0,0 +1,63 @@ +package com.wafflestudio.toyproject.memoWithTags.user + +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.SignatureAlgorithm +import io.jsonwebtoken.security.Keys +import java.util.Date +import javax.crypto.SecretKey + +object JwtUtil { + private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256) + private const val ACCESS_TOKEN_EXPIRATION: Long = 1000 * 60 * 120 // 2시간 + private const val REFRESH_TOKEN_EXPIRATION: Long = 1000 * 60 * 60 * 24 * 14 // 14일 + + // Access Token 생성 + fun generateAccessToken(userEmail: String): String { + return Jwts.builder() + .setSubject(userEmail) + .setIssuedAt(Date()) + .setExpiration(Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION)) + .signWith(secretKey) + .compact() + } + + // Refresh Token 생성 + fun generateRefreshToken(userEmail: String): String { + return Jwts.builder() + .setSubject(userEmail) + .setIssuedAt(Date()) + .setExpiration(Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION)) + .signWith(secretKey) + .compact() + } + + // Access Token 검증 및 userEmail + fun validateAccessTokenGetUserId(accessToken: String): String? { + return try { + val claims = + Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(accessToken) + .body + if (claims.expiration.before(Date())) { + null + } else { + claims.subject + } + } catch (e: Exception) { + null + } + } +} + +// catch (e: ExpiredJwtException) { +// // 토큰 만료 처리 +// null +// } catch (e: MalformedJwtException) { +// // 잘못된 토큰 처리 +// null +// } catch (e: Exception) { +// // 기타 예외 처리 +// null +// } 예외 분리해서 이후 처리 diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/UserArgumentResolver.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/UserArgumentResolver.kt new file mode 100644 index 0000000..3a9ef32 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/UserArgumentResolver.kt @@ -0,0 +1,39 @@ +package com.wafflestudio.toyproject.memoWithTags.user + +import com.wafflestudio.toyproject.memoWithTags.user.contoller.User +import com.wafflestudio.toyproject.memoWithTags.user.service.UserService +import org.springframework.core.MethodParameter +import org.springframework.stereotype.Component +import org.springframework.web.bind.support.WebDataBinderFactory +import org.springframework.web.context.request.NativeWebRequest +import org.springframework.web.method.support.HandlerMethodArgumentResolver +import org.springframework.web.method.support.ModelAndViewContainer + +@Component +class UserArgumentResolver( + private val userService: UserService +) : HandlerMethodArgumentResolver { + override fun supportsParameter(parameter: MethodParameter): Boolean { + return parameter.parameterType == User::class.java && + parameter.hasParameterAnnotation(AuthUser::class.java) + } + + override fun resolveArgument( + parameter: MethodParameter, + mavContainer: ModelAndViewContainer?, + webRequest: NativeWebRequest, + binderFactory: WebDataBinderFactory? + ): Any? { + return runCatching { + val accessToken = requireNotNull( + webRequest.getHeader("Authorization")?.split(" ")?.let { + if (it.getOrNull(0) == "Bearer") it.getOrNull(1) else null + } + ) + + userService.authenticate(accessToken) + }.getOrElse { _ -> + throw AuthenticationFailedException() + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/UserException.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/UserException.kt new file mode 100644 index 0000000..bfe9021 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/UserException.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.toyproject.memoWithTags.user + +sealed class UserException : RuntimeException() + +class EmailNotFoundException : UserException() +class AuthenticationFailedException : UserException() diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/contoller/User.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/contoller/User.kt new file mode 100644 index 0000000..a02fcc3 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/contoller/User.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.toyproject.memoWithTags.user.contoller + +import com.wafflestudio.toyproject.memoWithTags.user.persistence.UserEntity + +class User( + val id: Long, + val email: String, + val nickname: String +) { + companion object { + fun fromEntity(entity: UserEntity): User { + return User( + id = entity.id!!, + email = entity.email, + nickname = entity.nickname + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/contoller/UserController.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/contoller/UserController.kt index 82858f9..76a8a12 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/contoller/UserController.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/contoller/UserController.kt @@ -18,11 +18,6 @@ class UserController { return LoginResponse("", "", 0L) // 기본값으로 LoginResponse 반환 } - @PostMapping("/api/v1/auth/logout") - fun logout(@RequestBody request: LogoutRequest) { - // 반환 타입이 Unit이므로 아무 작업 없이 비워 둬도 OK - } - @PostMapping("/api/v1/auth/verify-email") fun verifyEmail(@RequestBody request: VerifyEmailRequest) { // 반환 타입이 Unit이므로 아무 작업 없이 비워 둬도 OK diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/persistence/UserEntity.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/persistence/UserEntity.kt index a6d5720..b7e5efe 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/persistence/UserEntity.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/persistence/UserEntity.kt @@ -1,3 +1,20 @@ package com.wafflestudio.toyproject.memoWithTags.user.persistence -class UserEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id + +@Entity(name = "users") +class UserEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + @Column(name = "email", nullable = false) + var email: String, + @Column(name = "nickname", nullable = false) + var nickname: String = "Writer", + @Column(name = "hashed_password", nullable = false) + var hashedPassword: String +) diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/persistence/UserRepository.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/persistence/UserRepository.kt new file mode 100644 index 0000000..4c8a66f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/persistence/UserRepository.kt @@ -0,0 +1,9 @@ +package com.wafflestudio.toyproject.memoWithTags.user.persistence + +import org.springframework.data.jpa.repository.JpaRepository + +interface UserRepository : JpaRepository { + fun findByEmail(username: String): UserEntity? + + fun existsByEmail(username: String): Boolean +} diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/service/UserService.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/service/UserService.kt index fe93156..b276d92 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/service/UserService.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/service/UserService.kt @@ -1,3 +1,51 @@ package com.wafflestudio.toyproject.memoWithTags.user.service -class UserService +import com.wafflestudio.toyproject.memoWithTags.user.AuthenticationFailedException +import com.wafflestudio.toyproject.memoWithTags.user.EmailNotFoundException +import com.wafflestudio.toyproject.memoWithTags.user.JwtUtil +import com.wafflestudio.toyproject.memoWithTags.user.contoller.User +import com.wafflestudio.toyproject.memoWithTags.user.persistence.UserEntity +import com.wafflestudio.toyproject.memoWithTags.user.persistence.UserRepository +import org.mindrot.jbcrypt.BCrypt +import org.springframework.stereotype.Service + +@Service +class UserService( + private val userRepository: UserRepository +) { + fun register( + email: String, + password: String + ): Boolean { + val encryptedPassword = BCrypt.hashpw(password, BCrypt.gensalt()) + val user = userRepository.save( + UserEntity( + email = email, + hashedPassword = encryptedPassword + ) + ) + // TODO: email 발송 + return true + } + + fun verifyEmail( + verificationCode: String + ): Boolean { + // TODO: 인증 코드 대조 + return true + } + + fun login( + email: String, + password: String + ): User { + val user = userRepository.findByEmail(email) ?: throw EmailNotFoundException() + return User.fromEntity(user) + } + + fun authenticate(token: String): User { + val userEmail = JwtUtil.validateAccessTokenGetUserId(token) ?: throw AuthenticationFailedException() + val user = userRepository.findByEmail(userEmail) ?: throw AuthenticationFailedException() + return User.fromEntity(user) + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..478c709 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=memo-with-tags diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6ced7bb..52a62b8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,6 @@ spring: profiles: active: ${SPRING_PROFILES_ACTIVE:dev} # 기본값 dev + + +# 이후 jwt도 yml에 넣어서 관리 예정 \ No newline at end of file diff --git a/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt b/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt new file mode 100644 index 0000000..6aa6912 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt @@ -0,0 +1,13 @@ +package com.wafflestudio.toyproject.memo_with_tags + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class MemoWithTagsApplicationTests { + + @Test + fun contextLoads() { + } + +} From 826c92d59ae1c5e74d69c0a2ed1b3ea7babb062c Mon Sep 17 00:00:00 2001 From: Leafguyk <81000589+Leafguyk@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:02:01 +0900 Subject: [PATCH 2/7] Update MemoWithTagsApplicationTests.kt --- .../memo_with_tags/MemoWithTagsApplicationTests.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt b/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt index 6aa6912..6e1f72f 100644 --- a/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt +++ b/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt @@ -6,8 +6,7 @@ import org.springframework.boot.test.context.SpringBootTest @SpringBootTest class MemoWithTagsApplicationTests { - @Test - fun contextLoads() { - } - + @Test + fun contextLoads() { + } } From bbf451f8cc7d1221349f33a223ec1c7448275540 Mon Sep 17 00:00:00 2001 From: Leafguyk <81000589+Leafguyk@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:10:05 +0900 Subject: [PATCH 3/7] Create MemoWithTagsApplication.kt --- .../memo_with_tags/MemoWithTagsApplication.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplication.kt diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplication.kt b/src/main/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplication.kt new file mode 100644 index 0000000..090037f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplication.kt @@ -0,0 +1,11 @@ +package com.wafflestudio.toyproject.memo_with_tags + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class MemoWithTagsApplication + +fun main(args: Array) { + runApplication(*args) +} From 03b7deb394ad9804dcdbc6937c1e34316b548454 Mon Sep 17 00:00:00 2001 From: Leafguyk <81000589+Leafguyk@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:12:07 +0900 Subject: [PATCH 4/7] Tag Feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tag 관련 Endpoint 추가 - 태그 생성 - 태그 업데이트 - 태그 삭제 --- .../memo/persistence/MemoAndTag.kt | 24 ++++++++++ .../memoWithTags/tag/TagException.kt | 6 +++ .../memoWithTags/tag/controller/Tag.kt | 7 +++ .../tag/controller/TagController.kt | 42 +++++++++++------ .../memoWithTags/tag/persistence/TagEntity.kt | 26 ++++++++++- .../tag/persistence/TagRepository.kt | 7 +++ .../memoWithTags/tag/service/TagService.kt | 45 ++++++++++++++++++- 7 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/memo/persistence/MemoAndTag.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/TagException.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/controller/Tag.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagRepository.kt diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/memo/persistence/MemoAndTag.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/memo/persistence/MemoAndTag.kt new file mode 100644 index 0000000..042820f --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/memo/persistence/MemoAndTag.kt @@ -0,0 +1,24 @@ +package com.wafflestudio.toyproject.memoWithTags.memo.persistence + +import com.wafflestudio.toyproject.memoWithTags.tag.persistence.TagEntity +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity(name = "memo_and_tag") +class MemoAndTag( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @ManyToOne + @JoinColumn(name = "memo_id") + var memo: MemoEntity, + + @ManyToOne + @JoinColumn(name = "tag_id") + var tag: TagEntity +) diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/TagException.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/TagException.kt new file mode 100644 index 0000000..95dc2c2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/TagException.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.toyproject.memoWithTags.tag + +sealed class TagException : RuntimeException() + +class TagNotFoundException : TagException() +class WrongUserException : TagException() diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/controller/Tag.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/controller/Tag.kt new file mode 100644 index 0000000..aef6c83 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/controller/Tag.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.toyproject.memoWithTags.tag.controller + +data class Tag( + val id: Long, + val name: String, + val color: String +) diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/controller/TagController.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/controller/TagController.kt index fd1de3b..b6326f2 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/controller/TagController.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/controller/TagController.kt @@ -1,7 +1,14 @@ package com.wafflestudio.toyproject.memoWithTags.tag.controller +import com.wafflestudio.toyproject.memoWithTags.tag.TagNotFoundException +import com.wafflestudio.toyproject.memoWithTags.tag.WrongUserException import com.wafflestudio.toyproject.memoWithTags.tag.service.TagService +import com.wafflestudio.toyproject.memoWithTags.user.AuthUser +import com.wafflestudio.toyproject.memoWithTags.user.AuthenticationFailedException +import com.wafflestudio.toyproject.memoWithTags.user.contoller.User +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping @@ -14,31 +21,38 @@ class TagController( private val tagService: TagService ) { @GetMapping("/api/v1/tag") - fun getTags(): List { - return emptyList() // 빈 리스트 반환 + fun getTags(@AuthUser user: User): List { + return tagService.getTags(user) } @PostMapping("/api/v1/tag") - fun createTag(@RequestBody request: CreateTagRequest): TagDto { - return TagDto(0L, "", "") // 기본값으로 TagDto 반환 + fun createTag(@RequestBody request: CreateTagRequest, @AuthUser user: User): Tag { + val tag = tagService.createTag(request.name, request.color, user) + return tag } @PutMapping("/api/v1/tag/{tagId}") - fun updateTag(@PathVariable id: Long, @RequestBody request: UpdateTagRequest): TagDto { - return TagDto(0L, "", "") // 기본값으로 TagDto 반환 + fun updateTag(@PathVariable tagId: Long, @RequestBody request: UpdateTagRequest, @AuthUser user: User): Tag { + return tagService.updateTag(tagId, request.name, request.color, user) } @DeleteMapping("/api/v1/tag/{tagId}") - fun deleteTag(@PathVariable id: Long) { - // 반환 타입이 Unit이므로 아무 작업 없이 비워 둬도 OK + fun deleteTag(@PathVariable tagId: Long, @AuthUser user: User): ResponseEntity { + tagService.deleteTag(tagId, user) + return ResponseEntity.noContent().build() } -} -data class TagDto( - val id: Long, - val name: String, - val color: String -) + @ExceptionHandler + fun handleTagNotFoundException(e: Exception): ResponseEntity { + val status = when (e) { + is TagNotFoundException -> 404 + is WrongUserException -> 403 + is AuthenticationFailedException -> 401 + else -> 404 + } + return ResponseEntity.status(status).build() + } +} data class CreateTagRequest( val name: String, diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagEntity.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagEntity.kt index 95169a6..08bf2e3 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagEntity.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagEntity.kt @@ -1,3 +1,27 @@ package com.wafflestudio.toyproject.memoWithTags.tag.persistence -class TagEntity +import com.wafflestudio.toyproject.memoWithTags.user.persistence.UserEntity +import jakarta.persistence.* +import java.time.Instant + +@Entity(name = "tags") +class TagEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name = "name", nullable = false) + var name: String, + + @Column(name = "color", nullable = false) + var color: String, + + @ManyToOne + var user: UserEntity, + + @Column(name = "created_at", nullable = false) + val createdAt: Instant = Instant.now(), + + @Column(name = "updated_at") + var updatedAt: Instant? = null +) diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagRepository.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagRepository.kt new file mode 100644 index 0000000..6cb0fa5 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagRepository.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.toyproject.memoWithTags.tag.persistence + +import org.springframework.data.jpa.repository.JpaRepository + +interface TagRepository : JpaRepository { + fun findByUserId(userId: Long): List +} diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/service/TagService.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/service/TagService.kt index 3339676..bef5d34 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/service/TagService.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/service/TagService.kt @@ -1,6 +1,49 @@ package com.wafflestudio.toyproject.memoWithTags.tag.service +import com.wafflestudio.toyproject.memoWithTags.tag.TagNotFoundException +import com.wafflestudio.toyproject.memoWithTags.tag.WrongUserException +import com.wafflestudio.toyproject.memoWithTags.tag.controller.Tag +import com.wafflestudio.toyproject.memoWithTags.tag.persistence.TagEntity +import com.wafflestudio.toyproject.memoWithTags.tag.persistence.TagRepository +import com.wafflestudio.toyproject.memoWithTags.user.AuthenticationFailedException +import com.wafflestudio.toyproject.memoWithTags.user.contoller.User +import com.wafflestudio.toyproject.memoWithTags.user.persistence.UserRepository import org.springframework.stereotype.Service +import java.time.Instant @Service -class TagService +class TagService( + private val tagRepository: TagRepository, + private val userRepository: UserRepository +) { + fun getTags(user: User): List { + return tagRepository.findByUserId(user.id).map { Tag(it.id!!, it.name, it.color) } + } + + fun createTag(name: String, color: String, user: User): Tag { + val userEntity = userRepository.findByEmail(user.email) ?: throw AuthenticationFailedException() + val tagEntity = TagEntity(name = name, color = color, user = userEntity) + val savedTagEntity = tagRepository.save(tagEntity) + return Tag(savedTagEntity.id!!, savedTagEntity.name, savedTagEntity.color) + } + + fun updateTag(id: Long, name: String, color: String, user: User): Tag { + val tagEntity = tagRepository.findById(id).orElseThrow { throw TagNotFoundException() } + if (tagEntity.user.email != user.email) { + throw WrongUserException() + } + tagEntity.name = name + tagEntity.color = color + tagEntity.updatedAt = Instant.now() + val savedTagEntity = tagRepository.save(tagEntity) + return Tag(savedTagEntity.id!!, savedTagEntity.name, savedTagEntity.color) + } + + fun deleteTag(tagId: Long, user: User) { + val tagEntity = tagRepository.findById(tagId).orElseThrow { throw TagNotFoundException() } + if (tagEntity.user.email != user.email) { + throw WrongUserException() + } + tagRepository.delete(tagEntity) + } +} From 71e98ff68f9e59eb6aa35496fbfcc054e161eb6b Mon Sep 17 00:00:00 2001 From: Leafguyk <81000589+Leafguyk@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:13:49 +0900 Subject: [PATCH 5/7] wildcard fix --- .../toyproject/memoWithTags/tag/persistence/TagEntity.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagEntity.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagEntity.kt index 08bf2e3..afd7b78 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagEntity.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/tag/persistence/TagEntity.kt @@ -1,7 +1,12 @@ package com.wafflestudio.toyproject.memoWithTags.tag.persistence import com.wafflestudio.toyproject.memoWithTags.user.persistence.UserEntity -import jakarta.persistence.* +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.ManyToOne import java.time.Instant @Entity(name = "tags") From 3406e318a3d52e9253ba8ac8e62b15ba8c108142 Mon Sep 17 00:00:00 2001 From: Leafguyk <81000589+Leafguyk@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:30:25 +0900 Subject: [PATCH 6/7] ktlint fix --- src/main/resources/application.properties | 2 +- .../memo_with_tags/MemoWithTagsApplicationTests.kt | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 478c709..8b13789 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1 @@ -spring.application.name=memo-with-tags + diff --git a/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt b/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt index 6e1f72f..e69de29 100644 --- a/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt +++ b/src/test/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplicationTests.kt @@ -1,12 +0,0 @@ -package com.wafflestudio.toyproject.memo_with_tags - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class MemoWithTagsApplicationTests { - - @Test - fun contextLoads() { - } -} From ed4eb0a852020d654a9bc726e0fe21b51167c540 Mon Sep 17 00:00:00 2001 From: Leafguyk <81000589+Leafguyk@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:31:25 +0900 Subject: [PATCH 7/7] ktlint fix --- .../memo_with_tags/MemoWithTagsApplication.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplication.kt b/src/main/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplication.kt index 090037f..e69de29 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplication.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memo_with_tags/MemoWithTagsApplication.kt @@ -1,11 +0,0 @@ -package com.wafflestudio.toyproject.memo_with_tags - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication - -@SpringBootApplication -class MemoWithTagsApplication - -fun main(args: Array) { - runApplication(*args) -}