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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.withtime.be.withtimebe.domain.member.entity.enums.Gender;
import org.withtime.be.withtimebe.domain.member.entity.enums.Role;
import org.withtime.be.withtimebe.domain.member.entity.enums.UserRank;
import org.withtime.be.withtimebe.domain.weather.entity.Region;
import org.withtime.be.withtimebe.global.common.BaseEntity;

import java.time.LocalDate;
Expand Down Expand Up @@ -75,6 +76,10 @@ public class Member extends BaseEntity {
@Column(name = "role", nullable = false)
private Role role;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "region_id")
private Region region;

public void changeUsername(String newUsername) {
this.username= newUsername;
}
Expand All @@ -92,4 +97,8 @@ public void updateAlarmSetting(Boolean pushAlarm, Boolean emailAlarm, Boolean sm
this.emailAlarm = emailAlarm;
this.smsAlarm = smsAlarm;
}

public void updateRegion(Region region) {
this.region = region;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
import org.namul.api.payload.response.DefaultResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.withtime.be.withtimebe.domain.member.entity.Member;
import org.withtime.be.withtimebe.domain.weather.dto.request.RegionReqDTO;
import org.withtime.be.withtimebe.domain.weather.dto.response.RegionResDTO;
import org.withtime.be.withtimebe.domain.weather.service.command.RegionCommandService;
import org.withtime.be.withtimebe.domain.weather.service.query.RegionQueryService;
import org.withtime.be.withtimebe.global.security.annotation.AuthenticatedMember;

@Slf4j
@RestController
Expand Down Expand Up @@ -190,6 +192,43 @@ public DefaultResponse<RegionResDTO.RegionSearchResult> searchRegions(
return DefaultResponse.ok(response);
}

@GetMapping("/users/current")
@Operation(summary = "현재 사용자 지역 조회 API by 지미",
description = "로그인한 사용자의 현재 지역 정보를 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패"),
@ApiResponse(responseCode = "404", description = "사용자 또는 지역을 찾을 수 없습니다.")
})
public DefaultResponse<RegionResDTO.UserRegion> getCurrentUserRegion(@AuthenticatedMember Member member) {
log.info("현재 사용자 지역 조회 API 호출");

RegionResDTO.UserRegion response = regionQueryService.getCurrentUserRegion(member);
return DefaultResponse.ok(response);
}

@PatchMapping("/users")
@Operation(summary = "사용자 지역 변경 API by 지미",
description = "로그인한 사용자의 지역을 설정/변경합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "변경 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터"),
@ApiResponse(responseCode = "401", description = "인증 실패"),
@ApiResponse(responseCode = "404",
description = """
다음과 같은 이유로 실패할 수 있습니다:
- MEMBER404_0: 사용자를 찾을 수 없습니다.
- WEATHER404_0: 지역을 찾을 수 없습니다.
""")
})
public DefaultResponse<RegionResDTO.UserRegionWithMessage> updateUserRegion(
@Valid @RequestBody RegionReqDTO.UpdateUserRegion reqDTO, @AuthenticatedMember Member member) {
log.info("사용자 지역 변경 API 호출: regionId={}", reqDTO.regionId());

RegionResDTO.UserRegionWithMessage response = regionCommandService.updateUserRegion(reqDTO, member);
return DefaultResponse.ok(response);
}

