diff --git a/README.md b/README.md index be1a20d..ce7872b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ##
- + ⭐스타를 눌러주세요 개발에 큰 도움이 됩니다!⭐️
@@ -39,29 +39,31 @@ line mode는 자신이 갖고있는 펫중 하나를 지정해서, 지정한 width, height범위에서 움직이게 해요. line mode를 사용할때, markdown 방식으로 이미지를 요청하면, width, height를 설정할 수 없어서 펫이 보이지 않을 수 있으니, HTMl방식을 사용해주세요. -_pet-id에 아무값도 입력하지 않으면, 첫번째 펫이 가져와져요._ - -변경 가능한 pet-id는 `https://render.gitanimals.org/users/{username}` 의 {username}을 자신의 깃허브 아이디로 변경 후 API를 요청하면 확인할 수 있어요. -API 응답의 `$.personas.[].id` 에 해당하는 값을 pet-id에 입력하면 돼요. > [!TIP] > **Img의 width와 height를 조절해서 펫의 이동영역을 조절할 수 있어요.** > width를 길게 height를 작게하면 (width = 1000, height = 60) 가로로 길게 움직이게 할 수 있어요. > 반대로, width를 작게 height를 길게하면 (width = 60, height = 1000) 세로로 길게 움직이게 할 수 있어요. -> img의 height를 펫의 세로 길이보다 충분히 크게 적용해주세요. 그러지 않으면 펫이 숨어버려요. +> 만약, 펫이 보이지 않는다면, img의 height를 펫의 세로 길이보다 충분히 크게 적용해주세요. + -**html** - ```html ``` +_pet-id에 아무값도 입력하지 않으면, 첫번째 펫이 가져와져요._ + +변경 가능한 pet-id는 `https://render.gitanimals.org/users/{username}` 의 {username}을 자신의 깃허브 아이디로 변경 후 API를 요청하면 확인할 수 있어요. +API 응답의 `$.personas.[].id` 에 해당하는 값을 pet-id에 입력하면 돼요. + +lines모드에서는 펫 레벨 위에 총 contributions수를 보여줘요. 원하지 않을경우, 쿼리 파라미터로 `contribution-view=false`를 담아 요청하세요. + ### farm mode farm mode는 갖고있는 모든 동물과 추가적인 정보를 보여줘요. diff --git a/gradle.properties b/gradle.properties index 91ef374..3370ffa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,3 +28,9 @@ netxVersion=0.4.2 ### Sentry ### sentryVersion=4.4.0 + +### RestAssured ### +restAssuredVersion=5.4.0 + +### H2version ### +h2Version=1.4.200 diff --git a/gradle/db.gradle b/gradle/db.gradle index 7d7afdc..61d4ab8 100644 --- a/gradle/db.gradle +++ b/gradle/db.gradle @@ -1,5 +1,5 @@ dependencies { implementation "mysql:mysql-connector-java:${mysqlConnectorVersion}" - testRuntimeOnly "com.h2database:h2" + testRuntimeOnly "com.h2database:h2:${h2Version}" } diff --git a/gradle/test.gradle b/gradle/test.gradle index 60b80e1..bf9f2e1 100644 --- a/gradle/test.gradle +++ b/gradle/test.gradle @@ -8,4 +8,7 @@ dependencies { testImplementation "io.kotest.extensions:kotest-extensions-spring:${kotestExtensionSpringVersion}" testImplementation "org.testcontainers:testcontainers:${testContainerVersion}" + + testImplementation "io.rest-assured:rest-assured:${restAssuredVersion}" + } diff --git a/src/main/kotlin/org/gitanimals/render/app/AnimationFacade.kt b/src/main/kotlin/org/gitanimals/render/app/AnimationFacade.kt index fd5d15d..3af8dae 100644 --- a/src/main/kotlin/org/gitanimals/render/app/AnimationFacade.kt +++ b/src/main/kotlin/org/gitanimals/render/app/AnimationFacade.kt @@ -1,5 +1,6 @@ package org.gitanimals.render.app +import org.gitanimals.render.domain.Mode import org.gitanimals.render.domain.User import org.gitanimals.render.domain.UserService import org.gitanimals.render.domain.event.Visited @@ -29,17 +30,17 @@ class AnimationFacade( } } - fun getLineAnimation(username: String, personaId: Long): String { + fun getLineAnimation(username: String, personaId: Long, mode: Mode): String { return when (userService.existsByName(username)) { true -> { - val svgAnimation = userService.getLineAnimationByUsername(username, personaId) + val svgAnimation = userService.getLineAnimationByUsername(username, personaId, mode) sagaManager.startSync(Visited(username)) svgAnimation } false -> { val user = createNewUser(username) - userService.getLineAnimationByUsername(user.name, personaId) + userService.getLineAnimationByUsername(user.name, personaId, mode) } } } diff --git a/src/main/kotlin/org/gitanimals/render/controller/AnimationController.kt b/src/main/kotlin/org/gitanimals/render/controller/AnimationController.kt index 0fd4e2d..4b3cd59 100644 --- a/src/main/kotlin/org/gitanimals/render/controller/AnimationController.kt +++ b/src/main/kotlin/org/gitanimals/render/controller/AnimationController.kt @@ -2,6 +2,7 @@ package org.gitanimals.render.controller import jakarta.servlet.http.HttpServletResponse import org.gitanimals.render.app.AnimationFacade +import org.gitanimals.render.domain.Mode import org.springframework.http.HttpHeaders import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -26,10 +27,17 @@ class AnimationController( fun getLineSvgAnimation( @PathVariable("username") username: String, @RequestParam(name = "pet-id", defaultValue = "0") personaId: Long, + @RequestParam(name = "contribution-view", defaultValue = "true") contributionView: Boolean, response: HttpServletResponse, ): String { response.cacheControl(3600) - return animationFacade.getLineAnimation(deleteBrackets(username), personaId) + + val mode = when (contributionView) { + true -> Mode.LINE + false -> Mode.LINE_NO_CONTRIBUTION + } + + return animationFacade.getLineAnimation(deleteBrackets(username), personaId, mode) } private fun deleteBrackets(username: String): String { diff --git a/src/main/kotlin/org/gitanimals/render/controller/filter/CorsFilter.kt b/src/main/kotlin/org/gitanimals/render/controller/filter/CorsFilter.kt new file mode 100644 index 0000000..4165d18 --- /dev/null +++ b/src/main/kotlin/org/gitanimals/render/controller/filter/CorsFilter.kt @@ -0,0 +1,41 @@ +package org.gitanimals.render.controller.filter + +import jakarta.servlet.Filter +import jakarta.servlet.FilterChain +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.HttpHeaders +import org.springframework.stereotype.Component + +@Component +class CorsFilter : Filter { + + override fun doFilter( + request: ServletRequest, + response: ServletResponse, + chain: FilterChain + ) { + (request as HttpServletRequest) + if (regexMatcher.matches(request.requestURI)) { + (response as HttpServletResponse).allowCors() + } + chain.doFilter(request, response) + } + + private fun HttpServletResponse.allowCors(): ServletResponse { + this.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*") + this.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "*") + this.addHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600") + this.addHeader( + HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, + "Origin, X-Requested-With, Content-Type, Accept, Authorization" + ) + return this + } + + private companion object { + private val regexMatcher = Regex("/users/.*") + } +} diff --git a/src/main/kotlin/org/gitanimals/render/domain/Mode.kt b/src/main/kotlin/org/gitanimals/render/domain/Mode.kt new file mode 100644 index 0000000..4c08dfe --- /dev/null +++ b/src/main/kotlin/org/gitanimals/render/domain/Mode.kt @@ -0,0 +1,8 @@ +package org.gitanimals.render.domain + +enum class Mode { + FARM, + LINE, + LINE_NO_CONTRIBUTION, + ; +} diff --git a/src/main/kotlin/org/gitanimals/render/domain/Persona.kt b/src/main/kotlin/org/gitanimals/render/domain/Persona.kt index 7f90b2d..5ddad62 100644 --- a/src/main/kotlin/org/gitanimals/render/domain/Persona.kt +++ b/src/main/kotlin/org/gitanimals/render/domain/Persona.kt @@ -35,13 +35,13 @@ class Persona( ) : this(type = type, level = Level(level), visible = visible) - fun toSvgForce(): String = type.load(this) + fun toSvgForce(mode: Mode): String = type.load(user!!, this, mode) - fun toSvg(): String { + fun toSvg(mode: Mode): String { if (!visible) { return "" } - return type.load(this) + return type.load(user!!, this, mode) } fun level(): Long = level.value diff --git a/src/main/kotlin/org/gitanimals/render/domain/PersonaType.kt b/src/main/kotlin/org/gitanimals/render/domain/PersonaType.kt index ea5ac10..f88adb6 100644 --- a/src/main/kotlin/org/gitanimals/render/domain/PersonaType.kt +++ b/src/main/kotlin/org/gitanimals/render/domain/PersonaType.kt @@ -7,7 +7,7 @@ import kotlin.random.Random enum class PersonaType(private val weight: Double) { GOOSE(1.0) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseSvg.replace("*{act}", act(persona.id)) @@ -30,7 +30,7 @@ enum class PersonaType(private val weight: Double) { }, GOOSE_SUNGLASSES(0.05) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseSunglassesSvg.replace("*{act}", act(persona.id)) @@ -53,7 +53,7 @@ enum class PersonaType(private val weight: Double) { }, GOOSE_KOTLIN(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseKotlinSvg.replace("*{act}", act(persona.id)) @@ -76,7 +76,7 @@ enum class PersonaType(private val weight: Double) { }, GOOSE_JAVA(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseJavaSvg.replace("*{act}", act(persona.id)) @@ -99,7 +99,7 @@ enum class PersonaType(private val weight: Double) { }, GOOSE_JS(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseJsSvg.replace("*{act}", act(persona.id)) @@ -122,7 +122,7 @@ enum class PersonaType(private val weight: Double) { }, GOOSE_NODE(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseNodeSvg.replace("*{act}", act(persona.id)) @@ -145,7 +145,7 @@ enum class PersonaType(private val weight: Double) { }, GOOSE_SWIFT(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseSwiftSvg.replace("*{act}", act(persona.id)) @@ -168,7 +168,7 @@ enum class PersonaType(private val weight: Double) { }, GOOSE_LINUX(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseLinuxSvg.replace("*{act}", act(persona.id)) @@ -191,7 +191,7 @@ enum class PersonaType(private val weight: Double) { }, GOOSE_SPRING(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val goose = gooseSpringSvg.replace("*{act}", act(persona.id)) @@ -214,7 +214,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK(0.9) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickSvg.replace("*{act}", act(persona.id)) @@ -237,7 +237,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK_SUNGLASSES(0.4) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickSunglassesSvg.replace("*{act}", act(persona.id)) @@ -260,7 +260,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK_KOTLIN(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickKotlinSvg.replace("*{act}", act(persona.id)) @@ -283,7 +283,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK_JAVA(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickJavaSvg.replace("*{act}", act(persona.id)) @@ -306,7 +306,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK_JS(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickJsSvg.replace("*{act}", act(persona.id)) @@ -329,7 +329,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK_NODE(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickNodeSvg.replace("*{act}", act(persona.id)) @@ -352,7 +352,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK_SWIFT(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickSwiftSvg.replace("*{act}", act(persona.id)) @@ -375,7 +375,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK_LINUX(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickLinuxSvg.replace("*{act}", act(persona.id)) @@ -398,7 +398,7 @@ enum class PersonaType(private val weight: Double) { }, LITTLE_CHICK_SPRING(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } val littleChick = littleChickSpringSvg.replace("*{act}", act(persona.id)) @@ -421,7 +421,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN(0.5) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinSvg.replace("*{act}", act(persona.id)) @@ -440,7 +440,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN_SUNGLASSES(0.2) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinSunglassesSvg.replace("*{act}", act(persona.id)) @@ -459,7 +459,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN_KOTLIN(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinKotlinSvg.replace("*{act}", act(persona.id)) @@ -478,7 +478,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN_JAVA(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinJavaSvg.replace("*{act}", act(persona.id)) @@ -497,7 +497,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN_JS(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinJsSvg.replace("*{act}", act(persona.id)) @@ -516,7 +516,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN_NODE(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinNodeSvg.replace("*{act}", act(persona.id)) @@ -535,7 +535,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN_SWIFT(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinSwiftSvg.replace("*{act}", act(persona.id)) @@ -554,7 +554,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN_LINUX(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinLinuxSvg.replace("*{act}", act(persona.id)) @@ -573,7 +573,7 @@ enum class PersonaType(private val weight: Double) { }, PENGUIN_SPRING(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return penguinSpringSvg.replace("*{act}", act(persona.id)) @@ -592,7 +592,7 @@ enum class PersonaType(private val weight: Double) { }, PIG(0.2) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigSvg.replace("*{act}", act(persona.id)) @@ -610,7 +610,7 @@ enum class PersonaType(private val weight: Double) { }, PIG_SUNGLASSES(0.08) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigSunglassesSvg.replace("*{act}", act(persona.id)) @@ -628,7 +628,7 @@ enum class PersonaType(private val weight: Double) { }, PIG_KOTLIN(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigKotlinSvg.replace("*{act}", act(persona.id)) @@ -646,7 +646,7 @@ enum class PersonaType(private val weight: Double) { }, PIG_JAVA(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigJavaSvg.replace("*{act}", act(persona.id)) @@ -664,7 +664,7 @@ enum class PersonaType(private val weight: Double) { }, PIG_JS(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigJsSvg.replace("*{act}", act(persona.id)) @@ -682,7 +682,7 @@ enum class PersonaType(private val weight: Double) { }, PIG_NODE(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigNodeSvg.replace("*{act}", act(persona.id)) @@ -700,7 +700,7 @@ enum class PersonaType(private val weight: Double) { }, PIG_SWIFT(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigSwiftSvg.replace("*{act}", act(persona.id)) @@ -718,7 +718,7 @@ enum class PersonaType(private val weight: Double) { }, PIG_LINUX(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigLinuxSvg.replace("*{act}", act(persona.id)) @@ -736,7 +736,7 @@ enum class PersonaType(private val weight: Double) { }, PIG_SPRING(0.01) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return pigSpringSvg.replace("*{act}", act(persona.id)) @@ -754,7 +754,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_RED(0.1) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeRedSvg.replace("*{act}", act(persona.id)) @@ -772,7 +772,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_RED_KOTLIN(0.001) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeRedKotlinSvg.replace("*{act}", act(persona.id)) @@ -790,7 +790,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_RED_JAVA(0.001) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeRedJavaSvg.replace("*{act}", act(persona.id)) @@ -808,7 +808,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_RED_JS(0.001) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeRedJsSvg.replace("*{act}", act(persona.id)) @@ -826,7 +826,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_RED_NODE(0.001) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeRedNodeSvg.replace("*{act}", act(persona.id)) @@ -844,7 +844,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_RED_SWIFT(0.001) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeRedSwiftSvg.replace("*{act}", act(persona.id)) @@ -862,7 +862,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_RED_LINUX(0.001) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeRedLinuxSvg.replace("*{act}", act(persona.id)) @@ -880,7 +880,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_BLUE(0.1) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeBlueSvg.replace("*{act}", act(persona.id)) @@ -898,7 +898,7 @@ enum class PersonaType(private val weight: Double) { }, SLIME_GREEN(0.1) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return slimeGreenSvg.replace("*{act}", act(persona.id)) @@ -916,7 +916,7 @@ enum class PersonaType(private val weight: Double) { }, FLAMINGO(0.05) { - override fun load(persona: Persona): String { + override fun loadSvg(user: User, persona: Persona, mode: Mode): String { check(persona.id != null) { "Save persona first before call load()" } return flamingoSvg.replace("*{position}", act(persona.id)) @@ -944,10 +944,32 @@ enum class PersonaType(private val weight: Double) { require(weight in 0.001..1.0) { "PersonaType's weight should be between 0.01 to 1.0" } } - abstract fun load(persona: Persona): String + fun load(user: User, persona: Persona, mode: Mode): String = + loadSvg(user, persona, mode) + .drawContribution(mode, user) + + abstract fun loadSvg(user: User, persona: Persona, mode: Mode): String protected abstract fun act(id: Long): String + protected fun String.drawContribution( + mode: Mode, + user: User + ): String { + return if (mode == Mode.LINE) { + this.replace( + "*{contributionx}", + (12 + (-1 * (user.contributionCount().toString().length))).toString() + ) + .replace( + "*{contribution}", + user.contributionCount().toSvg(0.0, 2.0) + ).replace("*{contribution-display}", "default") + } else { + this.replace("*{contribution-display}", "none") + } + } + companion object { private val maxWeight = lazy { @@ -1040,15 +1062,19 @@ enum class PersonaType(private val weight: Double) { } private fun Long.toSvg(levelStartX: Double, xIncrease: Double): String { + return this.toSvgWithY(-1.0, levelStartX, xIncrease) + } + + private fun Long.toSvgWithY(startY: Double, startX: Double, xIncrease: Double): String { val level = this.toString() - var currentX = levelStartX + var currentX = startX val builder = StringBuilder() level.withIndex().forEach { val index = it.index val number = it.value.digitToInt() val numberSvg = numberSvgs[number] - builder.append("") + builder.append("") .append(numberSvg) .append("") diff --git a/src/main/kotlin/org/gitanimals/render/domain/User.kt b/src/main/kotlin/org/gitanimals/render/domain/User.kt index fd48946..ec87260 100644 --- a/src/main/kotlin/org/gitanimals/render/domain/User.kt +++ b/src/main/kotlin/org/gitanimals/render/domain/User.kt @@ -120,12 +120,12 @@ class User( visit += 1 } - fun createLineAnimation(personaId: Long): String { + fun createLineAnimation(personaId: Long, mode: Mode): String { val builder = StringBuilder().openLine() val persona = personas.find { it.id!! >= personaId } ?: throw IllegalArgumentException("Cannot find persona by id \"$personaId\"") - builder.append(persona.toSvgForce()) + builder.append(persona.toSvgForce(mode)) return builder.closeSvg() } @@ -139,13 +139,15 @@ class User( .append(field.fillBackground()) personas.asSequence() - .forEach { builder.append(it.toSvg()) } + .forEach { builder.append(it.toSvg(Mode.FARM)) } return builder.append(field.loadComponent(name, contributions.totalCount())) .append(field.drawBorder()) .closeSvg() } + fun contributionCount(): Long = contributions.totalCount() + private fun List.totalCount(): Long { var totalCount = 0L this.forEach { totalCount += it.contribution } diff --git a/src/main/kotlin/org/gitanimals/render/domain/UserService.kt b/src/main/kotlin/org/gitanimals/render/domain/UserService.kt index 66df64e..006c315 100644 --- a/src/main/kotlin/org/gitanimals/render/domain/UserService.kt +++ b/src/main/kotlin/org/gitanimals/render/domain/UserService.kt @@ -17,8 +17,8 @@ class UserService( return getUserByName(username).createFarmAnimation() } - fun getLineAnimationByUsername(username: String, personaId: Long): String { - return getUserByName(username).createLineAnimation(personaId) + fun getLineAnimationByUsername(username: String, personaId: Long, mode: Mode): String { + return getUserByName(username).createLineAnimation(personaId, mode) } @Retryable(retryFor = [ObjectOptimisticLockingFailureException::class], maxAttempts = 10) diff --git a/src/main/resources/persona/animal/flamingo.svg b/src/main/resources/persona/animal/flamingo.svg index 60f0b67..1a22fe8 100644 --- a/src/main/resources/persona/animal/flamingo.svg +++ b/src/main/resources/persona/animal/flamingo.svg @@ -23,6 +23,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose-java.svg b/src/main/resources/persona/animal/goose-java.svg index 99ef06e..a4cf2c0 100644 --- a/src/main/resources/persona/animal/goose-java.svg +++ b/src/main/resources/persona/animal/goose-java.svg @@ -94,6 +94,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose-js.svg b/src/main/resources/persona/animal/goose-js.svg index 9f3adc3..96d3f8a 100644 --- a/src/main/resources/persona/animal/goose-js.svg +++ b/src/main/resources/persona/animal/goose-js.svg @@ -94,6 +94,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose-kotlin.svg b/src/main/resources/persona/animal/goose-kotlin.svg index 14fce5f..538a3a3 100644 --- a/src/main/resources/persona/animal/goose-kotlin.svg +++ b/src/main/resources/persona/animal/goose-kotlin.svg @@ -94,6 +94,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose-linux.svg b/src/main/resources/persona/animal/goose-linux.svg index d4f764d..41c90d4 100644 --- a/src/main/resources/persona/animal/goose-linux.svg +++ b/src/main/resources/persona/animal/goose-linux.svg @@ -94,6 +94,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose-node.svg b/src/main/resources/persona/animal/goose-node.svg index 87ca8d9..8ac1622 100644 --- a/src/main/resources/persona/animal/goose-node.svg +++ b/src/main/resources/persona/animal/goose-node.svg @@ -94,6 +94,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose-spring.svg b/src/main/resources/persona/animal/goose-spring.svg index c624112..b1e04a0 100644 --- a/src/main/resources/persona/animal/goose-spring.svg +++ b/src/main/resources/persona/animal/goose-spring.svg @@ -94,6 +94,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose-sunglasses.svg b/src/main/resources/persona/animal/goose-sunglasses.svg index 56d0856..45d642c 100644 --- a/src/main/resources/persona/animal/goose-sunglasses.svg +++ b/src/main/resources/persona/animal/goose-sunglasses.svg @@ -64,6 +64,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose-swift.svg b/src/main/resources/persona/animal/goose-swift.svg index b14a16e..b108cc4 100644 --- a/src/main/resources/persona/animal/goose-swift.svg +++ b/src/main/resources/persona/animal/goose-swift.svg @@ -94,6 +94,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/goose.svg b/src/main/resources/persona/animal/goose.svg index 40a0f7d..68d9530 100644 --- a/src/main/resources/persona/animal/goose.svg +++ b/src/main/resources/persona/animal/goose.svg @@ -64,6 +64,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/little-chick-java.svg b/src/main/resources/persona/animal/little-chick-java.svg index aa14328..8653fbc 100644 --- a/src/main/resources/persona/animal/little-chick-java.svg +++ b/src/main/resources/persona/animal/little-chick-java.svg @@ -95,6 +95,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/little-chick-js.svg b/src/main/resources/persona/animal/little-chick-js.svg index 0a375bc..f1224e8 100644 --- a/src/main/resources/persona/animal/little-chick-js.svg +++ b/src/main/resources/persona/animal/little-chick-js.svg @@ -95,6 +95,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/little-chick-kotlin.svg b/src/main/resources/persona/animal/little-chick-kotlin.svg index 07160e9..e904a3c 100644 --- a/src/main/resources/persona/animal/little-chick-kotlin.svg +++ b/src/main/resources/persona/animal/little-chick-kotlin.svg @@ -95,6 +95,18 @@ + + + + + *{contribution} + + + + diff --git a/src/main/resources/persona/animal/little-chick-linux.svg b/src/main/resources/persona/animal/little-chick-linux.svg index 9dd4d23..549158a 100644 --- a/src/main/resources/persona/animal/little-chick-linux.svg +++ b/src/main/resources/persona/animal/little-chick-linux.svg @@ -95,6 +95,18 @@ + + + + + *{contribution} + + + + diff --git a/src/main/resources/persona/animal/little-chick-node.svg b/src/main/resources/persona/animal/little-chick-node.svg index edc72a8..2a2aff3 100644 --- a/src/main/resources/persona/animal/little-chick-node.svg +++ b/src/main/resources/persona/animal/little-chick-node.svg @@ -95,6 +95,18 @@ + + + + + *{contribution} + + + + diff --git a/src/main/resources/persona/animal/little-chick-spring.svg b/src/main/resources/persona/animal/little-chick-spring.svg index 9101ee6..82c0442 100644 --- a/src/main/resources/persona/animal/little-chick-spring.svg +++ b/src/main/resources/persona/animal/little-chick-spring.svg @@ -95,6 +95,18 @@ + + + + + *{contribution} + + + + diff --git a/src/main/resources/persona/animal/little-chick-sunglasses.svg b/src/main/resources/persona/animal/little-chick-sunglasses.svg index fd762e5..dd48a1e 100644 --- a/src/main/resources/persona/animal/little-chick-sunglasses.svg +++ b/src/main/resources/persona/animal/little-chick-sunglasses.svg @@ -65,6 +65,18 @@ + + + + + *{contribution} + + + + diff --git a/src/main/resources/persona/animal/little-chick-swift.svg b/src/main/resources/persona/animal/little-chick-swift.svg index def84f0..5f2abe9 100644 --- a/src/main/resources/persona/animal/little-chick-swift.svg +++ b/src/main/resources/persona/animal/little-chick-swift.svg @@ -95,6 +95,18 @@ + + + + + *{contribution} + + + + diff --git a/src/main/resources/persona/animal/little-chick.svg b/src/main/resources/persona/animal/little-chick.svg index 152fbd4..8db00e9 100644 --- a/src/main/resources/persona/animal/little-chick.svg +++ b/src/main/resources/persona/animal/little-chick.svg @@ -65,6 +65,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/penguin-java.svg b/src/main/resources/persona/animal/penguin-java.svg index 92cad65..fee78e2 100644 --- a/src/main/resources/persona/animal/penguin-java.svg +++ b/src/main/resources/persona/animal/penguin-java.svg @@ -94,6 +94,17 @@ + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/slime-green.svg b/src/main/resources/persona/animal/slime-green.svg index 3eec3ef..4072ca3 100644 --- a/src/main/resources/persona/animal/slime-green.svg +++ b/src/main/resources/persona/animal/slime-green.svg @@ -63,6 +63,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/slime-red-java.svg b/src/main/resources/persona/animal/slime-red-java.svg index f9ada6f..10bd180 100644 --- a/src/main/resources/persona/animal/slime-red-java.svg +++ b/src/main/resources/persona/animal/slime-red-java.svg @@ -63,6 +63,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/slime-red-js.svg b/src/main/resources/persona/animal/slime-red-js.svg index 79ade6a..b991584 100644 --- a/src/main/resources/persona/animal/slime-red-js.svg +++ b/src/main/resources/persona/animal/slime-red-js.svg @@ -63,6 +63,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/slime-red-kotlin.svg b/src/main/resources/persona/animal/slime-red-kotlin.svg index 1ef89cf..aa82035 100644 --- a/src/main/resources/persona/animal/slime-red-kotlin.svg +++ b/src/main/resources/persona/animal/slime-red-kotlin.svg @@ -63,6 +63,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/slime-red-linux.svg b/src/main/resources/persona/animal/slime-red-linux.svg index b4fd032..472c0b2 100644 --- a/src/main/resources/persona/animal/slime-red-linux.svg +++ b/src/main/resources/persona/animal/slime-red-linux.svg @@ -63,6 +63,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/slime-red-node.svg b/src/main/resources/persona/animal/slime-red-node.svg index 3da0e56..69e5bc5 100644 --- a/src/main/resources/persona/animal/slime-red-node.svg +++ b/src/main/resources/persona/animal/slime-red-node.svg @@ -63,6 +63,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/slime-red-swift.svg b/src/main/resources/persona/animal/slime-red-swift.svg index bd86607..94b5f33 100644 --- a/src/main/resources/persona/animal/slime-red-swift.svg +++ b/src/main/resources/persona/animal/slime-red-swift.svg @@ -63,6 +63,17 @@ + + + + + *{contribution} + + + diff --git a/src/main/resources/persona/animal/slime-red.svg b/src/main/resources/persona/animal/slime-red.svg index 8e6d6f2..8699746 100644 --- a/src/main/resources/persona/animal/slime-red.svg +++ b/src/main/resources/persona/animal/slime-red.svg @@ -63,6 +63,17 @@ + + + + + *{contribution} + + + diff --git a/src/test/kotlin/org/gitanimals/render/controller/Api.kt b/src/test/kotlin/org/gitanimals/render/controller/Api.kt new file mode 100644 index 0000000..3f9cf2a --- /dev/null +++ b/src/test/kotlin/org/gitanimals/render/controller/Api.kt @@ -0,0 +1,15 @@ +package org.gitanimals.render.controller + +import io.restassured.RestAssured +import io.restassured.http.ContentType +import io.restassured.response.ExtractableResponse +import io.restassured.response.Response + +fun users(username: String): ExtractableResponse = + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .accept(ContentType.JSON) + .`when`().log().all() + .get("/users/$username") + .then().log().all() + .extract() diff --git a/src/test/kotlin/org/gitanimals/render/controller/filter/CorsFilterTest.kt b/src/test/kotlin/org/gitanimals/render/controller/filter/CorsFilterTest.kt new file mode 100644 index 0000000..e3ca0f5 --- /dev/null +++ b/src/test/kotlin/org/gitanimals/render/controller/filter/CorsFilterTest.kt @@ -0,0 +1,63 @@ +package org.gitanimals.render.controller.filter + +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.collections.shouldExistInOrder +import io.restassured.RestAssured +import io.restassured.http.Header +import org.gitanimals.render.controller.users +import org.gitanimals.render.domain.User +import org.gitanimals.render.domain.UserRepository +import org.gitanimals.render.supports.RedisContainer +import org.junit.jupiter.api.DisplayName +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.server.LocalServerPort +import org.springframework.test.context.ContextConfiguration + +@ContextConfiguration( + classes = [ + RedisContainer::class + ] +) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DisplayName("Cors 적용 테스트의") +internal class CorsFilterTest( + @LocalServerPort private val port: Int, + private val userRepository: UserRepository, +) : DescribeSpec({ + + beforeSpec { + RestAssured.port = port + } + + afterEach { userRepository.deleteAll() } + + describe("/users/{username} api는") { + context("호출되면, ") { + it("cors 허용 header들을 추가해서 반환한다.") { + val user = userRepository.saveAndFlush(user) + + val response = users(user.name) + + response.headers().shouldExistInOrder( + listOf( + { it == Header("Access-Control-Allow-Origin", "*") }, + { it == Header("Access-Control-Allow-Methods", "*") }, + { it == Header("Access-Control-Max-Age", "3600") }, + { + it == Header( + "Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept, Authorization" + ) + } + ) + ) + } + } + } + +}) { + + private companion object { + private val user = User.newUser("devxb", mutableMapOf(2024 to 1000)) + } +} diff --git a/src/test/kotlin/org/gitanimals/render/domain/UserFixture.kt b/src/test/kotlin/org/gitanimals/render/domain/UserFixture.kt index 2aec862..1438a3a 100644 --- a/src/test/kotlin/org/gitanimals/render/domain/UserFixture.kt +++ b/src/test/kotlin/org/gitanimals/render/domain/UserFixture.kt @@ -1,9 +1,10 @@ package org.gitanimals.render.domain import org.gitanimals.render.domain.value.Contribution +import kotlin.random.Random fun user( - id: Long = 0L, + id: Long = Random.nextLong(), name: String = "devxb", personas: MutableList = mutableListOf(), contributions: MutableList = mutableListOf(), diff --git a/src/test/kotlin/org/gitanimals/render/supports/RedisContainer.kt b/src/test/kotlin/org/gitanimals/render/supports/RedisContainer.kt new file mode 100644 index 0000000..fda4dbd --- /dev/null +++ b/src/test/kotlin/org/gitanimals/render/supports/RedisContainer.kt @@ -0,0 +1,27 @@ +package org.gitanimals.render.supports + + +import org.springframework.boot.test.context.TestConfiguration +import org.testcontainers.containers.GenericContainer +import org.testcontainers.utility.DockerImageName + +@TestConfiguration +internal class RedisContainer { + init { + val redis: GenericContainer<*> = GenericContainer(DockerImageName.parse("redis:7.2.3")) + .withExposedPorts(6379) + + runCatching { + redis.start() + }.onFailure { + if (it is com.github.dockerjava.api.exception.InternalServerErrorException) { + redis.start() + } + } + + System.setProperty( + "netx.port", + redis.getMappedPort(6379).toString() + ) + } +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..f509631 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,28 @@ +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:test;MODE=MySQL +spring.jpa.hibernated.ddl-auto=create +spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect +spring.datasource.hikari.maximum-pool-size=4 +spring.datasource.hikari.pool-name=H2_TEST_POOL + +### FOR DEBUGGING ### +logging.level.org.hibernate.SQL=debug +logging.level.org.hibernate.type.descriptor.sql=trace +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.highlight_sql=true +spring.jpa.properties.hibernate.use_sql_comments=true + +### NETX ### +netx.mode=redis +netx.host=localhost +netx.port= +netx.group=render +netx.node-id=1 +netx.node-name=render-1 +netx.recovery-milli=1000 +netx.orphan-milli=60000 +netx.backpressure=40 +netx.logging.level=info + +### GITHUB ### +github.token=1234 diff --git a/src/test/resources/persona/goose/test.svg b/src/test/resources/persona/goose/test.svg index 05b591f..763d1db 100644 --- a/src/test/resources/persona/goose/test.svg +++ b/src/test/resources/persona/goose/test.svg @@ -1,95 +1,367 @@ - + + + + + *{contribution} + + + + - + + - - - - - - + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -100,9 +372,11 @@ - + - + @@ -110,4 +384,5 @@ - + +