Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions src/main/kotlin/com/doongjun/commitmon/api/CommitmonController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.doongjun.commitmon.api

import com.doongjun.commitmon.api.data.CommitmonResponse
import io.swagger.v3.oas.annotations.Operation
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/v1/commitmons")
class CommitmonController {
@Operation(summary = "커밋몬 목록 조회")
@GetMapping
fun getAll(): CommitmonResponse = CommitmonResponse.of()
}
50 changes: 50 additions & 0 deletions src/main/kotlin/com/doongjun/commitmon/api/MeController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.doongjun.commitmon.api

import com.doongjun.commitmon.api.data.ChangeCommitmonRequest
import com.doongjun.commitmon.api.data.MeDetailResponse
import com.doongjun.commitmon.api.data.MeResponse
import com.doongjun.commitmon.app.UserFetchType
import com.doongjun.commitmon.app.UserService
import com.doongjun.commitmon.extension.currentUserId
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.GetMapping
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.RestController

@RestController
@RequestMapping("/api/v1/me")
class MeController(
private val userService: UserService,
) {
@Operation(summary = "로그인 유저 조회")
@GetMapping
fun get(): MeResponse =
MeResponse.from(
user = userService.getSimple(currentUserId!!),
)

@Operation(summary = "로그인 유저 조회 (팔로워, 팔로잉 포함)")
@GetMapping("/detail")
fun getDetail(): MeDetailResponse =
MeDetailResponse.from(
user =
userService.get(
id = currentUserId!!,
userFetchType = UserFetchType.ALL,
),
)

@Operation(summary = "로그인 유저 커밋몬 변경")
@PostMapping("/commitmon/change")
fun changeCommitmon(
@Valid @RequestBody request: ChangeCommitmonRequest,
) {
userService.changeCommitmon(
id = currentUserId!!,
commitmon = request.commitmon!!,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.doongjun.commitmon.api.data

import com.doongjun.commitmon.domain.Commitmon
import jakarta.validation.constraints.NotNull

data class ChangeCommitmonRequest(
@field:NotNull
val commitmon: Commitmon?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.doongjun.commitmon.api.data

import com.doongjun.commitmon.domain.Commitmon
import com.doongjun.commitmon.domain.CommitmonLevel

data class CommitmonResponse(
val commitmons: List<CommitmonData>,
) {
data class CommitmonData(
val commitmon: Commitmon,
val name: String,
val nameKo: String,
val level: CommitmonLevel,
val seed: Int,
val imageUrl: String,
)

companion object {
private val commitmons =
Commitmon.entries.filter { it != Commitmon.EGG }.map {
CommitmonData(
commitmon = it,
name = it.name.lowercase(),
nameKo = it.nameKo,
level = it.level,
seed = it.seed ?: 0,
imageUrl = "https://s3-commitmon.s3.ap-northeast-2.amazonaws.com/static/${it.assetName}.png",
)
}

fun of(): CommitmonResponse = CommitmonResponse(commitmons)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.doongjun.commitmon.api.data

import com.doongjun.commitmon.app.data.GetSimpleUserDto
import com.doongjun.commitmon.app.data.GetUserDto
import com.doongjun.commitmon.domain.Commitmon

data class MeDetailResponse(
val id: Long,
val name: String,
val totalCommitCount: Long,
val commitmon: Commitmon,
val exp: Int = 0,
val followers: List<GetSimpleUserDto>,
val following: List<GetSimpleUserDto>,
) {
companion object {
fun from(user: GetUserDto): MeDetailResponse =
MeDetailResponse(
id = user.id,
name = user.name,
totalCommitCount = user.totalCommitCount,
commitmon = user.commitmon,
exp = user.exp,
followers = user.followers,
following = user.following,
)
}
}
23 changes: 23 additions & 0 deletions src/main/kotlin/com/doongjun/commitmon/api/data/MeResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.doongjun.commitmon.api.data

import com.doongjun.commitmon.app.data.GetSimpleUserDto
import com.doongjun.commitmon.domain.Commitmon

data class MeResponse(
val id: Long,
val name: String,
val totalCommitCount: Long,
val commitmon: Commitmon,
val exp: Int = 0,
) {
companion object {
fun from(user: GetSimpleUserDto): MeResponse =
MeResponse(
id = user.id,
name = user.name,
totalCommitCount = user.totalCommitCount,
commitmon = user.commitmon,
exp = user.exp,
)
}
}
26 changes: 23 additions & 3 deletions src/main/kotlin/com/doongjun/commitmon/app/UserService.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.doongjun.commitmon.app

import com.doongjun.commitmon.app.data.CreateUserDto
import com.doongjun.commitmon.app.data.GetSimpleUserDto
import com.doongjun.commitmon.app.data.GetUserDto
import com.doongjun.commitmon.app.data.UpdateUserDto
import com.doongjun.commitmon.domain.Commitmon
import com.doongjun.commitmon.domain.User
import com.doongjun.commitmon.domain.UserRepository
import org.springframework.cache.annotation.Cacheable
Expand All @@ -18,13 +20,18 @@ class UserService(
@Transactional(readOnly = true)
fun existsByName(name: String) = userRepository.existsByName(name)

@Transactional(readOnly = true)
fun getSimple(id: Long): GetSimpleUserDto =
userRepository.findByIdOrNull(id)?.let { user -> GetSimpleUserDto.from(user) }
?: throw NoSuchElementException("Failed to fetch user by id: $id")

@Transactional(readOnly = true)
fun get(
id: Long,
userFetchType: UserFetchType,
): GetUserDto =
userRepository.findByIdOrNull(id)?.let { user -> GetUserDto.from(user, userFetchType) }
?: throw IllegalArgumentException("Failed to fetch user by id: $id")
?: throw NoSuchElementException("Failed to fetch user by id: $id")

@Cacheable(value = ["userInfo"], key = "#name + '-' + #userFetchType.title")
@Transactional(readOnly = true)
Expand All @@ -33,7 +40,7 @@ class UserService(
userFetchType: UserFetchType,
): GetUserDto =
userRepository.findByName(name)?.let { user -> GetUserDto.from(user, userFetchType) }
?: throw IllegalArgumentException("Failed to fetch user by name: $name")
?: throw NoSuchElementException("Failed to fetch user by name: $name")

fun create(dto: CreateUserDto): Long {
val user =
Expand All @@ -54,7 +61,7 @@ class UserService(
) {
val user =
userRepository.findByIdOrNull(id)
?: throw IllegalArgumentException("Failed to fetch user by id: $id")
?: throw NoSuchElementException("Failed to fetch user by id: $id")

user.update(
totalCommitCount = dto.totalCommitCount,
Expand All @@ -64,4 +71,17 @@ class UserService(

userRepository.save(user)
}

fun changeCommitmon(
id: Long,
commitmon: Commitmon,
) {
val user =
userRepository.findByIdOrNull(id)
?: throw NoSuchElementException("Failed to fetch user by id: $id")

user.changeCommitmon(commitmon)

userRepository.save(user)
}
}
14 changes: 14 additions & 0 deletions src/main/kotlin/com/doongjun/commitmon/app/data/GetUserDto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,27 @@ import com.doongjun.commitmon.app.UserFetchType
import com.doongjun.commitmon.core.NoArgs
import com.doongjun.commitmon.domain.Commitmon
import com.doongjun.commitmon.domain.User
import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@NoArgs
@JsonIgnoreProperties(value = ["followerIds", "followingIds", "followers", "following"])
data class GetUserDto(
val id: Long,
val name: String,
val totalCommitCount: Long,
val commitmon: Commitmon,
val exp: Int = 0,
val fetchedUsers: List<GetSimpleUserDto>,
private val followerIds: List<Long>,
private val followingIds: List<Long>,
) {
val followers: List<GetSimpleUserDto> by lazy {
fetchedUsers.filter { it.id in followerIds }
}
val following: List<GetSimpleUserDto> by lazy {
fetchedUsers.filter { it.id in followingIds }
}

fun toSimple(): GetSimpleUserDto =
GetSimpleUserDto(
id = id,
Expand All @@ -39,12 +50,15 @@ data class GetUserDto(
UserFetchType.ALL ->
listOf(user.followers, user.following)
.flatten()
.distinct()
.map { GetSimpleUserDto.from(it) }
UserFetchType.MUTUAL ->
user.mutualFollowers
.map { GetSimpleUserDto.from(it) }
UserFetchType.SOLO -> emptyList()
},
followerIds = user.followers.map { it.id },
followingIds = user.following.map { it.id },
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpHeaders
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository
Expand All @@ -29,6 +30,7 @@ class JwtFilter(
UsernamePasswordAuthenticationToken(
userId,
null,
listOf(SimpleGrantedAuthority("ROLE_USER")),
)

authenticationToken.details = WebAuthenticationDetailsSource().buildDetails(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class SecurityConfig(
.permitAll()
.requestMatchers("/api/v1/account/**")
.permitAll()
.requestMatchers("/api/v1/commitmons/**")
.permitAll()
.anyRequest()
.authenticated()
}.exceptionHandling { exception ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class GlobalExceptionHandler {

@ExceptionHandler(AccountExpiredException::class)
protected fun handleAccountExpiredException(e: AccountExpiredException): ResponseEntity<ErrorResponse> {
log.error("AccessDeniedException", e)
log.error("AccountExpiredException", e)
val response = ErrorResponse.of(ErrorCode.UNAUTHORIZED)
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response)
}
Expand All @@ -90,4 +90,11 @@ class GlobalExceptionHandler {
val response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE)
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response)
}

@ExceptionHandler(NoSuchElementException::class)
protected fun handleNoSuchElementException(e: NoSuchElementException): ResponseEntity<ErrorResponse> {
log.error("NoSuchElementException", e)
val response = ErrorResponse.of(ErrorCode.NOT_FOUND)
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum class ErrorCode(
INVALID_INPUT_VALUE(400, "A001", "Invalid input value."),
METHOD_NOT_ALLOWED(405, "A002", "Invalid input value."),
INTERNAL_SERVER_ERROR(500, "A003", "Server Error."),
NOT_FOUND(404, "A004", "Not Found."),
INVALID_TYPE_VALUE(400, "A005", "Invalid type value."),
ACCESS_DENIED(403, "A006", "Access is denied."),
UNAUTHORIZED(401, "A007", "Unauthorized."),
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/doongjun/commitmon/domain/Commitmon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ enum class Commitmon(
METALGREYMON(
CommitmonLevel.PERFECT,
listOf(EGG, BOTAMON, KOROMON, AGUMON, GREYMON),
assetName = "metalGreymon",
assetName = "metalgreymon",
nameKo = "메탈그레이몬",
seed = 1,
),
Expand All @@ -33,7 +33,7 @@ enum class Commitmon(

PUNIMON(CommitmonLevel.BABY, listOf(EGG), assetName = "punimon", nameKo = "푸니몬", seed = 2),
TSUNOMON(CommitmonLevel.IN_TRAINING, listOf(EGG, PUNIMON), assetName = "tsunomon", nameKo = "뿔몬", seed = 2),
GABUMON(CommitmonLevel.ROOKIE, listOf(EGG, PUNIMON, TSUNOMON), assetName = "gabumon", nameKo = "가루몬", seed = 2),
GABUMON(CommitmonLevel.ROOKIE, listOf(EGG, PUNIMON, TSUNOMON), assetName = "gabumon", nameKo = "파피몬", seed = 2),
GARURUMON(CommitmonLevel.CHAMPION, listOf(EGG, PUNIMON, TSUNOMON, GABUMON), assetName = "garurumon", nameKo = "가루몬", seed = 2),
WEREGARURUMON(
CommitmonLevel.PERFECT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.doongjun.commitmon.domain

enum class CommitmonLevel(
val exp: Long,
private val order: Int,
val order: Int,
) {
EGG(0, 0), // 알
BABY(100, 1), // 유아기
Expand Down
20 changes: 18 additions & 2 deletions src/main/kotlin/com/doongjun/commitmon/domain/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class User(
var commitmon: Commitmon = Commitmon.randomCommitmon(CommitmonLevel.fromExp(totalCommitCount))
protected set

@Column(name = "auto_level_up", nullable = false)
var autoLevelUp: Boolean = true

@OneToMany(mappedBy = "follower", fetch = LAZY, cascade = [ALL], orphanRemoval = true)
protected val mutableFollowers: MutableSet<Follow> = toFollowers(followers).toMutableSet()
val followers: List<User> get() = mutableFollowers.map { it.following }
Expand All @@ -59,12 +62,25 @@ class User(
following: List<User>,
) {
this.totalCommitCount = totalCommitCount
syncCommitmonLevel(CommitmonLevel.fromExp(totalCommitCount))
updateFollowers(followers)
updateFollowing(following)

if (this.autoLevelUp) {
autoLevelUpCommitmon(CommitmonLevel.fromExp(totalCommitCount))
}
}

fun changeCommitmon(commitmon: Commitmon) {
val currentLevel = CommitmonLevel.fromExp(totalCommitCount)
if (commitmon.level.order > currentLevel.order) {
throw IllegalArgumentException("Cannot change to higher level")
}

this.commitmon = commitmon
this.autoLevelUp = commitmon.level == currentLevel
}

private fun syncCommitmonLevel(level: CommitmonLevel) {
private fun autoLevelUpCommitmon(level: CommitmonLevel) {
if (this.commitmon.level != level) {
this.commitmon = Commitmon.randomLevelTreeCommitmon(level, this.commitmon)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.doongjun.commitmon.extension

import org.springframework.security.core.context.SecurityContextHolder

val currentUserId get() = SecurityContextHolder.getContext().authentication?.principal as Long?
Loading