@DeleteMapping("/codes/{regionCodeId}")
@Operation(summary = "지역코드 삭제 API by 지미 [Only Admin]", description = "지역코드를 삭제합니다(해당 코드를 사용하는 지역이 없어야 함. 관리자용).")
@ApiResponses(value = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public DefaultResponse<WeatherSyncResDTO.ManualTriggerResult> manualTrigger(

@GetMapping("/{regionId}/weekly")
@Operation(
summary = "지역별 주간 날씨 기반 추천 조회",
summary = "지역별 주간 날씨 기반 추천 조회 by 지미",
description = """
특정 지역의 7일치(오늘 기준) 날씨 데이터를 바탕으로 한 데이트 추천 정보를 제공합니다.

Expand Down Expand Up @@ -100,7 +100,7 @@ public DefaultResponse<WeatherResDTO.WeeklyRecommendation> getWeeklyRecommendati

@GetMapping("/{regionId}/precipitation")
@Operation(
summary = "지역별 7일간 강수확률 조회",
summary = "지역별 7일간 강수확률 조회 by 지미",
description = """
특정 지역의 7일간 강수확률 정보만 간단하게 조회합니다.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,25 @@ public static RegionResDTO.DeleteRegion toDeleteRegion(Region region) {
.message("지역이 성공적으로 삭제되었습니다.")
.build();
}

public static RegionResDTO.UserRegion toUserRegion(Region region) {
return RegionResDTO.UserRegion.builder()
.regionId(region.getId())
.name(region.getName())
.latitude(region.getLatitude())
.longitude(region.getLongitude())
.gridX(region.getGridX())
.gridY(region.getGridY())
.regionCode(toRegionCodeInfo(region.getRegionCode()))
.build();
}

public static RegionResDTO.UserRegionWithMessage toUserRegionWithMessage(Region region, String message) {
return RegionResDTO.UserRegionWithMessage.builder()
.regionId(region.getId())
.name(region.getName())
.regionCode(toRegionCodeInfo(region.getRegionCode()))
.message(message)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,10 @@ public record CreateRegionWithNewCode(
String regionCodeName
) {
}

public record UpdateUserRegion(
@NotNull(message = "지역 ID는 필수 입력값입니다.")
Long regionId
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,25 @@ public record DeleteRegion(
String message
) {
}

@Builder
public record UserRegion(
Long regionId,
String name,
BigDecimal latitude,
BigDecimal longitude,
BigDecimal gridX,
BigDecimal gridY,
RegionCodeInfo regionCode
) {
}

@Builder
public record UserRegionWithMessage(
Long regionId,
String name,
RegionCodeInfo regionCode,
String message
) {
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.withtime.be.withtimebe.domain.weather.service.command;

import org.withtime.be.withtimebe.domain.member.entity.Member;
import org.withtime.be.withtimebe.domain.weather.dto.request.RegionReqDTO;
import org.withtime.be.withtimebe.domain.weather.dto.response.RegionResDTO;

Expand All @@ -14,4 +15,6 @@ public interface RegionCommandService {
RegionResDTO.DeleteRegionCode deleteRegionCode(Long regionCodeId);

RegionResDTO.DeleteRegion deleteRegion(Long regionId);

RegionResDTO.UserRegionWithMessage updateUserRegion(RegionReqDTO.UpdateUserRegion reqDTO, Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.reactive.function.client.WebClient;
import org.withtime.be.withtimebe.domain.member.entity.Member;
import org.withtime.be.withtimebe.domain.member.repository.MemberRepository;
import org.withtime.be.withtimebe.domain.weather.converter.RegionConverter;
import org.withtime.be.withtimebe.domain.weather.dto.request.RegionReqDTO;
import org.withtime.be.withtimebe.domain.weather.dto.response.RegionResDTO;
import org.withtime.be.withtimebe.domain.weather.entity.Region;
import org.withtime.be.withtimebe.domain.weather.entity.RegionCode;
import org.withtime.be.withtimebe.domain.weather.repository.RegionCodeRepository;
import org.withtime.be.withtimebe.domain.weather.repository.RegionRepository;
import org.withtime.be.withtimebe.global.error.code.MemberErrorCode;
import org.withtime.be.withtimebe.global.error.code.RegionErrorCode;
import org.withtime.be.withtimebe.global.error.code.WeatherErrorCode;
import org.withtime.be.withtimebe.global.error.exception.MemberException;
import org.withtime.be.withtimebe.global.error.exception.RegionException;
import org.withtime.be.withtimebe.global.error.exception.WeatherException;

import java.math.BigDecimal;
Expand All @@ -29,6 +36,7 @@ public class RegionCommandServiceImpl implements RegionCommandService {

private final RegionRepository regionRepository;
private final RegionCodeRepository regionCodeRepository;
private final MemberRepository memberRepository;

@Value("${weather.api.key}")
private String apiKey;
Expand Down Expand Up @@ -128,6 +136,21 @@ public RegionResDTO.DeleteRegion deleteRegion(Long regionId) {
return RegionConverter.toDeleteRegion(region);
}

@Override
@Transactional
public RegionResDTO.UserRegionWithMessage updateUserRegion(RegionReqDTO.UpdateUserRegion reqDTO, Member member) {
Member currentMember = memberRepository.findByEmail(member.getEmail())
.orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND));

Region region = regionRepository.findByIdWithRegionCode(reqDTO.regionId())
.orElseThrow(() -> new RegionException(RegionErrorCode.REGION_NOT_FOUND));

currentMember.updateRegion(region);

String message = "지역이 성공적으로 변경되었습니다.";
return RegionConverter.toUserRegionWithMessage(region, message);
}

// ==== 내부 유틸리티 메서드들 ====

private void validateDuplicateRegionCode(String landRegCode, String tempRegCode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.withtime.be.withtimebe.domain.weather.service.query;

import org.withtime.be.withtimebe.domain.member.entity.Member;
import org.withtime.be.withtimebe.domain.weather.dto.response.RegionResDTO;

public interface RegionQueryService {
Expand All @@ -11,4 +12,6 @@ public interface RegionQueryService {
RegionResDTO.RegionInfo getRegionById(Long regionId);

RegionResDTO.RegionSearchResult searchRegions(String keyword);

RegionResDTO.UserRegion getCurrentUserRegion(Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.withtime.be.withtimebe.domain.member.entity.Member;
import org.withtime.be.withtimebe.domain.member.repository.MemberRepository;
import org.withtime.be.withtimebe.domain.weather.converter.RegionConverter;
import org.withtime.be.withtimebe.domain.weather.dto.response.RegionResDTO;
import org.withtime.be.withtimebe.domain.weather.entity.Region;
import org.withtime.be.withtimebe.domain.weather.repository.RegionCodeRepository;
import org.withtime.be.withtimebe.domain.weather.repository.RegionRepository;
import org.withtime.be.withtimebe.global.error.code.MemberErrorCode;
import org.withtime.be.withtimebe.global.error.code.RegionErrorCode;
import org.withtime.be.withtimebe.global.error.code.WeatherErrorCode;
import org.withtime.be.withtimebe.global.error.exception.MemberException;
import org.withtime.be.withtimebe.global.error.exception.RegionException;
import org.withtime.be.withtimebe.global.error.exception.WeatherException;

import java.util.List;
Expand All @@ -20,6 +27,9 @@ public class RegionQueryServiceImpl implements RegionQueryService{

private final RegionCodeRepository regionCodeRepository;
private final RegionRepository regionRepository;
private final MemberRepository memberRepository;

private static final Long DEFAULT_REGION_ID = 1L;

@Override
public RegionResDTO.RegionCodeList getAllRegionCodes() {
Expand All @@ -45,4 +55,28 @@ public RegionResDTO.RegionSearchResult searchRegions(String keyword) {
List<Region> regions = regionRepository.searchByNameContaining(keyword);
return RegionConverter.toSearchResult(regions, keyword);
}

@Override
@Transactional
public RegionResDTO.UserRegion getCurrentUserRegion(Member member) {
// 현재 로그인한 사용자 조회
Member currentMember = memberRepository.findByEmail(member.getEmail())
.orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND));

Region userRegion = currentMember.getRegion();

// 사용자에게 지역이 설정되지 않은 경우 기본 지역 반환
if (userRegion == null) {
userRegion = regionRepository.findByIdWithRegionCode(DEFAULT_REGION_ID)
.orElseThrow(() -> new RegionException(RegionErrorCode.REGION_NOT_FOUND));

currentMember.updateRegion(userRegion);
memberRepository.save(currentMember);

log.info("사용자 {}에게 기본 지역({})이 자동 설정되었습니다.",
currentMember.getUsername(), userRegion.getName());
}

return RegionConverter.toUserRegion(userRegion);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.withtime.be.withtimebe.global.error.code;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.namul.api.payload.code.BaseErrorCode;
import org.namul.api.payload.code.dto.supports.DefaultResponseErrorReasonDTO;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum RegionErrorCode implements BaseErrorCode {

// ==== 지역 관련 에러 (404) ====
REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION404_0", "지역을 찾을 수 없습니다."),
;
private final HttpStatus httpStatus;
private final String code;
private final String message;

@Override
public DefaultResponseErrorReasonDTO getReason() {
return DefaultResponseErrorReasonDTO.builder()
.httpStatus(this.httpStatus)
.code(this.code)
.message(this.message)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.withtime.be.withtimebe.global.error.exception;

import org.namul.api.payload.code.BaseErrorCode;
import org.namul.api.payload.error.exception.ServerApplicationException;

public class RegionException extends ServerApplicationException {
public RegionException(BaseErrorCode baseErrorCode) {
super(baseErrorCode);
}
}