diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index def21ecc..bf28e5e6 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -21,7 +21,7 @@ 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 run: | @@ -33,6 +33,13 @@ jobs: - 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 Docker Admin 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 with: @@ -42,4 +49,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"] 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/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..dbbcd96b --- /dev/null +++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/application/AdminService.kt @@ -0,0 +1,101 @@ +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.Crag +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 + +@Service +class AdminService( + private val cragAdminRepository: CragAdminRepository, + private val colorAdminRepository: ColorAdminRepository, + private val gradeAdminRepository: GradeAdminRepository +) { + + @Transactional(readOnly = true) + fun getAllCrag(): List<CragResult.WithGradeCount> { + return cragAdminRepository.findAll().map { crag -> + val gradeCount = gradeAdminRepository.findByCrag(crag).size + CragResult.WithGradeCount( + cragResult = CragResult.from(crag), + gradeCount = gradeCount + ) + } + } + + @Transactional + fun createCrag( + request: SaveCrag.Request + ) { + val location = Location(request.longitude.toDouble(), request.latitude.toDouble()) + val crag = Crag(null, request.name, request.roadAddress, location, request.kakaoPlaceId.toLong()) + + cragAdminRepository.save(crag) + } + + @Transactional(readOnly = true) + fun getCrag( + id: Long + ): CragResult { + val crag = cragAdminRepository.findById(id) + ?: throw EntityNotFoundException("Crag with ID $id not found") + + 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 { + val color = Color(null, request.name, request.hex) + val res = colorAdminRepository.save(color) + + return SaveCragColor.Response( + name = res.name, + hex = res.hex + ) + } + + @Transactional + fun createGrade( + cragId: Long, + request: SaveCragGrade.Request + ): SaveCragGrade.Response { + 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, crag.id!!, color, request.gradeOrder.toInt()) + val res = gradeAdminRepository.save(grade) + + return SaveCragGrade.Response( + colorName = res.color.name, + colorHex = res.color.hex, + gradeOrder = res.order + ) + } + + @Transactional(readOnly = true) + 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..864f649d --- /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.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..b2dce622 --- /dev/null +++ b/clog-admin/src/main/kotlin/org/depromeet/clog/server/admin/api/presentation/dto/CragResult.kt @@ -0,0 +1,31 @@ +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.location.longitude, + latitude = crag.location.latitude, + kakaoPlaceId = crag.kakaoPlaceId + ) + } + } + + data class WithGradeCount( + val cragResult: CragResult, + val gradeCount: Int + ) +} 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..f10580ae --- /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.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/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() + } +} 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..d414865f --- /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.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..d61e931d --- /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.Grade + +interface GradeAdminRepository { + + fun save(grade: Grade): Grade + + fun findByCrag(crag: Crag): List<Grade> +} 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-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> 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-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 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 b99e03ef..8290dc59 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 @@ -3,9 +3,17 @@ package org.depromeet.clog.server.api import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.scheduling.annotation.EnableScheduling +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.FilterType import java.util.TimeZone -@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\\..*"]) + ] +) @EnableScheduling class ClogApplication 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..9f9db621 --- /dev/null +++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/ColorAdminAdapter.kt @@ -0,0 +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.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 { + val entity = colorJpaRepository.save(colorMapper.toEntity(color)) + return colorMapper.toDomain(entity) + } + + override fun findAll(): List<Color> { + return colorJpaRepository.findAll().map { colorMapper.toDomain(it) } + } + + override fun findByNameAndHex(name: String, hex: String): Color? { + 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 new file mode 100644 index 00000000..6bb793ee --- /dev/null +++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/CragAdminAdapter.kt @@ -0,0 +1,29 @@ +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.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 { + val entity = cragJpaRepository.save(cragMapper.toEntity(crag)) + return cragMapper.toDomain(entity) + } + + override fun findById(id: Long): Crag? { + val entity = cragJpaRepository.findByIdOrNull(id) + return cragMapper.toDomain(entity!!) + } + + override fun findAll(): List<Crag> { + 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 new file mode 100644 index 00000000..08519f80 --- /dev/null +++ b/clog-infrastructure/src/main/kotlin/org/depromeet/clog/server/infrastructure/admin/GradeAdminAdapter.kt @@ -0,0 +1,26 @@ +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.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 { + val entity = gradeJpaRepository.save(gradeMapper.toEntity(grade)) + return gradeMapper.toDomain(entity) + } + + override fun findByCrag(crag: Crag): List<Grade> { + return gradeJpaRepository.findByCrag(cragMapper.toEntity(crag)).map { gradeMapper.toDomain(it) } + } +} 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 +} 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> }