From 5e33f6dcdad7c76cfdaa174b56d3a9fe0c70abd3 Mon Sep 17 00:00:00 2001
From: dae won <eodnjs01477@gmail.com>
Date: Tue, 11 Mar 2025 22:43:56 +0900
Subject: [PATCH 1/9] =?UTF-8?q?feat:=20admin=20domain=20repository=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/domain/crag/ColorAdminRepository.kt | 12 +++++++++
 .../admin/domain/crag/CragAdminRepository.kt  | 12 +++++++++
 .../admin/domain/crag/GradeAdminRepository.kt | 11 ++++++++
 .../global/properties/KakaoMapProperties.kt   |  9 +++++++
 clog-infrastructure/build.gradle.kts          |  1 +
 .../infrastructure/admin/ColorAdminAdapter.kt | 25 ++++++++++++++++++
 .../infrastructure/admin/CragAdminAdapter.kt  | 26 +++++++++++++++++++
 .../infrastructure/admin/GradeAdminAdapter.kt | 23 ++++++++++++++++
 .../infrastructure/crag/ColorJpaRepository.kt |  5 +++-
 9 files changed, 123 insertions(+), 1 deletion(-)
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/ColorAdminRepository.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/CragAdminRepository.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/GradeAdminRepository.kt
 create mode 100644 clog-global-utils/src/main/kotlin/org/depromeet/clog/server/global/properties/KakaoMapProperties.kt
 create mode 100644 clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt
 create mode 100644 clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt
 create mode 100644 clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt

diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/ColorAdminRepository.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/ColorAdminRepository.kt
new file mode 100644
index 00000000..b7d23548
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/ColorAdminRepository.kt
@@ -0,0 +1,12 @@
+package org.depromeet.clog.server.admin.domain.crag
+
+import org.depromeet.clog.server.domain.crag.domain.Color
+
+interface ColorAdminRepository {
+
+    fun save(color: Color): Color
+
+    fun findAll(): List<Color>
+
+    fun findByNameAndHex(name: String, hex: String): Color?
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/CragAdminRepository.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/CragAdminRepository.kt
new file mode 100644
index 00000000..07fb203c
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/CragAdminRepository.kt
@@ -0,0 +1,12 @@
+package org.depromeet.clog.server.admin.domain.crag
+
+import org.depromeet.clog.server.domain.crag.domain.Crag
+
+interface CragAdminRepository {
+
+    fun save(crag: Crag): Crag
+
+    fun findById(id: Long): Crag?
+
+    fun findAll(): List<Crag>
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/GradeAdminRepository.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/GradeAdminRepository.kt
new file mode 100644
index 00000000..ead15377
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/GradeAdminRepository.kt
@@ -0,0 +1,11 @@
+package org.depromeet.clog.server.admin.domain.crag
+
+import org.depromeet.clog.server.domain.crag.domain.Crag
+import org.depromeet.clog.server.domain.crag.domain.Grade
+
+interface GradeAdminRepository {
+
+    fun save(grade: Grade): Grade
+
+    fun findByCrag(crag: Crag): List<Grade>
+}
diff --git a/clog-global-utils/src/main/kotlin/org/depromeet/clog/server/global/properties/KakaoMapProperties.kt b/clog-global-utils/src/main/kotlin/org/depromeet/clog/server/global/properties/KakaoMapProperties.kt
new file mode 100644
index 00000000..74874743
--- /dev/null
+++ b/clog-global-utils/src/main/kotlin/org/depromeet/clog/server/global/properties/KakaoMapProperties.kt
@@ -0,0 +1,9 @@
+package org.depromeet.clog.server.global.properties
+
+import org.springframework.boot.context.properties.ConfigurationProperties
+
+@ConfigurationProperties(prefix = "kakao.map")
+data class KakaoMapProperties(
+    val restApiKey: String,
+    val localSearchBaseUrl: String
+)
diff --git a/clog-infrastructure/build.gradle.kts b/clog-infrastructure/build.gradle.kts
index d5a9b76c..ce50517c 100644
--- a/clog-infrastructure/build.gradle.kts
+++ b/clog-infrastructure/build.gradle.kts
@@ -1,6 +1,7 @@
 dependencies {
     implementation(project(":clog-global-utils"))
     implementation(project(":clog-domain"))
+    implementation(project(":clog-admin"))
 
     implementation("org.springframework.boot:spring-boot-starter-data-jpa")
     implementation("com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.10.0")
diff --git a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt
new file mode 100644
index 00000000..8ce2f69a
--- /dev/null
+++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt
@@ -0,0 +1,25 @@
+package org.depromeet.clog.server.infrastructure.admin
+
+import org.depromeet.clog.server.admin.domain.crag.ColorAdminRepository
+import org.depromeet.clog.server.domain.crag.domain.Color
+import org.depromeet.clog.server.infrastructure.crag.ColorEntity
+import org.depromeet.clog.server.infrastructure.crag.ColorJpaRepository
+import org.springframework.stereotype.Component
+
+@Component
+class ColorAdminAdapter(
+    private val colorJpaRepository: ColorJpaRepository
+) : ColorAdminRepository {
+
+    override fun save(color: Color): Color {
+        return colorJpaRepository.save(ColorEntity.fromDomain(color)).toDomain()
+    }
+
+    override fun findAll(): List<Color> {
+        return colorJpaRepository.findAll().map { it.toDomain() }
+    }
+
+    override fun findByNameAndHex(name: String, hex: String): Color? {
+        return colorJpaRepository.findByNameAndHex(name, hex).toDomain()
+    }
+}
diff --git a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt
new file mode 100644
index 00000000..21bf79bc
--- /dev/null
+++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt
@@ -0,0 +1,26 @@
+package org.depromeet.clog.server.infrastructure.admin
+
+import org.depromeet.clog.server.admin.domain.crag.CragAdminRepository
+import org.depromeet.clog.server.domain.crag.domain.Crag
+import org.depromeet.clog.server.infrastructure.crag.CragEntity
+import org.depromeet.clog.server.infrastructure.crag.CragJpaRepository
+import org.springframework.data.repository.findByIdOrNull
+import org.springframework.stereotype.Component
+
+@Component
+class CragAdminAdapter(
+    private val cragJpaRepository: CragJpaRepository
+) : CragAdminRepository {
+
+    override fun save(crag: Crag): Crag {
+        return cragJpaRepository.save(CragEntity.fromDomain(crag)).toDomain()
+    }
+
+    override fun findById(id: Long): Crag? {
+        return cragJpaRepository.findByIdOrNull(id)?.toDomain()
+    }
+
+    override fun findAll(): List<Crag> {
+        return cragJpaRepository.findAll().map { it.toDomain() }
+    }
+}
diff --git a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt
new file mode 100644
index 00000000..042cb7a4
--- /dev/null
+++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt
@@ -0,0 +1,23 @@
+package org.depromeet.clog.server.infrastructure.admin
+
+import org.depromeet.clog.server.admin.domain.crag.GradeAdminRepository
+import org.depromeet.clog.server.domain.crag.domain.Crag
+import org.depromeet.clog.server.domain.crag.domain.Grade
+import org.depromeet.clog.server.infrastructure.crag.CragEntity
+import org.depromeet.clog.server.infrastructure.crag.GradeEntity
+import org.depromeet.clog.server.infrastructure.crag.GradeJpaRepository
+import org.springframework.stereotype.Component
+
+@Component
+class GradeAdminAdapter(
+    private val gradeJpaRepository: GradeJpaRepository
+) : GradeAdminRepository {
+
+    override fun save(grade: Grade): Grade {
+        return gradeJpaRepository.save(GradeEntity.fromDomain(grade)).toDomain()
+    }
+
+    override fun findByCrag(crag: Crag): List<Grade> {
+        return gradeJpaRepository.findByCrag(CragEntity.fromDomain(crag)).map { it.toDomain() }
+    }
+}
diff --git a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/crag/ColorJpaRepository.kt b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/crag/ColorJpaRepository.kt
index 237f5660..406f5962 100644
--- a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/crag/ColorJpaRepository.kt
+++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/crag/ColorJpaRepository.kt
@@ -2,4 +2,7 @@ package org.depromeet.clog.server.infrastructure.crag
 
 import org.springframework.data.jpa.repository.JpaRepository
 
-interface ColorJpaRepository : JpaRepository<ColorEntity, Long>
+interface ColorJpaRepository : JpaRepository<ColorEntity, Long> {
+
+    fun findByNameAndHex(name: String, hex: String): ColorEntity
+}

From 74840f66d4c00feb8bd8b8b2d999fce599e792f4 Mon Sep 17 00:00:00 2001
From: dae won <eodnjs01477@gmail.com>
Date: Tue, 11 Mar 2025 22:49:38 +0900
Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20secur?=
 =?UTF-8?q?ity=20=EC=B6=94=EA=B0=80=20-=20=EA=B8=B0=EB=B3=B8=20session=20?=
 =?UTF-8?q?=EC=82=AC=EC=9A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/common/AdminUserDetailsService.kt   | 27 +++++++++++
 .../admin/common/config/SecurityConfig.kt     | 47 +++++++++++++++++++
 2 files changed, 74 insertions(+)
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/common/AdminUserDetailsService.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/common/config/SecurityConfig.kt

diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/common/AdminUserDetailsService.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/common/AdminUserDetailsService.kt
new file mode 100644
index 00000000..5f5f22d3
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/common/AdminUserDetailsService.kt
@@ -0,0 +1,27 @@
+package org.depromeet.clog.server.admin.common
+
+import org.depromeet.clog.server.domain.user.domain.Provider
+import org.depromeet.clog.server.domain.user.domain.UserRepository
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.core.userdetails.UsernameNotFoundException
+import org.springframework.stereotype.Service
+
+@Service
+class AdminUserDetailsService(
+    private val userRepository: UserRepository
+) : UserDetailsService {
+
+    override fun loadUserByUsername(username: String): UserDetails {
+        val user = userRepository.findByLoginIdAndProvider(username, Provider.LOCAL)
+            ?: throw UsernameNotFoundException("User not found with username: $username")
+
+        return User.builder()
+            .username(user.loginId)
+            .password(user.name)
+            .authorities(listOf(SimpleGrantedAuthority("ROLE_USER")))
+            .build()
+    }
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/common/config/SecurityConfig.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/common/config/SecurityConfig.kt
new file mode 100644
index 00000000..e8e39ebb
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/common/config/SecurityConfig.kt
@@ -0,0 +1,47 @@
+package org.depromeet.clog.server.admin.common.config
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.security.web.SecurityFilterChain
+
+@Configuration
+@EnableWebSecurity
+class SecurityConfig(
+    private val userDetailsService: UserDetailsService
+) {
+
+    @Bean
+    @Throws(Exception::class)
+    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+        http
+            .userDetailsService(userDetailsService)
+            .authorizeHttpRequests { requests ->
+                requests
+                    .requestMatchers("/", "/css/**", "/js/**", "/images/**").permitAll()
+                    .anyRequest().authenticated()
+            }
+            .formLogin { form ->
+                form
+                    .loginPage("/admin/login")
+                    .defaultSuccessUrl("/admin/index", true)
+                    .permitAll()
+            }
+            .logout { logout ->
+                logout
+                    .logoutSuccessUrl("/admin/login?logout")
+                    .permitAll()
+            }
+
+        return http.build()
+    }
+
+    @Bean
+    fun passwordEncoder(): PasswordEncoder {
+        return BCryptPasswordEncoder()
+    }
+}

From 5409574194bbcfdde17a33ae90ec285cd242a4aa Mon Sep 17 00:00:00 2001
From: dae won <eodnjs01477@gmail.com>
Date: Tue, 11 Mar 2025 23:19:00 +0900
Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20API?=
 =?UTF-8?q?=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../clog/server/admin/ClogAdminApplication.kt | 10 +-
 .../admin/api/application/AdminService.kt     | 87 +++++++++++++++++
 .../admin/api/presentation/AdminController.kt | 97 +++++++++++++++++++
 .../admin/api/presentation/dto/ColorResult.kt | 18 ++++
 .../admin/api/presentation/dto/CragResult.kt  | 26 +++++
 .../admin/api/presentation/dto/GradeResult.kt | 20 ++++
 .../admin/api/presentation/dto/SaveCrag.kt    | 23 +++++
 .../api/presentation/dto/SaveCragColor.kt     | 14 +++
 .../api/presentation/dto/SaveCragGrade.kt     | 16 +++
 clog-admin/src/main/resources/application.yml | 10 +-
 .../clog/server/api/ClogApplication.kt        | 10 +-
 11 files changed, 328 insertions(+), 3 deletions(-)
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/AdminController.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/ColorResult.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/GradeResult.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCrag.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCragColor.kt
 create mode 100644 clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCragGrade.kt

diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/ClogAdminApplication.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/ClogAdminApplication.kt
index 57e8f0fb..222a67e7 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/ClogAdminApplication.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/ClogAdminApplication.kt
@@ -2,8 +2,16 @@ package org.depromeet.clog.server.admin
 
 import org.springframework.boot.autoconfigure.SpringBootApplication
 import org.springframework.boot.runApplication
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.context.annotation.FilterType
 
-@SpringBootApplication(scanBasePackages = ["org.depromeet.clog.server"])
+@SpringBootApplication
+@ComponentScan(
+    basePackages = ["org.depromeet.clog.server"],
+    excludeFilters = [
+        ComponentScan.Filter(type = FilterType.REGEX, pattern = ["org\\.depromeet\\.clog\\.server\\.api\\..*"])
+    ]
+)
 class ClogAdminApplication
 
 fun main(args: Array<String>) {
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
new file mode 100644
index 00000000..9d6f4654
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
@@ -0,0 +1,87 @@
+package org.depromeet.clog.server.admin.api.application
+
+import jakarta.persistence.EntityNotFoundException
+import org.depromeet.clog.server.admin.api.presentation.dto.*
+import org.depromeet.clog.server.admin.domain.crag.ColorAdminRepository
+import org.depromeet.clog.server.admin.domain.crag.CragAdminRepository
+import org.depromeet.clog.server.admin.domain.crag.GradeAdminRepository
+import org.depromeet.clog.server.domain.crag.domain.Color
+import org.depromeet.clog.server.domain.crag.domain.Coordinate
+import org.depromeet.clog.server.domain.crag.domain.Crag
+import org.depromeet.clog.server.domain.crag.domain.Grade
+import org.springframework.stereotype.Service
+
+@Service
+class AdminService(
+    private val cragAdminRepository: CragAdminRepository,
+    private val colorAdminRepository: ColorAdminRepository,
+    private val gradeAdminRepository: GradeAdminRepository
+) {
+
+    fun getAllCrag(): List<Crag> {
+        return cragAdminRepository.findAll()
+    }
+
+    fun createCrag(
+        request: SaveCrag.Request
+    ) {
+        val coordinate = Coordinate(request.longitude.toDouble(), request.latitude.toDouble())
+        val crag = Crag(null, request.name, request.roadAddress, coordinate, request.kakaoPlaceId.toLong())
+
+        cragAdminRepository.save(crag)
+    }
+
+    fun getCrag(
+        id: Long
+    ): CragResult {
+        val crag = cragAdminRepository.findById(id)
+            ?: throw EntityNotFoundException("Crag with ID $id not found")
+
+        return CragResult.from(crag)
+    }
+
+    fun getAllColor(): List<ColorResult> {
+        return colorAdminRepository.findAll().map { ColorResult.from(it) }
+    }
+
+    fun createColor(
+        request: SaveCragColor.Request
+    ): SaveCragColor.Response {
+        val color = Color(null, request.name, request.hex)
+        val res = colorAdminRepository.save(color)
+
+        return SaveCragColor.Response(
+            name = res.name,
+            hex = res.hex
+        )
+    }
+
+    fun createGrade(
+        cragId: Long,
+        request: SaveCragGrade.Request
+    ): SaveCragGrade.Response {
+        val color = colorAdminRepository.findByNameAndHex(request.colorName, request.colorHex)
+            ?: throw EntityNotFoundException("Color ${request.colorName} not found")
+
+        val crag: Crag = cragAdminRepository.findById(cragId)
+            ?: throw NoSuchElementException("Crag not found with id: $cragId")
+
+        val grade = Grade(null, color, crag, request.gradeOrder.toInt())
+        val res = gradeAdminRepository.save(grade)
+
+        return SaveCragGrade.Response(
+            colorName = res.color.name,
+            colorHex = res.color.hex,
+            gradeOrder = res.order
+        )
+    }
+
+    fun getGrades(
+        id: Long
+    ): List<GradeResult> {
+        val crag = cragAdminRepository.findById(id)
+            ?: throw EntityNotFoundException("Crag with ID $id not found")
+
+        return gradeAdminRepository.findByCrag(crag).map { GradeResult.from(it) }
+    }
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/AdminController.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/AdminController.kt
new file mode 100644
index 00000000..15c10637
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/AdminController.kt
@@ -0,0 +1,97 @@
+package org.depromeet.clog.server.admin.api.presentation
+
+import org.depromeet.clog.server.admin.api.application.AdminService
+import org.depromeet.clog.server.admin.api.presentation.dto.SaveCrag
+import org.depromeet.clog.server.admin.api.presentation.dto.SaveCragColor
+import org.depromeet.clog.server.admin.api.presentation.dto.SaveCragGrade
+import org.depromeet.clog.server.infrastructure.configuration.properties.KakaoMapProperties
+import org.springframework.stereotype.Controller
+import org.springframework.ui.Model
+import org.springframework.web.bind.annotation.*
+
+@Controller
+@RequestMapping("/admin")
+class AdminController(
+    private val adminService: AdminService,
+    private val kakaoMapProperties: KakaoMapProperties
+) {
+
+    companion object {
+        private const val LOGIN_PAGE = "admin/login"
+    }
+
+    @GetMapping("/login")
+    fun loginPage(): String = LOGIN_PAGE
+
+    @GetMapping("/index")
+    fun index(model: Model): String {
+        val result = adminService.getAllCrag()
+        model.addAttribute("crags", result)
+
+        return "admin/cragList"
+    }
+
+    @GetMapping("/add/crags")
+    fun saveCragPage(model: Model): String {
+        model.addAttribute("crag", SaveCrag.Request())
+        model.addAttribute("kakaoKey", kakaoMapProperties.restApiKey)
+
+        return "admin/cragAdd"
+    }
+
+    @PostMapping("/add/crags")
+    fun saveCrag(@ModelAttribute("crag") request: SaveCrag.Request): String {
+        val sanitized = request.sanitized()
+        adminService.createCrag(sanitized)
+
+        return "redirect:/admin/add/crags"
+    }
+
+    @GetMapping("/crags/{id}/details")
+    fun cragDetailsPage(@PathVariable id: Long, model: Model): String {
+        val crag = adminService.getCrag(id)
+        val grades = adminService.getGrades(id)
+        model.addAttribute("crag", crag)
+        model.addAttribute("grades", grades)
+
+        return "admin/cragDetails"
+    }
+
+    @GetMapping("/crags/colors")
+    fun getCragColors(model: Model): String {
+        val result = adminService.getAllColor()
+        model.addAttribute("colors", result)
+
+        return "admin/colorList"
+    }
+
+    @GetMapping("/crags/add/colors")
+    fun saveCragColorPage(model: Model): String {
+        model.addAttribute("color", SaveCragColor.Request())
+
+        return "admin/cragColorAdd"
+    }
+
+    @PostMapping("/crags/add/colors")
+    fun saveCragColorPage(@ModelAttribute("color") request: SaveCragColor.Request): String {
+        adminService.createColor(request)
+
+        return "redirect:/admin/crags/colors"
+    }
+
+    @GetMapping("/crags/{id}/add/grades")
+    fun saveCragGradePage(@PathVariable id: Long, model: Model): String {
+        model.addAttribute("cragId", id)
+        model.addAttribute("grade", SaveCragGrade.Request())
+        model.addAttribute("colors", adminService.getAllColor())
+
+        return "admin/cragGradeAdd"
+    }
+
+    @PostMapping("/crags/{id}/add/grades")
+    fun saveCragGrade(@PathVariable id: Long, @ModelAttribute("grade") request: SaveCragGrade.Request): String {
+        adminService.createGrade(id, request)
+
+        return "redirect:/admin/crags/$id/details"
+    }
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/ColorResult.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/ColorResult.kt
new file mode 100644
index 00000000..7c0bbd0d
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/ColorResult.kt
@@ -0,0 +1,18 @@
+package org.depromeet.clog.server.admin.api.presentation.dto
+
+import org.depromeet.clog.server.domain.crag.domain.Color
+
+data class ColorResult(
+    val name: String,
+    val hex: String
+) {
+
+    companion object {
+        fun from(color: Color): ColorResult {
+            return ColorResult(
+                name = color.name,
+                hex = color.hex
+            )
+        }
+    }
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
new file mode 100644
index 00000000..c277f908
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
@@ -0,0 +1,26 @@
+package org.depromeet.clog.server.admin.api.presentation.dto
+
+import org.depromeet.clog.server.domain.crag.domain.Crag
+
+data class CragResult(
+    val id: Long,
+    val name: String,
+    val roadAddress: String,
+    val longitude: Double,
+    val latitude: Double,
+    val kakaoPlaceId: Long
+) {
+
+    companion object {
+        fun from(crag: Crag): CragResult {
+            return CragResult(
+                id = crag.id!!,
+                name = crag.name,
+                roadAddress = crag.roadAddress,
+                longitude = crag.coordinate.longitude,
+                latitude = crag.coordinate.latitude,
+                kakaoPlaceId = crag.kakaoPlaceId
+            )
+        }
+    }
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/GradeResult.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/GradeResult.kt
new file mode 100644
index 00000000..2c305a01
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/GradeResult.kt
@@ -0,0 +1,20 @@
+package org.depromeet.clog.server.admin.api.presentation.dto
+
+import org.depromeet.clog.server.domain.crag.domain.Grade
+
+data class GradeResult(
+    val colorName: String,
+    val colorHex: String,
+    val gradeOrder: Int?
+) {
+
+    companion object {
+        fun from(grade: Grade): GradeResult {
+            return GradeResult(
+                colorName = grade.color.name,
+                colorHex = grade.color.hex,
+                gradeOrder = grade.order
+            )
+        }
+    }
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCrag.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCrag.kt
new file mode 100644
index 00000000..2f315503
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCrag.kt
@@ -0,0 +1,23 @@
+package org.depromeet.clog.server.admin.api.presentation.dto
+
+object SaveCrag {
+
+    data class Request(
+        val name: String = "",
+        val roadAddress: String = "",
+        val longitude: String = "",
+        val latitude: String = "",
+        val kakaoPlaceId: String = ""
+    ) {
+
+        fun sanitized(): Request {
+            return Request(
+                name = name.replace(",", ""),
+                roadAddress = roadAddress.replace(",", ""),
+                longitude = longitude.replace(",", ""),
+                latitude = latitude.replace(",", ""),
+                kakaoPlaceId = kakaoPlaceId.replace(",", "")
+            )
+        }
+    }
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCragColor.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCragColor.kt
new file mode 100644
index 00000000..6f9c24c2
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCragColor.kt
@@ -0,0 +1,14 @@
+package org.depromeet.clog.server.admin.api.presentation.dto
+
+object SaveCragColor {
+
+    data class Request(
+        val name: String = "",
+        val hex: String = ""
+    )
+
+    data class Response(
+        val name: String,
+        val hex: String
+    )
+}
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCragGrade.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCragGrade.kt
new file mode 100644
index 00000000..ddf676b5
--- /dev/null
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/SaveCragGrade.kt
@@ -0,0 +1,16 @@
+package org.depromeet.clog.server.admin.api.presentation.dto
+
+object SaveCragGrade {
+
+    data class Request(
+        val colorName: String = "",
+        val colorHex: String = "",
+        val gradeOrder: String = ""
+    )
+
+    data class Response(
+        val colorName: String,
+        val colorHex: String,
+        val gradeOrder: Int?
+    )
+}
diff --git a/clog-admin/src/main/resources/application.yml b/clog-admin/src/main/resources/application.yml
index 34882ba8..b9bda6ec 100644
--- a/clog-admin/src/main/resources/application.yml
+++ b/clog-admin/src/main/resources/application.yml
@@ -23,4 +23,12 @@ spring:
 jwt:
   secret: ${JWT_SECRET}
   access-token-expiration-millis: 3600000
-  refresh-token-expiration-millis: 604800000
\ No newline at end of file
+  refresh-token-expiration-millis: 604800000
+
+ncp:
+  storage:
+    access-key: ${STORAGE_ACCESS_KEY}
+    secret-key: ${STORAGE_SECRET_KEY}
+    end-point: ${STORAGE_END_POINT}
+    bucket-name: ${STORAGE_BUCKET_NAME}
+    region: ${STORAGE_REGION}
\ No newline at end of file
diff --git a/clog-api/src/main/kotlin/org/depromeet/clog/server/api/ClogApplication.kt b/clog-api/src/main/kotlin/org/depromeet/clog/server/api/ClogApplication.kt
index fb6d4450..0c82c6a6 100644
--- a/clog-api/src/main/kotlin/org/depromeet/clog/server/api/ClogApplication.kt
+++ b/clog-api/src/main/kotlin/org/depromeet/clog/server/api/ClogApplication.kt
@@ -2,8 +2,16 @@ package org.depromeet.clog.server.api
 
 import org.springframework.boot.autoconfigure.SpringBootApplication
 import org.springframework.boot.runApplication
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.context.annotation.FilterType
 
-@SpringBootApplication(scanBasePackages = ["org.depromeet.clog.server"])
+@SpringBootApplication
+@ComponentScan(
+    basePackages = ["org.depromeet.clog.server"],
+    excludeFilters = [
+        ComponentScan.Filter(type = FilterType.REGEX, pattern = ["org\\.depromeet\\.clog\\.server\\.admin\\..*"])
+    ]
+)
 class ClogApplication
 
 fun main(args: Array<String>) {

From 4d454e40050f3835b6176beaf16103e9443bd4e7 Mon Sep 17 00:00:00 2001
From: dae won <eodnjs01477@gmail.com>
Date: Tue, 11 Mar 2025 23:32:58 +0900
Subject: [PATCH 4/9] =?UTF-8?q?chore:=20dockerfile=20=EC=B6=94=EA=B0=80=20?=
 =?UTF-8?q?=EB=B0=8F=20deploy.yml=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .github/workflows/dev-deploy.yml | 21 +++++++++++++--------
 clog-admin/Dockerfile            |  7 +++++++
 2 files changed, 20 insertions(+), 8 deletions(-)
 create mode 100644 clog-admin/Dockerfile

diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml
index def21ecc..adea9a72 100644
--- a/.github/workflows/dev-deploy.yml
+++ b/.github/workflows/dev-deploy.yml
@@ -21,17 +21,21 @@ jobs:
           java-version: '21'
 
       - name: Build with Gradle (skip tests)
-        run: ./gradlew build -x test
+        run: ./gradlew :clog-api:build :clog-admin:build -x test
 
-      - name: Build Docker Image
+      - name: Build API Docker Image
         run: |
-          docker build -f clog-api/Dockerfile -t ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE }}:${{ github.ref_name }} .
+          docker build -f clog-api/Dockerfile -t ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_API_IMAGE }}:${{ github.ref_name }} .
 
-      - name: Login to Docker Registry
-        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ secrets.DOCKER_REGISTRY }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
+      - name: Push API Docker Image
+        run: docker push ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_API_IMAGE }}:${{ github.ref_name }}
 
-      - name: Push Docker Image
-        run: docker push ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE }}:${{ github.ref_name }}
+      - name: Build Admin Docker Image
+        run: |
+          docker build -f clog-admin/Dockerfile -t ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_ADMIN_IMAGE }}:${{ github.ref_name }} .
+
+      - name: Push Admin Docker Image
+        run: docker push ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_ADMIN_IMAGE }}:${{ github.ref_name }}
 
       - name: Deploy on Remote Server via SSH
         uses: appleboy/ssh-action@v0.1.8
