From 78e26b54df40c8ba44c7857cf6346213c309351e Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Wed, 30 Jul 2025 14:24:55 +0900 Subject: [PATCH 01/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B4=88=EC=95=88=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../admin/controller/AdminController.java | 69 ++++ .../server/common/config/SecurityConfig.java | 7 +- .../resources/templates/admin/dashboard.html | 296 ++++++++++++++++++ src/main/resources/templates/admin/login.html | 106 +++++++ 5 files changed, 477 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/wayble/server/admin/controller/AdminController.java create mode 100644 src/main/resources/templates/admin/dashboard.html create mode 100644 src/main/resources/templates/admin/login.html diff --git a/build.gradle b/build.gradle index eb48ecc6..d42df073 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,7 @@ dependencies { implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0' implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' diff --git a/src/main/java/com/wayble/server/admin/controller/AdminController.java b/src/main/java/com/wayble/server/admin/controller/AdminController.java new file mode 100644 index 00000000..fa8057dc --- /dev/null +++ b/src/main/java/com/wayble/server/admin/controller/AdminController.java @@ -0,0 +1,69 @@ +package com.wayble.server.admin.controller; + +import jakarta.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Slf4j +@Controller +@RequestMapping("/admin") +public class AdminController { + + @Value("${spring.admin.username}") + private String adminUsername; + + @Value("${spring.admin.password}") + private String adminPassword; + + @GetMapping("") + public String adminLoginPage(HttpSession session, Model model) { + // 이미 로그인된 경우 대시보드로 리다이렉트 + if (session.getAttribute("adminLoggedIn") != null) { + return "redirect:/admin/dashboard"; + } + return "admin/login"; + } + + @PostMapping("/login") + public String adminLogin( + @RequestParam String username, + @RequestParam String password, + HttpSession session, + Model model + ) { + if (adminUsername.equals(username) && adminPassword.equals(password)) { + session.setAttribute("adminLoggedIn", true); + session.setAttribute("adminUsername", username); + log.info("관리자 로그인 성공: {}", username); + return "redirect:/admin/dashboard"; + } else { + model.addAttribute("error", "잘못된 사용자명 또는 비밀번호입니다."); + log.warn("관리자 로그인 실패 시도: {}", username); + return "admin/login"; + } + } + + @GetMapping("/dashboard") + public String adminDashboard(HttpSession session, Model model) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + return "admin/dashboard"; + } + + @PostMapping("/logout") + public String adminLogout(HttpSession session) { + session.invalidate(); + log.info("관리자 로그아웃"); + return "redirect:/admin"; + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/common/config/SecurityConfig.java b/src/main/java/com/wayble/server/common/config/SecurityConfig.java index 62a873c0..cb4c69b7 100644 --- a/src/main/java/com/wayble/server/common/config/SecurityConfig.java +++ b/src/main/java/com/wayble/server/common/config/SecurityConfig.java @@ -29,7 +29,9 @@ public PasswordEncoder passwordEncoder() { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(csrf -> csrf.disable()) + .csrf(csrf -> csrf + .ignoringRequestMatchers("/admin/**") // 관리자 페이지는 CSRF 비활성화 + ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() @@ -43,7 +45,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/v3/api-docs/**", "/", "/index", - "/index.html" + "/index.html", + "/admin/**" // 관리자 페이지 허용 ).permitAll() .anyRequest().authenticated() ) diff --git a/src/main/resources/templates/admin/dashboard.html b/src/main/resources/templates/admin/dashboard.html new file mode 100644 index 00000000..efeff3eb --- /dev/null +++ b/src/main/resources/templates/admin/dashboard.html @@ -0,0 +1,296 @@ + + + + + + Wayble 관리자 대시보드 + + + + + + + + +
+ +
+

대시보드

+

Wayble 서비스 현황을 한눈에 확인하세요

+
+ + +
+ +
+
+
+
+
+ + + +
+
+
+
+
전체 웨이블존
+
1,234개
+
+
+
+
+
+ + +
+
+
+
+
+ + + +
+
+
+
+
전체 사용자
+
5,678명
+
+
+
+
+
+ + +
+
+
+
+
+ + + +
+
+
+
+
오늘 방문자
+
892명
+
+
+
+
+
+ + +
+
+
+
+
+ + + +
+
+
+
+
ES 동기화
+
정상
+
+
+
+
+
+
+ + +
+ +
+
+

최근 활동

+
+
+
+
+ + + +
+
+
+

새로운 웨이블존 등록

+

5분 전

+
+
+ +
+
+
+ + + +
+
+
+

신규 사용자 가입

+

15분 전

+
+
+ +
+
+
+ + + +
+
+
+

데이터 동기화 완료

+

30분 전

+
+
+
+
+
+ + +
+
+

시스템 상태

+
+
+ API 서버 + + 정상 + +
+ +
+ 데이터베이스 + + 정상 + +
+ +
+ Elasticsearch + + 정상 + +
+ +
+ 파일 스토리지 + + 정상 + +
+
+
+
+
+ + + +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/admin/login.html b/src/main/resources/templates/admin/login.html new file mode 100644 index 00000000..fb835969 --- /dev/null +++ b/src/main/resources/templates/admin/login.html @@ -0,0 +1,106 @@ + + + + + + Wayble 관리자 로그인 + + + + +
+
+
+ + + +
+

+ Wayble 관리자 +

+

+ 관리자 계정으로 로그인하세요 +

+
+ +
+ +
+
+
+ + + +
+
+

+
+
+
+ +
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+

+ © 2025 Wayble. 관리자 전용 페이지입니다. +

+
+
+ + + + \ No newline at end of file From b33d6621aa7e728b829b008cb14807536bc5f22f Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Wed, 30 Jul 2025 15:04:44 +0900 Subject: [PATCH 02/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EC=B4=88?= =?UTF-8?q?=EC=95=88=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminWaybleZoneController.java | 40 +++++++ .../admin/dto/AdminWaybleZoneDetailDto.java | 57 +++++++++ .../admin/dto/AdminWaybleZonePageDto.java | 34 ++++++ .../dto/AdminWaybleZoneThumbnailDto.java | 16 +++ .../admin/service/AdminUserService.java | 4 + .../admin/service/AdminWaybleZoneService.java | 31 +++++ .../repository/WaybleZoneRepository.java | 2 + .../WaybleZoneRepositoryCustom.java | 9 ++ .../repository/WaybleZoneRepositoryImpl.java | 110 ++++++++++++++++++ 9 files changed, 303 insertions(+) create mode 100644 src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java create mode 100644 src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneDetailDto.java create mode 100644 src/main/java/com/wayble/server/admin/dto/AdminWaybleZonePageDto.java create mode 100644 src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneThumbnailDto.java create mode 100644 src/main/java/com/wayble/server/admin/service/AdminUserService.java create mode 100644 src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java diff --git a/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java b/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java new file mode 100644 index 00000000..de2bd719 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java @@ -0,0 +1,40 @@ +package com.wayble.server.admin.controller; + +import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; +import com.wayble.server.admin.service.AdminWaybleZoneService; +import com.wayble.server.common.response.CommonResponse; + +import lombok.RequiredArgsConstructor; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping("/api/v1/admin/wayble-zones") +public class AdminWaybleZoneController { + + private final AdminWaybleZoneService adminWaybleZoneService; + + @GetMapping() + public CommonResponse> findByCondition( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size) + { + return CommonResponse.success(adminWaybleZoneService.findWaybleZonesByPage(page, size)); + } + + @GetMapping("/count") + public CommonResponse findAllWaybleZoneCount() { + return CommonResponse.success(adminWaybleZoneService.getTotalWaybleZoneCounts()); + } + + @GetMapping("/{waybleZoneId}") + public CommonResponse findWaybleZoneById(@PathVariable("waybleZoneId") long waybleZoneId) { + return CommonResponse.success(adminWaybleZoneService.findWaybleZoneById(waybleZoneId)); + } +} diff --git a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneDetailDto.java b/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneDetailDto.java new file mode 100644 index 00000000..af2104a1 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneDetailDto.java @@ -0,0 +1,57 @@ +package com.wayble.server.admin.dto; + +import com.wayble.server.wayblezone.entity.WaybleZoneFacility; +import com.wayble.server.wayblezone.entity.WaybleZoneType; + +import java.time.LocalDateTime; +import java.util.List; + +public record AdminWaybleZoneDetailDto( + Long zoneId, + String zoneName, + String contactNumber, + WaybleZoneType zoneType, + String fullAddress, + Double latitude, + Double longitude, + double rating, + long reviewCount, + long likes, + String mainImageUrl, + LocalDateTime createdAt, + LocalDateTime lastModifiedAt, + LocalDateTime syncedAt, + + // 시설 정보 + FacilityInfo facility, + + // 운영시간 정보 + List operatingHours, + + // 이미지 목록 + List imageUrls, + + // 최근 리뷰 (최대 5개) + List recentReviews +) { + + public record FacilityInfo( + WaybleZoneFacility facilityType, + String description + ) {} + + public record OperatingHourInfo( + String dayOfWeek, + String openTime, + String closeTime, + boolean isHoliday + ) {} + + public record RecentReviewInfo( + Long reviewId, + String content, + double rating, + String userName, + LocalDateTime createdAt + ) {} +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZonePageDto.java b/src/main/java/com/wayble/server/admin/dto/AdminWaybleZonePageDto.java new file mode 100644 index 00000000..c41a497c --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/AdminWaybleZonePageDto.java @@ -0,0 +1,34 @@ +package com.wayble.server.admin.dto; + +import java.util.List; + +public record AdminWaybleZonePageDto( + List content, + int currentPage, + int pageSize, + long totalElements, + int totalPages, + boolean hasNext, + boolean hasPrevious +) { + public static AdminWaybleZonePageDto of( + List content, + int currentPage, + int pageSize, + long totalElements + ) { + int totalPages = (int) Math.ceil((double) totalElements / pageSize); + boolean hasNext = currentPage < totalPages - 1; + boolean hasPrevious = currentPage > 0; + + return new AdminWaybleZonePageDto( + content, + currentPage, + pageSize, + totalElements, + totalPages, + hasNext, + hasPrevious + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneThumbnailDto.java b/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneThumbnailDto.java new file mode 100644 index 00000000..7bd59364 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneThumbnailDto.java @@ -0,0 +1,16 @@ +package com.wayble.server.admin.dto; + +import com.wayble.server.wayblezone.entity.WaybleZoneFacility; +import com.wayble.server.wayblezone.entity.WaybleZoneType; + +public record AdminWaybleZoneThumbnailDto( + Long zoneId, + String zoneName, + WaybleZoneType zoneType, + long reviewCount, + long likes, + double rating, + String address, + WaybleZoneFacility facilityInfo // 시설 정보를 문자열로 요약 +) { +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java new file mode 100644 index 00000000..81a04e4a --- /dev/null +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -0,0 +1,4 @@ +package com.wayble.server.admin.service; + +public class AdminUserService { +} diff --git a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java new file mode 100644 index 00000000..d5d06edd --- /dev/null +++ b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java @@ -0,0 +1,31 @@ +package com.wayble.server.admin.service; + +import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; +import com.wayble.server.wayblezone.repository.WaybleZoneRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +@RequiredArgsConstructor +public class AdminWaybleZoneService { + private final WaybleZoneRepository waybleZoneRepository; + + public long getTotalWaybleZoneCounts() { + return waybleZoneRepository.count(); + } + + public List findWaybleZonesByPage(int page, int size) { + return waybleZoneRepository.findWaybleZonesWithPaging(page, size); + } + + public AdminWaybleZoneDetailDto findWaybleZoneById(long waybleZoneId) { + Optional optionalWaybleZone = waybleZoneRepository.findAdminWaybleZoneDetailById(waybleZoneId); + return optionalWaybleZone.orElse(null); + } +} diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepository.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepository.java index c34cd3e9..175b4329 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepository.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepository.java @@ -7,4 +7,6 @@ public interface WaybleZoneRepository extends JpaRepository, WaybleZoneRepositoryCustom { List findByAddress_CityContainingAndZoneType(String city, com.wayble.server.wayblezone.entity.WaybleZoneType category); + + long count(); } diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java index e19a92e4..bd07f703 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java @@ -1,10 +1,13 @@ package com.wayble.server.wayblezone.repository; +import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; import com.wayble.server.explore.dto.search.response.WaybleZoneDistrictResponseDto; import com.wayble.server.wayblezone.entity.WaybleZone; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; public interface WaybleZoneRepositoryCustom { @@ -15,4 +18,10 @@ public interface WaybleZoneRepositoryCustom { // ES 동기화 관련 메서드들 List findUnsyncedZones(int limit); List findZonesModifiedAfter(LocalDateTime since, int limit); + + // 페이징 조회 메서드 (관리자용) + List findWaybleZonesWithPaging(int page, int size); + + // 상세 조회 메서드 (관리자용) + Optional findAdminWaybleZoneDetailById(Long zoneId); } diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java index 6838c042..9194d58e 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java @@ -2,6 +2,8 @@ import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; +import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; import com.wayble.server.explore.dto.common.FacilityResponseDto; import com.wayble.server.explore.dto.common.WaybleZoneInfoResponseDto; import com.wayble.server.explore.dto.search.response.WaybleZoneDistrictResponseDto; @@ -10,6 +12,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import static com.wayble.server.wayblezone.entity.QWaybleZone.waybleZone; @@ -125,6 +128,113 @@ public List findZonesModifiedAfter(LocalDateTime since, int limit) { .fetch(); } + @Override + public List findWaybleZonesWithPaging(int page, int size) { + return queryFactory + .select(Projections.constructor(AdminWaybleZoneThumbnailDto.class, + waybleZone.id, + waybleZone.zoneName, + waybleZone.zoneType, + waybleZone.reviewCount, + waybleZone.likes, + waybleZone.rating, + waybleZone.address.state + .concat(" ") + .concat(waybleZone.address.city.coalesce("")) + .concat(" ") + .concat(waybleZone.address.district.coalesce("")) + .concat(" ") + .concat(waybleZone.address.streetAddress.coalesce("")) + .concat(" ") + .concat(waybleZone.address.detailAddress.coalesce("")), + waybleZone.facility + )) + .from(waybleZone) + .leftJoin(waybleZone.facility) + .orderBy(waybleZone.id.asc()) + .offset((long) page * size) + .limit(size) + .fetch(); + } + + @Override + public Optional findAdminWaybleZoneDetailById(Long zoneId) { + // 1. WaybleZone을 모든 연관 엔티티와 함께 fetch join으로 조회 + WaybleZone zone = queryFactory + .selectFrom(waybleZone) + .leftJoin(waybleZone.facility).fetchJoin() + .leftJoin(waybleZone.operatingHours).fetchJoin() + .leftJoin(waybleZone.waybleZoneImageList).fetchJoin() + .leftJoin(waybleZone.reviewList).fetchJoin() + .where(waybleZone.id.eq(zoneId)) + .fetchOne(); + + if (zone == null) { + return Optional.empty(); + } + + // 2. DTO 변환 + AdminWaybleZoneDetailDto.FacilityInfo facilityInfo = null; + if (zone.getFacility() != null) { + facilityInfo = new AdminWaybleZoneDetailDto.FacilityInfo( + zone.getFacility(), + zone.getFacility() + " 이용 가능" + ); + } + + // 3. 운영시간 정보 변환 + List operatingHours = zone.getOperatingHours().stream() + .map(hour -> new AdminWaybleZoneDetailDto.OperatingHourInfo( + hour.getDayOfWeek().toString(), + hour.getStartTime().toString(), + hour.getCloseTime().toString(), + hour.getIsClosed() + )) + .collect(Collectors.toList()); + + // 4. 이미지 URL 목록 변환 + List imageUrls = zone.getWaybleZoneImageList().stream() + .map(image -> image.getImageUrl()) + .collect(Collectors.toList()); + + // 5. 최근 리뷰 정보 변환 (최대 5개, 최신순) + List recentReviews = zone.getReviewList().stream() + .sorted((r1, r2) -> r2.getCreatedAt().compareTo(r1.getCreatedAt())) // 최신순 정렬 + .limit(5) + .map(review -> new AdminWaybleZoneDetailDto.RecentReviewInfo( + review.getId(), + review.getContent(), + review.getRating(), + review.getUser().getUsername(), // User 엔티티에서 이름 가져오기 + review.getCreatedAt() + )) + .collect(Collectors.toList()); + + // 6. 최종 DTO 생성 + AdminWaybleZoneDetailDto detailDto = new AdminWaybleZoneDetailDto( + zone.getId(), + zone.getZoneName(), + zone.getContactNumber(), + zone.getZoneType(), + zone.getAddress().toFullAddress(), + zone.getAddress().getLatitude(), + zone.getAddress().getLongitude(), + zone.getRating(), + zone.getReviewCount(), + zone.getLikes(), + zone.getMainImageUrl(), + zone.getCreatedAt(), + zone.getLastModifiedAt(), + zone.getSyncedAt(), + facilityInfo, + operatingHours, + imageUrls, + recentReviews + ); + + return Optional.of(detailDto); + } + // 내부 클래스로 visit count 담을 DTO public static class VisitCount { public final Long zoneId; From f44612db8ace8c3cad61976b6422a3872ea8f73f Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Wed, 30 Jul 2025 15:36:29 +0900 Subject: [PATCH 03/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EC=9B=A8=EC=9D=B4=EB=B8=94=EC=A1=B4=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminWaybleZoneController.java | 3 +- .../AdminWaybleZoneViewController.java | 65 ++++ .../admin/service/AdminWaybleZoneService.java | 23 +- .../repository/WaybleZoneRepositoryImpl.java | 38 +-- .../resources/templates/admin/dashboard.html | 2 +- .../templates/admin/wayble-zone-detail.html | 292 ++++++++++++++++++ .../templates/admin/wayble-zones.html | 236 ++++++++++++++ 7 files changed, 621 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneViewController.java create mode 100644 src/main/resources/templates/admin/wayble-zone-detail.html create mode 100644 src/main/resources/templates/admin/wayble-zones.html diff --git a/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java b/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java index de2bd719..cd56f3e4 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java +++ b/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Optional; @RestController @RequiredArgsConstructor @@ -34,7 +35,7 @@ public CommonResponse findAllWaybleZoneCount() { } @GetMapping("/{waybleZoneId}") - public CommonResponse findWaybleZoneById(@PathVariable("waybleZoneId") long waybleZoneId) { + public CommonResponse> findWaybleZoneById(@PathVariable("waybleZoneId") long waybleZoneId) { return CommonResponse.success(adminWaybleZoneService.findWaybleZoneById(waybleZoneId)); } } diff --git a/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneViewController.java b/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneViewController.java new file mode 100644 index 00000000..e25efe49 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneViewController.java @@ -0,0 +1,65 @@ +package com.wayble.server.admin.controller; + +import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.AdminWaybleZonePageDto; +import com.wayble.server.admin.service.AdminWaybleZoneService; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Optional; + +@Slf4j +@Controller +@RequestMapping("/admin/wayble-zones") +@RequiredArgsConstructor +public class AdminWaybleZoneViewController { + + private final AdminWaybleZoneService adminWaybleZoneService; + + @GetMapping + public String getWaybleZones(HttpSession session, Model model, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "100") int size) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + // 페이징 데이터 조회 + AdminWaybleZonePageDto pageData = adminWaybleZoneService.getWaybleZonesWithPaging(page, size); + + model.addAttribute("pageData", pageData); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + + log.debug("웨이블존 목록 조회 - 페이지: {}, 전체: {}", page, pageData.totalElements()); + + return "admin/wayble-zones"; + } + + @GetMapping("/{id}") + public String getWaybleZoneDetail(HttpSession session, Model model, @PathVariable Long id) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + Optional waybleZoneOpt = adminWaybleZoneService.findWaybleZoneById(id); + if (waybleZoneOpt.isEmpty()) { + return "redirect:/admin/wayble-zones?error=notfound"; + } + + model.addAttribute("waybleZone", waybleZoneOpt.get()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + + log.debug("웨이블존 상세 조회 - ID: {}", id); + + return "admin/wayble-zone-detail"; + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java index d5d06edd..04c272d2 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java @@ -1,17 +1,20 @@ package com.wayble.server.admin.service; import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.AdminWaybleZonePageDto; import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; import com.wayble.server.wayblezone.repository.WaybleZoneRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; +@Slf4j @Service -@Transactional +@Transactional(readOnly = true) @RequiredArgsConstructor public class AdminWaybleZoneService { private final WaybleZoneRepository waybleZoneRepository; @@ -20,12 +23,24 @@ public long getTotalWaybleZoneCounts() { return waybleZoneRepository.count(); } + public AdminWaybleZonePageDto getWaybleZonesWithPaging(int page, int size) { + // 페이징 데이터 조회 + List content = waybleZoneRepository.findWaybleZonesWithPaging(page, size); + + // 전체 개수 조회 + long totalElements = waybleZoneRepository.count(); + + log.debug("웨이블존 페이징 조회 - 페이지: {}, 크기: {}, 전체: {}", page, size, totalElements); + + // 페이징 응답 DTO 생성 + return AdminWaybleZonePageDto.of(content, page, size, totalElements); + } + public List findWaybleZonesByPage(int page, int size) { return waybleZoneRepository.findWaybleZonesWithPaging(page, size); } - public AdminWaybleZoneDetailDto findWaybleZoneById(long waybleZoneId) { - Optional optionalWaybleZone = waybleZoneRepository.findAdminWaybleZoneDetailById(waybleZoneId); - return optionalWaybleZone.orElse(null); + public Optional findWaybleZoneById(Long waybleZoneId) { + return waybleZoneRepository.findAdminWaybleZoneDetailById(waybleZoneId); } } diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java index 9194d58e..24d2023b 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java @@ -159,13 +159,10 @@ public List findWaybleZonesWithPaging(int page, int @Override public Optional findAdminWaybleZoneDetailById(Long zoneId) { - // 1. WaybleZone을 모든 연관 엔티티와 함께 fetch join으로 조회 + // 1. 기본 WaybleZone 정보와 facility 조회 WaybleZone zone = queryFactory .selectFrom(waybleZone) .leftJoin(waybleZone.facility).fetchJoin() - .leftJoin(waybleZone.operatingHours).fetchJoin() - .leftJoin(waybleZone.waybleZoneImageList).fetchJoin() - .leftJoin(waybleZone.reviewList).fetchJoin() .where(waybleZone.id.eq(zoneId)) .fetchOne(); @@ -182,35 +179,12 @@ public Optional findAdminWaybleZoneDetailById(Long zon ); } - // 3. 운영시간 정보 변환 - List operatingHours = zone.getOperatingHours().stream() - .map(hour -> new AdminWaybleZoneDetailDto.OperatingHourInfo( - hour.getDayOfWeek().toString(), - hour.getStartTime().toString(), - hour.getCloseTime().toString(), - hour.getIsClosed() - )) - .collect(Collectors.toList()); - - // 4. 이미지 URL 목록 변환 - List imageUrls = zone.getWaybleZoneImageList().stream() - .map(image -> image.getImageUrl()) - .collect(Collectors.toList()); - - // 5. 최근 리뷰 정보 변환 (최대 5개, 최신순) - List recentReviews = zone.getReviewList().stream() - .sorted((r1, r2) -> r2.getCreatedAt().compareTo(r1.getCreatedAt())) // 최신순 정렬 - .limit(5) - .map(review -> new AdminWaybleZoneDetailDto.RecentReviewInfo( - review.getId(), - review.getContent(), - review.getRating(), - review.getUser().getUsername(), // User 엔티티에서 이름 가져오기 - review.getCreatedAt() - )) - .collect(Collectors.toList()); + // 3. 빈 리스트로 초기화 (컬렉션은 나중에 필요시 별도 쿼리로 로드) + List operatingHours = List.of(); + List imageUrls = List.of(); + List recentReviews = List.of(); - // 6. 최종 DTO 생성 + // 4. 최종 DTO 생성 AdminWaybleZoneDetailDto detailDto = new AdminWaybleZoneDetailDto( zone.getId(), zone.getZoneName(), diff --git a/src/main/resources/templates/admin/dashboard.html b/src/main/resources/templates/admin/dashboard.html index efeff3eb..51625ecc 100644 --- a/src/main/resources/templates/admin/dashboard.html +++ b/src/main/resources/templates/admin/dashboard.html @@ -237,7 +237,7 @@

시스템 상태

관리 메뉴

- +
diff --git a/src/main/resources/templates/admin/wayble-zone-detail.html b/src/main/resources/templates/admin/wayble-zone-detail.html new file mode 100644 index 00000000..80a443d6 --- /dev/null +++ b/src/main/resources/templates/admin/wayble-zone-detail.html @@ -0,0 +1,292 @@ + + + + + + 웨이블존 상세정보 - Wayble 관리자 + + + + + + + +
+ +
+
+
+

+
+ + + ID: +
+
+
+ +
+
+
+ +
+ +
+
+

기본 정보

+
+
+
웨이블존명
+
+
+
+
연락처
+
+
+
+
주소
+
+
+
+
위도
+
+
+
+
경도
+
+
+
+
등록일
+
+
+
+
최종 수정일
+
+
+
+
+
+ + +
+
+

통계 정보

+
+
+
+
+ + + + +
+
+
평점
+
+ +
+
+
+ + + + +
+
+
리뷰 수
+
+ +
+
+
+ + + + +
+
+
좋아요
+
+
+
+
+ + +
+
+

시설 정보

+
+
+
시설 타입
+
+
+
+
설명
+
+
+
+
+
+ + +
+
+

운영시간

+
+
+ + +
+
+
+
+ + +
+
+

이미지 갤러리

+
+
+ 웨이블존 이미지 +
+
+
+
+ + +
+
+

최근 리뷰 (최대 5개)

+
+
+
+
+
+ +
+ +
+ +
+

+
+
+
+
+
+
+ + +
+
+

동기화 정보

+
+
+
최종 동기화 시간
+
+
+
+
동기화 상태
+
+ + 동기화 완료 + + + 동기화 필요 + +
+
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/admin/wayble-zones.html b/src/main/resources/templates/admin/wayble-zones.html new file mode 100644 index 00000000..3cdd55e8 --- /dev/null +++ b/src/main/resources/templates/admin/wayble-zones.html @@ -0,0 +1,236 @@ + + + + + + 웨이블존 관리 - Wayble 관리자 + + + + + + + +
+ +
+
+
+

웨이블존 목록

+

+ 총 개의 웨이블존이 등록되어 있습니다 +

+
+
+
+ / 페이지 +
+
+
+
+ + +
+
+
+ + + +
+
+

웨이블존을 찾을 수 없습니다.

+
+
+
+ + +
+
+
+
+ +
+ +
+
+

+ + +
+ +
+
+ + + + + + + +
+
+ +
+
+
+ + +
+
+
+
+ + + + +
+
평점
+
+ +
+
+ + + + +
+
리뷰
+
+ +
+
+ + + + +
+
좋아요
+
+
+
+
+
+
+ + +
+ + + +

웨이블존이 없습니다

+

등록된 웨이블존이 아직 없습니다.

+
+
+
+ + +
+ +
+
+ + \ No newline at end of file From 636a8f7dd003fd3e1d08e41e897f1499e22d9e48 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 18:12:32 +0900 Subject: [PATCH 04/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=EC=97=90=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=EC=83=81=ED=83=9C=20=EC=A0=95=EC=83=81=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=ED=91=9C=ED=98=84=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 15 ++++++ .../server/admin/dto/SystemStatusDto.java | 13 +++++ .../admin/service/AdminSystemService.java | 50 +++++++++++++++++++ .../resources/templates/admin/dashboard.html | 20 +++++--- 4 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/wayble/server/admin/dto/SystemStatusDto.java create mode 100644 src/main/java/com/wayble/server/admin/service/AdminSystemService.java diff --git a/src/main/java/com/wayble/server/admin/controller/AdminController.java b/src/main/java/com/wayble/server/admin/controller/AdminController.java index fa8057dc..1abef01b 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminController.java +++ b/src/main/java/com/wayble/server/admin/controller/AdminController.java @@ -1,6 +1,9 @@ package com.wayble.server.admin.controller; +import com.wayble.server.admin.dto.SystemStatusDto; +import com.wayble.server.admin.service.AdminSystemService; import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; @@ -13,8 +16,11 @@ @Slf4j @Controller @RequestMapping("/admin") +@RequiredArgsConstructor public class AdminController { + private final AdminSystemService adminSystemService; + @Value("${spring.admin.username}") private String adminUsername; @@ -56,7 +62,16 @@ public String adminDashboard(HttpSession session, Model model) { return "redirect:/admin"; } + // 시스템 상태 조회 + SystemStatusDto systemStatus = SystemStatusDto.of( + adminSystemService.isApiServerHealthy(), + adminSystemService.isDatabaseHealthy(), + adminSystemService.isElasticsearchHealthy(), + adminSystemService.isFileStorageHealthy() + ); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + model.addAttribute("systemStatus", systemStatus); return "admin/dashboard"; } diff --git a/src/main/java/com/wayble/server/admin/dto/SystemStatusDto.java b/src/main/java/com/wayble/server/admin/dto/SystemStatusDto.java new file mode 100644 index 00000000..eb310243 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/SystemStatusDto.java @@ -0,0 +1,13 @@ +package com.wayble.server.admin.dto; + +public record SystemStatusDto( + boolean apiServerStatus, + boolean databaseStatus, + boolean elasticsearchStatus, + boolean fileStorageStatus +) { + + public static SystemStatusDto of(boolean apiServer, boolean database, boolean elasticsearch, boolean fileStorage) { + return new SystemStatusDto(apiServer, database, elasticsearch, fileStorage); + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminSystemService.java b/src/main/java/com/wayble/server/admin/service/AdminSystemService.java new file mode 100644 index 00000000..af4bf804 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/service/AdminSystemService.java @@ -0,0 +1,50 @@ +package com.wayble.server.admin.service; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.sql.DataSource; +import java.sql.Connection; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminSystemService { + + private final ElasticsearchClient elasticsearchClient; + private final DataSource dataSource; + + public boolean isElasticsearchHealthy() { + try { + HealthResponse response = elasticsearchClient.cluster().health(); + log.debug("Elasticsearch health status: {}", response.status()); + return response.status() != co.elastic.clients.elasticsearch._types.HealthStatus.Red; + } catch (Exception e) { + log.error("Elasticsearch health check failed", e); + return false; + } + } + + public boolean isDatabaseHealthy() { + try (Connection connection = dataSource.getConnection()) { + return connection.isValid(5); // 5초 타임아웃 + } catch (Exception e) { + log.error("Database health check failed", e); + return false; + } + } + + public boolean isApiServerHealthy() { + // API 서버가 실행 중이면 true (이 메서드가 호출되는 것 자체가 서버가 살아있다는 증거) + return true; + } + + public boolean isFileStorageHealthy() { + // 파일 스토리지 상태 체크 (예: S3 연결 확인 등) + // 현재는 단순히 true로 반환 + return true; + } +} \ No newline at end of file diff --git a/src/main/resources/templates/admin/dashboard.html b/src/main/resources/templates/admin/dashboard.html index 51625ecc..651c5994 100644 --- a/src/main/resources/templates/admin/dashboard.html +++ b/src/main/resources/templates/admin/dashboard.html @@ -203,29 +203,33 @@

시스템 상태
API 서버 - - 정상 +
데이터베이스 - - 정상 +
Elasticsearch - - 정상 +
파일 스토리지 - - 정상 +

From 1fe30176048751e8face5dccae70e359d8c8c057 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 18:27:01 +0900 Subject: [PATCH 05/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/AdminSystemService.java | 1 - .../resources/templates/admin/dashboard.html | 246 ++++++------------ 2 files changed, 84 insertions(+), 163 deletions(-) diff --git a/src/main/java/com/wayble/server/admin/service/AdminSystemService.java b/src/main/java/com/wayble/server/admin/service/AdminSystemService.java index af4bf804..5d7bee4b 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminSystemService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminSystemService.java @@ -44,7 +44,6 @@ public boolean isApiServerHealthy() { public boolean isFileStorageHealthy() { // 파일 스토리지 상태 체크 (예: S3 연결 확인 등) - // 현재는 단순히 true로 반환 return true; } } \ No newline at end of file diff --git a/src/main/resources/templates/admin/dashboard.html b/src/main/resources/templates/admin/dashboard.html index 651c5994..c4992619 100644 --- a/src/main/resources/templates/admin/dashboard.html +++ b/src/main/resources/templates/admin/dashboard.html @@ -58,28 +58,7 @@

대시보드

-
- -
-
-
-
-
- - - -
-
-
-
-
전체 웨이블존
-
1,234개
-
-
-
-
-
- +
@@ -101,42 +80,42 @@

대시보드

- +
-
+
- +
-
오늘 방문자
-
892명
+
전체 웨이블존
+
1,234개
- +
-
+
- +
-
ES 동기화
-
정상
+
오늘 방문자
+
892명
@@ -144,156 +123,99 @@

대시보드

- -
- -
-
-

최근 활동

-
-
-
-
- - + +
+
+

관리 메뉴

+

Wayble 서비스 관리 도구

+
+ +
+ + +
+
+
+
+ +
+

유저 관리

+

사용자 계정 조회, 수정, 삭제 및 권한 관리

-
-

새로운 웨이블존 등록

-

5분 전

+
+ 관리하기 + + +
+
+
-
-
-
- - + + +
+
+
+
+ +
+

웨이블존 관리

+

웨이블존 조회, 수정, 삭제 및 상세 정보 관리

-
-

신규 사용자 가입

-

15분 전

+
+ 관리하기 + + +
+
+ -
-
-
- - + +
+
+
+
+
+ +
+

시스템 상태

-
-

데이터 동기화 완료

-

30분 전

+
+
+ API 서버 + + +
+
+ 데이터베이스 + + +
+
+ Elasticsearch + + +
- - -
-
-

시스템 상태

-
-
- API 서버 - - -
- -
- 데이터베이스 - - -
- -
- Elasticsearch - - -
- -
- 파일 스토리지 - - -
-
-
-
-
- - -
From d222d74c9458d53763c95b0d2bdd4371994e4f75 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 18:33:12 +0900 Subject: [PATCH 06/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20=EC=8B=A4=EC=A0=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=88=98=EC=99=80=20=EC=9B=A8=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=A1=B4=20=EA=B0=9C=EC=88=98=EA=B0=80=20=EB=9C=A8?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 10 +++++++++ .../admin/controller/AdminUserController.java | 21 +++++++++++++++++++ .../admin/service/AdminUserService.java | 19 +++++++++++++++++ .../resources/templates/admin/dashboard.html | 4 ++-- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/wayble/server/admin/controller/AdminUserController.java diff --git a/src/main/java/com/wayble/server/admin/controller/AdminController.java b/src/main/java/com/wayble/server/admin/controller/AdminController.java index 1abef01b..d4aa00ee 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminController.java +++ b/src/main/java/com/wayble/server/admin/controller/AdminController.java @@ -2,6 +2,8 @@ import com.wayble.server.admin.dto.SystemStatusDto; import com.wayble.server.admin.service.AdminSystemService; +import com.wayble.server.admin.service.AdminUserService; +import com.wayble.server.admin.service.AdminWaybleZoneService; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,6 +22,8 @@ public class AdminController { private final AdminSystemService adminSystemService; + private final AdminUserService adminUserService; + private final AdminWaybleZoneService adminWaybleZoneService; @Value("${spring.admin.username}") private String adminUsername; @@ -70,8 +74,14 @@ public String adminDashboard(HttpSession session, Model model) { adminSystemService.isFileStorageHealthy() ); + // 통계 데이터 조회 + long totalUserCount = adminUserService.getTotalUserCount(); + long totalWaybleZoneCount = adminWaybleZoneService.getTotalWaybleZoneCounts(); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); model.addAttribute("systemStatus", systemStatus); + model.addAttribute("totalUserCount", totalUserCount); + model.addAttribute("totalWaybleZoneCount", totalWaybleZoneCount); return "admin/dashboard"; } diff --git a/src/main/java/com/wayble/server/admin/controller/AdminUserController.java b/src/main/java/com/wayble/server/admin/controller/AdminUserController.java new file mode 100644 index 00000000..48d0ce75 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/controller/AdminUserController.java @@ -0,0 +1,21 @@ +package com.wayble.server.admin.controller; + +import com.wayble.server.admin.service.AdminUserService; +import com.wayble.server.common.response.CommonResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/admin/users") +public class AdminUserController { + + private final AdminUserService adminUserService; + + @GetMapping("/count") + public CommonResponse getTotalUserCount() { + return CommonResponse.success(adminUserService.getTotalUserCount()); + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java index 81a04e4a..07852b3f 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminUserService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -1,4 +1,23 @@ package com.wayble.server.admin.service; +import com.wayble.server.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor public class AdminUserService { + + private final UserRepository userRepository; + + public long getTotalUserCount() { + try { + return userRepository.count(); + } catch (Exception e) { + log.error("사용자 수 조회 실패", e); + return 0; + } + } } diff --git a/src/main/resources/templates/admin/dashboard.html b/src/main/resources/templates/admin/dashboard.html index c4992619..a951062c 100644 --- a/src/main/resources/templates/admin/dashboard.html +++ b/src/main/resources/templates/admin/dashboard.html @@ -73,7 +73,7 @@

대시보드

전체 사용자
-
5,678명
+
0명
@@ -94,7 +94,7 @@

대시보드

전체 웨이블존
-
1,234개
+
0개
From 8db4eb24ef6f1f3dd76f0adf01fa801739fd13bb Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 18:54:58 +0900 Subject: [PATCH 07/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9C=A0=EC=A0=80=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminUserController.java | 24 +- .../controller/AdminUserViewController.java | 65 +++++ .../server/admin/dto/AdminUserDetailDto.java | 25 ++ .../server/admin/dto/AdminUserPageDto.java | 29 +++ .../admin/dto/AdminUserThumbnailDto.java | 20 ++ .../admin/service/AdminUserService.java | 28 +++ .../user/repository/UserRepository.java | 25 ++ .../resources/templates/admin/dashboard.html | 2 +- .../templates/admin/user-detail.html | 213 ++++++++++++++++ src/main/resources/templates/admin/users.html | 232 ++++++++++++++++++ 10 files changed, 659 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/wayble/server/admin/controller/AdminUserViewController.java create mode 100644 src/main/java/com/wayble/server/admin/dto/AdminUserDetailDto.java create mode 100644 src/main/java/com/wayble/server/admin/dto/AdminUserPageDto.java create mode 100644 src/main/java/com/wayble/server/admin/dto/AdminUserThumbnailDto.java create mode 100644 src/main/resources/templates/admin/user-detail.html create mode 100644 src/main/resources/templates/admin/users.html diff --git a/src/main/java/com/wayble/server/admin/controller/AdminUserController.java b/src/main/java/com/wayble/server/admin/controller/AdminUserController.java index 48d0ce75..df3d4983 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminUserController.java +++ b/src/main/java/com/wayble/server/admin/controller/AdminUserController.java @@ -1,14 +1,19 @@ package com.wayble.server.admin.controller; +import com.wayble.server.admin.dto.AdminUserDetailDto; +import com.wayble.server.admin.dto.AdminUserThumbnailDto; import com.wayble.server.admin.service.AdminUserService; import com.wayble.server.common.response.CommonResponse; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; @RestController @RequiredArgsConstructor +@Validated @RequestMapping("/api/v1/admin/users") public class AdminUserController { @@ -18,4 +23,17 @@ public class AdminUserController { public CommonResponse getTotalUserCount() { return CommonResponse.success(adminUserService.getTotalUserCount()); } + + @GetMapping() + public CommonResponse> findByCondition( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "100") int size) + { + return CommonResponse.success(adminUserService.findUsersByPage(page, size)); + } + + @GetMapping("/{userId}") + public CommonResponse> findUserById(@PathVariable("userId") long userId) { + return CommonResponse.success(adminUserService.findUserById(userId)); + } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/controller/AdminUserViewController.java b/src/main/java/com/wayble/server/admin/controller/AdminUserViewController.java new file mode 100644 index 00000000..25b11b50 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/controller/AdminUserViewController.java @@ -0,0 +1,65 @@ +package com.wayble.server.admin.controller; + +import com.wayble.server.admin.dto.AdminUserDetailDto; +import com.wayble.server.admin.dto.AdminUserPageDto; +import com.wayble.server.admin.service.AdminUserService; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Optional; + +@Slf4j +@Controller +@RequestMapping("/admin/users") +@RequiredArgsConstructor +public class AdminUserViewController { + + private final AdminUserService adminUserService; + + @GetMapping + public String getUsers(HttpSession session, Model model, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "100") int size) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + // 페이징 데이터 조회 + AdminUserPageDto pageData = adminUserService.getUsersWithPaging(page, size); + + model.addAttribute("pageData", pageData); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + + log.debug("사용자 목록 조회 - 페이지: {}, 전체: {}", page, pageData.totalElements()); + + return "admin/users"; + } + + @GetMapping("/{id}") + public String getUserDetail(HttpSession session, Model model, @PathVariable Long id) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + Optional userOpt = adminUserService.findUserById(id); + if (userOpt.isEmpty()) { + return "redirect:/admin/users?error=notfound"; + } + + model.addAttribute("user", userOpt.get()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + + log.debug("사용자 상세 조회 - ID: {}", id); + + return "admin/user-detail"; + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/dto/AdminUserDetailDto.java b/src/main/java/com/wayble/server/admin/dto/AdminUserDetailDto.java new file mode 100644 index 00000000..0ced9de1 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/AdminUserDetailDto.java @@ -0,0 +1,25 @@ +package com.wayble.server.admin.dto; + +import com.wayble.server.user.entity.Gender; +import com.wayble.server.user.entity.LoginType; +import com.wayble.server.user.entity.UserType; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public record AdminUserDetailDto( + Long id, + String nickname, + String username, + String email, + LocalDate birthDate, + Gender gender, + LoginType loginType, + UserType userType, + String profileImageUrl, + String disabilityType, + String mobilityAid, + LocalDateTime createdAt, + LocalDateTime updatedAt +) { +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/dto/AdminUserPageDto.java b/src/main/java/com/wayble/server/admin/dto/AdminUserPageDto.java new file mode 100644 index 00000000..b1041800 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/AdminUserPageDto.java @@ -0,0 +1,29 @@ +package com.wayble.server.admin.dto; + +import java.util.List; + +public record AdminUserPageDto( + List content, + int page, + int size, + long totalElements, + int totalPages, + boolean first, + boolean last, + boolean hasNext, + boolean hasPrevious +) { + + public static AdminUserPageDto of(List content, int page, int size, long totalElements) { + int totalPages = (int) Math.ceil((double) totalElements / size); + boolean first = page == 0; + boolean last = page >= totalPages - 1; + boolean hasNext = page < totalPages - 1; + boolean hasPrevious = page > 0; + + return new AdminUserPageDto( + content, page, size, totalElements, totalPages, + first, last, hasNext, hasPrevious + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/dto/AdminUserThumbnailDto.java b/src/main/java/com/wayble/server/admin/dto/AdminUserThumbnailDto.java new file mode 100644 index 00000000..b4a69ed1 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/AdminUserThumbnailDto.java @@ -0,0 +1,20 @@ +package com.wayble.server.admin.dto; + +import com.wayble.server.user.entity.Gender; +import com.wayble.server.user.entity.LoginType; +import com.wayble.server.user.entity.UserType; + +import java.time.LocalDate; + +public record AdminUserThumbnailDto( + Long id, + String nickname, + String email, + LocalDate birthDate, + Gender gender, + LoginType loginType, + UserType userType, + String disabilityType, + String mobilityAid +) { +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java index 07852b3f..64cc3c99 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminUserService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -1,12 +1,20 @@ package com.wayble.server.admin.service; +import com.wayble.server.admin.dto.AdminUserDetailDto; +import com.wayble.server.admin.dto.AdminUserPageDto; +import com.wayble.server.admin.dto.AdminUserThumbnailDto; import com.wayble.server.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; @Slf4j @Service +@Transactional(readOnly = true) @RequiredArgsConstructor public class AdminUserService { @@ -20,4 +28,24 @@ public long getTotalUserCount() { return 0; } } + + public AdminUserPageDto getUsersWithPaging(int page, int size) { + int offset = page * size; + + List content = userRepository.findUsersWithPaging(offset, size); + long totalElements = userRepository.count(); + + log.debug("사용자 페이징 조회 - 페이지: {}, 크기: {}, 전체: {}", page, size, totalElements); + + return AdminUserPageDto.of(content, page, size, totalElements); + } + + public List findUsersByPage(int page, int size) { + int offset = page * size; + return userRepository.findUsersWithPaging(offset, size); + } + + public Optional findUserById(Long userId) { + return userRepository.findAdminUserDetailById(userId); + } } diff --git a/src/main/java/com/wayble/server/user/repository/UserRepository.java b/src/main/java/com/wayble/server/user/repository/UserRepository.java index 01289862..6fd1f2cf 100644 --- a/src/main/java/com/wayble/server/user/repository/UserRepository.java +++ b/src/main/java/com/wayble/server/user/repository/UserRepository.java @@ -1,15 +1,40 @@ package com.wayble.server.user.repository; +import com.wayble.server.admin.dto.AdminUserDetailDto; +import com.wayble.server.admin.dto.AdminUserThumbnailDto; import com.wayble.server.user.entity.LoginType; import com.wayble.server.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface UserRepository extends JpaRepository { boolean existsByEmailAndLoginType(String email, LoginType loginType); Optional findByEmailAndLoginType(String email, LoginType loginType); + + @Query(""" + SELECT new com.wayble.server.admin.dto.AdminUserThumbnailDto( + u.id, u.nickname, u.email, u.birthDate, u.gender, + u.loginType, u.userType, u.disabilityType, u.mobilityAid + ) + FROM User u + ORDER BY u.createdAt DESC + LIMIT :size OFFSET :offset + """) + List findUsersWithPaging(@Param("offset") int offset, @Param("size") int size); + + @Query(""" + SELECT new com.wayble.server.admin.dto.AdminUserDetailDto( + u.id, u.nickname, u.username, u.email, u.birthDate, u.gender, + u.loginType, u.userType, u.profileImageUrl, u.disabilityType, + u.mobilityAid, u.createdAt, u.updatedAt + ) + FROM User u + WHERE u.id = :userId + """) + Optional findAdminUserDetailById(@Param("userId") Long userId); } diff --git a/src/main/resources/templates/admin/dashboard.html b/src/main/resources/templates/admin/dashboard.html index a951062c..1ed57ff8 100644 --- a/src/main/resources/templates/admin/dashboard.html +++ b/src/main/resources/templates/admin/dashboard.html @@ -132,7 +132,7 @@

관리 메뉴

- +
diff --git a/src/main/resources/templates/admin/user-detail.html b/src/main/resources/templates/admin/user-detail.html new file mode 100644 index 00000000..2ac2d843 --- /dev/null +++ b/src/main/resources/templates/admin/user-detail.html @@ -0,0 +1,213 @@ + + + + + + 사용자 상세정보 - Wayble 관리자 + + + + + + + +
+ +
+
+
+

+

사용자 상세 정보

+
+
+ + + + +
+
+
+ +
+ +
+
+
+

기본 정보

+
+
+
+
+
사용자 ID
+
+
+
+
닉네임
+
+
+
+
사용자명
+
+
+
+
이메일
+
+
+
+
생년월일
+
+
+
+
성별
+
+
+
+
로그인 타입
+
+
+
+
사용자 타입
+
+
+
+
프로필 이미지
+
+
+ 프로필 이미지 + +
+ 설정되지 않음 +
+
+
+
+
+ + +
+
+

시간 정보

+
+
+
+
+
가입일
+
+
+
+
마지막 수정일
+
+
+
+
+
+
+ + +
+
+
+

장애 관련 정보

+
+
+
+
+
장애 유형
+
+ + + 설정되지 않음 +
+
+
+
이동 보조 수단
+
+ + + 설정되지 않음 +
+
+
+
+
+ + +
+
+

활동 통계

+
+
+
+
-
+
작성한 리뷰 수
+
+
+
-
+
좋아요한 장소 수
+
+
+
+ 통계 정보는 추후 구현 예정입니다 +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/admin/users.html b/src/main/resources/templates/admin/users.html new file mode 100644 index 00000000..ab1a6e9c --- /dev/null +++ b/src/main/resources/templates/admin/users.html @@ -0,0 +1,232 @@ + + + + + + 사용자 관리 - Wayble 관리자 + + + + + + + +
+ +
+
+
+

사용자 목록

+

+ 총 명의 사용자가 등록되어 있습니다 +

+
+
+
+ / 페이지 +
+
+
+
+ + +
+
+
+ + + +
+
+

사용자를 찾을 수 없습니다.

+
+
+
+ + +
+
+
+
+ +
+ +
+
+

+ + + + +
+ +
+
+ + + + + + +
+
+ + + + + + + + + + + + +
+
+
+ + +
+
+
+
장애 유형
+
+
+
+
이동 보조 수단
+
+
+
+ 장애 관련 정보 없음 +
+
+
+
+
+
+ + +
+ + + +

사용자가 없습니다

+

등록된 사용자가 아직 없습니다.

+
+
+
+ + +
+ +
+
+ + \ No newline at end of file From 1e364d6f3ecd2274d8b889699b592819c5fcdf08 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 19:02:58 +0900 Subject: [PATCH 08/26] =?UTF-8?q?[chore]=20admin=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=82=B4=EB=B6=80=20=EA=B5=AC=EC=A1=B0=20=EC=84=B8?= =?UTF-8?q?=EB=B6=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/{ => user}/AdminUserController.java | 6 +++--- .../controller/{ => user}/AdminUserViewController.java | 6 +++--- .../{ => wayblezone}/AdminWaybleZoneController.java | 6 +++--- .../{ => wayblezone}/AdminWaybleZoneViewController.java | 6 +++--- .../server/admin/dto/{ => user}/AdminUserDetailDto.java | 2 +- .../server/admin/dto/{ => user}/AdminUserPageDto.java | 2 +- .../server/admin/dto/{ => user}/AdminUserThumbnailDto.java | 2 +- .../dto/{ => wayblezone}/AdminWaybleZoneDetailDto.java | 2 +- .../admin/dto/{ => wayblezone}/AdminWaybleZonePageDto.java | 2 +- .../dto/{ => wayblezone}/AdminWaybleZoneThumbnailDto.java | 2 +- .../com/wayble/server/admin/service/AdminUserService.java | 6 +++--- .../wayble/server/admin/service/AdminWaybleZoneService.java | 6 +++--- .../com/wayble/server/user/repository/UserRepository.java | 4 ++-- .../wayblezone/repository/WaybleZoneRepositoryCustom.java | 4 ++-- .../wayblezone/repository/WaybleZoneRepositoryImpl.java | 4 ++-- 15 files changed, 30 insertions(+), 30 deletions(-) rename src/main/java/com/wayble/server/admin/controller/{ => user}/AdminUserController.java (87%) rename src/main/java/com/wayble/server/admin/controller/{ => user}/AdminUserViewController.java (92%) rename src/main/java/com/wayble/server/admin/controller/{ => wayblezone}/AdminWaybleZoneController.java (86%) rename src/main/java/com/wayble/server/admin/controller/{ => wayblezone}/AdminWaybleZoneViewController.java (92%) rename src/main/java/com/wayble/server/admin/dto/{ => user}/AdminUserDetailDto.java (92%) rename src/main/java/com/wayble/server/admin/dto/{ => user}/AdminUserPageDto.java (94%) rename src/main/java/com/wayble/server/admin/dto/{ => user}/AdminUserThumbnailDto.java (90%) rename src/main/java/com/wayble/server/admin/dto/{ => wayblezone}/AdminWaybleZoneDetailDto.java (96%) rename src/main/java/com/wayble/server/admin/dto/{ => wayblezone}/AdminWaybleZonePageDto.java (94%) rename src/main/java/com/wayble/server/admin/dto/{ => wayblezone}/AdminWaybleZoneThumbnailDto.java (89%) diff --git a/src/main/java/com/wayble/server/admin/controller/AdminUserController.java b/src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java similarity index 87% rename from src/main/java/com/wayble/server/admin/controller/AdminUserController.java rename to src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java index df3d4983..385ef563 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminUserController.java +++ b/src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java @@ -1,7 +1,7 @@ -package com.wayble.server.admin.controller; +package com.wayble.server.admin.controller.user; -import com.wayble.server.admin.dto.AdminUserDetailDto; -import com.wayble.server.admin.dto.AdminUserThumbnailDto; +import com.wayble.server.admin.dto.user.AdminUserDetailDto; +import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; import com.wayble.server.admin.service.AdminUserService; import com.wayble.server.common.response.CommonResponse; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/wayble/server/admin/controller/AdminUserViewController.java b/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java similarity index 92% rename from src/main/java/com/wayble/server/admin/controller/AdminUserViewController.java rename to src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java index 25b11b50..cced107e 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminUserViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java @@ -1,7 +1,7 @@ -package com.wayble.server.admin.controller; +package com.wayble.server.admin.controller.user; -import com.wayble.server.admin.dto.AdminUserDetailDto; -import com.wayble.server.admin.dto.AdminUserPageDto; +import com.wayble.server.admin.dto.user.AdminUserDetailDto; +import com.wayble.server.admin.dto.user.AdminUserPageDto; import com.wayble.server.admin.service.AdminUserService; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java similarity index 86% rename from src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java rename to src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java index cd56f3e4..47bdc99c 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneController.java +++ b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java @@ -1,7 +1,7 @@ -package com.wayble.server.admin.controller; +package com.wayble.server.admin.controller.wayblezone; -import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; -import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; import com.wayble.server.admin.service.AdminWaybleZoneService; import com.wayble.server.common.response.CommonResponse; diff --git a/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneViewController.java b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java similarity index 92% rename from src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneViewController.java rename to src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java index e25efe49..b2263b3b 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminWaybleZoneViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java @@ -1,7 +1,7 @@ -package com.wayble.server.admin.controller; +package com.wayble.server.admin.controller.wayblezone; -import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; -import com.wayble.server.admin.dto.AdminWaybleZonePageDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZonePageDto; import com.wayble.server.admin.service.AdminWaybleZoneService; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/wayble/server/admin/dto/AdminUserDetailDto.java b/src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java similarity index 92% rename from src/main/java/com/wayble/server/admin/dto/AdminUserDetailDto.java rename to src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java index 0ced9de1..67115398 100644 --- a/src/main/java/com/wayble/server/admin/dto/AdminUserDetailDto.java +++ b/src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java @@ -1,4 +1,4 @@ -package com.wayble.server.admin.dto; +package com.wayble.server.admin.dto.user; import com.wayble.server.user.entity.Gender; import com.wayble.server.user.entity.LoginType; diff --git a/src/main/java/com/wayble/server/admin/dto/AdminUserPageDto.java b/src/main/java/com/wayble/server/admin/dto/user/AdminUserPageDto.java similarity index 94% rename from src/main/java/com/wayble/server/admin/dto/AdminUserPageDto.java rename to src/main/java/com/wayble/server/admin/dto/user/AdminUserPageDto.java index b1041800..0df43e74 100644 --- a/src/main/java/com/wayble/server/admin/dto/AdminUserPageDto.java +++ b/src/main/java/com/wayble/server/admin/dto/user/AdminUserPageDto.java @@ -1,4 +1,4 @@ -package com.wayble.server.admin.dto; +package com.wayble.server.admin.dto.user; import java.util.List; diff --git a/src/main/java/com/wayble/server/admin/dto/AdminUserThumbnailDto.java b/src/main/java/com/wayble/server/admin/dto/user/AdminUserThumbnailDto.java similarity index 90% rename from src/main/java/com/wayble/server/admin/dto/AdminUserThumbnailDto.java rename to src/main/java/com/wayble/server/admin/dto/user/AdminUserThumbnailDto.java index b4a69ed1..c88e2c16 100644 --- a/src/main/java/com/wayble/server/admin/dto/AdminUserThumbnailDto.java +++ b/src/main/java/com/wayble/server/admin/dto/user/AdminUserThumbnailDto.java @@ -1,4 +1,4 @@ -package com.wayble.server.admin.dto; +package com.wayble.server.admin.dto.user; import com.wayble.server.user.entity.Gender; import com.wayble.server.user.entity.LoginType; diff --git a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneDetailDto.java b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java similarity index 96% rename from src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneDetailDto.java rename to src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java index af2104a1..979ebf2e 100644 --- a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneDetailDto.java +++ b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java @@ -1,4 +1,4 @@ -package com.wayble.server.admin.dto; +package com.wayble.server.admin.dto.wayblezone; import com.wayble.server.wayblezone.entity.WaybleZoneFacility; import com.wayble.server.wayblezone.entity.WaybleZoneType; diff --git a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZonePageDto.java b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java similarity index 94% rename from src/main/java/com/wayble/server/admin/dto/AdminWaybleZonePageDto.java rename to src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java index c41a497c..8904c822 100644 --- a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZonePageDto.java +++ b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java @@ -1,4 +1,4 @@ -package com.wayble.server.admin.dto; +package com.wayble.server.admin.dto.wayblezone; import java.util.List; diff --git a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneThumbnailDto.java b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneThumbnailDto.java similarity index 89% rename from src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneThumbnailDto.java rename to src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneThumbnailDto.java index 7bd59364..a1e851d4 100644 --- a/src/main/java/com/wayble/server/admin/dto/AdminWaybleZoneThumbnailDto.java +++ b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneThumbnailDto.java @@ -1,4 +1,4 @@ -package com.wayble.server.admin.dto; +package com.wayble.server.admin.dto.wayblezone; import com.wayble.server.wayblezone.entity.WaybleZoneFacility; import com.wayble.server.wayblezone.entity.WaybleZoneType; diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java index 64cc3c99..58bc4772 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminUserService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -1,8 +1,8 @@ package com.wayble.server.admin.service; -import com.wayble.server.admin.dto.AdminUserDetailDto; -import com.wayble.server.admin.dto.AdminUserPageDto; -import com.wayble.server.admin.dto.AdminUserThumbnailDto; +import com.wayble.server.admin.dto.user.AdminUserDetailDto; +import com.wayble.server.admin.dto.user.AdminUserPageDto; +import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; import com.wayble.server.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java index 04c272d2..d4f20f73 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java @@ -1,8 +1,8 @@ package com.wayble.server.admin.service; -import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; -import com.wayble.server.admin.dto.AdminWaybleZonePageDto; -import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZonePageDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; import com.wayble.server.wayblezone.repository.WaybleZoneRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/wayble/server/user/repository/UserRepository.java b/src/main/java/com/wayble/server/user/repository/UserRepository.java index 6fd1f2cf..5bd3715f 100644 --- a/src/main/java/com/wayble/server/user/repository/UserRepository.java +++ b/src/main/java/com/wayble/server/user/repository/UserRepository.java @@ -1,7 +1,7 @@ package com.wayble.server.user.repository; -import com.wayble.server.admin.dto.AdminUserDetailDto; -import com.wayble.server.admin.dto.AdminUserThumbnailDto; +import com.wayble.server.admin.dto.user.AdminUserDetailDto; +import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; import com.wayble.server.user.entity.LoginType; import com.wayble.server.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java index bd07f703..482c072d 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java @@ -1,7 +1,7 @@ package com.wayble.server.wayblezone.repository; -import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; -import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; import com.wayble.server.explore.dto.search.response.WaybleZoneDistrictResponseDto; import com.wayble.server.wayblezone.entity.WaybleZone; diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java index 24d2023b..46591d93 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java @@ -2,8 +2,8 @@ import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; -import com.wayble.server.admin.dto.AdminWaybleZoneDetailDto; -import com.wayble.server.admin.dto.AdminWaybleZoneThumbnailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; import com.wayble.server.explore.dto.common.FacilityResponseDto; import com.wayble.server.explore.dto.common.WaybleZoneInfoResponseDto; import com.wayble.server.explore.dto.search.response.WaybleZoneDistrictResponseDto; From 06e632e29edc4e6683c259f2e29952a432952aa4 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 19:09:20 +0900 Subject: [PATCH 09/26] =?UTF-8?q?[chore]=20View=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=EC=A1=B0=20=EC=84=B8=EB=B6=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/user/AdminUserViewController.java | 6 +++--- .../wayblezone/AdminWaybleZoneViewController.java | 6 +++--- .../com/wayble/server/user/repository/UserRepository.java | 4 ++-- src/main/resources/templates/admin/dashboard.html | 4 ++-- .../resources/templates/admin/{ => user}/user-detail.html | 0 src/main/resources/templates/admin/{ => user}/users.html | 0 .../admin/{ => wayblezone}/wayble-zone-detail.html | 0 .../templates/admin/{ => wayblezone}/wayble-zones.html | 0 8 files changed, 10 insertions(+), 10 deletions(-) rename src/main/resources/templates/admin/{ => user}/user-detail.html (100%) rename src/main/resources/templates/admin/{ => user}/users.html (100%) rename src/main/resources/templates/admin/{ => wayblezone}/wayble-zone-detail.html (100%) rename src/main/resources/templates/admin/{ => wayblezone}/wayble-zones.html (100%) diff --git a/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java b/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java index cced107e..2c27123f 100644 --- a/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java @@ -40,7 +40,7 @@ public String getUsers(HttpSession session, Model model, log.debug("사용자 목록 조회 - 페이지: {}, 전체: {}", page, pageData.totalElements()); - return "admin/users"; + return "admin/user/users"; } @GetMapping("/{id}") @@ -52,7 +52,7 @@ public String getUserDetail(HttpSession session, Model model, @PathVariable Long Optional userOpt = adminUserService.findUserById(id); if (userOpt.isEmpty()) { - return "redirect:/admin/users?error=notfound"; + return "redirect:/admin/user/users?error=notfound"; } model.addAttribute("user", userOpt.get()); @@ -60,6 +60,6 @@ public String getUserDetail(HttpSession session, Model model, @PathVariable Long log.debug("사용자 상세 조회 - ID: {}", id); - return "admin/user-detail"; + return "admin/user/user-detail"; } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java index b2263b3b..f42f3b2f 100644 --- a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java @@ -40,7 +40,7 @@ public String getWaybleZones(HttpSession session, Model model, log.debug("웨이블존 목록 조회 - 페이지: {}, 전체: {}", page, pageData.totalElements()); - return "admin/wayble-zones"; + return "admin/wayblezone/wayble-zones"; } @GetMapping("/{id}") @@ -52,7 +52,7 @@ public String getWaybleZoneDetail(HttpSession session, Model model, @PathVariabl Optional waybleZoneOpt = adminWaybleZoneService.findWaybleZoneById(id); if (waybleZoneOpt.isEmpty()) { - return "redirect:/admin/wayble-zones?error=notfound"; + return "redirect:/admin/wayblezone/wayble-zones?error=notfound"; } model.addAttribute("waybleZone", waybleZoneOpt.get()); @@ -60,6 +60,6 @@ public String getWaybleZoneDetail(HttpSession session, Model model, @PathVariabl log.debug("웨이블존 상세 조회 - ID: {}", id); - return "admin/wayble-zone-detail"; + return "admin/wayblezone/wayble-zone-detail"; } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/user/repository/UserRepository.java b/src/main/java/com/wayble/server/user/repository/UserRepository.java index 5bd3715f..fdf75a70 100644 --- a/src/main/java/com/wayble/server/user/repository/UserRepository.java +++ b/src/main/java/com/wayble/server/user/repository/UserRepository.java @@ -17,7 +17,7 @@ public interface UserRepository extends JpaRepository { Optional findByEmailAndLoginType(String email, LoginType loginType); @Query(""" - SELECT new com.wayble.server.admin.dto.AdminUserThumbnailDto( + SELECT new com.wayble.server.admin.dto.user.AdminUserThumbnailDto( u.id, u.nickname, u.email, u.birthDate, u.gender, u.loginType, u.userType, u.disabilityType, u.mobilityAid ) @@ -28,7 +28,7 @@ public interface UserRepository extends JpaRepository { List findUsersWithPaging(@Param("offset") int offset, @Param("size") int size); @Query(""" - SELECT new com.wayble.server.admin.dto.AdminUserDetailDto( + SELECT new com.wayble.server.admin.dto.user.AdminUserDetailDto( u.id, u.nickname, u.username, u.email, u.birthDate, u.gender, u.loginType, u.userType, u.profileImageUrl, u.disabilityType, u.mobilityAid, u.createdAt, u.updatedAt diff --git a/src/main/resources/templates/admin/dashboard.html b/src/main/resources/templates/admin/dashboard.html index 1ed57ff8..1f96cc51 100644 --- a/src/main/resources/templates/admin/dashboard.html +++ b/src/main/resources/templates/admin/dashboard.html @@ -31,7 +31,7 @@
-

Wayble 관리자

+

Wayble 관리자 페이지

@@ -54,7 +54,7 @@

Wayble 관리자

대시보드

-

Wayble 서비스 현황을 한눈에 확인하세요

+

Wayble 서비스 현황

diff --git a/src/main/resources/templates/admin/user-detail.html b/src/main/resources/templates/admin/user/user-detail.html similarity index 100% rename from src/main/resources/templates/admin/user-detail.html rename to src/main/resources/templates/admin/user/user-detail.html diff --git a/src/main/resources/templates/admin/users.html b/src/main/resources/templates/admin/user/users.html similarity index 100% rename from src/main/resources/templates/admin/users.html rename to src/main/resources/templates/admin/user/users.html diff --git a/src/main/resources/templates/admin/wayble-zone-detail.html b/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html similarity index 100% rename from src/main/resources/templates/admin/wayble-zone-detail.html rename to src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html diff --git a/src/main/resources/templates/admin/wayble-zones.html b/src/main/resources/templates/admin/wayblezone/wayble-zones.html similarity index 100% rename from src/main/resources/templates/admin/wayble-zones.html rename to src/main/resources/templates/admin/wayblezone/wayble-zones.html From 508da9e791c6dd70c2ec3bda3120eeaaeed537ab Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 23:23:10 +0900 Subject: [PATCH 10/26] =?UTF-8?q?[feat]=20soft=20delete=EB=90=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 2 + .../controller/user/AdminUserController.java | 18 ++ .../user/AdminUserViewController.java | 40 +++ .../admin/service/AdminUserService.java | 95 +++++++ .../user/repository/UserRepository.java | 22 ++ .../resources/templates/admin/dashboard.html | 26 +- .../admin/user/deleted-user-detail.html | 235 ++++++++++++++++++ .../templates/admin/user/deleted-users.html | 235 ++++++++++++++++++ 8 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/templates/admin/user/deleted-user-detail.html create mode 100644 src/main/resources/templates/admin/user/deleted-users.html diff --git a/src/main/java/com/wayble/server/admin/controller/AdminController.java b/src/main/java/com/wayble/server/admin/controller/AdminController.java index d4aa00ee..0548850f 100644 --- a/src/main/java/com/wayble/server/admin/controller/AdminController.java +++ b/src/main/java/com/wayble/server/admin/controller/AdminController.java @@ -76,11 +76,13 @@ public String adminDashboard(HttpSession session, Model model) { // 통계 데이터 조회 long totalUserCount = adminUserService.getTotalUserCount(); + long totalDeletedUserCount = adminUserService.getTotalDeletedUserCount(); long totalWaybleZoneCount = adminWaybleZoneService.getTotalWaybleZoneCounts(); model.addAttribute("adminUsername", session.getAttribute("adminUsername")); model.addAttribute("systemStatus", systemStatus); model.addAttribute("totalUserCount", totalUserCount); + model.addAttribute("totalDeletedUserCount", totalDeletedUserCount); model.addAttribute("totalWaybleZoneCount", totalWaybleZoneCount); return "admin/dashboard"; } diff --git a/src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java b/src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java index 385ef563..302efbf6 100644 --- a/src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java +++ b/src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java @@ -36,4 +36,22 @@ public CommonResponse> findByCondition( public CommonResponse> findUserById(@PathVariable("userId") long userId) { return CommonResponse.success(adminUserService.findUserById(userId)); } + + @GetMapping("/deleted/count") + public CommonResponse getTotalDeletedUserCount() { + return CommonResponse.success(adminUserService.getTotalDeletedUserCount()); + } + + @GetMapping("/deleted") + public CommonResponse> findDeletedUsers( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "100") int size) + { + return CommonResponse.success(adminUserService.findDeletedUsersByPage(page, size)); + } + + @GetMapping("/deleted/{userId}") + public CommonResponse> findDeletedUserById(@PathVariable("userId") long userId) { + return CommonResponse.success(adminUserService.findDeletedUserById(userId)); + } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java b/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java index 2c27123f..6f535c80 100644 --- a/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java @@ -62,4 +62,44 @@ public String getUserDetail(HttpSession session, Model model, @PathVariable Long return "admin/user/user-detail"; } + + @GetMapping("/deleted") + public String getDeletedUsers(HttpSession session, Model model, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "100") int size) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + // 페이징 데이터 조회 + AdminUserPageDto pageData = adminUserService.getDeletedUsersWithPaging(page, size); + + model.addAttribute("pageData", pageData); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + + log.debug("삭제된 사용자 목록 조회 - 페이지: {}, 전체: {}", page, pageData.totalElements()); + + return "admin/user/deleted-users"; + } + + @GetMapping("/deleted/{id}") + public String getDeletedUserDetail(HttpSession session, Model model, @PathVariable Long id) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + Optional userOpt = adminUserService.findDeletedUserById(id); + if (userOpt.isEmpty()) { + return "redirect:/admin/users/deleted?error=notfound"; + } + + model.addAttribute("user", userOpt.get()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + + log.debug("삭제된 사용자 상세 조회 - ID: {}", id); + + return "admin/user/deleted-user-detail"; + } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java index 58bc4772..48cd439b 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminUserService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -3,12 +3,20 @@ import com.wayble.server.admin.dto.user.AdminUserDetailDto; import com.wayble.server.admin.dto.user.AdminUserPageDto; import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; +import com.wayble.server.user.entity.Gender; +import com.wayble.server.user.entity.LoginType; +import com.wayble.server.user.entity.UserType; import com.wayble.server.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -48,4 +56,91 @@ public List findUsersByPage(int page, int size) { public Optional findUserById(Long userId) { return userRepository.findAdminUserDetailById(userId); } + + public long getTotalDeletedUserCount() { + try { + return userRepository.countDeletedUsers(); + } catch (Exception e) { + log.error("삭제된 사용자 수 조회 실패", e); + return 0; + } + } + + public AdminUserPageDto getDeletedUsersWithPaging(int page, int size) { + int offset = page * size; + + List rawResults = userRepository.findDeletedUsersWithPaging(offset, size); + List content = rawResults.stream() + .map(this::convertToThumbnailDto) + .toList(); + long totalElements = userRepository.countDeletedUsers(); + + log.debug("삭제된 사용자 페이징 조회 - 페이지: {}, 크기: {}, 전체: {}", page, size, totalElements); + + return AdminUserPageDto.of(content, page, size, totalElements); + } + + public List findDeletedUsersByPage(int page, int size) { + int offset = page * size; + List rawResults = userRepository.findDeletedUsersWithPaging(offset, size); + return rawResults.stream() + .map(this::convertToThumbnailDto) + .toList(); + } + + public Optional findDeletedUserById(Long userId) { + List rawResults = userRepository.findDeletedUserDetailById(userId); + if (rawResults.isEmpty()) { + return Optional.empty(); + } + return Optional.of(convertToDetailDto(rawResults.get(0))); + } + + private AdminUserThumbnailDto convertToThumbnailDto(Object[] row) { + Long id = convertToLong(row[0]); + String nickname = (String) row[1]; + String email = (String) row[2]; + LocalDate birthDate = row[3] != null ? ((Date) row[3]).toLocalDate() : null; + Gender gender = row[4] != null ? Gender.valueOf((String) row[4]) : null; + LoginType loginType = LoginType.valueOf((String) row[5]); + UserType userType = UserType.valueOf((String) row[6]); + String disabilityType = (String) row[7]; + String mobilityAid = (String) row[8]; + + return new AdminUserThumbnailDto(id, nickname, email, birthDate, gender, + loginType, userType, disabilityType, mobilityAid); + } + + private AdminUserDetailDto convertToDetailDto(Object[] row) { + Long id = convertToLong(row[0]); + String nickname = (String) row[1]; + String username = (String) row[2]; + String email = (String) row[3]; + LocalDate birthDate = row[4] != null ? ((Date) row[4]).toLocalDate() : null; + Gender gender = row[5] != null ? Gender.valueOf((String) row[5]) : null; + LoginType loginType = LoginType.valueOf((String) row[6]); + UserType userType = UserType.valueOf((String) row[7]); + String profileImageUrl = (String) row[8]; + String disabilityType = (String) row[9]; + String mobilityAid = (String) row[10]; + Timestamp createdAtStamp = (Timestamp) row[11]; + LocalDateTime createdAt = createdAtStamp.toLocalDateTime(); + Timestamp updatedAtStamp = (Timestamp) row[12]; + LocalDateTime updatedAt = updatedAtStamp.toLocalDateTime(); + + return new AdminUserDetailDto(id, nickname, username, email, birthDate, gender, + loginType, userType, profileImageUrl, disabilityType, + mobilityAid, createdAt, updatedAt); + } + + private Long convertToLong(Object value) { + if (value instanceof Long) { + return (Long) value; + } else if (value instanceof BigInteger) { + return ((BigInteger) value).longValue(); + } else if (value instanceof Integer) { + return ((Integer) value).longValue(); + } + throw new IllegalArgumentException("Cannot convert " + value.getClass() + " to Long"); + } } diff --git a/src/main/java/com/wayble/server/user/repository/UserRepository.java b/src/main/java/com/wayble/server/user/repository/UserRepository.java index fdf75a70..2c0bf11a 100644 --- a/src/main/java/com/wayble/server/user/repository/UserRepository.java +++ b/src/main/java/com/wayble/server/user/repository/UserRepository.java @@ -37,4 +37,26 @@ public interface UserRepository extends JpaRepository { WHERE u.id = :userId """) Optional findAdminUserDetailById(@Param("userId") Long userId); + + @Query(value = """ + SELECT u.id, u.nickname, u.email, u.birth_date, u.gender, + u.login_type, u.user_type, u.disability_type, u.mobility_aid + FROM user u + WHERE u.deleted_at IS NOT NULL + ORDER BY u.deleted_at DESC + LIMIT :size OFFSET :offset + """, nativeQuery = true) + List findDeletedUsersWithPaging(@Param("offset") int offset, @Param("size") int size); + + @Query(value = """ + SELECT u.id, u.nickname, u.username, u.email, u.birth_date, u.gender, + u.login_type, u.user_type, u.profile_image_url, u.disability_type, + u.mobility_aid, u.createdAt, u.updatedAt + FROM user u + WHERE u.id = :userId AND u.deleted_at IS NOT NULL + """, nativeQuery = true) + List findDeletedUserDetailById(@Param("userId") Long userId); + + @Query(value = "SELECT COUNT(*) FROM user WHERE deleted_at IS NOT NULL", nativeQuery = true) + long countDeletedUsers(); } diff --git a/src/main/resources/templates/admin/dashboard.html b/src/main/resources/templates/admin/dashboard.html index 1f96cc51..4acb70af 100644 --- a/src/main/resources/templates/admin/dashboard.html +++ b/src/main/resources/templates/admin/dashboard.html @@ -130,7 +130,7 @@

관리 메뉴

Wayble 서비스 관리 도구

-
+
@@ -177,6 +177,30 @@

+
+
+
+
+ + + +
+

삭제된 사용자

+

soft delete된 사용자 계정 조회 및 관리

+
0명
+
+
+ 관리하기 + + + +
+
+
+
+
diff --git a/src/main/resources/templates/admin/user/deleted-user-detail.html b/src/main/resources/templates/admin/user/deleted-user-detail.html new file mode 100644 index 00000000..ff9adb84 --- /dev/null +++ b/src/main/resources/templates/admin/user/deleted-user-detail.html @@ -0,0 +1,235 @@ + + + + + + 삭제된 사용자 상세정보 - Wayble 관리자 + + + + + + + +
+ +
+
+
+

+

삭제된 사용자 상세 정보

+
+
+ + 삭제됨 + + + + + +
+
+
+ +
+ +
+
+
+

기본 정보

+
+
+
+
+
사용자 ID
+
+
+
+
닉네임
+
+
+
+
사용자명
+
+
+
+
이메일
+
+
+
+
생년월일
+
+
+
+
성별
+
+
+
+
로그인 타입
+
+
+
+
사용자 타입
+
+
+
+
프로필 이미지
+
+
+ 프로필 이미지 + +
+ 설정되지 않음 +
+
+
+
+
+ + +
+
+

시간 정보

+
+
+
+
+
가입일
+
+
+
+
마지막 수정일
+
+
+
+
+
+
+ + +
+
+
+

장애 관련 정보

+
+
+
+
+
장애 유형
+
+ + + 설정되지 않음 +
+
+
+
이동 보조 수단
+
+ + + 설정되지 않음 +
+
+
+
+
+ + +
+
+
+
+ + + +
+
+

삭제된 사용자

+
+

이 사용자는 삭제된 상태입니다. 모든 관련 데이터가 비활성화되어 있습니다.

+
+
+
+
+
+ + +
+
+

활동 통계

+
+
+
+
-
+
작성한 리뷰 수
+
+
+
-
+
좋아요한 장소 수
+
+
+
+ 삭제된 사용자의 통계는 표시되지 않습니다 +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/admin/user/deleted-users.html b/src/main/resources/templates/admin/user/deleted-users.html new file mode 100644 index 00000000..9f7c4288 --- /dev/null +++ b/src/main/resources/templates/admin/user/deleted-users.html @@ -0,0 +1,235 @@ + + + + + + 삭제된 사용자 관리 - Wayble 관리자 + + + + + + + +
+ +
+
+
+

삭제된 사용자 목록

+

+ 총 명의 삭제된 사용자가 있습니다 +

+
+
+
+ / 페이지 +
+
+
+
+ + +
+
+
+ + + +
+
+

삭제된 사용자를 찾을 수 없습니다.

+
+
+
+ + +
+
+
+
+ +
+ +
+
+

+ + 삭제됨 + + + + + +
+ +
+
+ + + + + + +
+
+ + + + + + + + + + + + +
+
+
+ + +
+
+
+
장애 유형
+
+
+
+
이동 보조 수단
+
+
+
+ 장애 관련 정보 없음 +
+
+
+
+
+
+ + +
+ + + +

삭제된 사용자가 없습니다

+

삭제된 사용자가 아직 없습니다.

+
+
+
+ + +
+ +
+
+ + \ No newline at end of file From 70182461bb27f051057d68b55f6825cfa464cc7f Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 23:29:32 +0900 Subject: [PATCH 11/26] =?UTF-8?q?[chore]=20User=20=EA=B4=80=EB=A0=A8=20Adm?= =?UTF-8?q?in=20Repository=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/repository/AdminUserRepository.java | 58 +++++++++++++++++++ .../admin/service/AdminUserService.java | 24 ++++---- .../user/repository/UserRepository.java | 49 ---------------- 3 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java diff --git a/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java b/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java new file mode 100644 index 00000000..a178e1d8 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java @@ -0,0 +1,58 @@ +package com.wayble.server.admin.repository; + +import com.wayble.server.admin.dto.user.AdminUserDetailDto; +import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; +import com.wayble.server.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface AdminUserRepository extends JpaRepository { + + @Query(""" + SELECT new com.wayble.server.admin.dto.user.AdminUserThumbnailDto( + u.id, u.nickname, u.email, u.birthDate, u.gender, + u.loginType, u.userType, u.disabilityType, u.mobilityAid + ) + FROM User u + ORDER BY u.createdAt DESC + LIMIT :size OFFSET :offset + """) + List findUsersWithPaging(@Param("offset") int offset, @Param("size") int size); + + @Query(""" + SELECT new com.wayble.server.admin.dto.user.AdminUserDetailDto( + u.id, u.nickname, u.username, u.email, u.birthDate, u.gender, + u.loginType, u.userType, u.profileImageUrl, u.disabilityType, + u.mobilityAid, u.createdAt, u.updatedAt + ) + FROM User u + WHERE u.id = :userId + """) + Optional findAdminUserDetailById(@Param("userId") Long userId); + + @Query(value = """ + SELECT u.id, u.nickname, u.email, u.birth_date, u.gender, + u.login_type, u.user_type, u.disability_type, u.mobility_aid + FROM user u + WHERE u.deleted_at IS NOT NULL + ORDER BY u.deleted_at DESC + LIMIT :size OFFSET :offset + """, nativeQuery = true) + List findDeletedUsersWithPaging(@Param("offset") int offset, @Param("size") int size); + + @Query(value = """ + SELECT u.id, u.nickname, u.username, u.email, u.birth_date, u.gender, + u.login_type, u.user_type, u.profile_image_url, u.disability_type, + u.mobility_aid, u.createdAt, u.updatedAt + FROM user u + WHERE u.id = :userId AND u.deleted_at IS NOT NULL + """, nativeQuery = true) + List findDeletedUserDetailById(@Param("userId") Long userId); + + @Query(value = "SELECT COUNT(*) FROM user WHERE deleted_at IS NOT NULL", nativeQuery = true) + long countDeletedUsers(); +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java index 48cd439b..2f8c5148 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminUserService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -3,10 +3,10 @@ import com.wayble.server.admin.dto.user.AdminUserDetailDto; import com.wayble.server.admin.dto.user.AdminUserPageDto; import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; +import com.wayble.server.admin.repository.AdminUserRepository; import com.wayble.server.user.entity.Gender; import com.wayble.server.user.entity.LoginType; import com.wayble.server.user.entity.UserType; -import com.wayble.server.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -26,11 +26,11 @@ @RequiredArgsConstructor public class AdminUserService { - private final UserRepository userRepository; + private final AdminUserRepository adminUserRepository; public long getTotalUserCount() { try { - return userRepository.count(); + return adminUserRepository.count(); } catch (Exception e) { log.error("사용자 수 조회 실패", e); return 0; @@ -40,8 +40,8 @@ public long getTotalUserCount() { public AdminUserPageDto getUsersWithPaging(int page, int size) { int offset = page * size; - List content = userRepository.findUsersWithPaging(offset, size); - long totalElements = userRepository.count(); + List content = adminUserRepository.findUsersWithPaging(offset, size); + long totalElements = adminUserRepository.count(); log.debug("사용자 페이징 조회 - 페이지: {}, 크기: {}, 전체: {}", page, size, totalElements); @@ -50,16 +50,16 @@ public AdminUserPageDto getUsersWithPaging(int page, int size) { public List findUsersByPage(int page, int size) { int offset = page * size; - return userRepository.findUsersWithPaging(offset, size); + return adminUserRepository.findUsersWithPaging(offset, size); } public Optional findUserById(Long userId) { - return userRepository.findAdminUserDetailById(userId); + return adminUserRepository.findAdminUserDetailById(userId); } public long getTotalDeletedUserCount() { try { - return userRepository.countDeletedUsers(); + return adminUserRepository.countDeletedUsers(); } catch (Exception e) { log.error("삭제된 사용자 수 조회 실패", e); return 0; @@ -69,11 +69,11 @@ public long getTotalDeletedUserCount() { public AdminUserPageDto getDeletedUsersWithPaging(int page, int size) { int offset = page * size; - List rawResults = userRepository.findDeletedUsersWithPaging(offset, size); + List rawResults = adminUserRepository.findDeletedUsersWithPaging(offset, size); List content = rawResults.stream() .map(this::convertToThumbnailDto) .toList(); - long totalElements = userRepository.countDeletedUsers(); + long totalElements = adminUserRepository.countDeletedUsers(); log.debug("삭제된 사용자 페이징 조회 - 페이지: {}, 크기: {}, 전체: {}", page, size, totalElements); @@ -82,14 +82,14 @@ public AdminUserPageDto getDeletedUsersWithPaging(int page, int size) { public List findDeletedUsersByPage(int page, int size) { int offset = page * size; - List rawResults = userRepository.findDeletedUsersWithPaging(offset, size); + List rawResults = adminUserRepository.findDeletedUsersWithPaging(offset, size); return rawResults.stream() .map(this::convertToThumbnailDto) .toList(); } public Optional findDeletedUserById(Long userId) { - List rawResults = userRepository.findDeletedUserDetailById(userId); + List rawResults = adminUserRepository.findDeletedUserDetailById(userId); if (rawResults.isEmpty()) { return Optional.empty(); } diff --git a/src/main/java/com/wayble/server/user/repository/UserRepository.java b/src/main/java/com/wayble/server/user/repository/UserRepository.java index 2c0bf11a..b375ea87 100644 --- a/src/main/java/com/wayble/server/user/repository/UserRepository.java +++ b/src/main/java/com/wayble/server/user/repository/UserRepository.java @@ -1,62 +1,13 @@ package com.wayble.server.user.repository; -import com.wayble.server.admin.dto.user.AdminUserDetailDto; -import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; import com.wayble.server.user.entity.LoginType; import com.wayble.server.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import java.util.List; import java.util.Optional; public interface UserRepository extends JpaRepository { boolean existsByEmailAndLoginType(String email, LoginType loginType); Optional findByEmailAndLoginType(String email, LoginType loginType); - - @Query(""" - SELECT new com.wayble.server.admin.dto.user.AdminUserThumbnailDto( - u.id, u.nickname, u.email, u.birthDate, u.gender, - u.loginType, u.userType, u.disabilityType, u.mobilityAid - ) - FROM User u - ORDER BY u.createdAt DESC - LIMIT :size OFFSET :offset - """) - List findUsersWithPaging(@Param("offset") int offset, @Param("size") int size); - - @Query(""" - SELECT new com.wayble.server.admin.dto.user.AdminUserDetailDto( - u.id, u.nickname, u.username, u.email, u.birthDate, u.gender, - u.loginType, u.userType, u.profileImageUrl, u.disabilityType, - u.mobilityAid, u.createdAt, u.updatedAt - ) - FROM User u - WHERE u.id = :userId - """) - Optional findAdminUserDetailById(@Param("userId") Long userId); - - @Query(value = """ - SELECT u.id, u.nickname, u.email, u.birth_date, u.gender, - u.login_type, u.user_type, u.disability_type, u.mobility_aid - FROM user u - WHERE u.deleted_at IS NOT NULL - ORDER BY u.deleted_at DESC - LIMIT :size OFFSET :offset - """, nativeQuery = true) - List findDeletedUsersWithPaging(@Param("offset") int offset, @Param("size") int size); - - @Query(value = """ - SELECT u.id, u.nickname, u.username, u.email, u.birth_date, u.gender, - u.login_type, u.user_type, u.profile_image_url, u.disability_type, - u.mobility_aid, u.createdAt, u.updatedAt - FROM user u - WHERE u.id = :userId AND u.deleted_at IS NOT NULL - """, nativeQuery = true) - List findDeletedUserDetailById(@Param("userId") Long userId); - - @Query(value = "SELECT COUNT(*) FROM user WHERE deleted_at IS NOT NULL", nativeQuery = true) - long countDeletedUsers(); } From 6ed14a81187be62b727b48c71caa04209cbc06f1 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Fri, 1 Aug 2025 23:35:50 +0900 Subject: [PATCH 12/26] =?UTF-8?q?[chore]=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EA=B4=80=EB=A0=A8=20Admin=20Repository=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/AdminWaybleZoneRepository.java | 7 ++ .../AdminWaybleZoneRepositoryCustom.java | 16 +++ .../AdminWaybleZoneRepositoryImpl.java | 102 ++++++++++++++++++ .../admin/service/AdminWaybleZoneService.java | 14 +-- .../WaybleZoneRepositoryCustom.java | 9 -- .../repository/WaybleZoneRepositoryImpl.java | 82 -------------- 6 files changed, 132 insertions(+), 98 deletions(-) create mode 100644 src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepository.java create mode 100644 src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java create mode 100644 src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java diff --git a/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepository.java b/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepository.java new file mode 100644 index 00000000..81d48308 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepository.java @@ -0,0 +1,7 @@ +package com.wayble.server.admin.repository; + +import com.wayble.server.wayblezone.entity.WaybleZone; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdminWaybleZoneRepository extends JpaRepository, AdminWaybleZoneRepositoryCustom { +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java b/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java new file mode 100644 index 00000000..55455959 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java @@ -0,0 +1,16 @@ +package com.wayble.server.admin.repository; + +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; + +import java.util.List; +import java.util.Optional; + +public interface AdminWaybleZoneRepositoryCustom { + + // 페이징 조회 메서드 (관리자용) + List findWaybleZonesWithPaging(int page, int size); + + // 상세 조회 메서드 (관리자용) + Optional findAdminWaybleZoneDetailById(Long zoneId); +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java b/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java new file mode 100644 index 00000000..7b2103f4 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java @@ -0,0 +1,102 @@ +package com.wayble.server.admin.repository; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; +import com.wayble.server.wayblezone.entity.WaybleZone; + +import java.util.List; +import java.util.Optional; + +import static com.wayble.server.wayblezone.entity.QWaybleZone.waybleZone; + +public class AdminWaybleZoneRepositoryImpl implements AdminWaybleZoneRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + public AdminWaybleZoneRepositoryImpl(JPAQueryFactory queryFactory) { + this.queryFactory = queryFactory; + } + + @Override + public List findWaybleZonesWithPaging(int page, int size) { + return queryFactory + .select(Projections.constructor(AdminWaybleZoneThumbnailDto.class, + waybleZone.id, + waybleZone.zoneName, + waybleZone.zoneType, + waybleZone.reviewCount, + waybleZone.likes, + waybleZone.rating, + waybleZone.address.state + .concat(" ") + .concat(waybleZone.address.city.coalesce("")) + .concat(" ") + .concat(waybleZone.address.district.coalesce("")) + .concat(" ") + .concat(waybleZone.address.streetAddress.coalesce("")) + .concat(" ") + .concat(waybleZone.address.detailAddress.coalesce("")), + waybleZone.facility + )) + .from(waybleZone) + .leftJoin(waybleZone.facility) + .orderBy(waybleZone.id.asc()) + .offset((long) page * size) + .limit(size) + .fetch(); + } + + @Override + public Optional findAdminWaybleZoneDetailById(Long zoneId) { + // 1. 기본 WaybleZone 정보와 facility 조회 + WaybleZone zone = queryFactory + .selectFrom(waybleZone) + .leftJoin(waybleZone.facility).fetchJoin() + .where(waybleZone.id.eq(zoneId)) + .fetchOne(); + + if (zone == null) { + return Optional.empty(); + } + + // 2. DTO 변환 + AdminWaybleZoneDetailDto.FacilityInfo facilityInfo = null; + if (zone.getFacility() != null) { + facilityInfo = new AdminWaybleZoneDetailDto.FacilityInfo( + zone.getFacility(), + zone.getFacility() + " 이용 가능" + ); + } + + // 3. 빈 리스트로 초기화 (컬렉션은 나중에 필요시 별도 쿼리로 로드) + List operatingHours = List.of(); + List imageUrls = List.of(); + List recentReviews = List.of(); + + // 4. 최종 DTO 생성 + AdminWaybleZoneDetailDto detailDto = new AdminWaybleZoneDetailDto( + zone.getId(), + zone.getZoneName(), + zone.getContactNumber(), + zone.getZoneType(), + zone.getAddress().toFullAddress(), + zone.getAddress().getLatitude(), + zone.getAddress().getLongitude(), + zone.getRating(), + zone.getReviewCount(), + zone.getLikes(), + zone.getMainImageUrl(), + zone.getCreatedAt(), + zone.getLastModifiedAt(), + zone.getSyncedAt(), + facilityInfo, + operatingHours, + imageUrls, + recentReviews + ); + + return Optional.of(detailDto); + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java index d4f20f73..49b1d76a 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java @@ -3,7 +3,7 @@ import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZonePageDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; -import com.wayble.server.wayblezone.repository.WaybleZoneRepository; +import com.wayble.server.admin.repository.AdminWaybleZoneRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -17,18 +17,18 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class AdminWaybleZoneService { - private final WaybleZoneRepository waybleZoneRepository; + private final AdminWaybleZoneRepository adminWaybleZoneRepository; public long getTotalWaybleZoneCounts() { - return waybleZoneRepository.count(); + return adminWaybleZoneRepository.count(); } public AdminWaybleZonePageDto getWaybleZonesWithPaging(int page, int size) { // 페이징 데이터 조회 - List content = waybleZoneRepository.findWaybleZonesWithPaging(page, size); + List content = adminWaybleZoneRepository.findWaybleZonesWithPaging(page, size); // 전체 개수 조회 - long totalElements = waybleZoneRepository.count(); + long totalElements = adminWaybleZoneRepository.count(); log.debug("웨이블존 페이징 조회 - 페이지: {}, 크기: {}, 전체: {}", page, size, totalElements); @@ -37,10 +37,10 @@ public AdminWaybleZonePageDto getWaybleZonesWithPaging(int page, int size) { } public List findWaybleZonesByPage(int page, int size) { - return waybleZoneRepository.findWaybleZonesWithPaging(page, size); + return adminWaybleZoneRepository.findWaybleZonesWithPaging(page, size); } public Optional findWaybleZoneById(Long waybleZoneId) { - return waybleZoneRepository.findAdminWaybleZoneDetailById(waybleZoneId); + return adminWaybleZoneRepository.findAdminWaybleZoneDetailById(waybleZoneId); } } diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java index 482c072d..e19a92e4 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryCustom.java @@ -1,13 +1,10 @@ package com.wayble.server.wayblezone.repository; -import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; -import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; import com.wayble.server.explore.dto.search.response.WaybleZoneDistrictResponseDto; import com.wayble.server.wayblezone.entity.WaybleZone; import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; public interface WaybleZoneRepositoryCustom { @@ -18,10 +15,4 @@ public interface WaybleZoneRepositoryCustom { // ES 동기화 관련 메서드들 List findUnsyncedZones(int limit); List findZonesModifiedAfter(LocalDateTime since, int limit); - - // 페이징 조회 메서드 (관리자용) - List findWaybleZonesWithPaging(int page, int size); - - // 상세 조회 메서드 (관리자용) - Optional findAdminWaybleZoneDetailById(Long zoneId); } diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java index 46591d93..d476c56b 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java @@ -2,8 +2,6 @@ import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; -import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; -import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; import com.wayble.server.explore.dto.common.FacilityResponseDto; import com.wayble.server.explore.dto.common.WaybleZoneInfoResponseDto; import com.wayble.server.explore.dto.search.response.WaybleZoneDistrictResponseDto; @@ -128,86 +126,6 @@ public List findZonesModifiedAfter(LocalDateTime since, int limit) { .fetch(); } - @Override - public List findWaybleZonesWithPaging(int page, int size) { - return queryFactory - .select(Projections.constructor(AdminWaybleZoneThumbnailDto.class, - waybleZone.id, - waybleZone.zoneName, - waybleZone.zoneType, - waybleZone.reviewCount, - waybleZone.likes, - waybleZone.rating, - waybleZone.address.state - .concat(" ") - .concat(waybleZone.address.city.coalesce("")) - .concat(" ") - .concat(waybleZone.address.district.coalesce("")) - .concat(" ") - .concat(waybleZone.address.streetAddress.coalesce("")) - .concat(" ") - .concat(waybleZone.address.detailAddress.coalesce("")), - waybleZone.facility - )) - .from(waybleZone) - .leftJoin(waybleZone.facility) - .orderBy(waybleZone.id.asc()) - .offset((long) page * size) - .limit(size) - .fetch(); - } - - @Override - public Optional findAdminWaybleZoneDetailById(Long zoneId) { - // 1. 기본 WaybleZone 정보와 facility 조회 - WaybleZone zone = queryFactory - .selectFrom(waybleZone) - .leftJoin(waybleZone.facility).fetchJoin() - .where(waybleZone.id.eq(zoneId)) - .fetchOne(); - - if (zone == null) { - return Optional.empty(); - } - - // 2. DTO 변환 - AdminWaybleZoneDetailDto.FacilityInfo facilityInfo = null; - if (zone.getFacility() != null) { - facilityInfo = new AdminWaybleZoneDetailDto.FacilityInfo( - zone.getFacility(), - zone.getFacility() + " 이용 가능" - ); - } - - // 3. 빈 리스트로 초기화 (컬렉션은 나중에 필요시 별도 쿼리로 로드) - List operatingHours = List.of(); - List imageUrls = List.of(); - List recentReviews = List.of(); - - // 4. 최종 DTO 생성 - AdminWaybleZoneDetailDto detailDto = new AdminWaybleZoneDetailDto( - zone.getId(), - zone.getZoneName(), - zone.getContactNumber(), - zone.getZoneType(), - zone.getAddress().toFullAddress(), - zone.getAddress().getLatitude(), - zone.getAddress().getLongitude(), - zone.getRating(), - zone.getReviewCount(), - zone.getLikes(), - zone.getMainImageUrl(), - zone.getCreatedAt(), - zone.getLastModifiedAt(), - zone.getSyncedAt(), - facilityInfo, - operatingHours, - imageUrls, - recentReviews - ); - - return Optional.of(detailDto); - } // 내부 클래스로 visit count 담을 DTO public static class VisitCount { From 8a3f8f1380e33637080f197018db828358e50dae Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 00:05:43 +0900 Subject: [PATCH 13/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wayblezone/AdminWaybleZoneController.java | 8 + .../AdminWaybleZoneViewController.java | 56 +++- .../wayblezone/AdminWaybleZoneCreateDto.java | 39 +++ .../admin/service/AdminWaybleZoneService.java | 27 ++ .../server/wayblezone/entity/WaybleZone.java | 27 ++ .../admin/wayblezone/wayble-zone-create.html | 239 ++++++++++++++++++ .../admin/wayblezone/wayble-zones.html | 7 + 7 files changed, 399 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneCreateDto.java create mode 100644 src/main/resources/templates/admin/wayblezone/wayble-zone-create.html diff --git a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java index 47bdc99c..66d3023c 100644 --- a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java +++ b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java @@ -1,10 +1,12 @@ package com.wayble.server.admin.controller.wayblezone; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneCreateDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; import com.wayble.server.admin.service.AdminWaybleZoneService; import com.wayble.server.common.response.CommonResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; @@ -38,4 +40,10 @@ public CommonResponse findAllWaybleZoneCount() { public CommonResponse> findWaybleZoneById(@PathVariable("waybleZoneId") long waybleZoneId) { return CommonResponse.success(adminWaybleZoneService.findWaybleZoneById(waybleZoneId)); } + + @PostMapping + public CommonResponse createWaybleZone(@Valid @RequestBody AdminWaybleZoneCreateDto createDto) { + Long waybleZoneId = adminWaybleZoneService.createWaybleZone(createDto); + return CommonResponse.success(waybleZoneId); + } } diff --git a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java index f42f3b2f..2be5511b 100644 --- a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java @@ -1,17 +1,19 @@ package com.wayble.server.admin.controller.wayblezone; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneCreateDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZonePageDto; import com.wayble.server.admin.service.AdminWaybleZoneService; +import com.wayble.server.wayblezone.entity.WaybleZoneType; import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; import java.util.Optional; @@ -62,4 +64,50 @@ public String getWaybleZoneDetail(HttpSession session, Model model, @PathVariabl return "admin/wayblezone/wayble-zone-detail"; } + + @GetMapping("/create") + public String createWaybleZoneForm(HttpSession session, Model model) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + model.addAttribute("createDto", new AdminWaybleZoneCreateDto( + "", "", null, "", "", "", "", "", null, null, "" + )); + model.addAttribute("waybleZoneTypes", WaybleZoneType.values()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + + return "admin/wayblezone/wayble-zone-create"; + } + + @PostMapping("/create") + public String createWaybleZone(HttpSession session, + @Valid @ModelAttribute("createDto") AdminWaybleZoneCreateDto createDto, + BindingResult bindingResult, + Model model, + RedirectAttributes redirectAttributes) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + if (bindingResult.hasErrors()) { + model.addAttribute("waybleZoneTypes", WaybleZoneType.values()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + return "admin/wayblezone/wayble-zone-create"; + } + + try { + Long waybleZoneId = adminWaybleZoneService.createWaybleZone(createDto); + redirectAttributes.addFlashAttribute("successMessage", "웨이블존이 성공적으로 생성되었습니다."); + return "redirect:/admin/wayble-zones/" + waybleZoneId; + } catch (Exception e) { + log.error("웨이블존 생성 실패", e); + model.addAttribute("errorMessage", "웨이블존 생성에 실패했습니다: " + e.getMessage()); + model.addAttribute("waybleZoneTypes", WaybleZoneType.values()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + return "admin/wayblezone/wayble-zone-create"; + } + } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneCreateDto.java b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneCreateDto.java new file mode 100644 index 00000000..c37fce9a --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneCreateDto.java @@ -0,0 +1,39 @@ +package com.wayble.server.admin.dto.wayblezone; + +import com.wayble.server.wayblezone.entity.WaybleZoneType; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record AdminWaybleZoneCreateDto( + @NotBlank(message = "웨이블존 이름은 필수입니다") + String zoneName, + + String contactNumber, + + @NotNull(message = "웨이블존 타입은 필수입니다") + WaybleZoneType zoneType, + + @NotBlank(message = "시/도는 필수입니다") + String state, + + @NotBlank(message = "시/군/구는 필수입니다") + String city, + + String district, + String streetAddress, + String detailAddress, + + @NotNull(message = "위도는 필수입니다") + @DecimalMin(value = "-90.0", message = "위도는 -90.0 이상이어야 합니다") + @DecimalMax(value = "90.0", message = "위도는 90.0 이하여야 합니다") + Double latitude, + + @NotNull(message = "경도는 필수입니다") + @DecimalMin(value = "-180.0", message = "경도는 -180.0 이상이어야 합니다") + @DecimalMax(value = "180.0", message = "경도는 180.0 이하여야 합니다") + Double longitude, + + String mainImageUrl +) { } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java index 49b1d76a..64cb986a 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java @@ -1,9 +1,12 @@ package com.wayble.server.admin.service; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneCreateDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZonePageDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; import com.wayble.server.admin.repository.AdminWaybleZoneRepository; +import com.wayble.server.explore.service.WaybleZoneDocumentService; +import com.wayble.server.wayblezone.entity.WaybleZone; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -18,6 +21,7 @@ @RequiredArgsConstructor public class AdminWaybleZoneService { private final AdminWaybleZoneRepository adminWaybleZoneRepository; + private final WaybleZoneDocumentService waybleZoneDocumentService; public long getTotalWaybleZoneCounts() { return adminWaybleZoneRepository.count(); @@ -43,4 +47,27 @@ public List findWaybleZonesByPage(int page, int siz public Optional findWaybleZoneById(Long waybleZoneId) { return adminWaybleZoneRepository.findAdminWaybleZoneDetailById(waybleZoneId); } + + @Transactional + public Long createWaybleZone(AdminWaybleZoneCreateDto adminWaybleZoneCreateDto) { + try { + WaybleZone waybleZone = WaybleZone.fromAdminDto(adminWaybleZoneCreateDto); + WaybleZone savedZone = adminWaybleZoneRepository.save(waybleZone); + + log.info("새 웨이블존 생성 완료 - ID: {}, 이름: {}", savedZone.getId(), savedZone.getZoneName()); + + try { + waybleZoneDocumentService.saveDocumentFromEntity(savedZone); + log.info("WaybleZoneDocument 동기화 완료 - ID: {}", savedZone.getId()); + } catch (Exception esException) { + log.warn("WaybleZoneDocument 동기화 실패, 데이터 불일치 발생 가능 - ID: {}, 오류: {}", + savedZone.getId(), esException.getMessage()); + } + + return savedZone.getId(); + } catch (Exception e) { + log.error("웨이블존 생성 실패", e); + throw new RuntimeException("웨이블존 생성에 실패했습니다", e); + } + } } diff --git a/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java b/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java index d3a3362c..e5490d0d 100644 --- a/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java +++ b/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java @@ -1,5 +1,6 @@ package com.wayble.server.wayblezone.entity; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneCreateDto; import com.wayble.server.common.entity.Address; import com.wayble.server.common.entity.BaseEntity; import com.wayble.server.review.entity.Review; @@ -130,4 +131,30 @@ public static WaybleZone from(WaybleZoneRegisterDto dto) { .syncedAt(null) .build(); } + + public static WaybleZone fromAdminDto(AdminWaybleZoneCreateDto dto) { + Address dtoAddress = Address + .builder() + .state(dto.state()) + .city(dto.city()) + .district(dto.district()) + .streetAddress(dto.streetAddress()) + .detailAddress(dto.detailAddress()) + .latitude(dto.latitude()) + .longitude(dto.longitude()) + .build(); + + return WaybleZone.builder() + .zoneName(dto.zoneName()) + .contactNumber(dto.contactNumber()) + .zoneType(dto.zoneType()) + .address(dtoAddress) + .mainImageUrl(null) + .rating(0.0) + .reviewCount(0) + .likes(0) + .lastModifiedAt(LocalDateTime.now()) + .syncedAt(null) + .build(); + } } diff --git a/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html b/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html new file mode 100644 index 00000000..671e4897 --- /dev/null +++ b/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html @@ -0,0 +1,239 @@ + + + + + + 웨이블존 생성 - Wayble 관리자 + + + + + + + +
+ +
+

새 웨이블존 생성

+

새로운 웨이블존을 등록하세요

+
+ + +
+
+
+ + + +
+
+

+
+
+
+ + +
+
+
+ + +
+ + +

+
+ + +
+ + +

+
+ + +
+ + +

+
+ + +
+

주소 정보

+
+ + +
+ + +

+
+ + +
+ + +

+
+ + +
+ + +

+
+ + +
+ + +

+
+ + +
+ + +

+
+ + +
+ + +

+
+ + +
+ + +

+
+
+
+ + +
+ + +

+
+
+ + +
+ + 취소 + + +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/admin/wayblezone/wayble-zones.html b/src/main/resources/templates/admin/wayblezone/wayble-zones.html index 3cdd55e8..ce5fffb6 100644 --- a/src/main/resources/templates/admin/wayblezone/wayble-zones.html +++ b/src/main/resources/templates/admin/wayblezone/wayble-zones.html @@ -64,6 +64,13 @@

웨이블존 목록

+ + + + + 새 웨이블존 생성 +
/ 페이지
From 018ac86d9ba88d0fea6d710466083515e689e0ad Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 00:14:32 +0900 Subject: [PATCH 14/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=83=9D=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/wayblezone/wayble-zone-create.html | 331 +++++++++++++----- 1 file changed, 240 insertions(+), 91 deletions(-) diff --git a/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html b/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html index 671e4897..a556c066 100644 --- a/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html +++ b/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html @@ -79,158 +79,307 @@

새 웨이블존 생성

-
-
-
- - -
- - -

+
+
+
+
+
+ + + +
- -
- - -

+

웨이블존 정보 입력

+

새로운 웨이블존의 상세 정보를 입력해주세요

+
+
+ + +
+ + +
+
+

기본 정보

+

웨이블존의 기본 정보를 입력하세요

+
+ +
+ +
+ +
+ +
+ + + +
+
+

+ + + +

+
- -
- - -

+ +
+ +
+ +
+ + + +
+
+

+ + + +

+
+ + +
+ +
+ +
+ + + +
+
+

+ + + +

+
+
- -
-

주소 정보

-
+ +
+
+

주소 정보

+

웨이블존의 정확한 위치 정보를 입력하세요

+
+ +
- + -

+

+ + + +

- + -

+

+ + + +

- + -

+

+ + + +

- + -

+

+ + + +

- + -

+

+ + + +

- - -

+ +
+ +
+ + + + +
+
+

+ + + +

- - -

+ +
+ +
+ + + + +
+
+

+ + + +

- -
- - -

+ +
+
+

이미지 정보

+

웨이블존의 대표 이미지를 설정하세요

+
+ + +
+ +
+ +
+ + + +
+
+

+ + + +

+
-
- - 취소 - - +
+
+ * 표시는 필수 입력 항목입니다 +
+
+ + + + + 취소 + + +
From 742ddc0101a9008a558c72c810f88a0bcdc4dd35 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 13:14:13 +0900 Subject: [PATCH 15/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wayblezone/AdminWaybleZoneController.java | 24 ++ .../AdminWaybleZoneViewController.java | 70 +++ .../wayblezone/AdminWaybleZoneDetailDto.java | 2 + .../wayblezone/AdminWaybleZoneUpdateDto.java | 63 +++ .../admin/exception/AdminErrorCase.java | 17 + .../AdminWaybleZoneRepositoryImpl.java | 3 + .../admin/service/AdminWaybleZoneService.java | 72 ++++ .../server/common/entity/BaseEntity.java | 4 + .../service/WaybleZoneDocumentService.java | 4 + .../server/wayblezone/entity/WaybleZone.java | 26 ++ .../admin/wayblezone/wayble-zone-detail.html | 7 + .../admin/wayblezone/wayble-zone-edit.html | 407 ++++++++++++++++++ 12 files changed, 699 insertions(+) create mode 100644 src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneUpdateDto.java create mode 100644 src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java create mode 100644 src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html diff --git a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java index 66d3023c..46ef540e 100644 --- a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java +++ b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java @@ -3,6 +3,7 @@ import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneCreateDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneUpdateDto; import com.wayble.server.admin.service.AdminWaybleZoneService; import com.wayble.server.common.response.CommonResponse; @@ -46,4 +47,27 @@ public CommonResponse createWaybleZone(@Valid @RequestBody AdminWaybleZone Long waybleZoneId = adminWaybleZoneService.createWaybleZone(createDto); return CommonResponse.success(waybleZoneId); } + + @PutMapping("/{waybleZoneId}") + public CommonResponse updateWaybleZone(@PathVariable("waybleZoneId") Long waybleZoneId, + @Valid @RequestBody AdminWaybleZoneUpdateDto updateDto) { + // DTO의 ID와 URL의 ID가 일치하는지 확인 + AdminWaybleZoneUpdateDto validatedDto = new AdminWaybleZoneUpdateDto( + waybleZoneId, + updateDto.zoneName(), + updateDto.contactNumber(), + updateDto.zoneType(), + updateDto.state(), + updateDto.city(), + updateDto.district(), + updateDto.streetAddress(), + updateDto.detailAddress(), + updateDto.latitude(), + updateDto.longitude(), + updateDto.mainImageUrl() + ); + + Long updatedWaybleZoneId = adminWaybleZoneService.updateWaybleZone(validatedDto); + return CommonResponse.success(updatedWaybleZoneId); + } } diff --git a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java index 2be5511b..a98e22e1 100644 --- a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java @@ -3,6 +3,7 @@ import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneCreateDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZonePageDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneUpdateDto; import com.wayble.server.admin.service.AdminWaybleZoneService; import com.wayble.server.wayblezone.entity.WaybleZoneType; import jakarta.servlet.http.HttpSession; @@ -110,4 +111,73 @@ public String createWaybleZone(HttpSession session, return "admin/wayblezone/wayble-zone-create"; } } + + @GetMapping("/{id}/edit") + public String editWaybleZoneForm(HttpSession session, Model model, @PathVariable Long id) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + Optional waybleZoneOpt = adminWaybleZoneService.findWaybleZoneById(id); + if (waybleZoneOpt.isEmpty()) { + return "redirect:/admin/wayble-zones?error=notfound"; + } + + AdminWaybleZoneDetailDto waybleZone = waybleZoneOpt.get(); + AdminWaybleZoneUpdateDto updateDto = AdminWaybleZoneUpdateDto.fromDetailDto(waybleZone); + + model.addAttribute("updateDto", updateDto); + model.addAttribute("waybleZoneTypes", WaybleZoneType.values()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + + return "admin/wayblezone/wayble-zone-edit"; + } + + @PostMapping("/{id}/edit") + public String updateWaybleZone(HttpSession session, + @PathVariable Long id, + @Valid @ModelAttribute("updateDto") AdminWaybleZoneUpdateDto updateDto, + BindingResult bindingResult, + Model model, + RedirectAttributes redirectAttributes) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + if (bindingResult.hasErrors()) { + model.addAttribute("waybleZoneTypes", WaybleZoneType.values()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + return "admin/wayblezone/wayble-zone-edit"; + } + + // DTO의 ID와 URL의 ID 일치 확인 + AdminWaybleZoneUpdateDto validatedDto = new AdminWaybleZoneUpdateDto( + id, + updateDto.zoneName(), + updateDto.contactNumber(), + updateDto.zoneType(), + updateDto.state(), + updateDto.city(), + updateDto.district(), + updateDto.streetAddress(), + updateDto.detailAddress(), + updateDto.latitude(), + updateDto.longitude(), + updateDto.mainImageUrl() + ); + + try { + Long waybleZoneId = adminWaybleZoneService.updateWaybleZone(validatedDto); + redirectAttributes.addFlashAttribute("successMessage", "웨이블존이 성공적으로 수정되었습니다."); + return "redirect:/admin/wayble-zones/" + waybleZoneId; + } catch (Exception e) { + log.error("웨이블존 수정 실패", e); + model.addAttribute("errorMessage", "웨이블존 수정에 실패했습니다: " + e.getMessage()); + model.addAttribute("waybleZoneTypes", WaybleZoneType.values()); + model.addAttribute("adminUsername", session.getAttribute("adminUsername")); + return "admin/wayblezone/wayble-zone-edit"; + } + } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java index 979ebf2e..44b3e807 100644 --- a/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java +++ b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java @@ -1,5 +1,6 @@ package com.wayble.server.admin.dto.wayblezone; +import com.wayble.server.common.entity.Address; import com.wayble.server.wayblezone.entity.WaybleZoneFacility; import com.wayble.server.wayblezone.entity.WaybleZoneType; @@ -11,6 +12,7 @@ public record AdminWaybleZoneDetailDto( String zoneName, String contactNumber, WaybleZoneType zoneType, + Address address, String fullAddress, Double latitude, Double longitude, diff --git a/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneUpdateDto.java b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneUpdateDto.java new file mode 100644 index 00000000..068a00af --- /dev/null +++ b/src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneUpdateDto.java @@ -0,0 +1,63 @@ +package com.wayble.server.admin.dto.wayblezone; + +import com.wayble.server.wayblezone.entity.WaybleZoneType; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record AdminWaybleZoneUpdateDto( + @NotNull(message = "웨이블존 ID는 필수입니다") + Long id, + + @NotBlank(message = "웨이블존 이름은 필수입니다") + String zoneName, + + String contactNumber, + + @NotNull(message = "웨이블존 타입은 필수입니다") + WaybleZoneType zoneType, + + @NotBlank(message = "시/도는 필수입니다") + String state, + + @NotBlank(message = "시/군/구는 필수입니다") + String city, + + String district, + String streetAddress, + String detailAddress, + + @NotNull(message = "위도는 필수입니다") + @DecimalMin(value = "-90.0", message = "위도는 -90.0 이상이어야 합니다") + @DecimalMax(value = "90.0", message = "위도는 90.0 이하여야 합니다") + Double latitude, + + @NotNull(message = "경도는 필수입니다") + @DecimalMin(value = "-180.0", message = "경도는 -180.0 이상이어야 합니다") + @DecimalMax(value = "180.0", message = "경도는 180.0 이하여야 합니다") + Double longitude, + + String mainImageUrl +) { + + /** + * AdminWaybleZoneDetailDto로부터 수정용 DTO를 생성 + */ + public static AdminWaybleZoneUpdateDto fromDetailDto(AdminWaybleZoneDetailDto detailDto) { + return new AdminWaybleZoneUpdateDto( + detailDto.zoneId(), + detailDto.zoneName(), + detailDto.contactNumber(), + detailDto.zoneType(), + detailDto.address().getState(), + detailDto.address().getCity(), + detailDto.address().getDistrict(), + detailDto.address().getStreetAddress(), + detailDto.address().getDetailAddress(), + detailDto.latitude(), + detailDto.longitude(), + detailDto.mainImageUrl() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java b/src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java new file mode 100644 index 00000000..3bdb7387 --- /dev/null +++ b/src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java @@ -0,0 +1,17 @@ +package com.wayble.server.admin.exception; + +import com.wayble.server.common.exception.ErrorCase; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum AdminErrorCase implements ErrorCase { + + USER_NOT_FOUND(404, 9001, "사용자를 찾을 수 없습니다."), + WAYBLE_ZONE_NOT_FOUND(404, 9002, "해당 웨이블존을 찾을 수 없습니다."); + + private final Integer httpStatusCode; + private final Integer errorCode; + private final String message; +} diff --git a/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java b/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java index 7b2103f4..e9c9a645 100644 --- a/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java +++ b/src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java @@ -4,6 +4,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; +import com.wayble.server.common.entity.Address; import com.wayble.server.wayblezone.entity.WaybleZone; import java.util.List; @@ -74,6 +75,7 @@ public Optional findAdminWaybleZoneDetailById(Long zon List operatingHours = List.of(); List imageUrls = List.of(); List recentReviews = List.of(); + Address address = zone.getAddress(); // 4. 최종 DTO 생성 AdminWaybleZoneDetailDto detailDto = new AdminWaybleZoneDetailDto( @@ -81,6 +83,7 @@ public Optional findAdminWaybleZoneDetailById(Long zon zone.getZoneName(), zone.getContactNumber(), zone.getZoneType(), + zone.getAddress(), zone.getAddress().toFullAddress(), zone.getAddress().getLatitude(), zone.getAddress().getLongitude(), diff --git a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java index 64cb986a..9ecdd9ce 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java @@ -4,9 +4,14 @@ import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneDetailDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZonePageDto; import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneThumbnailDto; +import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneUpdateDto; +import com.wayble.server.admin.exception.AdminErrorCase; import com.wayble.server.admin.repository.AdminWaybleZoneRepository; +import com.wayble.server.common.exception.ApplicationException; import com.wayble.server.explore.service.WaybleZoneDocumentService; +import com.wayble.server.user.repository.UserPlaceWaybleZoneMappingRepository; import com.wayble.server.wayblezone.entity.WaybleZone; +import com.wayble.server.wayblezone.repository.WaybleZoneRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -22,6 +27,8 @@ public class AdminWaybleZoneService { private final AdminWaybleZoneRepository adminWaybleZoneRepository; private final WaybleZoneDocumentService waybleZoneDocumentService; + private final WaybleZoneRepository waybleZoneRepository; + private final UserPlaceWaybleZoneMappingRepository userPlaceWaybleZoneMappingRepository; public long getTotalWaybleZoneCounts() { return adminWaybleZoneRepository.count(); @@ -70,4 +77,69 @@ public Long createWaybleZone(AdminWaybleZoneCreateDto adminWaybleZoneCreateDto) throw new RuntimeException("웨이블존 생성에 실패했습니다", e); } } + + @Transactional + public Long updateWaybleZone(AdminWaybleZoneUpdateDto updateDto) { + try { + // 기존 웨이블존 조회 + WaybleZone waybleZone = waybleZoneRepository.findById(updateDto.id()) + .orElseThrow(() -> new ApplicationException(AdminErrorCase.WAYBLE_ZONE_NOT_FOUND)); + + // 주소 정보 업데이트 + com.wayble.server.common.entity.Address updatedAddress = com.wayble.server.common.entity.Address + .builder() + .state(updateDto.state()) + .city(updateDto.city()) + .district(updateDto.district()) + .streetAddress(updateDto.streetAddress()) + .detailAddress(updateDto.detailAddress()) + .latitude(updateDto.latitude()) + .longitude(updateDto.longitude()) + .build(); + + // 웨이블존 정보 업데이트 + waybleZone.updateZoneName(updateDto.zoneName()); + waybleZone.updateContactNumber(updateDto.contactNumber()); + waybleZone.updateZoneType(updateDto.zoneType()); + waybleZone.updateAddress(updatedAddress); + waybleZone.updateMainImageUrl(updateDto.mainImageUrl()); + + WaybleZone savedZone = waybleZoneRepository.save(waybleZone); + + log.info("웨이블존 수정 완료 - ID: {}, 이름: {}", savedZone.getId(), savedZone.getZoneName()); + + try { + waybleZoneDocumentService.saveDocumentFromEntity(savedZone); + log.info("WaybleZoneDocument 동기화 완료 - ID: {}", savedZone.getId()); + } catch (Exception esException) { + log.warn("WaybleZoneDocument 동기화 실패, 데이터 불일치 발생 가능 - ID: {}, 오류: {}", + savedZone.getId(), esException.getMessage()); + } + + return savedZone.getId(); + } catch (ApplicationException e) { + throw e; + } catch (Exception e) { + log.error("웨이블존 수정 실패 - ID: {}", updateDto.id(), e); + throw new RuntimeException("웨이블존 수정에 실패했습니다", e); + } + } + + @Transactional + public void deleteWaybleZone(Long waybleZoneId) { + WaybleZone waybleZone = waybleZoneRepository.findById(waybleZoneId) + .orElseThrow(() -> new ApplicationException(AdminErrorCase.WAYBLE_ZONE_NOT_FOUND)); + + waybleZone.getReviewList().forEach(review -> { + review.softDelete(); + }); + waybleZone.getOperatingHours().forEach(operatingHour -> { + operatingHour.softDelete(); + }); + waybleZone.getFacility().softDelete(); + userPlaceWaybleZoneMappingRepository.deleteAll(waybleZone.getUserPlaceMappings()); + + waybleZone.softDelete(); + waybleZoneDocumentService.deleteDocumentById(waybleZoneId); + } } diff --git a/src/main/java/com/wayble/server/common/entity/BaseEntity.java b/src/main/java/com/wayble/server/common/entity/BaseEntity.java index 9044360d..d0db82bd 100644 --- a/src/main/java/com/wayble/server/common/entity/BaseEntity.java +++ b/src/main/java/com/wayble/server/common/entity/BaseEntity.java @@ -27,4 +27,8 @@ public class BaseEntity { public void restore() { this.deletedAt = null; } + + public void softDelete() { + this.deletedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/wayble/server/explore/service/WaybleZoneDocumentService.java b/src/main/java/com/wayble/server/explore/service/WaybleZoneDocumentService.java index c694ed20..2647ea05 100644 --- a/src/main/java/com/wayble/server/explore/service/WaybleZoneDocumentService.java +++ b/src/main/java/com/wayble/server/explore/service/WaybleZoneDocumentService.java @@ -29,4 +29,8 @@ public void saveDocumentFromEntity(WaybleZone waybleZone) { public void saveDocumentFromDto(WaybleZoneRegisterDto dto) { waybleZoneDocumentRepository.save(WaybleZoneDocument.fromDto(dto)); } + + public void deleteDocumentById(Long id) { + waybleZoneDocumentRepository.deleteById(id); + } } diff --git a/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java b/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java index e5490d0d..cd59e586 100644 --- a/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java +++ b/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java @@ -103,6 +103,32 @@ public void addLikes(long count) { this.markAsModified(); // 변경 시 자동으로 수정 시간 갱신 } + // 관리자 업데이트 메서드들 + public void updateZoneName(String zoneName) { + this.zoneName = zoneName; + this.markAsModified(); + } + + public void updateContactNumber(String contactNumber) { + this.contactNumber = contactNumber; + this.markAsModified(); + } + + public void updateZoneType(WaybleZoneType zoneType) { + this.zoneType = zoneType; + this.markAsModified(); + } + + public void updateAddress(Address address) { + this.address = address; + this.markAsModified(); + } + + public void updateMainImageUrl(String mainImageUrl) { + this.mainImageUrl = mainImageUrl; + this.markAsModified(); + } + // ES 동기화 관련 메서드들 public void markAsModified() { this.lastModifiedAt = LocalDateTime.now(); diff --git a/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html b/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html index 80a443d6..ca341b08 100644 --- a/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html +++ b/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html @@ -72,6 +72,13 @@

+ + + + + 편집 + + +
+
+
+ + +
+ +
+

웨이블존 정보 수정

+

웨이블존 정보를 수정하세요

+
+ + +
+
+
+ + + +
+
+

+
+
+
+ + +
+
+
+ + + +
+
+

+
+
+
+ + +
+
+
+
+
+ + + +
+
+
+

웨이블존 정보 수정

+

웨이블존의 정보를 수정해주세요

+
+
+
+ +
+ + + +
+ + +
+
+

기본 정보

+

웨이블존의 기본 정보를 수정하세요

+
+ +
+ +
+ +
+ +
+ + + +
+
+

+ + + +

+
+ + +
+ +
+ +
+ + + +
+
+

+ + + +

+
+ + +
+ +
+ +
+ + + +
+
+

+ + + +

+
+
+
+ + +
+
+

주소 정보

+

웨이블존의 정확한 위치 정보를 수정하세요

+
+ +
+ + +
+ + +

+ + + +

+
+ + +
+ + +

+ + + +

+
+ + +
+ + +

+ + + +

+
+ + +
+ + +

+ + + +

+
+ + +
+ + +

+ + + +

+
+ + +
+ +
+ +
+ + + + +
+
+

+ + + +

+
+ + +
+ +
+ +
+ + + + +
+
+

+ + + +

+
+
+
+ + +
+
+

이미지 정보

+

웨이블존의 대표 이미지를 수정하세요

+
+ + +
+ +
+ +
+ + + +
+
+

+ + + +

+
+
+
+ + +
+
+ * 표시는 필수 입력 항목입니다 +
+
+ + + + + 취소 + + +
+
+
+
+
+ + \ No newline at end of file From 14a2dfc09879fd1287d9f0d688c7066675f9ee78 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 14:30:13 +0900 Subject: [PATCH 16/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminWaybleZoneViewController.java | 21 +++++ .../admin/service/AdminWaybleZoneService.java | 78 ++++++++++++++--- .../WaybleZoneVisitLogRepository.java | 7 ++ .../admin/wayblezone/wayble-zone-detail.html | 86 +++++++++++++++++++ 4 files changed, 178 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java index a98e22e1..ce88238b 100644 --- a/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java @@ -180,4 +180,25 @@ public String updateWaybleZone(HttpSession session, return "admin/wayblezone/wayble-zone-edit"; } } + + @PostMapping("/{id}/delete") + public String deleteWaybleZone(HttpSession session, + @PathVariable Long id, + RedirectAttributes redirectAttributes) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + try { + adminWaybleZoneService.deleteWaybleZone(id); + redirectAttributes.addFlashAttribute("successMessage", "웨이블존이 성공적으로 삭제되었습니다."); + log.info("웨이블존 삭제 완료 - ID: {}, 관리자: {}", id, session.getAttribute("adminUsername")); + return "redirect:/admin/wayble-zones"; + } catch (Exception e) { + log.error("웨이블존 삭제 실패 - ID: {}", id, e); + redirectAttributes.addFlashAttribute("errorMessage", "웨이블존 삭제에 실패했습니다: " + e.getMessage()); + return "redirect:/admin/wayble-zones/" + id; + } + } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java index 9ecdd9ce..2a8090de 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java @@ -12,6 +12,7 @@ import com.wayble.server.user.repository.UserPlaceWaybleZoneMappingRepository; import com.wayble.server.wayblezone.entity.WaybleZone; import com.wayble.server.wayblezone.repository.WaybleZoneRepository; +import com.wayble.server.wayblezone.repository.WaybleZoneVisitLogRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -29,6 +30,7 @@ public class AdminWaybleZoneService { private final WaybleZoneDocumentService waybleZoneDocumentService; private final WaybleZoneRepository waybleZoneRepository; private final UserPlaceWaybleZoneMappingRepository userPlaceWaybleZoneMappingRepository; + private final WaybleZoneVisitLogRepository waybleZoneVisitLogRepository; public long getTotalWaybleZoneCounts() { return adminWaybleZoneRepository.count(); @@ -127,19 +129,67 @@ public Long updateWaybleZone(AdminWaybleZoneUpdateDto updateDto) { @Transactional public void deleteWaybleZone(Long waybleZoneId) { - WaybleZone waybleZone = waybleZoneRepository.findById(waybleZoneId) - .orElseThrow(() -> new ApplicationException(AdminErrorCase.WAYBLE_ZONE_NOT_FOUND)); - - waybleZone.getReviewList().forEach(review -> { - review.softDelete(); - }); - waybleZone.getOperatingHours().forEach(operatingHour -> { - operatingHour.softDelete(); - }); - waybleZone.getFacility().softDelete(); - userPlaceWaybleZoneMappingRepository.deleteAll(waybleZone.getUserPlaceMappings()); - - waybleZone.softDelete(); - waybleZoneDocumentService.deleteDocumentById(waybleZoneId); + try { + WaybleZone waybleZone = waybleZoneRepository.findById(waybleZoneId) + .orElseThrow(() -> new ApplicationException(AdminErrorCase.WAYBLE_ZONE_NOT_FOUND)); + + log.info("웨이블존 삭제 시작 - ID: {}, 이름: {}", waybleZoneId, waybleZone.getZoneName()); + + // 연관된 리뷰들 soft delete + waybleZone.getReviewList().forEach(review -> { + review.softDelete(); + // 리뷰 이미지들도 함께 soft delete + review.getReviewImageList().forEach(reviewImage -> reviewImage.softDelete()); + }); + log.debug("웨이블존 리뷰 삭제 완료 - ID: {}, 리뷰 개수: {}", waybleZoneId, waybleZone.getReviewList().size()); + + // 운영시간 정보 soft delete + waybleZone.getOperatingHours().forEach(operatingHour -> { + operatingHour.softDelete(); + }); + log.debug("웨이블존 운영시간 삭제 완료 - ID: {}, 운영시간 개수: {}", waybleZoneId, waybleZone.getOperatingHours().size()); + + // 시설 정보 soft delete (null 체크) + if (waybleZone.getFacility() != null) { + waybleZone.getFacility().softDelete(); + log.debug("웨이블존 시설 정보 삭제 완료 - ID: {}", waybleZoneId); + } + + // 웨이블존 이미지들 soft delete + waybleZone.getWaybleZoneImageList().forEach(image -> image.softDelete()); + log.debug("웨이블존 이미지 삭제 완료 - ID: {}, 이미지 개수: {}", waybleZoneId, waybleZone.getWaybleZoneImageList().size()); + + // 사용자 즐겨찾기 매핑 hard delete (참조 관계 제거) + userPlaceWaybleZoneMappingRepository.deleteAll(waybleZone.getUserPlaceMappings()); + log.debug("웨이블존 사용자 매핑 삭제 완료 - ID: {}, 매핑 개수: {}", waybleZoneId, waybleZone.getUserPlaceMappings().size()); + + // 방문 로그 삭제 (해당 웨이블존의 모든 방문 로그 삭제) + try { + waybleZoneVisitLogRepository.deleteByZoneId(waybleZoneId); + log.debug("웨이블존 방문 로그 삭제 완료 - ID: {}", waybleZoneId); + } catch (Exception e) { + log.warn("웨이블존 방문 로그 삭제 실패 - ID: {}, 오류: {}", waybleZoneId, e.getMessage()); + } + + // 웨이블존 자체 soft delete + waybleZone.softDelete(); + log.info("웨이블존 soft delete 완료 - ID: {}", waybleZoneId); + + // Elasticsearch 문서 삭제 + try { + waybleZoneDocumentService.deleteDocumentById(waybleZoneId); + log.info("WaybleZoneDocument 삭제 완료 - ID: {}", waybleZoneId); + } catch (Exception esException) { + log.warn("WaybleZoneDocument 삭제 실패, 수동 정리 필요 - ID: {}, 오류: {}", + waybleZoneId, esException.getMessage()); + } + + log.info("웨이블존 삭제 완료 - ID: {}", waybleZoneId); + } catch (ApplicationException e) { + throw e; + } catch (Exception e) { + log.error("웨이블존 삭제 실패 - ID: {}", waybleZoneId, e); + throw new RuntimeException("웨이블존 삭제에 실패했습니다", e); + } } } diff --git a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneVisitLogRepository.java b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneVisitLogRepository.java index cee2219b..6bb074d0 100644 --- a/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneVisitLogRepository.java +++ b/src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneVisitLogRepository.java @@ -2,6 +2,9 @@ import com.wayble.server.wayblezone.entity.WaybleZoneVisitLog; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; @@ -9,4 +12,8 @@ public interface WaybleZoneVisitLogRepository extends JpaRepository findByUserIdAndZoneId(Long userId, Long zoneId); + + @Modifying + @Query("DELETE FROM WaybleZoneVisitLog v WHERE v.zoneId = :zoneId") + void deleteByZoneId(@Param("zoneId") Long zoneId); } diff --git a/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html b/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html index ca341b08..ebff87b3 100644 --- a/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html +++ b/src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html @@ -79,6 +79,13 @@

편집 +

+ + + \ No newline at end of file From 5e451542575eaeeedd956dde17eb0e2b1acd3d49 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 15:03:32 +0900 Subject: [PATCH 17/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9C=A0=EC=A0=80=20=EB=B3=B5?= =?UTF-8?q?=EA=B5=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/AdminUserViewController.java | 27 +++++- .../admin/repository/AdminUserRepository.java | 38 +++++++- .../admin/service/AdminUserService.java | 50 ++++++++++- .../admin/user/deleted-user-detail.html | 90 +++++++++++++++++++ 4 files changed, 197 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java b/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java index 6f535c80..687f5cd9 100644 --- a/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java +++ b/src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java @@ -8,10 +8,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; import java.util.Optional; @@ -102,4 +100,25 @@ public String getDeletedUserDetail(HttpSession session, Model model, @PathVariab return "admin/user/deleted-user-detail"; } + + @PostMapping("/deleted/{id}/restore") + public String restoreUser(HttpSession session, + @PathVariable Long id, + RedirectAttributes redirectAttributes) { + // 로그인 확인 + if (session.getAttribute("adminLoggedIn") == null) { + return "redirect:/admin"; + } + + try { + adminUserService.restoreUser(id); + redirectAttributes.addFlashAttribute("successMessage", "사용자가 성공적으로 복원되었습니다."); + log.info("사용자 복원 완료 - ID: {}, 관리자: {}", id, session.getAttribute("adminUsername")); + return "redirect:/admin/users/" + id; + } catch (Exception e) { + log.error("사용자 복원 실패 - ID: {}", id, e); + redirectAttributes.addFlashAttribute("errorMessage", "사용자 복원에 실패했습니다: " + e.getMessage()); + return "redirect:/admin/users/deleted/" + id; + } + } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java b/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java index a178e1d8..1378c84b 100644 --- a/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java +++ b/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java @@ -4,6 +4,7 @@ import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; import com.wayble.server.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -47,7 +48,7 @@ public interface AdminUserRepository extends JpaRepository { @Query(value = """ SELECT u.id, u.nickname, u.username, u.email, u.birth_date, u.gender, u.login_type, u.user_type, u.profile_image_url, u.disability_type, - u.mobility_aid, u.createdAt, u.updatedAt + u.mobility_aid, NOW(), NOW() FROM user u WHERE u.id = :userId AND u.deleted_at IS NOT NULL """, nativeQuery = true) @@ -55,4 +56,39 @@ public interface AdminUserRepository extends JpaRepository { @Query(value = "SELECT COUNT(*) FROM user WHERE deleted_at IS NOT NULL", nativeQuery = true) long countDeletedUsers(); + + @Modifying + @Query(value = """ + UPDATE user + SET deleted_at = NULL + WHERE id = :userId AND deleted_at IS NOT NULL + """, nativeQuery = true) + int restoreUserById(@Param("userId") Long userId); + + @Modifying + @Query(value = """ + UPDATE review + SET deleted_at = NULL + WHERE user_id = :userId AND deleted_at IS NOT NULL + """, nativeQuery = true) + int restoreUserReviews(@Param("userId") Long userId); + + @Modifying + @Query(value = """ + UPDATE review_image ri + INNER JOIN review r ON ri.review_id = r.id + SET ri.deleted_at = NULL + WHERE r.user_id = :userId AND ri.deleted_at IS NOT NULL + """, nativeQuery = true) + int restoreUserReviewImages(@Param("userId") Long userId); + + @Modifying + @Query(value = """ + UPDATE user_place + SET deleted_at = NULL + WHERE user_id = :userId AND deleted_at IS NOT NULL + """, nativeQuery = true) + int restoreUserPlaces(@Param("userId") Long userId); + + } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java index 2f8c5148..59cb7bdf 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminUserService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -3,7 +3,9 @@ import com.wayble.server.admin.dto.user.AdminUserDetailDto; import com.wayble.server.admin.dto.user.AdminUserPageDto; import com.wayble.server.admin.dto.user.AdminUserThumbnailDto; +import com.wayble.server.admin.exception.AdminErrorCase; import com.wayble.server.admin.repository.AdminUserRepository; +import com.wayble.server.common.exception.ApplicationException; import com.wayble.server.user.entity.Gender; import com.wayble.server.user.entity.LoginType; import com.wayble.server.user.entity.UserType; @@ -93,7 +95,49 @@ public Optional findDeletedUserById(Long userId) { if (rawResults.isEmpty()) { return Optional.empty(); } - return Optional.of(convertToDetailDto(rawResults.get(0))); + + Object[] row = rawResults.get(0); + log.debug("삭제된 사용자 데이터 조회 - ID: {}, 데이터: {}", userId, java.util.Arrays.toString(row)); + + return Optional.of(convertToDetailDto(row)); + } + + @Transactional + public void restoreUser(Long userId) { + try { + // 삭제된 사용자가 존재하는지 먼저 확인 + Optional deletedUserOpt = findDeletedUserById(userId); + if (deletedUserOpt.isEmpty()) { + throw new ApplicationException(AdminErrorCase.USER_NOT_FOUND); + } + + log.info("사용자 복원 시작 - ID: {}, 이메일: {}", userId, deletedUserOpt.get().email()); + + // 사용자 계정 복원 + int restoredUser = adminUserRepository.restoreUserById(userId); + log.debug("사용자 계정 복원 완료 - ID: {}, 처리된 행: {}", userId, restoredUser); + + // 연관된 리뷰들 복원 + int restoredReviews = adminUserRepository.restoreUserReviews(userId); + log.debug("사용자 리뷰 복원 완료 - ID: {}, 복원된 리뷰: {}", userId, restoredReviews); + + // 리뷰 이미지들 복원 + int restoredReviewImages = adminUserRepository.restoreUserReviewImages(userId); + log.debug("사용자 리뷰 이미지 복원 완료 - ID: {}, 복원된 이미지: {}", userId, restoredReviewImages); + + // 사용자 즐겨찾기 복원 + int restoredUserPlaces = adminUserRepository.restoreUserPlaces(userId); + log.debug("사용자 즐겨찾기 복원 완료 - ID: {}, 복원된 즐겨찾기: {}", userId, restoredUserPlaces); + + log.info("사용자 복원 완료 - ID: {}, 계정: {}, 리뷰: {}, 리뷰이미지: {}, 즐겨찾기: {}", + userId, restoredUser, restoredReviews, restoredReviewImages, restoredUserPlaces); + + } catch (ApplicationException e) { + throw e; + } catch (Exception e) { + log.error("사용자 복원 실패 - ID: {}", userId, e); + throw new RuntimeException("사용자 복원에 실패했습니다", e); + } } private AdminUserThumbnailDto convertToThumbnailDto(Object[] row) { @@ -124,9 +168,9 @@ private AdminUserDetailDto convertToDetailDto(Object[] row) { String disabilityType = (String) row[9]; String mobilityAid = (String) row[10]; Timestamp createdAtStamp = (Timestamp) row[11]; - LocalDateTime createdAt = createdAtStamp.toLocalDateTime(); + LocalDateTime createdAt = createdAtStamp != null ? createdAtStamp.toLocalDateTime() : null; Timestamp updatedAtStamp = (Timestamp) row[12]; - LocalDateTime updatedAt = updatedAtStamp.toLocalDateTime(); + LocalDateTime updatedAt = updatedAtStamp != null ? updatedAtStamp.toLocalDateTime() : null; return new AdminUserDetailDto(id, nickname, username, email, birthDate, gender, loginType, userType, profileImageUrl, disabilityType, diff --git a/src/main/resources/templates/admin/user/deleted-user-detail.html b/src/main/resources/templates/admin/user/deleted-user-detail.html index ff9adb84..056db866 100644 --- a/src/main/resources/templates/admin/user/deleted-user-detail.html +++ b/src/main/resources/templates/admin/user/deleted-user-detail.html @@ -66,6 +66,13 @@

삭제된 사용자 상세 정보

+ 삭제됨 @@ -77,6 +84,10 @@

+

@@ -231,5 +242,84 @@

활동 통계

+ + + + + \ No newline at end of file From 918daaeb00fa91d973100719623b506e0034bcbe Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 15:14:22 +0900 Subject: [PATCH 18/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9C=A0=EC=A0=80=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0,=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=88=98=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/dto/user/AdminUserDetailDto.java | 4 +- .../admin/repository/AdminUserRepository.java | 20 ++++++--- .../admin/service/AdminUserService.java | 41 +++++++++++++++++-- .../admin/user/deleted-user-detail.html | 12 +++--- .../templates/admin/user/user-detail.html | 9 +--- 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java b/src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java index 67115398..d7034acc 100644 --- a/src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java +++ b/src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java @@ -20,6 +20,8 @@ public record AdminUserDetailDto( String disabilityType, String mobilityAid, LocalDateTime createdAt, - LocalDateTime updatedAt + LocalDateTime updatedAt, + Long reviewCount, + Long userPlaceCount ) { } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java b/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java index 1378c84b..9f6a2ec9 100644 --- a/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java +++ b/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java @@ -25,15 +25,13 @@ public interface AdminUserRepository extends JpaRepository { List findUsersWithPaging(@Param("offset") int offset, @Param("size") int size); @Query(""" - SELECT new com.wayble.server.admin.dto.user.AdminUserDetailDto( - u.id, u.nickname, u.username, u.email, u.birthDate, u.gender, - u.loginType, u.userType, u.profileImageUrl, u.disabilityType, - u.mobilityAid, u.createdAt, u.updatedAt - ) + SELECT u.id, u.nickname, u.username, u.email, u.birthDate, u.gender, + u.loginType, u.userType, u.profileImageUrl, u.disabilityType, + u.mobilityAid, u.createdAt, u.updatedAt FROM User u WHERE u.id = :userId """) - Optional findAdminUserDetailById(@Param("userId") Long userId); + List findAdminUserDetailByIdRaw(@Param("userId") Long userId); @Query(value = """ SELECT u.id, u.nickname, u.email, u.birth_date, u.gender, @@ -90,5 +88,15 @@ u.mobility_aid, NOW(), NOW() """, nativeQuery = true) int restoreUserPlaces(@Param("userId") Long userId); + @Query(value = "SELECT COUNT(*) FROM review WHERE user_id = :userId AND deleted_at IS NULL", nativeQuery = true) + long countUserReviews(@Param("userId") Long userId); + + @Query(value = "SELECT COUNT(*) FROM user_place WHERE user_id = :userId AND deleted_at IS NULL", nativeQuery = true) + long countUserPlaces(@Param("userId") Long userId); + + @Query(value = "SELECT COUNT(*) FROM review WHERE user_id = :userId", nativeQuery = true) + long countUserReviewsIncludingDeleted(@Param("userId") Long userId); + @Query(value = "SELECT COUNT(*) FROM user_place WHERE user_id = :userId", nativeQuery = true) + long countUserPlacesIncludingDeleted(@Param("userId") Long userId); } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java index 59cb7bdf..bcc5d99f 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminUserService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -56,7 +56,16 @@ public List findUsersByPage(int page, int size) { } public Optional findUserById(Long userId) { - return adminUserRepository.findAdminUserDetailById(userId); + List rawResults = adminUserRepository.findAdminUserDetailByIdRaw(userId); + if (rawResults.isEmpty()) { + return Optional.empty(); + } + + Object[] row = rawResults.get(0); + long reviewCount = adminUserRepository.countUserReviews(userId); + long userPlaceCount = adminUserRepository.countUserPlaces(userId); + + return Optional.of(convertToDetailDtoWithStats(row, reviewCount, userPlaceCount)); } public long getTotalDeletedUserCount() { @@ -99,7 +108,11 @@ public Optional findDeletedUserById(Long userId) { Object[] row = rawResults.get(0); log.debug("삭제된 사용자 데이터 조회 - ID: {}, 데이터: {}", userId, java.util.Arrays.toString(row)); - return Optional.of(convertToDetailDto(row)); + // 삭제된 사용자의 경우 삭제된 것들도 포함하여 통계 조회 + long reviewCount = adminUserRepository.countUserReviewsIncludingDeleted(userId); + long userPlaceCount = adminUserRepository.countUserPlacesIncludingDeleted(userId); + + return Optional.of(convertToDetailDto(row, reviewCount, userPlaceCount)); } @Transactional @@ -155,7 +168,7 @@ private AdminUserThumbnailDto convertToThumbnailDto(Object[] row) { loginType, userType, disabilityType, mobilityAid); } - private AdminUserDetailDto convertToDetailDto(Object[] row) { + private AdminUserDetailDto convertToDetailDto(Object[] row, long reviewCount, long userPlaceCount) { Long id = convertToLong(row[0]); String nickname = (String) row[1]; String username = (String) row[2]; @@ -174,7 +187,27 @@ private AdminUserDetailDto convertToDetailDto(Object[] row) { return new AdminUserDetailDto(id, nickname, username, email, birthDate, gender, loginType, userType, profileImageUrl, disabilityType, - mobilityAid, createdAt, updatedAt); + mobilityAid, createdAt, updatedAt, reviewCount, userPlaceCount); + } + + private AdminUserDetailDto convertToDetailDtoWithStats(Object[] row, long reviewCount, long userPlaceCount) { + Long id = convertToLong(row[0]); + String nickname = (String) row[1]; + String username = (String) row[2]; + String email = (String) row[3]; + LocalDate birthDate = row[4] != null ? (LocalDate) row[4] : null; + Gender gender = row[5] != null ? (Gender) row[5] : null; + LoginType loginType = (LoginType) row[6]; + UserType userType = (UserType) row[7]; + String profileImageUrl = (String) row[8]; + String disabilityType = (String) row[9]; + String mobilityAid = (String) row[10]; + LocalDateTime createdAt = row[11] != null ? (LocalDateTime) row[11] : null; + LocalDateTime updatedAt = row[12] != null ? (LocalDateTime) row[12] : null; + + return new AdminUserDetailDto(id, nickname, username, email, birthDate, gender, + loginType, userType, profileImageUrl, disabilityType, + mobilityAid, createdAt, updatedAt, reviewCount, userPlaceCount); } private Long convertToLong(Object value) { diff --git a/src/main/resources/templates/admin/user/deleted-user-detail.html b/src/main/resources/templates/admin/user/deleted-user-detail.html index 056db866..bbcbc058 100644 --- a/src/main/resources/templates/admin/user/deleted-user-detail.html +++ b/src/main/resources/templates/admin/user/deleted-user-detail.html @@ -225,16 +225,16 @@

활동 통계

-
-
-
작성한 리뷰 수
+
0
+
작성한 리뷰 수 (삭제된 것 포함)
-
-
-
좋아요한 장소 수
+
0
+
좋아요한 장소 수 (삭제된 것 포함)
-
- 삭제된 사용자의 통계는 표시되지 않습니다 +
+ 복원 시 활성 상태인 데이터만 다시 표시됩니다
diff --git a/src/main/resources/templates/admin/user/user-detail.html b/src/main/resources/templates/admin/user/user-detail.html index 2ac2d843..cc03e71a 100644 --- a/src/main/resources/templates/admin/user/user-detail.html +++ b/src/main/resources/templates/admin/user/user-detail.html @@ -192,18 +192,13 @@

활동 통계

-
-
+
0
작성한 리뷰 수
-
-
+
0
좋아요한 장소 수
-
-
- 통계 정보는 추후 구현 예정입니다 -
-
From 2fd6ff42b4409cfc5cb8bcff3ffc8425f8c5e492 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 20:00:14 +0900 Subject: [PATCH 19/26] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9C=84=EB=8F=84=20=EA=B2=BD?= =?UTF-8?q?=EB=8F=84=20=ED=8E=B8=EC=A7=91=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/admin/wayblezone/wayble-zone-create.html | 8 ++++---- .../templates/admin/wayblezone/wayble-zone-edit.html | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html b/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html index a556c066..f3396c70 100644 --- a/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html +++ b/src/main/resources/templates/admin/wayblezone/wayble-zone-create.html @@ -281,10 +281,10 @@

주소 정보

+ placeholder="예: 37.5665123456">
@@ -308,10 +308,10 @@

주소 정보

+ placeholder="예: 126.9780123456">
diff --git a/src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html b/src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html index aaa7672b..e7739d30 100644 --- a/src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html +++ b/src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html @@ -300,10 +300,10 @@

주소 정보

+ placeholder="예: 37.5665123456">
@@ -327,10 +327,10 @@

주소 정보

+ placeholder="예: 126.9780123456">
From 8131c46380caa966a68c2ac59a04a779fde80807 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 20:44:35 +0900 Subject: [PATCH 20/26] =?UTF-8?q?[test]=20CI/CD=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-develop.yml | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/cd-develop.yml b/.github/workflows/cd-develop.yml index e7867d16..7a7541fc 100644 --- a/.github/workflows/cd-develop.yml +++ b/.github/workflows/cd-develop.yml @@ -1,10 +1,10 @@ name: CI/CD on: -# push: -# branches: [ "develop" ] - pull_request: - branches: [ "main" ] + push: + branches: [ "feature/seungmin" ] +# pull_request: +# branches: [ "main" ] permissions: contents: read @@ -180,23 +180,23 @@ jobs: exit 1 fi - # ✅ 배포 성공 알림 (Discord) - - name: Send success webhook to Discord - if: success() - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \ - ${{ secrets.DISCORD_WEBHOOK_URL }} - - # ❌ 배포 실패 알림 (Discord) - - name: Send failure webhook to Discord - if: failure() - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": \"❌ EC2 배포 실패! 확인이 필요합니다.\"}" \ - ${{ secrets.DISCORD_WEBHOOK_URL }} +# # ✅ 배포 성공 알림 (Discord) +# - name: Send success webhook to Discord +# if: success() +# run: | +# curl -H "Content-Type: application/json" \ +# -X POST \ +# -d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \ +# ${{ secrets.DISCORD_WEBHOOK_URL }} +# +# # ❌ 배포 실패 알림 (Discord) +# - name: Send failure webhook to Discord +# if: failure() +# run: | +# curl -H "Content-Type: application/json" \ +# -X POST \ +# -d "{\"content\": \"❌ EC2 배포 실패! 확인이 필요합니다.\"}" \ +# ${{ secrets.DISCORD_WEBHOOK_URL }} From e443229cafd32010644b158b7425b85a548a9fdf Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 21:14:00 +0900 Subject: [PATCH 21/26] =?UTF-8?q?[fix]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/repository/AdminUserRepository.java | 31 +++++++++---------- .../admin/service/AdminUserService.java | 24 +++++++++----- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java b/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java index 9f6a2ec9..2047e351 100644 --- a/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java +++ b/src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java @@ -13,24 +13,23 @@ public interface AdminUserRepository extends JpaRepository { - @Query(""" - SELECT new com.wayble.server.admin.dto.user.AdminUserThumbnailDto( - u.id, u.nickname, u.email, u.birthDate, u.gender, - u.loginType, u.userType, u.disabilityType, u.mobilityAid - ) - FROM User u - ORDER BY u.createdAt DESC + @Query(value = """ + SELECT u.id, u.nickname, u.email, u.birth_date, u.gender, + u.login_type, u.user_type, u.disability_type, u.mobility_aid + FROM user u + WHERE u.deleted_at IS NULL + ORDER BY u.created_at DESC LIMIT :size OFFSET :offset - """) - List findUsersWithPaging(@Param("offset") int offset, @Param("size") int size); + """, nativeQuery = true) + List findUsersWithPagingRaw(@Param("offset") int offset, @Param("size") int size); - @Query(""" - SELECT u.id, u.nickname, u.username, u.email, u.birthDate, u.gender, - u.loginType, u.userType, u.profileImageUrl, u.disabilityType, - u.mobilityAid, u.createdAt, u.updatedAt - FROM User u - WHERE u.id = :userId - """) + @Query(value = """ + SELECT u.id, u.nickname, u.username, u.email, u.birth_date, u.gender, + u.login_type, u.user_type, u.profile_image_url, u.disability_type, + u.mobility_aid, u.created_at, u.updated_at + FROM user u + WHERE u.id = :userId AND u.deleted_at IS NULL + """, nativeQuery = true) List findAdminUserDetailByIdRaw(@Param("userId") Long userId); @Query(value = """ diff --git a/src/main/java/com/wayble/server/admin/service/AdminUserService.java b/src/main/java/com/wayble/server/admin/service/AdminUserService.java index bcc5d99f..1c79ee71 100644 --- a/src/main/java/com/wayble/server/admin/service/AdminUserService.java +++ b/src/main/java/com/wayble/server/admin/service/AdminUserService.java @@ -42,7 +42,10 @@ public long getTotalUserCount() { public AdminUserPageDto getUsersWithPaging(int page, int size) { int offset = page * size; - List content = adminUserRepository.findUsersWithPaging(offset, size); + List rawResults = adminUserRepository.findUsersWithPagingRaw(offset, size); + List content = rawResults.stream() + .map(this::convertToThumbnailDto) + .toList(); long totalElements = adminUserRepository.count(); log.debug("사용자 페이징 조회 - 페이지: {}, 크기: {}, 전체: {}", page, size, totalElements); @@ -52,7 +55,10 @@ public AdminUserPageDto getUsersWithPaging(int page, int size) { public List findUsersByPage(int page, int size) { int offset = page * size; - return adminUserRepository.findUsersWithPaging(offset, size); + List rawResults = adminUserRepository.findUsersWithPagingRaw(offset, size); + return rawResults.stream() + .map(this::convertToThumbnailDto) + .toList(); } public Optional findUserById(Long userId) { @@ -195,15 +201,17 @@ private AdminUserDetailDto convertToDetailDtoWithStats(Object[] row, long review String nickname = (String) row[1]; String username = (String) row[2]; String email = (String) row[3]; - LocalDate birthDate = row[4] != null ? (LocalDate) row[4] : null; - Gender gender = row[5] != null ? (Gender) row[5] : null; - LoginType loginType = (LoginType) row[6]; - UserType userType = (UserType) row[7]; + LocalDate birthDate = row[4] != null ? ((Date) row[4]).toLocalDate() : null; + Gender gender = row[5] != null ? Gender.valueOf((String) row[5]) : null; + LoginType loginType = LoginType.valueOf((String) row[6]); + UserType userType = UserType.valueOf((String) row[7]); String profileImageUrl = (String) row[8]; String disabilityType = (String) row[9]; String mobilityAid = (String) row[10]; - LocalDateTime createdAt = row[11] != null ? (LocalDateTime) row[11] : null; - LocalDateTime updatedAt = row[12] != null ? (LocalDateTime) row[12] : null; + Timestamp createdAtStamp = (Timestamp) row[11]; + LocalDateTime createdAt = createdAtStamp != null ? createdAtStamp.toLocalDateTime() : null; + Timestamp updatedAtStamp = (Timestamp) row[12]; + LocalDateTime updatedAt = updatedAtStamp != null ? updatedAtStamp.toLocalDateTime() : null; return new AdminUserDetailDto(id, nickname, username, email, birthDate, gender, loginType, userType, profileImageUrl, disabilityType, From 659dbf141cb2bffcfeedf02593b8c7cd2f831d91 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 21:49:32 +0900 Subject: [PATCH 22/26] =?UTF-8?q?[feat]=20BaseEntity=EC=97=90=EC=84=9C=20c?= =?UTF-8?q?reatedAt,=20updatedAt=EC=9D=84=20created=5Fat,=20updated=5Fat?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/wayble/server/common/entity/BaseEntity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/wayble/server/common/entity/BaseEntity.java b/src/main/java/com/wayble/server/common/entity/BaseEntity.java index d0db82bd..9247cef8 100644 --- a/src/main/java/com/wayble/server/common/entity/BaseEntity.java +++ b/src/main/java/com/wayble/server/common/entity/BaseEntity.java @@ -16,9 +16,11 @@ public class BaseEntity { @CreatedDate + @Column(name = "created_at") private LocalDateTime createdAt; @LastModifiedDate + @Column(name = "updated_at") private LocalDateTime updatedAt; @Column(name = "deleted_at") From 82b8fbee16c7aacfa49e2ea751e8b156cae64dad Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 21:57:55 +0900 Subject: [PATCH 23/26] =?UTF-8?q?[chore]=20CI/CD=20workflow=20=EC=9B=90?= =?UTF-8?q?=EB=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-develop.yml | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/cd-develop.yml b/.github/workflows/cd-develop.yml index 7a7541fc..e3ba4f71 100644 --- a/.github/workflows/cd-develop.yml +++ b/.github/workflows/cd-develop.yml @@ -1,10 +1,10 @@ name: CI/CD on: - push: - branches: [ "feature/seungmin" ] -# pull_request: -# branches: [ "main" ] +# push: +# branches: [ "feature/seungmin" ] + pull_request: + branches: [ "main" ] permissions: contents: read @@ -180,23 +180,23 @@ jobs: exit 1 fi -# # ✅ 배포 성공 알림 (Discord) -# - name: Send success webhook to Discord -# if: success() -# run: | -# curl -H "Content-Type: application/json" \ -# -X POST \ -# -d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \ -# ${{ secrets.DISCORD_WEBHOOK_URL }} -# -# # ❌ 배포 실패 알림 (Discord) -# - name: Send failure webhook to Discord -# if: failure() -# run: | -# curl -H "Content-Type: application/json" \ -# -X POST \ -# -d "{\"content\": \"❌ EC2 배포 실패! 확인이 필요합니다.\"}" \ -# ${{ secrets.DISCORD_WEBHOOK_URL }} + # ✅ 배포 성공 알림 (Discord) + - name: Send success webhook to Discord + if: success() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \ + ${{ secrets.DISCORD_WEBHOOK_URL }} + + # ❌ 배포 실패 알림 (Discord) + - name: Send failure webhook to Discord + if: failure() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\": \"❌ EC2 배포 실패! 확인이 필요합니다.\"}" \ + ${{ secrets.DISCORD_WEBHOOK_URL }} From be4878a5fd339414dd499044f179fe6405e3a421 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 23:50:29 +0900 Subject: [PATCH 24/26] =?UTF-8?q?[fix]=20=EC=84=9C=EB=B2=84=EC=97=90=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=EC=9D=B4=20=EA=B0=80=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/wayble/server/common/config/SecurityConfig.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/wayble/server/common/config/SecurityConfig.java b/src/main/java/com/wayble/server/common/config/SecurityConfig.java index 24e3d02e..2c96a974 100644 --- a/src/main/java/com/wayble/server/common/config/SecurityConfig.java +++ b/src/main/java/com/wayble/server/common/config/SecurityConfig.java @@ -36,9 +36,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http .csrf(csrf -> csrf.disable()) .cors(cors -> cors.configurationSource(corsConfigurationSource())) - .csrf(csrf -> csrf - .ignoringRequestMatchers("/admin/**") // 관리자 페이지는 CSRF 비활성화 - ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() From f7731de178b39b05e1adcafc2eb4a8c70983bbf3 Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 23:50:53 +0900 Subject: [PATCH 25/26] =?UTF-8?q?[test]=20workflow=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-develop.yml | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/cd-develop.yml b/.github/workflows/cd-develop.yml index e3ba4f71..ed6fc112 100644 --- a/.github/workflows/cd-develop.yml +++ b/.github/workflows/cd-develop.yml @@ -1,8 +1,8 @@ name: CI/CD on: -# push: -# branches: [ "feature/seungmin" ] + push: + branches: [ "feature/seungmin" ] pull_request: branches: [ "main" ] @@ -181,22 +181,22 @@ jobs: fi # ✅ 배포 성공 알림 (Discord) - - name: Send success webhook to Discord - if: success() - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \ - ${{ secrets.DISCORD_WEBHOOK_URL }} - - # ❌ 배포 실패 알림 (Discord) - - name: Send failure webhook to Discord - if: failure() - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": \"❌ EC2 배포 실패! 확인이 필요합니다.\"}" \ - ${{ secrets.DISCORD_WEBHOOK_URL }} +# - name: Send success webhook to Discord +# if: success() +# run: | +# curl -H "Content-Type: application/json" \ +# -X POST \ +# -d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \ +# ${{ secrets.DISCORD_WEBHOOK_URL }} +# +# # ❌ 배포 실패 알림 (Discord) +# - name: Send failure webhook to Discord +# if: failure() +# run: | +# curl -H "Content-Type: application/json" \ +# -X POST \ +# -d "{\"content\": \"❌ EC2 배포 실패! 확인이 필요합니다.\"}" \ +# ${{ secrets.DISCORD_WEBHOOK_URL }} From 7ecf2783aacc4570e477723f1d3db6733823e36f Mon Sep 17 00:00:00 2001 From: KiSeungMin Date: Sat, 2 Aug 2025 23:56:46 +0900 Subject: [PATCH 26/26] =?UTF-8?q?[chore]=20workflow=20=EC=9B=90=EB=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-develop.yml | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/cd-develop.yml b/.github/workflows/cd-develop.yml index ed6fc112..e3ba4f71 100644 --- a/.github/workflows/cd-develop.yml +++ b/.github/workflows/cd-develop.yml @@ -1,8 +1,8 @@ name: CI/CD on: - push: - branches: [ "feature/seungmin" ] +# push: +# branches: [ "feature/seungmin" ] pull_request: branches: [ "main" ] @@ -181,22 +181,22 @@ jobs: fi # ✅ 배포 성공 알림 (Discord) -# - name: Send success webhook to Discord -# if: success() -# run: | -# curl -H "Content-Type: application/json" \ -# -X POST \ -# -d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \ -# ${{ secrets.DISCORD_WEBHOOK_URL }} -# -# # ❌ 배포 실패 알림 (Discord) -# - name: Send failure webhook to Discord -# if: failure() -# run: | -# curl -H "Content-Type: application/json" \ -# -X POST \ -# -d "{\"content\": \"❌ EC2 배포 실패! 확인이 필요합니다.\"}" \ -# ${{ secrets.DISCORD_WEBHOOK_URL }} + - name: Send success webhook to Discord + if: success() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \ + ${{ secrets.DISCORD_WEBHOOK_URL }} + + # ❌ 배포 실패 알림 (Discord) + - name: Send failure webhook to Discord + if: failure() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\": \"❌ EC2 배포 실패! 확인이 필요합니다.\"}" \ + ${{ secrets.DISCORD_WEBHOOK_URL }}