Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/main/java/com/semosan/api/common/status/ErrorStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ public enum ErrorStatus implements BaseStatus {
/**
* Admin
*/
ADMIN_LOGIN_FAILED(HttpStatus.UNAUTHORIZED, "ADM_401_1", "아이디 또는 비밀번호가 올바르지 않습니다.");
ADMIN_LOGIN_FAILED(HttpStatus.UNAUTHORIZED, "ADM_401_1", "아이디 또는 비밀번호가 올바르지 않습니다."),
RESTAURANT_SECTION_NOT_FOUND(HttpStatus.NOT_FOUND, "ADM_404_1", "맛집 섹션을 찾을 수 없습니다."),
RESTAURANT_NOT_FOUND(HttpStatus.NOT_FOUND, "ADM_404_2", "맛집을 찾을 수 없습니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/semosan/api/common/status/SuccessStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,15 @@ public enum SuccessStatus implements BaseStatus {
/**
* Admin
*/
ADMIN_LOGIN_SUCCESS(HttpStatus.OK, "ADM_200_1", "관리자 로그인에 성공했습니다.");
ADMIN_LOGIN_SUCCESS(HttpStatus.OK, "ADM_200_1", "관리자 로그인에 성공했습니다."),
ADMIN_MOUNTAIN_UPDATE_SUCCESS(HttpStatus.OK, "ADM_200_2", "산 정보 수정에 성공했습니다."),
ADMIN_MOUNTAIN_VISIBILITY_UPDATE_SUCCESS(HttpStatus.OK, "ADM_200_3", "산 공개 상태 변경에 성공했습니다."),
ADMIN_RESTAURANT_SECTION_UPDATE_SUCCESS(HttpStatus.OK, "ADM_200_4", "맛집 섹션 수정에 성공했습니다."),
ADMIN_RESTAURANT_SECTION_DELETE_SUCCESS(HttpStatus.OK, "ADM_200_5", "맛집 섹션 삭제에 성공했습니다."),
ADMIN_RESTAURANT_UPDATE_SUCCESS(HttpStatus.OK, "ADM_200_6", "맛집 수정에 성공했습니다."),
ADMIN_RESTAURANT_DELETE_SUCCESS(HttpStatus.OK, "ADM_200_7", "맛집 삭제에 성공했습니다."),
ADMIN_RESTAURANT_SECTION_CREATE_SUCCESS(HttpStatus.CREATED, "ADM_201_1", "맛집 섹션 생성에 성공했습니다."),
ADMIN_RESTAURANT_CREATE_SUCCESS(HttpStatus.CREATED, "ADM_201_2", "맛집 생성에 성공했습니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.semosan.api.domain.admin.controller;

import com.semosan.api.common.response.ApiResponse;
import com.semosan.api.common.status.SuccessStatus;
import com.semosan.api.domain.admin.controller.docs.AdminMountainControllerDocs;
import com.semosan.api.domain.admin.dto.request.AdminMountainUpdateRequest;
import com.semosan.api.domain.admin.dto.request.AdminMountainVisibilityRequest;
import com.semosan.api.domain.admin.dto.request.AdminRestaurantRequest;
import com.semosan.api.domain.admin.dto.request.AdminRestaurantSectionRequest;
import com.semosan.api.domain.admin.service.AdminMountainService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/admin")
@RequiredArgsConstructor
public class AdminMountainController implements AdminMountainControllerDocs {

private final AdminMountainService adminMountainService;

@PutMapping("/mountains/{mountainId}")
@Override
public ResponseEntity<ApiResponse<Void>> updateMountain(
@PathVariable Long mountainId,
@Valid @RequestBody AdminMountainUpdateRequest request
) {
adminMountainService.updateMountain(mountainId, request);
return ApiResponse.success(SuccessStatus.ADMIN_MOUNTAIN_UPDATE_SUCCESS);
}

@PatchMapping("/mountains/{mountainId}/visibility")
@Override
public ResponseEntity<ApiResponse<Void>> updateVisibility(
@PathVariable Long mountainId,
@Valid @RequestBody AdminMountainVisibilityRequest request
) {
adminMountainService.updateVisibility(mountainId, request);
return ApiResponse.success(SuccessStatus.ADMIN_MOUNTAIN_VISIBILITY_UPDATE_SUCCESS);
}

@PostMapping("/mountains/{mountainId}/restaurant-sections")
@Override
public ResponseEntity<ApiResponse<Long>> createRestaurantSection(
@PathVariable Long mountainId,
@Valid @RequestBody AdminRestaurantSectionRequest request
) {
Long sectionId = adminMountainService.createRestaurantSection(mountainId, request);
return ApiResponse.success(SuccessStatus.ADMIN_RESTAURANT_SECTION_CREATE_SUCCESS, sectionId);
}

@PutMapping("/restaurant-sections/{sectionId}")
@Override
public ResponseEntity<ApiResponse<Void>> updateRestaurantSection(
@PathVariable Long sectionId,
@Valid @RequestBody AdminRestaurantSectionRequest request
) {
adminMountainService.updateRestaurantSection(sectionId, request);
return ApiResponse.success(SuccessStatus.ADMIN_RESTAURANT_SECTION_UPDATE_SUCCESS);
}

@DeleteMapping("/restaurant-sections/{sectionId}")
@Override
public ResponseEntity<ApiResponse<Void>> deleteRestaurantSection(@PathVariable Long sectionId) {
adminMountainService.deleteRestaurantSection(sectionId);
return ApiResponse.success(SuccessStatus.ADMIN_RESTAURANT_SECTION_DELETE_SUCCESS);
}

@PostMapping("/restaurant-sections/{sectionId}/restaurants")
@Override
public ResponseEntity<ApiResponse<Long>> createRestaurant(
@PathVariable Long sectionId,
@Valid @RequestBody AdminRestaurantRequest request
) {
Long restaurantId = adminMountainService.createRestaurant(sectionId, request);
return ApiResponse.success(SuccessStatus.ADMIN_RESTAURANT_CREATE_SUCCESS, restaurantId);
}

@PutMapping("/restaurants/{restaurantId}")
@Override
public ResponseEntity<ApiResponse<Void>> updateRestaurant(
@PathVariable Long restaurantId,
@Valid @RequestBody AdminRestaurantRequest request
) {
adminMountainService.updateRestaurant(restaurantId, request);
return ApiResponse.success(SuccessStatus.ADMIN_RESTAURANT_UPDATE_SUCCESS);
}

@DeleteMapping("/restaurants/{restaurantId}")
@Override
public ResponseEntity<ApiResponse<Void>> deleteRestaurant(@PathVariable Long restaurantId) {
adminMountainService.deleteRestaurant(restaurantId);
return ApiResponse.success(SuccessStatus.ADMIN_RESTAURANT_DELETE_SUCCESS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.semosan.api.domain.admin.controller.docs;

import com.semosan.api.common.response.ApiResponse;
import com.semosan.api.domain.admin.dto.request.AdminMountainUpdateRequest;
import com.semosan.api.domain.admin.dto.request.AdminMountainVisibilityRequest;
import com.semosan.api.domain.admin.dto.request.AdminRestaurantRequest;
import com.semosan.api.domain.admin.dto.request.AdminRestaurantSectionRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

@Tag(name = "Admin Mountain", description = "관리자 산 정보 관리 API")
public interface AdminMountainControllerDocs {

@Operation(summary = "산 정보 수정", description = "산의 이름, 주소, 고도, 난이도, 소요시간, 이미지를 수정합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "산 정보 수정 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "산을 찾을 수 없음")
})
ResponseEntity<ApiResponse<Void>> updateMountain(
@PathVariable Long mountainId,
@Valid @RequestBody AdminMountainUpdateRequest request
);

@Operation(summary = "산 공개/비공개 처리", description = "산의 공개 상태를 변경합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "공개 상태 변경 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "산을 찾을 수 없음")
})
ResponseEntity<ApiResponse<Void>> updateVisibility(
@PathVariable Long mountainId,
@Valid @RequestBody AdminMountainVisibilityRequest request
);

@Operation(summary = "맛집 섹션 생성", description = "산에 맛집 섹션을 추가합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "맛집 섹션 생성 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "산을 찾을 수 없음")
})
ResponseEntity<ApiResponse<Long>> createRestaurantSection(
@PathVariable Long mountainId,
@Valid @RequestBody AdminRestaurantSectionRequest request
);

@Operation(summary = "맛집 섹션 수정", description = "맛집 섹션의 제목을 수정합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "맛집 섹션 수정 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "맛집 섹션을 찾을 수 없음")
})
ResponseEntity<ApiResponse<Void>> updateRestaurantSection(
@PathVariable Long sectionId,
@Valid @RequestBody AdminRestaurantSectionRequest request
);