@@ -42,4 +46,5 @@ jobs:
           port: ${{ secrets.SSH_PORT }}
           script: |
             docker compose -f /root/docker-compose.yml pull api
-            docker compose -f /root/docker-compose.yml up -d api
+            docker compose -f /root/docker-compose.yml pull admin
+            docker compose -f /root/docker-compose.yml up -d api admin
diff --git a/clog-admin/Dockerfile b/clog-admin/Dockerfile
new file mode 100644
index 00000000..54a4cea9
--- /dev/null
+++ b/clog-admin/Dockerfile
@@ -0,0 +1,7 @@
+FROM openjdk:21-slim
+
+WORKDIR /data/www
+
+COPY clog-admin/build/libs/*.jar admin.jar
+
+ENTRYPOINT ["java", "-jar", "admin.jar"]

From 28a0b6df8bc7c7c4a575e8ff9b9565c5464dd972 Mon Sep 17 00:00:00 2001
From: dae won <eodnjs01477@gmail.com>
Date: Tue, 11 Mar 2025 23:46:02 +0900
Subject: [PATCH 5/9] =?UTF-8?q?chore:=20deploy.yml=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .github/workflows/dev-deploy.yml | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml
index adea9a72..bf28e5e6 100644
--- a/.github/workflows/dev-deploy.yml
+++ b/.github/workflows/dev-deploy.yml
@@ -23,18 +23,21 @@ jobs:
       - name: Build with Gradle (skip tests)
         run: ./gradlew :clog-api:build :clog-admin:build -x test
 
-      - name: Build API Docker Image
+      - name: Build Docker Image
         run: |
-          docker build -f clog-api/Dockerfile -t ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_API_IMAGE }}:${{ github.ref_name }} .
+          docker build -f clog-api/Dockerfile -t ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE }}:${{ github.ref_name }} .
 
-      - name: Push API Docker Image
-        run: docker push ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_API_IMAGE }}:${{ github.ref_name }}
+      - name: Login to Docker Registry
+        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ secrets.DOCKER_REGISTRY }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
 
-      - name: Build Admin Docker Image
+      - name: Push Docker Image
+        run: docker push ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE }}:${{ github.ref_name }}
+
+      - name: Build Docker Admin Image
         run: |
           docker build -f clog-admin/Dockerfile -t ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_ADMIN_IMAGE }}:${{ github.ref_name }} .
 
-      - name: Push Admin Docker Image
+      - name: Push Docker Admin Image
         run: docker push ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_ADMIN_IMAGE }}:${{ github.ref_name }}
 
       - name: Deploy on Remote Server via SSH

From 4fb343dcad62ccc046754372bbcf7311e809dcaf Mon Sep 17 00:00:00 2001
From: dae won <eodnjs01477@gmail.com>
Date: Wed, 12 Mar 2025 00:41:20 +0900
Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EC=95=94=EC=9E=A5=20=EB=82=9C?=
 =?UTF-8?q?=EC=9D=B4=EB=8F=84=20=EA=B0=9C=EC=88=98=20=ED=91=9C=EC=8B=9C=20?=
 =?UTF-8?q?view=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/api/application/AdminService.kt     | 10 ++++--
 .../admin/api/presentation/dto/CragResult.kt  |  5 +++
 .../resources/templates/admin/cragList.html   | 33 +++----------------
 3 files changed, 18 insertions(+), 30 deletions(-)

diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
index 9d6f4654..f1ca3075 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
@@ -18,8 +18,14 @@ class AdminService(
     private val gradeAdminRepository: GradeAdminRepository
 ) {
 
-    fun getAllCrag(): List<Crag> {
-        return cragAdminRepository.findAll()
+    fun getAllCrag(): List<CragResult.WithGradeCount> {
+        return cragAdminRepository.findAll().map { crag ->
+            val gradeCount = gradeAdminRepository.findByCrag(crag).size
+            CragResult.WithGradeCount(
+                cragResult = CragResult.from(crag),
+                gradeCount = gradeCount
+            )
+        }
     }
 
     fun createCrag(
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
index c277f908..87bc9ca4 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
@@ -23,4 +23,9 @@ data class CragResult(
             )
         }
     }
+
+    data class WithGradeCount(
+        val cragResult: CragResult,
+        val gradeCount: Int
+    )
 }
diff --git a/clog-admin/src/main/resources/templates/admin/cragList.html b/clog-admin/src/main/resources/templates/admin/cragList.html
index aca697b9..d8233070 100644
--- a/clog-admin/src/main/resources/templates/admin/cragList.html
+++ b/clog-admin/src/main/resources/templates/admin/cragList.html
@@ -48,6 +48,7 @@ <h1 class="h3 mb-2 text-gray-800">암장 관리</h1>
                                             <th>암장 ID</th>
                                             <th>암장 이름</th>
                                             <th>도로명 주소</th>
+                                            <th>난이도 개수</th>
 <!--                                            <th>경도</th>-->
 <!--                                            <th>위도</th>-->
 <!--                                            <th>카카오 맵 ID</th>-->
@@ -57,10 +58,11 @@ <h1 class="h3 mb-2 text-gray-800">암장 관리</h1>
                                     <tbody>
                                         <tr th:each="crag : ${crags}">
                                             <td>
-                                                <a th:href="@{/admin/crags/{id}/details(id=${crag.id})}" th:text="${crag.id}"></a>
+                                                <a th:href="@{/admin/crags/{id}/details(id=${crag.cragResult.id})}" th:text="${crag.cragResult.id}"></a>
                                             </td>
-                                            <td th:text="${crag.name}"></td>
-                                            <td th:text="${crag.roadAddress}"></td>
+                                            <td th:text="${crag.cragResult.name}"></td>
+                                            <td th:text="${crag.cragResult.roadAddress}"></td>
+                                            <td th:text="${crag.gradeCount}"></td>
 <!--                                            <td th:text="${crag.coordinate.latitude}"></td>-->
 <!--                                            <td th:text="${crag.coordinate.longitude}"></td>-->
 <!--                                            <td th:text="${crag.kakaoPlaceId}"></td>-->
@@ -93,30 +95,5 @@ <h1 class="h3 mb-2 text-gray-800">암장 관리</h1>
     </a>
     <th:block th:replace="~{fragments/admin/fragments.html :: admJS}"/>
 
-<script th:src="@{/webjars/jquery/jquery.min.js}"></script>
-<script th:src="@{/webjars/datatables/js/jquery.dataTables.min.js}"></script>
-
-<script>
-    $(document).ready(function() {
-        $('#dataTable').DataTable({
-            "processing": true,
-            "serverSide": true,
-            "ajax": {
-                "url": "/admin/index",
-                "type": "GET",
-                "data": function (d) {
-                    d.page = d.start / d.length; // DataTables가 요청하는 page 계산
-                    d.size = d.length; // DataTables가 요청하는 size
-                }
-            },
-            "columns": [
-                { "data": "id" },
-                { "data": "name" },
-                { "data": "roadAddress" }
-            ]
-        });
-    });
-</script>
-
 </body>
 </html>
\ No newline at end of file

From 3d68d703c116f243d062622a30bb152af8a30fbc Mon Sep 17 00:00:00 2001
From: dae won <eodnjs01477@gmail.com>
Date: Wed, 12 Mar 2025 01:48:10 +0900
Subject: [PATCH 7/9] =?UTF-8?q?feat:=20hex=20code=20=EC=83=89=EC=83=81=20?=
 =?UTF-8?q?=ED=91=9C=EC=8B=9C=20view=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../src/main/resources/templates/admin/colorList.html      | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/clog-admin/src/main/resources/templates/admin/colorList.html b/clog-admin/src/main/resources/templates/admin/colorList.html
index c1165001..35d2db65 100644
--- a/clog-admin/src/main/resources/templates/admin/colorList.html
+++ b/clog-admin/src/main/resources/templates/admin/colorList.html
@@ -52,7 +52,12 @@ <h1 class="h3 mb-2 text-gray-800">색상 관리</h1>
                                     <tbody>
                                         <tr th:each="color : ${colors}">
                                             <td th:text="${color.name}"></td>
-                                            <td th:text="${color.hex}"></td>
+                                            <td>
+                                                <div style="display: flex; align-items: center;">
+                                                    <div th:style="'width: 20px; height: 20px; background-color: ' + ${color.hex} + '; border: 1px solid #000; margin-right: 10px;'"></div>
+                                                    <span th:text="${color.hex}"></span>
+                                                </div>
+                                            </td>
                                         </tr>
                                     </tbody>
                                 </table>

From bcb43d17591bff4fc8b36eda8bcb600587563cca Mon Sep 17 00:00:00 2001
From: dae won <eodnjs01477@gmail.com>
Date: Wed, 12 Mar 2025 15:43:46 +0900
Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?=
 =?UTF-8?q?=EC=85=98=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../clog/server/admin/api/application/AdminService.kt     | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
index f1ca3075..975fbc0c 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
@@ -10,6 +10,7 @@ import org.depromeet.clog.server.domain.crag.domain.Coordinate
 import org.depromeet.clog.server.domain.crag.domain.Crag
 import org.depromeet.clog.server.domain.crag.domain.Grade
 import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
 
 @Service
 class AdminService(
@@ -18,6 +19,7 @@ class AdminService(
     private val gradeAdminRepository: GradeAdminRepository
 ) {
 
+    @Transactional(readOnly = true)
     fun getAllCrag(): List<CragResult.WithGradeCount> {
         return cragAdminRepository.findAll().map { crag ->
             val gradeCount = gradeAdminRepository.findByCrag(crag).size
@@ -28,6 +30,7 @@ class AdminService(
         }
     }
 
+    @Transactional
     fun createCrag(
         request: SaveCrag.Request
     ) {
@@ -37,6 +40,7 @@ class AdminService(
         cragAdminRepository.save(crag)
     }
 
+    @Transactional(readOnly = true)
     fun getCrag(
         id: Long
     ): CragResult {
@@ -46,10 +50,12 @@ class AdminService(
         return CragResult.from(crag)
     }
 
+    @Transactional(readOnly = true)
     fun getAllColor(): List<ColorResult> {
         return colorAdminRepository.findAll().map { ColorResult.from(it) }
     }
 
+    @Transactional
     fun createColor(
         request: SaveCragColor.Request
     ): SaveCragColor.Response {
@@ -62,6 +68,7 @@ class AdminService(
         )
     }
 
+    @Transactional
     fun createGrade(
         cragId: Long,
         request: SaveCragGrade.Request
@@ -82,6 +89,7 @@ class AdminService(
         )
     }
 
+    @Transactional(readOnly = true)
     fun getGrades(
         id: Long
     ): List<GradeResult> {

From 8cbfd8ceee29bcde95f7ef2da389f579f429b112 Mon Sep 17 00:00:00 2001
From: bigcir <eodnjs01477@gmail.com>
Date: Sat, 22 Mar 2025 17:18:33 +0900
Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?=
 =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=97=90=20=EB=A7=9E=EC=B6=B0=20?=
 =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 clog-admin/build.gradle.kts                      |  1 -
 .../server/admin/api/application/AdminService.kt | 14 +++++++-------
 .../admin/api/presentation/dto/ColorResult.kt    |  2 +-
 .../admin/api/presentation/dto/CragResult.kt     |  4 ++--
 .../admin/api/presentation/dto/GradeResult.kt    |  2 +-
 .../admin/domain/crag/ColorAdminRepository.kt    |  2 +-
 .../admin/domain/crag/GradeAdminRepository.kt    |  2 +-
 .../resources/templates/admin/cragDetails.html   | 16 ++++++++--------
 .../infrastructure/admin/ColorAdminAdapter.kt    | 13 ++++++++-----
 .../infrastructure/admin/CragAdminAdapter.kt     | 11 +++++++----
 .../infrastructure/admin/GradeAdminAdapter.kt    | 13 ++++++++-----
 .../infrastructure/crag/GradeJpaRepository.kt    |  2 ++
 12 files changed, 46 insertions(+), 36 deletions(-)

diff --git a/clog-admin/build.gradle.kts b/clog-admin/build.gradle.kts
index 15896013..537ac57d 100644
--- a/clog-admin/build.gradle.kts
+++ b/clog-admin/build.gradle.kts
@@ -3,7 +3,6 @@ dependencies {
     implementation(project(":clog-domain"))
     implementation(project(":clog-infrastructure"))
 
-    implementation("org.springframework.boot:spring-boot-devtools")
     implementation("org.springframework.boot:spring-boot-starter-data-jpa")
     implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
     implementation("org.springframework.boot:spring-boot-starter-security")
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
index 975fbc0c..dbbcd96b 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt
@@ -5,10 +5,10 @@ import org.depromeet.clog.server.admin.api.presentation.dto.*
 import org.depromeet.clog.server.admin.domain.crag.ColorAdminRepository
 import org.depromeet.clog.server.admin.domain.crag.CragAdminRepository
 import org.depromeet.clog.server.admin.domain.crag.GradeAdminRepository
-import org.depromeet.clog.server.domain.crag.domain.Color
-import org.depromeet.clog.server.domain.crag.domain.Coordinate
 import org.depromeet.clog.server.domain.crag.domain.Crag
-import org.depromeet.clog.server.domain.crag.domain.Grade
+import org.depromeet.clog.server.domain.crag.domain.Location
+import org.depromeet.clog.server.domain.crag.domain.color.Color
+import org.depromeet.clog.server.domain.crag.domain.grade.Grade
 import org.springframework.stereotype.Service
 import org.springframework.transaction.annotation.Transactional
 
@@ -34,8 +34,8 @@ class AdminService(
     fun createCrag(
         request: SaveCrag.Request
     ) {
-        val coordinate = Coordinate(request.longitude.toDouble(), request.latitude.toDouble())
-        val crag = Crag(null, request.name, request.roadAddress, coordinate, request.kakaoPlaceId.toLong())
+        val location = Location(request.longitude.toDouble(), request.latitude.toDouble())
+        val crag = Crag(null, request.name, request.roadAddress, location, request.kakaoPlaceId.toLong())
 
         cragAdminRepository.save(crag)
     }
@@ -73,13 +73,13 @@ class AdminService(
         cragId: Long,
         request: SaveCragGrade.Request
     ): SaveCragGrade.Response {
-        val color = colorAdminRepository.findByNameAndHex(request.colorName, request.colorHex)
+        val color: Color = colorAdminRepository.findByNameAndHex(request.colorName, request.colorHex)
             ?: throw EntityNotFoundException("Color ${request.colorName} not found")
 
         val crag: Crag = cragAdminRepository.findById(cragId)
             ?: throw NoSuchElementException("Crag not found with id: $cragId")
 
-        val grade = Grade(null, color, crag, request.gradeOrder.toInt())
+        val grade = Grade(null, crag.id!!, color, request.gradeOrder.toInt())
         val res = gradeAdminRepository.save(grade)
 
         return SaveCragGrade.Response(
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/ColorResult.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/ColorResult.kt
index 7c0bbd0d..864f649d 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/ColorResult.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/ColorResult.kt
@@ -1,6 +1,6 @@
 package org.depromeet.clog.server.admin.api.presentation.dto
 
-import org.depromeet.clog.server.domain.crag.domain.Color
+import org.depromeet.clog.server.domain.crag.domain.color.Color
 
 data class ColorResult(
     val name: String,
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
index 87bc9ca4..b2dce622 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt
@@ -17,8 +17,8 @@ data class CragResult(
                 id = crag.id!!,
                 name = crag.name,
                 roadAddress = crag.roadAddress,
-                longitude = crag.coordinate.longitude,
-                latitude = crag.coordinate.latitude,
+                longitude = crag.location.longitude,
+                latitude = crag.location.latitude,
                 kakaoPlaceId = crag.kakaoPlaceId
             )
         }
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/GradeResult.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/GradeResult.kt
index 2c305a01..f10580ae 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/GradeResult.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/GradeResult.kt
@@ -1,6 +1,6 @@
 package org.depromeet.clog.server.admin.api.presentation.dto
 
-import org.depromeet.clog.server.domain.crag.domain.Grade
+import org.depromeet.clog.server.domain.crag.domain.grade.Grade
 
 data class GradeResult(
     val colorName: String,
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/ColorAdminRepository.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/ColorAdminRepository.kt
index b7d23548..d414865f 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/ColorAdminRepository.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/ColorAdminRepository.kt
@@ -1,6 +1,6 @@
 package org.depromeet.clog.server.admin.domain.crag
 
-import org.depromeet.clog.server.domain.crag.domain.Color
+import org.depromeet.clog.server.domain.crag.domain.color.Color
 
 interface ColorAdminRepository {
 
diff --git a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/GradeAdminRepository.kt b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/GradeAdminRepository.kt
index ead15377..d61e931d 100644
--- a/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/GradeAdminRepository.kt
+++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/domain/crag/GradeAdminRepository.kt
@@ -1,7 +1,7 @@
 package org.depromeet.clog.server.admin.domain.crag
 
 import org.depromeet.clog.server.domain.crag.domain.Crag
-import org.depromeet.clog.server.domain.crag.domain.Grade
+import org.depromeet.clog.server.domain.crag.domain.grade.Grade
 
 interface GradeAdminRepository {
 
diff --git a/clog-admin/src/main/resources/templates/admin/cragDetails.html b/clog-admin/src/main/resources/templates/admin/cragDetails.html
index f56d8f3d..f1784d70 100644
--- a/clog-admin/src/main/resources/templates/admin/cragDetails.html
+++ b/clog-admin/src/main/resources/templates/admin/cragDetails.html
@@ -50,14 +50,14 @@ <h6 class="m-0 font-weight-bold text-primary" th:text="${crag.name}">암장 이
                   <th>도로명 주소</th>
                   <td th:text="${crag.roadAddress}"></td>
                 </tr>
-                <tr>
-                  <th>경도</th>
-                  <td th:text="${crag.coordinate.longitude}"></td>
-                </tr>
-                <tr>
-                  <th>위도</th>
-                  <td th:text="${crag.coordinate.latitude}"></td>
-                </tr>
+<!--                <tr>-->
+<!--                  <th>경도</th>-->
+<!--                  <td th:text="${crag.coordinate.longitude}"></td>-->
+<!--                </tr>-->
+<!--                <tr>-->
+<!--                  <th>위도</th>-->
+<!--                  <td th:text="${crag.coordinate.latitude}"></td>-->
+<!--                </tr>-->
                 </tbody>
               </table>
             </div>
diff --git a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt
index 8ce2f69a..9f9db621 100644
--- a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt
+++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt
@@ -1,25 +1,28 @@
 package org.depromeet.clog.server.infrastructure.admin
 
 import org.depromeet.clog.server.admin.domain.crag.ColorAdminRepository
-import org.depromeet.clog.server.domain.crag.domain.Color
-import org.depromeet.clog.server.infrastructure.crag.ColorEntity
+import org.depromeet.clog.server.domain.crag.domain.color.Color
 import org.depromeet.clog.server.infrastructure.crag.ColorJpaRepository
+import org.depromeet.clog.server.infrastructure.mappers.ColorMapper
 import org.springframework.stereotype.Component
 
 @Component
 class ColorAdminAdapter(
+    private val colorMapper: ColorMapper,
     private val colorJpaRepository: ColorJpaRepository
 ) : ColorAdminRepository {
 
     override fun save(color: Color): Color {
-        return colorJpaRepository.save(ColorEntity.fromDomain(color)).toDomain()
+        val entity = colorJpaRepository.save(colorMapper.toEntity(color))
+        return colorMapper.toDomain(entity)
     }
 
     override fun findAll(): List<Color> {
-        return colorJpaRepository.findAll().map { it.toDomain() }
+        return colorJpaRepository.findAll().map { colorMapper.toDomain(it) }
     }
 
     override fun findByNameAndHex(name: String, hex: String): Color? {
-        return colorJpaRepository.findByNameAndHex(name, hex).toDomain()
+        val entity = colorJpaRepository.findByNameAndHex(name, hex)
+        return colorMapper.toDomain(entity)
     }
 }
diff --git a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt
index 21bf79bc..6bb793ee 100644
--- a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt
+++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt
@@ -2,25 +2,28 @@ package org.depromeet.clog.server.infrastructure.admin
 
 import org.depromeet.clog.server.admin.domain.crag.CragAdminRepository
 import org.depromeet.clog.server.domain.crag.domain.Crag
-import org.depromeet.clog.server.infrastructure.crag.CragEntity
 import org.depromeet.clog.server.infrastructure.crag.CragJpaRepository
+import org.depromeet.clog.server.infrastructure.mappers.CragMapper
 import org.springframework.data.repository.findByIdOrNull
 import org.springframework.stereotype.Component
 
 @Component
 class CragAdminAdapter(
+    private val cragMapper: CragMapper,
     private val cragJpaRepository: CragJpaRepository
 ) : CragAdminRepository {
 
     override fun save(crag: Crag): Crag {
-        return cragJpaRepository.save(CragEntity.fromDomain(crag)).toDomain()
+        val entity = cragJpaRepository.save(cragMapper.toEntity(crag))
+        return cragMapper.toDomain(entity)
     }
 
     override fun findById(id: Long): Crag? {
-        return cragJpaRepository.findByIdOrNull(id)?.toDomain()
+        val entity = cragJpaRepository.findByIdOrNull(id)
+        return cragMapper.toDomain(entity!!)
     }
 
     override fun findAll(): List<Crag> {
-        return cragJpaRepository.findAll().map { it.toDomain() }
+        return cragJpaRepository.findAll().map { cragMapper.toDomain(it) }
     }
 }
diff --git a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt
index 042cb7a4..08519f80 100644
--- a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt
+++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt
@@ -2,22 +2,25 @@ package org.depromeet.clog.server.infrastructure.admin
 
 import org.depromeet.clog.server.admin.domain.crag.GradeAdminRepository
 import org.depromeet.clog.server.domain.crag.domain.Crag
-import org.depromeet.clog.server.domain.crag.domain.Grade
-import org.depromeet.clog.server.infrastructure.crag.CragEntity
-import org.depromeet.clog.server.infrastructure.crag.GradeEntity
+import org.depromeet.clog.server.domain.crag.domain.grade.Grade
 import org.depromeet.clog.server.infrastructure.crag.GradeJpaRepository
+import org.depromeet.clog.server.infrastructure.mappers.CragMapper
+import org.depromeet.clog.server.infrastructure.mappers.GradeMapper
 import org.springframework.stereotype.Component
 
 @Component
 class GradeAdminAdapter(
+    private val cragMapper: CragMapper,
+    private val gradeMapper: GradeMapper,
     private val gradeJpaRepository: GradeJpaRepository
 ) : GradeAdminRepository {
 
     override fun save(grade: Grade): Grade {
-        return gradeJpaRepository.save(GradeEntity.fromDomain(grade)).toDomain()
+        val entity = gradeJpaRepository.save(gradeMapper.toEntity(grade))
+        return gradeMapper.toDomain(entity)
     }
 
     override fun findByCrag(crag: Crag): List<Grade> {
-        return gradeJpaRepository.findByCrag(CragEntity.fromDomain(crag)).map { it.toDomain() }
+        return gradeJpaRepository.findByCrag(cragMapper.toEntity(crag)).map { gradeMapper.toDomain(it) }
     }
 }
diff --git a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/crag/GradeJpaRepository.kt b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/crag/GradeJpaRepository.kt
index 3fc464d5..e22c5aed 100644
--- a/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/crag/GradeJpaRepository.kt
+++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/crag/GradeJpaRepository.kt
@@ -24,4 +24,6 @@ interface GradeJpaRepository : JpaRepository<GradeEntity, Long>, KotlinJdslJpqlE
         @Param("cursor") cursor: Long?,
         pageable: Pageable
     ): List<GradeEntity>
+
+    fun findByCrag(crag: CragEntity): MutableList<GradeEntity>
 }