Skip to content

Commit 49c2144

Browse files
authored
Merge pull request #60 from Sportize/feat/admin
✨ Feat: Admin 도메인 도입 및 Geocoding 기반 체육시설·매칭 관리 기능 확장
2 parents b3a3917 + 56d1600 commit 49c2144

31 files changed

+398
-51
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.be.sportizebe.domain.admin.controller;
2+
3+
import com.be.sportizebe.domain.admin.service.AdminService;
4+
import com.be.sportizebe.domain.facility.dto.request.FacilityCreateRequest;
5+
import com.be.sportizebe.domain.facility.dto.request.FacilityUpdateRequest;
6+
import com.be.sportizebe.domain.facility.dto.response.FacilityResponse;
7+
import com.be.sportizebe.domain.match.dto.request.MatchCreateRequest;
8+
import com.be.sportizebe.domain.match.dto.response.MatchResponse;
9+
import com.be.sportizebe.global.cache.dto.UserAuthInfo;
10+
import com.be.sportizebe.global.response.BaseResponse;
11+
import io.swagger.v3.oas.annotations.Operation;
12+
import io.swagger.v3.oas.annotations.tags.Tag;
13+
import jakarta.validation.Valid;
14+
import lombok.RequiredArgsConstructor;
15+
import org.springframework.http.HttpStatus;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
18+
import org.springframework.web.bind.annotation.*;
19+
20+
21+
@RestController
22+
@RequiredArgsConstructor
23+
@RequestMapping("/api/admin")
24+
@Tag(name = "admin", description = "관리자 전용 API")
25+
public class AdminController {
26+
27+
private final AdminService adminService;
28+
29+
@Operation(summary = "체육시설 등록 (관리자 전용)", description = "새로운 체육시설을 등록합니다.")
30+
@PostMapping("/facilities")
31+
public ResponseEntity<BaseResponse<FacilityResponse>> createFacility(
32+
@RequestBody @Valid FacilityCreateRequest request
33+
) {
34+
FacilityResponse response = adminService.createFacility(request);
35+
return ResponseEntity.status(HttpStatus.CREATED)
36+
.body(BaseResponse.success("체육시설 등록 성공", response));
37+
}
38+
39+
@Operation(summary = "체육시설 수정 (관리자 전용)", description = "체육시설 정보를 수정합니다. null 필드는 수정하지 않습니다.")
40+
@PutMapping("/facilities/{facilityId}")
41+
public ResponseEntity<BaseResponse<FacilityResponse>> updateFacility(
42+
@PathVariable Long facilityId,
43+
@RequestBody @Valid FacilityUpdateRequest request
44+
) {
45+
FacilityResponse response = adminService.updateFacility(facilityId, request);
46+
return ResponseEntity.ok(BaseResponse.success("체육시설 수정 성공", response));
47+
}
48+
49+
@Operation(summary = "체육시설 삭제 (관리자 전용)", description = "체육시설을 삭제합니다.")
50+
@DeleteMapping("/facilities/{facilityId}")
51+
public ResponseEntity<BaseResponse<Void>> deleteFacility(
52+
@PathVariable Long facilityId
53+
) {
54+
adminService.deleteFacility(facilityId);
55+
return ResponseEntity.ok(BaseResponse.success("체육시설 삭제 성공", null));
56+
}
57+
58+
@Operation(summary = "매칭 생성 (관리자 전용)", description = "체육시설 기반 매칭을 생성합니다.")
59+
@PostMapping("/matches")
60+
public ResponseEntity<BaseResponse<MatchResponse>> createMatch(
61+
@AuthenticationPrincipal UserAuthInfo userAuthInfo,
62+
@RequestBody @Valid MatchCreateRequest request
63+
) {
64+
MatchResponse response = adminService.createMatch(userAuthInfo.getId(), request);
65+
return ResponseEntity.status(HttpStatus.CREATED)
66+
.body(BaseResponse.success("매칭 생성 성공", response));
67+
}
68+
69+
@Operation(summary = "매칭 삭제 (관리자 전용)", description = "매칭을 강제 삭제합니다.")
70+
@DeleteMapping("/matches/{matchId}")
71+
public ResponseEntity<BaseResponse<Void>> deleteMatch(
72+
@PathVariable Long matchId
73+
) {
74+
adminService.deleteMatch(matchId);
75+
return ResponseEntity.ok(BaseResponse.success("매칭 삭제 성공", null));
76+
}
77+
}

