diff --git a/src/main/kotlin/com/ixfp/gitmon/client/github/GithubApiService.kt b/src/main/kotlin/com/ixfp/gitmon/client/github/GithubApiService.kt index 3c493a3..99708ff 100644 --- a/src/main/kotlin/com/ixfp/gitmon/client/github/GithubApiService.kt +++ b/src/main/kotlin/com/ixfp/gitmon/client/github/GithubApiService.kt @@ -59,13 +59,14 @@ class GithubApiService( githubUsername: String, repo: String, path: String, - commitMessage: String = "Upsert File by API", + commitMessage: String, + sha: String = "", ): GithubContent { val request = GithubUpsertFileRequest( - message = "Add New File", + message = commitMessage, content = Base64Encoder.encodeBase64(content), - sha = "", + sha = sha, ) val response = githubResourceApiClient.upsertFile( diff --git a/src/main/kotlin/com/ixfp/gitmon/controller/IPostingController.kt b/src/main/kotlin/com/ixfp/gitmon/controller/IPostingController.kt index 7a5857d..68a3d6f 100644 --- a/src/main/kotlin/com/ixfp/gitmon/controller/IPostingController.kt +++ b/src/main/kotlin/com/ixfp/gitmon/controller/IPostingController.kt @@ -103,4 +103,11 @@ interface IPostingController { githubUsername: String, postingId: Long, ): ResponseEntity> + + fun updatePosting( + id: Long, + title: String, + content: MultipartFile, + member: Member, + ): ResponseEntity> } diff --git a/src/main/kotlin/com/ixfp/gitmon/controller/PostingController.kt b/src/main/kotlin/com/ixfp/gitmon/controller/PostingController.kt index 268d5d7..98d76c7 100644 --- a/src/main/kotlin/com/ixfp/gitmon/controller/PostingController.kt +++ b/src/main/kotlin/com/ixfp/gitmon/controller/PostingController.kt @@ -12,6 +12,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestAttribute import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam @@ -37,6 +38,20 @@ class PostingController( return ApiResponseHelper.created(posting) } + @PutMapping( + consumes = [MediaType.MULTIPART_FORM_DATA_VALUE], + ) + override fun updatePosting( + @RequestParam id: Long, + @RequestParam title: String, + @RequestPart content: MultipartFile, + @RequestAttribute(AUTHENTICATED_MEMBER) member: Member, + ): ResponseEntity> { + val updatedPosting = postingService.update(member, id, title, content) + val posting = postingService.findPosting(updatedPosting) + return ApiResponseHelper.success(posting) + } + @GetMapping("/{exposedMemberId}") override fun getPostingList( @PathVariable exposedMemberId: String, diff --git a/src/main/kotlin/com/ixfp/gitmon/db/posting/PostingEntity.kt b/src/main/kotlin/com/ixfp/gitmon/db/posting/PostingEntity.kt index 15c20df..5f7b009 100644 --- a/src/main/kotlin/com/ixfp/gitmon/db/posting/PostingEntity.kt +++ b/src/main/kotlin/com/ixfp/gitmon/db/posting/PostingEntity.kt @@ -13,9 +13,9 @@ class PostingEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0, - val title: String, + var title: String, val refMemberId: Long, - val githubFilePath: String, - val githubFileSha: String, - val githubDownloadUrl: String, + var githubFilePath: String, + var githubFileSha: String, + var githubDownloadUrl: String, ) : BaseEntity() diff --git a/src/main/kotlin/com/ixfp/gitmon/domain/posting/CommitMessageGenerator.kt b/src/main/kotlin/com/ixfp/gitmon/domain/posting/CommitMessageGenerator.kt new file mode 100644 index 0000000..85a2182 --- /dev/null +++ b/src/main/kotlin/com/ixfp/gitmon/domain/posting/CommitMessageGenerator.kt @@ -0,0 +1,19 @@ +package com.ixfp.gitmon.domain.posting + +object CommitMessageGenerator { + fun createMessage(title: String): String { + return "Create \"$title\" - by Gitmon πŸ‘Ύ" + } + + fun updateMessage(title: String): String { + return "Update \"$title\" - by Gitmon πŸ‘Ύ" + } + + fun deleteMessage(title: String): String { + return "Delete \"$title\" - by Gitmon πŸ‘Ύ" + } + + fun imageUploadMessage(): String { + return "Upload image - by Gitmon πŸ‘Ύ" + } +} diff --git a/src/main/kotlin/com/ixfp/gitmon/domain/posting/Posting.kt b/src/main/kotlin/com/ixfp/gitmon/domain/posting/Posting.kt new file mode 100644 index 0000000..b0f76b3 --- /dev/null +++ b/src/main/kotlin/com/ixfp/gitmon/domain/posting/Posting.kt @@ -0,0 +1,14 @@ +package com.ixfp.gitmon.domain.posting + +import java.time.LocalDateTime + +data class Posting( + val id: Long, + val title: String, + val refMemberId: Long, + val githubFilePath: String, + val githubFileSha: String, + val githubDownloadUrl: String, + val createdAt: LocalDateTime, + val updatedAt: LocalDateTime, +) diff --git a/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingReader.kt b/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingReader.kt index 1924b8a..beb5066 100644 --- a/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingReader.kt +++ b/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingReader.kt @@ -11,20 +11,23 @@ import org.springframework.stereotype.Component class PostingReader( private val postingRepository: PostingRepository, ) { - fun findPostingListByMemberId(memberId: Long): List { - return postingRepository.findByRefMemberId(memberId).map { toPostingItem(it) } + fun findPostingListByMemberId(memberId: Long): List { + return postingRepository.findByRefMemberId(memberId).map { toPosting(it) } } - fun findPostingById(postingId: Long): PostingReadDto { + fun findPostingById(postingId: Long): Posting { return postingRepository.findById(postingId) .orElseThrow { IllegalArgumentException("Posting with id $postingId not found") } - .let { toPostingItem(it) } + .let { toPosting(it) } } - private fun toPostingItem(entity: PostingEntity): PostingReadDto { - return PostingReadDto( + private fun toPosting(entity: PostingEntity): Posting { + return Posting( id = entity.id, title = entity.title, + refMemberId = entity.refMemberId, + githubFilePath = entity.githubFilePath, + githubFileSha = entity.githubFileSha, githubDownloadUrl = entity.githubDownloadUrl, createdAt = entity.createdAt, updatedAt = entity.updatedAt, diff --git a/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingService.kt b/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingService.kt index f317437..88523da 100644 --- a/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingService.kt +++ b/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingService.kt @@ -5,6 +5,7 @@ import com.ixfp.gitmon.common.aop.WrapWith import com.ixfp.gitmon.domain.member.Member import com.ixfp.gitmon.domain.member.MemberService import com.ixfp.gitmon.domain.posting.exception.InvalidImageExtensionException +import com.ixfp.gitmon.domain.posting.exception.PostingAuthorizationException import com.ixfp.gitmon.domain.posting.exception.PostingExceptionStrategy import com.ixfp.gitmon.domain.posting.exception.PostingRepositoryNotFoundException import io.github.oshai.kotlinlogging.KotlinLogging.logger @@ -23,6 +24,7 @@ class PostingService( fun findPostingListByMemberExposedId(memberExposedId: String): List { val member = memberService.getMemberByExposedId(memberExposedId) return postingReader.findPostingListByMemberId(member.id) + .map { toPostingReadDto(it) } } fun findPostingListByGithubUsername(githubUsername: String): List { @@ -32,16 +34,16 @@ class PostingService( val postingList = postingReader.findPostingListByMemberId(member.id) log.info { "[PostingService] ν¬μŠ€νŒ… λͺ©λ‘ 쑰회 μ™„λ£Œ. githubUsername=${member.githubUsername}, postingList.size=${postingList.size}" } - return postingList + return postingList.map { toPostingReadDto(it) } } - fun findPosting(postingId: Long): PostingReadDto? { + fun findPosting(postingId: Long): PostingReadDto { log.info { "[PostingService] ν¬μŠ€νŒ… 쑰회 μ‹œμž‘. postingId=$postingId" } val posting = postingReader.findPostingById(postingId) log.info { "[PostingService] ν¬μŠ€νŒ… 쑰회 μ™„λ£Œ. posting=$posting" } - return posting + return toPostingReadDto(posting) } fun findPosting( @@ -56,7 +58,7 @@ class PostingService( .find { it.id == postingId } log.info { "[PostingService] ν¬μŠ€νŒ… 쑰회 μ™„λ£Œ. githubUsername=${member.githubUsername}, posting=$posting" } - return posting + return posting?.let { toPostingReadDto(it) } } fun create( @@ -78,6 +80,7 @@ class PostingService( githubUsername = member.githubUsername, repo = member.repoName, path = "$title.md", + commitMessage = CommitMessageGenerator.createMessage(title = title), ) val savedPostingId = @@ -95,6 +98,48 @@ class PostingService( return savedPostingId } + fun update( + member: Member, + postingId: Long, + title: String, + content: MultipartFile, + ): Long { + log.info { "PostingService#update start. member=$member, postingId=$postingId, title=$title, content.size=${content.size}" } + val posting = postingReader.findPostingById(postingId) + + if (posting.refMemberId != member.id) { + log.warn { "본인이 μž‘μ„±ν•˜μ§€ μ•Šμ€ ν¬μŠ€νŒ…μ— λŒ€ν•œ μˆ˜μ • μ‹œλ„. memberId=${member.id}, postingId=$postingId" } + throw PostingAuthorizationException() + } + + val githubAccessToken = memberService.getGithubAccessTokenById(member.id) + + val githubContent = + githubApiService.upsertFile( + githubAccessToken = githubAccessToken, + content = content, + githubUsername = member.githubUsername, + repo = member.repoName ?: throw PostingRepositoryNotFoundException(), + path = "$title.md", + commitMessage = CommitMessageGenerator.updateMessage(title = title), + sha = posting.githubFileSha, + ) + + val updatedPostingId = + postingWriter.update( + postingId, + PostingUpdateDto( + title = title, + githubFilePath = githubContent.path, + githubFileSha = githubContent.sha, + githubDownloadUrl = githubContent.download_url, + ), + ) + + log.info { "PostingService#update end. updatedPostingId=$updatedPostingId, contentSha=${githubContent.sha}" } + return updatedPostingId + } + private fun uploadImage( member: Member, content: MultipartFile, @@ -116,6 +161,7 @@ class PostingService( githubUsername = member.githubUsername, repo = member.repoName, path = path, + commitMessage = CommitMessageGenerator.imageUploadMessage(), ) return githubContent.download_url @@ -136,6 +182,16 @@ class PostingService( return null } + private fun toPostingReadDto(posting: Posting): PostingReadDto { + return PostingReadDto( + id = posting.id, + title = posting.title, + githubDownloadUrl = posting.githubDownloadUrl, + createdAt = posting.createdAt, + updatedAt = posting.updatedAt, + ) + } + companion object { private val log = logger {} } diff --git a/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingUpdateDto.kt b/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingUpdateDto.kt new file mode 100644 index 0000000..173a90a --- /dev/null +++ b/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingUpdateDto.kt @@ -0,0 +1,8 @@ +package com.ixfp.gitmon.domain.posting + +data class PostingUpdateDto( + val title: String, + val githubFilePath: String, + val githubFileSha: String, + val githubDownloadUrl: String, +) diff --git a/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingWriter.kt b/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingWriter.kt index 517826c..16477d4 100644 --- a/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingWriter.kt +++ b/src/main/kotlin/com/ixfp/gitmon/domain/posting/PostingWriter.kt @@ -4,6 +4,7 @@ import com.ixfp.gitmon.common.aop.WrapWith import com.ixfp.gitmon.db.exception.DbExceptionStrategy import com.ixfp.gitmon.db.posting.PostingEntity import com.ixfp.gitmon.db.posting.PostingRepository +import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Component @WrapWith(DbExceptionStrategy::class) @@ -24,4 +25,22 @@ class PostingWriter( return saved.id } + + fun update( + postingId: Long, + postingUpdateDto: PostingUpdateDto, + ): Long { + val postingEntity = + postingRepository.findByIdOrNull(postingId) + ?: throw IllegalArgumentException("Posting with id $postingId not found") + + postingEntity.title = postingUpdateDto.title + postingEntity.githubFilePath = postingUpdateDto.githubFilePath + postingEntity.githubFileSha = postingUpdateDto.githubFileSha + postingEntity.githubDownloadUrl = postingUpdateDto.githubDownloadUrl + + val updated = postingRepository.save(postingEntity) + + return updated.id + } } diff --git a/src/main/kotlin/com/ixfp/gitmon/domain/posting/exception/PostingExceptions.kt b/src/main/kotlin/com/ixfp/gitmon/domain/posting/exception/PostingExceptions.kt index 3c60937..ba6a6a1 100644 --- a/src/main/kotlin/com/ixfp/gitmon/domain/posting/exception/PostingExceptions.kt +++ b/src/main/kotlin/com/ixfp/gitmon/domain/posting/exception/PostingExceptions.kt @@ -17,6 +17,11 @@ class InvalidImageExtensionException( cause: Throwable? = null, ) : PostingException(message, cause) +class PostingAuthorizationException( + message: String = "κ²Œμ‹œκΈ€μ— λŒ€ν•œ κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.", + cause: Throwable? = null, +) : PostingException(message, cause) + class PostingUnexpectedException( message: String = "μ˜ˆμƒμΉ˜ λͺ»ν•œ κ²Œμ‹œκΈ€ 도메인 μ˜ˆμ™Έκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", cause: Throwable? = null,