Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.somemore.center.dto.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.somemore.center.domain.Center;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.UUID;
import lombok.Builder;

@Builder
@JsonNaming(SnakeCaseStrategy.class)
@Schema(description = "기관 정보 응답 DTO")
public record CenterInfoResponse(
@Schema(description = "기관 아이디", example = "123e4567-e89b-12d3-a456-426614174000")
UUID id,
@Schema(description = "기관 이름", example = "환경 봉사 센터")
String name
) {

public static CenterInfoResponse from(Center center) {
return CenterInfoResponse.builder()
.id(center.getId())
.name(center.getName())
.build();
}

public static CenterInfoResponse of(UUID centerId, String name) {
return CenterInfoResponse.builder()
.id(centerId)
.name(name)
.build();
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/somemore/global/common/utils/GeoUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.somemore.global.common.utils;

public class GeoUtils {

private static final double EARTH_RADIUS = 6371.0;

public static double[] calculateMaxMinCoordinates(double latitude, double longitude,
double radius) {
double latRad = Math.toRadians(latitude);
double latDiff = radius / EARTH_RADIUS;
double maxLatRad = latRad + latDiff;
double minLatRad = latRad - latDiff;

double maxLat = Math.toDegrees(maxLatRad);
double minLat = Math.toDegrees(minLatRad);

double lonDiff = radius / (EARTH_RADIUS * Math.cos(latRad));
double maxLon = longitude + Math.toDegrees(lonDiff);
double minLon = longitude - Math.toDegrees(lonDiff);

return new double[]{minLat, minLon, maxLat, maxLon};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ public enum ExceptionMessage {
NOT_EXISTS_COMMUNITY_COMMENT("존재하지 않는 댓글 입니다."),
NOT_EXISTS_LOCATION("존재하지 않는 위치 ID 입니다."),
NOT_EXISTS_RECRUIT_BOARD("존재하지 않는 봉사 모집글 ID 입니다."),
UNAUTHORIZED_RECRUIT_BOARD("자신이 작성한 봉사 모집글이 아닙니다."),
UNAUTHORIZED_RECRUIT_BOARD("해당 봉사 모집글에 권한이 없습니다."),
UPLOAD_FAILED("파일 업로드에 실패했습니다."),
INVALID_FILE_TYPE("지원하지 않는 파일 형식입니다."),
FILE_SIZE_EXCEEDED("파일 크기가 허용된 한도를 초과했습니다."),
EMPTY_FILE("파일이 존재하지 않습니다."),
INSTANTIATION_NOT_ALLOWED("인스턴스화 할 수 없는 클래스 입니다.")
INSTANTIATION_NOT_ALLOWED("인스턴스화 할 수 없는 클래스 입니다."),
;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.somemore.location.dto.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.somemore.location.domain.Location;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import lombok.Builder;

@Builder
@JsonNaming(SnakeCaseStrategy.class)
@Schema(description = "위치 조회 응답 DTO")
public record LocationResponseDto(
@Schema(description = "주소", example = "서울특별시 강남구 테헤란로 123")
String address,
@Schema(description = "위도", example = "37.5665")
BigDecimal latitude,
@Schema(description = "경도", example = "126.9780")
BigDecimal longitude
) {

public static LocationResponseDto from(Location location) {
return LocationResponseDto.builder()
.address(location.getAddress())
.latitude(location.getLatitude())
.longitude(location.getLongitude())
.build();
}

public static LocationResponseDto of(String address, BigDecimal latitude,
BigDecimal longitude) {
return LocationResponseDto.builder()
.address(address)
.latitude(latitude)
.longitude(longitude)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.somemore.recruitboard.controller;

import static org.springframework.data.domain.Sort.Direction.DESC;

import com.somemore.global.common.response.ApiResponse;
import com.somemore.recruitboard.domain.RecruitStatus;
import com.somemore.recruitboard.domain.VolunteerType;
import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition;
import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition;
import com.somemore.recruitboard.dto.response.RecruitBoardDetailResponseDto;
import com.somemore.recruitboard.dto.response.RecruitBoardResponseDto;
import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto;
import com.somemore.recruitboard.dto.response.RecruitBoardWithLocationResponseDto;
import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
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.RestController;

@Tag(name = "Recruit Board Query API", description = "봉사 활동 모집 조회 관련 API")
@RequiredArgsConstructor
@RequestMapping("/api")
@RestController
public class RecruitBoardQueryController {

private final RecruitBoardQueryUseCase recruitBoardQueryUseCase;

@GetMapping("/recruit-board/{id}")
@Operation(summary = "봉사 모집글 상세 조회", description = "특정 모집글의 상세 정보를 조회합니다.")
public ApiResponse<RecruitBoardWithLocationResponseDto> getById(
@PathVariable Long id
) {
return ApiResponse.ok(
200,
recruitBoardQueryUseCase.getWithLocationById(id),
"봉사 활동 모집 상세 조회 성공"
);
}

@GetMapping("/recruit-boards")
@Operation(summary = "전체 모집글 조회", description = "모든 봉사 모집글 목록을 조회합니다.")
public ApiResponse<Page<RecruitBoardWithCenterResponseDto>> getAll(
@PageableDefault(size = 10, page = 0, sort = "created_at", direction = DESC)
Pageable pageable
) {
RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder()
.pageable(pageable)
.build();

return ApiResponse.ok(
200,
recruitBoardQueryUseCase.getAllWithCenter(condition),
"봉사 활동 모집글 리스트 조회 성공"
);
}

@GetMapping("/recruit-boards/search")
@Operation(summary = "모집글 검색 조회", description = "검색 조건을 기반으로 모집글을 조회합니다.")
public ApiResponse<Page<RecruitBoardWithCenterResponseDto>> getAllBySearch(
@PageableDefault(size = 10, page = 0, sort = "created_at", direction = DESC) Pageable pageable,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) VolunteerType type,
@RequestParam(required = false) String region,
@RequestParam(required = false) Boolean admitted,
@RequestParam(required = false) RecruitStatus status
) {
RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder()
.keyword(keyword)
.type(type)
.region(region)
.admitted(admitted)
.status(status)
.pageable(pageable)
.build();

return ApiResponse.ok(
200,
recruitBoardQueryUseCase.getAllWithCenter(condition),
"봉사 활동 모집글 검색 조회 성공"
);
}

@GetMapping("/recruit-boards/nearby")
@Operation(summary = "근처 모집글 조회", description = "주변 반경 내의 봉사 모집글을 조회합니다.")
public ApiResponse<Page<RecruitBoardDetailResponseDto>> getNearby(
@RequestParam double latitude,
@RequestParam double longitude,
@RequestParam(required = false, defaultValue = "5") double radius,
@RequestParam(required = false) String keyword,
@PageableDefault(sort = "created_at", direction = DESC) Pageable pageable
) {
RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder()
.latitude(latitude)
.longitude(longitude)
.radius(radius)
.keyword(keyword)
.pageable(pageable)
.build();

return ApiResponse.ok(
200,
recruitBoardQueryUseCase.getRecruitBoardsNearby(condition),
"근처 봉사 활동 모집글 조회 성공"
);
}

@GetMapping("/recruit-boards/center/{centerId}")
@Operation(summary = "특정 기관 모집글 조회", description = "특정 기관의 봉사 모집글을 조회합니다.")
public ApiResponse<Page<RecruitBoardResponseDto>> getRecruitBoardsByCenterId(
@PathVariable UUID centerId,
@PageableDefault(sort = "created_at", direction = DESC) Pageable pageable
) {
return ApiResponse.ok(
200,
recruitBoardQueryUseCase.getRecruitBoardsByCenterId(centerId, pageable),
"기관 봉사 활동 모집글 조회 성공"
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.somemore.recruitboard.domain.mapping;

import com.somemore.recruitboard.domain.RecruitBoard;
import java.math.BigDecimal;

public record RecruitBoardDetail(
RecruitBoard recruitBoard,
String address,
BigDecimal latitude,
BigDecimal longitude,
String centerName
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.somemore.recruitboard.domain.mapping;

import com.somemore.recruitboard.domain.RecruitBoard;

public record RecruitBoardWithCenter(
RecruitBoard recruitBoard,
String centerName
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.somemore.recruitboard.domain.mapping;

import com.somemore.recruitboard.domain.RecruitBoard;
import java.math.BigDecimal;

public record RecruitBoardWithLocation(
RecruitBoard recruitBoard,
String address,
BigDecimal latitude,
BigDecimal longitude
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.somemore.recruitboard.dto.condition;

import lombok.Builder;
import org.springframework.data.domain.Pageable;

@Builder
public record RecruitBoardNearByCondition(
Double latitude,
Double longitude,
Double radius,
String keyword,
Pageable pageable
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.somemore.recruitboard.dto.condition;

import com.somemore.recruitboard.domain.RecruitStatus;
import com.somemore.recruitboard.domain.VolunteerType;
import lombok.Builder;
import org.springframework.data.domain.Pageable;

@Builder
public record RecruitBoardSearchCondition(
String keyword,
VolunteerType type,
String region,
Boolean admitted,
RecruitStatus status,
Pageable pageable
) {

}
Loading
Loading