@Operation(summary = "맛집 섹션 삭제", description = "맛집 섹션과 소속 맛집을 모두 삭제합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "맛집 섹션 삭제 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "맛집 섹션을 찾을 수 없음")
})
ResponseEntity<ApiResponse<Void>> deleteRestaurantSection(@PathVariable Long sectionId);

@Operation(summary = "맛집 추가", description = "맛집 섹션에 맛집을 추가합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "맛집 생성 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "맛집 섹션을 찾을 수 없음")
})
ResponseEntity<ApiResponse<Long>> createRestaurant(
@PathVariable Long sectionId,
@Valid @RequestBody AdminRestaurantRequest request
);

@Operation(summary = "맛집 수정", description = "맛집 정보를 수정합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "맛집 수정 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "맛집을 찾을 수 없음")
})
ResponseEntity<ApiResponse<Void>> updateRestaurant(
@PathVariable Long restaurantId,
@Valid @RequestBody AdminRestaurantRequest request
);

@Operation(summary = "맛집 삭제", description = "맛집을 삭제합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "맛집 삭제 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "맛집을 찾을 수 없음")
})
ResponseEntity<ApiResponse<Void>> deleteRestaurant(@PathVariable Long restaurantId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.semosan.api.domain.admin.dto.request;

import com.semosan.api.domain.mountain.enums.Difficulty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.util.List;

public record AdminMountainUpdateRequest(
@NotBlank String name,
@NotBlank String address,
@NotNull Double altitude,
@NotNull Difficulty difficulty,
Integer duration,
List<String> imageUrls
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.semosan.api.domain.admin.dto.request;

import jakarta.validation.constraints.NotNull;

public record AdminMountainVisibilityRequest(
@NotNull Boolean isPublic
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.semosan.api.domain.admin.dto.request;

import jakarta.validation.constraints.NotBlank;

public record AdminRestaurantRequest(
@NotBlank String name,
String category,
String menu,
String description,
String imageUrl,
String mapUrl,
String blogUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.semosan.api.domain.admin.dto.request;

import jakarta.validation.constraints.NotBlank;

public record AdminRestaurantSectionRequest(
@NotBlank String title
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.semosan.api.domain.admin.service;

import com.semosan.api.common.exception.GeneralException;
import com.semosan.api.common.status.ErrorStatus;
import com.semosan.api.domain.admin.dto.request.AdminMountainUpdateRequest;
import com.semosan.api.domain.admin.dto.request.AdminMountainVisibilityRequest;
import com.semosan.api.domain.admin.dto.request.AdminRestaurantRequest;
import com.semosan.api.domain.admin.dto.request.AdminRestaurantSectionRequest;
import com.semosan.api.domain.mountain.entity.Mountain;
import com.semosan.api.domain.mountain.entity.Restaurant;
import com.semosan.api.domain.mountain.entity.RestaurantSection;
import com.semosan.api.domain.mountain.repository.MountainRepository;
import com.semosan.api.domain.mountain.repository.RestaurantRepository;
import com.semosan.api.domain.mountain.repository.RestaurantSectionRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class AdminMountainService {

private final MountainRepository mountainRepository;
private final RestaurantSectionRepository restaurantSectionRepository;
private final RestaurantRepository restaurantRepository;

@Transactional
public void updateMountain(Long mountainId, AdminMountainUpdateRequest request) {
Mountain mountain = findMountainById(mountainId);
mountain.updateInfo(request.name(), request.address(), request.altitude(),
request.difficulty(), request.duration());
mountain.updateImageUrls(request.imageUrls());
}

@Transactional
public void updateVisibility(Long mountainId, AdminMountainVisibilityRequest request) {
Mountain mountain = findMountainById(mountainId);
mountain.updateVisibility(request.isPublic());
}

@Transactional
public Long createRestaurantSection(Long mountainId, AdminRestaurantSectionRequest request) {
Mountain mountain = findMountainById(mountainId);
RestaurantSection section = RestaurantSection.create(mountain, request.title());
return restaurantSectionRepository.save(section).getId();
}

@Transactional
public void updateRestaurantSection(Long sectionId, AdminRestaurantSectionRequest request) {
RestaurantSection section = restaurantSectionRepository.findById(sectionId)
.orElseThrow(() -> new GeneralException(ErrorStatus.RESTAURANT_SECTION_NOT_FOUND));
section.updateTitle(request.title());
}

@Transactional
public void deleteRestaurantSection(Long sectionId) {
RestaurantSection section = restaurantSectionRepository.findById(sectionId)
.orElseThrow(() -> new GeneralException(ErrorStatus.RESTAURANT_SECTION_NOT_FOUND));
restaurantSectionRepository.delete(section);
}

@Transactional
public Long createRestaurant(Long sectionId, AdminRestaurantRequest request) {
RestaurantSection section = restaurantSectionRepository.findById(sectionId)
.orElseThrow(() -> new GeneralException(ErrorStatus.RESTAURANT_SECTION_NOT_FOUND));
Restaurant restaurant = Restaurant.create(section, request.name(), request.category(),
request.menu(), request.description(), request.imageUrl(),
request.mapUrl(), request.blogUrl());
return restaurantRepository.save(restaurant).getId();
}

@Transactional
public void updateRestaurant(Long restaurantId, AdminRestaurantRequest request) {
Restaurant restaurant = restaurantRepository.findById(restaurantId)
.orElseThrow(() -> new GeneralException(ErrorStatus.RESTAURANT_NOT_FOUND));
restaurant.update(request.name(), request.category(), request.menu(),
request.description(), request.imageUrl(), request.mapUrl(), request.blogUrl());
}

@Transactional
public void deleteRestaurant(Long restaurantId) {
Restaurant restaurant = restaurantRepository.findById(restaurantId)
.orElseThrow(() -> new GeneralException(ErrorStatus.RESTAURANT_NOT_FOUND));
restaurantRepository.delete(restaurant);
}

private Mountain findMountainById(Long mountainId) {
return mountainRepository.findById(mountainId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MOUNTAIN_NOT_FOUND));
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/semosan/api/domain/mountain/entity/Mountain.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ public class Mountain extends BaseEntity {
@Column(name = "is_public", nullable = false)
private boolean isPublic = true;

public void updateInfo(String name, String address, Double altitude, Difficulty difficulty, Integer duration) {
this.name = name;
this.address = address;
this.altitude = altitude;
this.difficulty = difficulty;
this.duration = duration;
}

public void updateImageUrls(List<String> imageUrls) {
this.imageUrls = imageUrls;
}

public void updateVisibility(boolean isPublic) {
this.isPublic = isPublic;
}

public void updateCoordinates(Double latitude, Double longitude) {
this.latitude = latitude;
this.longitude = longitude;
Expand Down
Loading