diff --git a/grow-api/src/main/kotlin/com/molohala/grow/api/info/InfoController.kt b/grow-api/src/main/kotlin/com/molohala/grow/api/info/InfoController.kt index abd2569..f483b47 100644 --- a/grow-api/src/main/kotlin/com/molohala/grow/api/info/InfoController.kt +++ b/grow-api/src/main/kotlin/com/molohala/grow/api/info/InfoController.kt @@ -2,6 +2,7 @@ package com.molohala.grow.api.info import com.molohala.grow.api.response.Response import com.molohala.grow.api.response.ResponseData +import com.molohala.grow.core.info.application.dto.req.EditUserInfoReq import com.molohala.grow.core.info.application.dto.req.NewSocialAccountReq import com.molohala.grow.core.info.application.service.InfoService import jakarta.validation.Valid @@ -23,6 +24,12 @@ class InfoController( @GetMapping("/me") fun getMyUserInfo() = ResponseData.ok("내 정보 조회 완료", infoService.getMyInfo()) + @PatchMapping("/me") + fun editMyInfo(@RequestBody data: EditUserInfoReq): Response { + infoService.editInfo(data.bio, data.job) + return Response.ok("내 정보 수정 완료") + } + @GetMapping("/user/{id}") fun getOtherUserInfo(@PathVariable id: Long) = ResponseData.ok("유저 정보 조회 완료", infoService.getUserInfo(id)) diff --git a/grow-api/src/main/kotlin/com/molohala/grow/api/language/LanguageController.kt b/grow-api/src/main/kotlin/com/molohala/grow/api/language/LanguageController.kt new file mode 100644 index 0000000..f1a3d8d --- /dev/null +++ b/grow-api/src/main/kotlin/com/molohala/grow/api/language/LanguageController.kt @@ -0,0 +1,25 @@ +package com.molohala.grow.api.language + +import com.molohala.grow.api.response.Response +import com.molohala.grow.api.response.ResponseData +import com.molohala.grow.core.language.application.dto.req.EditLanguageReq +import com.molohala.grow.core.language.application.service.LanguageService +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/language") +class LanguageController( + private val languageService: LanguageService +) { + @GetMapping + fun getAll() = ResponseData.ok("언어 리스트 조회 완료", languageService.getAvailableLanguage()) + + @GetMapping("/me") + fun languages() = ResponseData.ok("사용하는 언어 조회 완료", languageService.getUsingLanguages()) + + @PatchMapping("/me") + fun editLanguage(@RequestBody data: EditLanguageReq): Response { + languageService.updateUsingLanguages(data.langs) + return Response.ok("사용하는 언어 갱신 완료") + } +} \ No newline at end of file diff --git a/grow-common/src/main/kotlin/com/molohala/grow/common/exception/GlobalExceptionCode.kt b/grow-common/src/main/kotlin/com/molohala/grow/common/exception/GlobalExceptionCode.kt index 97fa8d0..32428fa 100644 --- a/grow-common/src/main/kotlin/com/molohala/grow/common/exception/GlobalExceptionCode.kt +++ b/grow-common/src/main/kotlin/com/molohala/grow/common/exception/GlobalExceptionCode.kt @@ -12,6 +12,8 @@ enum class GlobalExceptionCode( METHOD_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "잘못된 메서드"), MEDIA_TYPE_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "잘못된 미디어 타입"), MEDIA_TYPE_MISS_MATCHED(HttpStatus.BAD_REQUEST, "잘못된 미디어 값"), + NO_BODY(HttpStatus.BAD_REQUEST, "요청 바디가 없음"), + INVALID_BODY(HttpStatus.BAD_REQUEST, "잘못된 요청 바디"), PARAMETER_NOT_FOUND(HttpStatus.BAD_REQUEST, "잘못된 파라미터"), INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "잘못된 파라미터"), INVALID_PERMISSION(HttpStatus.BAD_REQUEST, "유효하지 않은 권한"), diff --git a/grow-common/src/main/kotlin/com/molohala/grow/common/exception/handler/CustomExceptionHandler.kt b/grow-common/src/main/kotlin/com/molohala/grow/common/exception/handler/CustomExceptionHandler.kt index 5d99528..c388bb1 100644 --- a/grow-common/src/main/kotlin/com/molohala/grow/common/exception/handler/CustomExceptionHandler.kt +++ b/grow-common/src/main/kotlin/com/molohala/grow/common/exception/handler/CustomExceptionHandler.kt @@ -4,6 +4,7 @@ import com.molohala.grow.common.exception.ErrorResponseEntity import com.molohala.grow.common.exception.GlobalExceptionCode import com.molohala.grow.common.exception.custom.CustomException import org.springframework.http.ResponseEntity +import org.springframework.http.converter.HttpMessageNotReadableException import org.springframework.validation.FieldError import org.springframework.web.HttpMediaTypeNotSupportedException import org.springframework.web.HttpRequestMethodNotSupportedException @@ -21,6 +22,13 @@ class CustomExceptionHandler { return ErrorResponseEntity.responseEntity(e.exceptionCode) } + @ExceptionHandler(HttpMessageNotReadableException::class) + protected fun handleHttpMessageNotReadableException(e: HttpMessageNotReadableException): ResponseEntity { + return ErrorResponseEntity.responseEntity( + if (e.cause == null) GlobalExceptionCode.NO_BODY else GlobalExceptionCode.INVALID_BODY + ) + } + @ExceptionHandler(MethodArgumentNotValidException::class) protected fun handleValidException(e: MethodArgumentNotValidException): ResponseEntity { val message = StringBuilder() diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/auth/application/service/AuthServiceImpl.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/auth/application/service/AuthServiceImpl.kt index 15324cb..89f8672 100644 --- a/grow-core/src/main/kotlin/com/molohala/grow/core/auth/application/service/AuthServiceImpl.kt +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/auth/application/service/AuthServiceImpl.kt @@ -33,7 +33,7 @@ class AuthServiceImpl( if (member == null) { member = save(userData) } else { - member.update(userData.email) + member.updateEmail(userData.email) } issueJwtToken.issueToken(member.email, member.role) } diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/dto/req/EditUserInfoReq.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/dto/req/EditUserInfoReq.kt new file mode 100644 index 0000000..fde8fba --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/dto/req/EditUserInfoReq.kt @@ -0,0 +1,6 @@ +package com.molohala.grow.core.info.application.dto.req + +data class EditUserInfoReq( + val bio: String?, + val job: String? +) \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/dto/res/InfoRes.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/dto/res/InfoRes.kt index 978ea56..a4867ca 100644 --- a/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/dto/res/InfoRes.kt +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/dto/res/InfoRes.kt @@ -6,6 +6,8 @@ data class InfoRes( val id: Long, val email: String, val name: String, + val bio: String, + val job: String, val createdAt: LocalDateTime, val socialAccounts: List, ) \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/service/InfoService.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/service/InfoService.kt index 97bc28e..3d802b7 100644 --- a/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/service/InfoService.kt +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/service/InfoService.kt @@ -11,4 +11,5 @@ interface InfoService { fun getUserInfo(userId: Long): InfoRes fun submitGithubId(name: String) fun submitSolvedAcInfo(name: String) + fun editInfo(bio: String?, job: String?) } \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/service/InfoServiceImpl.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/service/InfoServiceImpl.kt index fb70e7d..7adfff0 100644 --- a/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/service/InfoServiceImpl.kt +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/info/application/service/InfoServiceImpl.kt @@ -10,6 +10,7 @@ import com.molohala.grow.core.info.application.dto.res.SocialAccountRes import com.molohala.grow.core.info.application.dto.res.SolvedAcInfoRes import com.molohala.grow.core.info.exception.InfoExceptionCode import com.molohala.grow.core.member.application.MemberSessionHolder +import com.molohala.grow.core.member.domain.consts.MemberJob import com.molohala.grow.core.member.domain.consts.SocialType import com.molohala.grow.core.member.domain.entity.SocialAccount import com.molohala.grow.core.member.repository.MemberJpaRepository @@ -67,6 +68,8 @@ class InfoServiceImpl( member.id, member.email, member.name, + member.bio, + member.job.display, member.createdAt, socials ) @@ -86,6 +89,8 @@ class InfoServiceImpl( member.id, member.email, member.name, + member.bio, + member.job.display, member.createdAt, socials ) @@ -126,4 +131,11 @@ class InfoServiceImpl( socialAccountJpaRepository.save(SocialAccount(name, SocialType.SOLVED_AC, member.id)) } + + @Transactional(rollbackOn = [Exception::class]) + override fun editInfo(bio: String?, job: String?) { + memberJpaRepository.save( + memberSessionHolder.current().apply { updateInfo(bio ?: this.bio, MemberJob.parse(job) ?: this.job) } + ) + } } \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/dto/req/EditLanguageReq.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/dto/req/EditLanguageReq.kt new file mode 100644 index 0000000..2ace552 --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/dto/req/EditLanguageReq.kt @@ -0,0 +1,7 @@ +package com.molohala.grow.core.language.application.dto.req + +import com.fasterxml.jackson.annotation.JsonCreator + +data class EditLanguageReq @JsonCreator constructor( + val langs: List +) \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/runner/LanguageLoader.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/runner/LanguageLoader.kt new file mode 100644 index 0000000..d413336 --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/runner/LanguageLoader.kt @@ -0,0 +1,43 @@ +package com.molohala.grow.core.language.application.runner + +import com.molohala.grow.core.language.domain.entity.Language +import com.molohala.grow.core.language.repository.LanguageJpaRepository +import org.springframework.boot.ApplicationArguments +import org.springframework.boot.ApplicationRunner +import org.springframework.stereotype.Component + +@Component +class LanguageLoader( + private val languageJpaRepository: LanguageJpaRepository +) : ApplicationRunner { + override fun run(args: ApplicationArguments?) { + val languages = languageJpaRepository.findAll() + val m = listOf( + "Python", + "HTML", + "CSS", + "JavaScript", + "TypeScript", + "C", + "C++", + "C#", + "Kotlin", + "Java", + "Swift", + "Dart", + "Go", + "Ruby", + "Rust", + "SQL", + "PHP", + "Scala", + "Lua", + ) + val addLang = ArrayList() + + for (s in m) if (languages.none { it.name.lowercase() == s.lowercase() }) addLang.add(Language(s)) + + languageJpaRepository.saveAll(addLang) + } + +} \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/service/LanguageService.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/service/LanguageService.kt new file mode 100644 index 0000000..0e8c659 --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/service/LanguageService.kt @@ -0,0 +1,9 @@ +package com.molohala.grow.core.language.application.service + +import com.molohala.grow.core.language.domain.entity.Language + +interface LanguageService { + fun updateUsingLanguages(langs: List) + fun getUsingLanguages(): List + fun getAvailableLanguage(): List +} \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/service/LanguageServiceImpl.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/service/LanguageServiceImpl.kt new file mode 100644 index 0000000..6d88504 --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/application/service/LanguageServiceImpl.kt @@ -0,0 +1,52 @@ +package com.molohala.grow.core.language.application.service + +import com.molohala.grow.common.exception.custom.CustomException +import com.molohala.grow.core.language.domain.entity.Language +import com.molohala.grow.core.language.domain.entity.MemberAndLanguage +import com.molohala.grow.core.language.exception.LanguageExceptionCode +import com.molohala.grow.core.language.repository.LanguageJpaRepository +import com.molohala.grow.core.language.repository.MemberLanguageJpaRepository +import com.molohala.grow.core.language.repository.MemberLanguageQueryRepository +import com.molohala.grow.core.member.application.MemberSessionHolder +import jakarta.transaction.Transactional +import org.springframework.stereotype.Service + +@Service +class LanguageServiceImpl( + private val memberSessionHolder: MemberSessionHolder, + private val languageJpaRepository: LanguageJpaRepository, + private val memberLanguageJpaRepository: MemberLanguageJpaRepository, + private val memberLanguageQueryRepository: MemberLanguageQueryRepository +) : LanguageService { + @Transactional(rollbackOn = [Exception::class]) + override fun updateUsingLanguages(langs: List) { + val member = memberSessionHolder.current() + val dbLang = languageJpaRepository.findAll() + + val owning = + memberLanguageJpaRepository + .findAllByMemberIdIs(member.id!!) + + memberLanguageJpaRepository.deleteAllInBatch(owning.filter { it.languageId !in langs }) + + val toAdd = ArrayList() + + for (lang in langs) { + val ent = dbLang.find { it.id == lang } + if (ent == null) + throw CustomException(LanguageExceptionCode.LANGUAGE_NOT_FOUND) + if (owning.none { it.languageId == lang }) toAdd.add(ent) // not owning + } + + memberLanguageJpaRepository.saveAllAndFlush( + toAdd.map { MemberAndLanguage(member.id, it.id!!) } + ) + } + + override fun getUsingLanguages(): List { + val member = memberSessionHolder.current() + return memberLanguageQueryRepository.getLanguagesByMemberId(member.id!!) + } + + override fun getAvailableLanguage(): List = languageJpaRepository.findAll() +} \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/domain/entity/Language.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/domain/entity/Language.kt new file mode 100644 index 0000000..b74b5d3 --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/domain/entity/Language.kt @@ -0,0 +1,12 @@ +package com.molohala.grow.core.language.domain.entity + +import jakarta.persistence.* + +@Entity(name = "tbl_langs") +data class Language( + val name: String, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(updatable = false, nullable = false, name = "id") + val id: Long? = null +) \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/domain/entity/MemberAndLanguage.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/domain/entity/MemberAndLanguage.kt new file mode 100644 index 0000000..914358a --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/domain/entity/MemberAndLanguage.kt @@ -0,0 +1,22 @@ +package com.molohala.grow.core.language.domain.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id + +@Entity(name = "tbl_many_member_language") +class MemberAndLanguage( + val memberId: Long, + val languageId: Long, + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column( + name = "id", + nullable = false, + updatable = false + ) + val id: Long? = null, +) \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/exception/LanguageExceptionCode.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/exception/LanguageExceptionCode.kt new file mode 100644 index 0000000..98fe79f --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/exception/LanguageExceptionCode.kt @@ -0,0 +1,13 @@ +package com.molohala.grow.core.language.exception + +import com.molohala.grow.common.exception.ExceptionCode +import org.springframework.http.HttpStatus + +enum class LanguageExceptionCode(private val status: HttpStatus, private val message: String) : ExceptionCode { + LANGUAGE_NOT_FOUND(HttpStatus.BAD_REQUEST, "해당하는 언어를 찾을 수 없음"), + ; + + override fun getHttpStatus() = status + override fun getExceptionName() = name + override fun getMessage() = message +} \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/LanguageJpaRepository.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/LanguageJpaRepository.kt new file mode 100644 index 0000000..767529d --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/LanguageJpaRepository.kt @@ -0,0 +1,6 @@ +package com.molohala.grow.core.language.repository + +import com.molohala.grow.core.language.domain.entity.Language +import org.springframework.data.jpa.repository.JpaRepository + +interface LanguageJpaRepository : JpaRepository \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageJpaRepository.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageJpaRepository.kt new file mode 100644 index 0000000..f010528 --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageJpaRepository.kt @@ -0,0 +1,8 @@ +package com.molohala.grow.core.language.repository + +import com.molohala.grow.core.language.domain.entity.MemberAndLanguage +import org.springframework.data.jpa.repository.JpaRepository + +interface MemberLanguageJpaRepository : JpaRepository { + fun findAllByMemberIdIs(memberId: Long): List +} \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageQueryDslRepository.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageQueryDslRepository.kt new file mode 100644 index 0000000..f78c6ba --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageQueryDslRepository.kt @@ -0,0 +1,29 @@ +package com.molohala.grow.core.language.repository + +import com.molohala.grow.core.language.domain.entity.Language +import com.molohala.grow.core.language.domain.entity.QLanguage.language +import com.molohala.grow.core.language.domain.entity.QMemberAndLanguage.memberAndLanguage +import com.querydsl.core.types.Projections +import com.querydsl.jpa.impl.JPAQueryFactory +import org.springframework.stereotype.Repository + +@Repository +class MemberLanguageQueryDslRepository( + private val queryFactory: JPAQueryFactory +) : MemberLanguageQueryRepository { + override fun getLanguagesByMemberId(memberId: Long): List { + return queryFactory + .select( + Projections.constructor( + Language::class.java, + language.name, + language.id + ) + ) + .from(memberAndLanguage) + .where(memberAndLanguage.memberId.eq(memberId)) + .innerJoin(language) + .on(memberAndLanguage.languageId.eq(language.id)) + .fetch() + } +} \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageQueryRepository.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageQueryRepository.kt new file mode 100644 index 0000000..b514def --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/language/repository/MemberLanguageQueryRepository.kt @@ -0,0 +1,7 @@ +package com.molohala.grow.core.language.repository + +import com.molohala.grow.core.language.domain.entity.Language + +interface MemberLanguageQueryRepository { + fun getLanguagesByMemberId(memberId: Long): List +} \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/member/domain/consts/MemberJob.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/member/domain/consts/MemberJob.kt new file mode 100644 index 0000000..94a41b6 --- /dev/null +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/member/domain/consts/MemberJob.kt @@ -0,0 +1,18 @@ +package com.molohala.grow.core.member.domain.consts + +enum class MemberJob(val display: String) { + SERVER("Server"), + WEB("Web"), + ANDROID("Android"), + IOS("iOS"), + DESIGNER("Designer"), + GAME("Game"), + DEVELOPER(""); + + companion object { + fun parse(job: String?): MemberJob? { + if (job != null) return entries.find { it.display.lowercase() == job.lowercase() || it.name.lowercase() == job.lowercase() } + return null + } + } +} \ No newline at end of file diff --git a/grow-core/src/main/kotlin/com/molohala/grow/core/member/domain/entity/Member.kt b/grow-core/src/main/kotlin/com/molohala/grow/core/member/domain/entity/Member.kt index 70c6a37..63b5a2d 100644 --- a/grow-core/src/main/kotlin/com/molohala/grow/core/member/domain/entity/Member.kt +++ b/grow-core/src/main/kotlin/com/molohala/grow/core/member/domain/entity/Member.kt @@ -1,6 +1,7 @@ package com.molohala.grow.core.member.domain.entity import com.molohala.grow.core.common.BaseIdAndTimeEntity +import com.molohala.grow.core.member.domain.consts.MemberJob import com.molohala.grow.core.member.domain.consts.MemberRole import com.molohala.grow.core.member.domain.consts.MemberState import jakarta.persistence.Column @@ -15,26 +16,42 @@ class Member( email: String, role: MemberRole, state: MemberState, + job: MemberJob = MemberJob.DEVELOPER, + bio: String = "", ) : BaseIdAndTimeEntity(null, null) { @Column(nullable = false) - var name = name + var name: String = name private set @Column(nullable = false, unique = true) - var email = email + var email: String = email private set @Enumerated(EnumType.STRING) @Column(nullable = false) - var role = role + var role: MemberRole = role private set @Enumerated(EnumType.STRING) @Column(nullable = false) - var state = state + var state: MemberState = state private set - fun update(email: String) { + @Column(nullable = false) + var bio: String = bio + private set + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + var job: MemberJob = job + private set + + fun updateEmail(email: String) { this.email = email } + + fun updateInfo(bio: String?, job: MemberJob?) { + if (bio != null) this.bio = bio + if (job != null) this.job = job + } } \ No newline at end of file