src/main/java/com/be/sportizebe/domain/admin/dto/request/.gitkeep

Whitespace-only changes.

src/main/java/com/be/sportizebe/domain/admin/dto/response/.gitkeep

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.be.sportizebe.domain.admin.exception;
2+
3+
import com.be.sportizebe.global.exception.model.BaseErrorCode;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
import org.springframework.http.HttpStatus;
7+
8+
@Getter
9+
@AllArgsConstructor
10+
public enum AdminErrorCode implements BaseErrorCode {
11+
ADMIN_ACCESS_DENIED("ADMIN_001", "관리자 권한이 없습니다.", HttpStatus.FORBIDDEN);
12+
13+
private final String code;
14+
private final String message;
15+
private final HttpStatus status;
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.be.sportizebe.domain.admin.service;
2+
3+
import com.be.sportizebe.domain.facility.dto.request.FacilityCreateRequest;
4+
import com.be.sportizebe.domain.facility.dto.request.FacilityUpdateRequest;
5+
import com.be.sportizebe.domain.facility.dto.response.FacilityResponse;
6+
import com.be.sportizebe.domain.match.dto.request.MatchCreateRequest;
7+
import com.be.sportizebe.domain.match.dto.response.MatchResponse;
8+
9+
public interface AdminService {
10+
11+
FacilityResponse createFacility(FacilityCreateRequest request); // 체육시설 등록
12+
13+
FacilityResponse updateFacility(Long facilityId, FacilityUpdateRequest request); // 체육시설 수정
14+
15+
void deleteFacility(Long facilityId); // 체육시설 삭제
16+
17+
MatchResponse createMatch(Long adminId, MatchCreateRequest request); // 매칭 생성
18+
19+
void deleteMatch(Long matchId); // 매칭 삭제
20+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.be.sportizebe.domain.admin.service;
2+
3+
import com.be.sportizebe.domain.facility.dto.request.FacilityCreateRequest;
4+
import com.be.sportizebe.domain.facility.dto.request.FacilityUpdateRequest;
5+
import com.be.sportizebe.domain.facility.dto.response.FacilityResponse;
6+
import com.be.sportizebe.domain.facility.service.SportsFacilityService;
7+
import com.be.sportizebe.domain.match.dto.request.MatchCreateRequest;
8+
import com.be.sportizebe.domain.match.dto.response.MatchResponse;
9+
import com.be.sportizebe.domain.match.service.MatchService;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Service;
12+
import org.springframework.transaction.annotation.Transactional;
13+
14+
@Service
15+
@RequiredArgsConstructor
16+
@Transactional(readOnly = true)
17+
public class AdminServiceImpl implements AdminService {
18+
19+
private final SportsFacilityService sportsFacilityService;
20+
private final MatchService matchService;
21+
22+
@Override
23+
@Transactional
24+
public FacilityResponse createFacility(FacilityCreateRequest request) {
25+
return sportsFacilityService.create(request);
26+
}
27+
28+
@Override
29+
@Transactional
30+
public FacilityResponse updateFacility(Long facilityId, FacilityUpdateRequest request) {
31+
return sportsFacilityService.update(facilityId, request);
32+
}
33+
34+
@Override
35+
@Transactional
36+
public void deleteFacility(Long facilityId) {
37+
sportsFacilityService.delete(facilityId);
38+
}
39+
40+
@Override
41+
@Transactional
42+
public MatchResponse createMatch(Long adminId, MatchCreateRequest request) {
43+
return matchService.createMatch(adminId, request);
44+
}
45+
46+
@Override
47+
@Transactional
48+
public void deleteMatch(Long matchId) {
49+
matchService.deleteMatch(matchId);
50+
}
51+
}

src/main/java/com/be/sportizebe/domain/facility/controller/SportsFacilityController.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// src/main/java/com/be/sportizebe/domain/facility/controller/SportsFacilityController.java
22
package com.be.sportizebe.domain.facility.controller;
33

4-
import com.be.sportizebe.domain.facility.dto.request.FacilityCreateRequest;
54
import com.be.sportizebe.domain.facility.dto.request.FacilityMarkerRequest;
65
import com.be.sportizebe.domain.facility.dto.request.FacilityNearRequest;
76
import com.be.sportizebe.domain.facility.dto.response.FacilityMarkerResponse;
@@ -43,12 +42,6 @@ public List<FacilityMarkerResponse> markers(
4342
return sportsFacilityService.getMarkers(request);
4443
}
4544

46-
@Operation(summary = "체육시설 등록", description = "체육시설을 등록합니다. (관리자/개발자용)")
47-
@PostMapping
48-
public FacilityResponse create(@Valid @RequestBody FacilityCreateRequest request) {
49-
return sportsFacilityService.create(request);
50-
}
51-
5245
@Operation(summary = "체육시설 단건 조회", description = "facilityId로 체육시설을 단건 조회합니다.")
5346
@GetMapping("/{facilityId}")
5447
public FacilityResponse getById(@PathVariable Long facilityId) {

src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityCreateRequest.java

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import com.be.sportizebe.domain.facility.entity.FacilityType;
44
import io.swagger.v3.oas.annotations.media.Schema;
5-
import jakarta.validation.constraints.DecimalMax;
6-
import jakarta.validation.constraints.DecimalMin;
75
import jakarta.validation.constraints.NotBlank;
86
import jakarta.validation.constraints.NotNull;
97

@@ -14,6 +12,10 @@ public record FacilityCreateRequest(
1412
@NotBlank
1513
String facilityName,
1614

15+
@Schema(description = "도로명 주소", example = "서울특별시 강남구 테헤란로 521")
16+
@NotBlank
17+
String address,
18+
1719
@Schema(description = "시설 소개", example = "샤워실/주차장 있음")
1820
String introduce,
1921

@@ -22,17 +24,7 @@ public record FacilityCreateRequest(
2224

2325
@Schema(description = "시설 종목 타입", example = "SOCCER")
2426
@NotNull
25-
FacilityType facilityType,
26-
27-
@Schema(description = "위도", example = "37.563")
28-
@DecimalMin("-90.0")
29-
@DecimalMax("90.0")
30-
double lat,
31-
32-
@Schema(description = "경도", example = "126.982")
33-
@DecimalMin("-180.0")
34-
@DecimalMax("180.0")
35-
double lng
27+
FacilityType facilityType
3628

3729
) {
3830
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.be.sportizebe.domain.facility.dto.request;
2+
3+
import com.be.sportizebe.domain.facility.entity.FacilityType;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
@Schema(description = "체육시설 수정 요청 (null 필드는 수정하지 않음)")
7+
public record FacilityUpdateRequest(
8+
9+
@Schema(description = "체육시설 이름", example = "OO 풋살장 (리모델링)")
10+
String facilityName,
11+
12+
@Schema(description = "도로명 주소 (변경 시 좌표 자동 재계산)", example = "서울특별시 강남구 테헤란로 521")
13+
String address,
14+
15+
@Schema(description = "시설 소개", example = "샤워실/주차장 있음")
16+
String introduce,
17+
18+
@Schema(description = "썸네일 이미지 URL", example = "https://example.com/facility/thumbnail.jpg")
19+
String thumbnailUrl,
20+
21+
@Schema(description = "시설 종목 타입", example = "SOCCER")
22+
FacilityType facilityType
23+
24+
) {}

src/main/java/com/be/sportizebe/domain/facility/dto/response/FacilityNearResponse.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public class FacilityNearResponse {
1515
@Schema(description = "시설명", example = "수원종합운동장")
1616
private String facilityName;
1717

18+
@Schema(description = "도로명 주소", example = "경기도 수원시 팔달구 월드컵로 310")
19+
private String address;
20+
1821
@Schema(description = "소개", example = "수원시 대표 종합 스포츠 시설 (축구, 육상 등)")
1922
private String introduce;
2023

0 commit comments

Comments
 (0)