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: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ dependencies {
//S3
implementation 'software.amazon.awssdk:s3:2.20.26'

//Flyway
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

Expand All @@ -22,8 +23,9 @@ public record GoogleSignUpRequestDto (
@Schema(example = "010-7689-3141")
String phoneNumber,

@Schema(example = "서울시 강서구")
String region,
@NotNull
@Schema(example = "1")
Long regionId,

@Size(max = 300, message = "경력사항은 300자 이내로 입력해주세요")
@Schema(example = "1년간 요식업 근무 경험")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.example.dgu.returnwork.domain.auth.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.*;

public record SignUpRequestDto (

Expand Down Expand Up @@ -33,8 +30,9 @@ public record SignUpRequestDto (
@Schema(example = "010-7689-3141")
String phoneNumber,

@Schema(example = "서울시 강서구")
String region,
@NotNull
@Schema(example = "1")
Long regionId,

@Size(max = 300, message = "경력사항은 300자 이내로 입력해주세요")
@Schema(example = "1년간 요식업 근무 경험")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void signUp(SignUpRequestDto request, MultipartFile profileImage) {

userValidator.validateBirthday(userBirthday);

Region userRegion = regionQueryService.findRegionByName(request.region());
Region userRegion = regionQueryService.findRegionById(request.regionId());

User user = User.builder()
.name(request.name())
Expand Down Expand Up @@ -114,7 +114,7 @@ public LoginUserResponseDto googleSignup(GoogleSignUpRequestDto request, User us

userValidator.validateBirthday(userBirthday);

Region userRegion = regionQueryService.findRegionByName(request.region());
Region userRegion = regionQueryService.findRegionById(request.regionId());

user.update(request.name(), request.phoneNumber(), userBirthday, userRegion, request.career());

Expand Down
19 changes: 17 additions & 2 deletions src/main/java/com/example/dgu/returnwork/domain/region/Region.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,23 @@ public class Region {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "name", nullable = false, unique = true, length = 50)
private String name;
@Column(nullable = false, length = 200)
private String fullAddress;

@Column(length = 50)
private String sido;

@Column(length = 50)
private String sigungu;

@Column(length = 50)
private String dongLi;

@Column(length = 50)
private String sidoNormalized;

@Column(length = 500)
private String searchKeywords;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.example.dgu.returnwork.domain.region.controller;

import com.example.dgu.returnwork.domain.region.dto.response.SearchRegionDto;
import com.example.dgu.returnwork.global.dto.PageResponseDto;
import com.example.dgu.returnwork.global.exception.CustomErrorResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import org.springframework.web.bind.annotation.RequestParam;

@Tag(name = "Region", description = "지역 검색 API")
public interface RegionApi {

@Operation(
summary = "지역 검색",
description = """
키워드로 지역을 검색하는 API입니다.
- 시/도, 시/군/구, 동/리 등 모든 주소 레벨에서 검색 가능
- 대소문자 구분 없이 검색
- 부분 검색 지원 (예: "서울", "종로", "청운동" 모두 검색 가능)
"""
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "지역 검색 성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = com.example.dgu.returnwork.global.response.ApiResponse.class),
examples = @ExampleObject(
name = "성공 응답",
value = """
{
"errorCode": null,
"message": "OK",
"result": {
"content": [
{
"id": 1,
"fullAddress": "서울특별시 종로구 청운동"
},
{
"id": 2,
"fullAddress": "서울특별시 종로구 신교동"
}
],
"currentPage": 0,
"totalPage": 5,
"totalElements": 45,
"hasNext": true
}
}
"""
)
)
),
@ApiResponse(
responseCode = "400",
description = "잘못된 요청",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = CustomErrorResponse.class),
examples = @ExampleObject(
name = "검증 실패",
summary = "검색 키워드가 비어있는 경우",
value = """
{
"status": 400,
"errorCode": "COMMON_002",
"message": "입력값 검증에 실패했습니다"
}
"""
)
)
)
})
PageResponseDto<SearchRegionDto> searchRegion(
@Parameter(
description = "검색할 지역 키워드 (시/도, 시/군/구, 동/리 등)",
example = "서울"
)
@RequestParam String searchKeyword,

@RequestParam(defaultValue = "0") Integer page
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.dgu.returnwork.domain.region.controller;

import com.example.dgu.returnwork.domain.region.dto.response.SearchRegionDto;
import com.example.dgu.returnwork.domain.region.service.RegionQueryService;
import com.example.dgu.returnwork.global.dto.PageResponseDto;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/regions")
public class RegionController implements RegionApi {

private final RegionQueryService regionQueryService;

@Override
@GetMapping
public PageResponseDto<SearchRegionDto> searchRegion (@RequestParam String searchKeyword,
@RequestParam(defaultValue = "0") Integer page) {
return regionQueryService.searchRegion(searchKeyword, page);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.dgu.returnwork.domain.region.dto.response;

import com.example.dgu.returnwork.domain.region.Region;

public record SearchRegionDto(
Long id,

String fullAddress
) {
public static SearchRegionDto from(Region region) {
return new SearchRegionDto(region.getId(), region.getFullAddress());
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.example.dgu.returnwork.domain.region.repository;

import com.example.dgu.returnwork.domain.region.Region;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.Optional;

public interface RegionRepository extends JpaRepository<Region, Long> {
Optional<Region> findByName(String name);
@Query("SELECT r FROM Region r WHERE LOWER(r.searchKeywords) LIKE LOWER(CONCAT('%', :keyword, '%'))")
Page<Region> searchByKeywordIgnoreCase(@Param("keyword") String keyword, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.example.dgu.returnwork.domain.region.service;

import com.example.dgu.returnwork.domain.region.Region;
import com.example.dgu.returnwork.domain.region.dto.response.SearchRegionDto;
import com.example.dgu.returnwork.domain.region.exception.RegionErrorCode;
import com.example.dgu.returnwork.domain.region.repository.RegionRepository;
import com.example.dgu.returnwork.global.dto.PageResponseDto;
import com.example.dgu.returnwork.global.exception.BaseException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -14,8 +18,18 @@
public class RegionQueryService {
private final RegionRepository regionRepository;

public Region findRegionByName(String regionName) {
return regionRepository.findByName(regionName)
public Region findRegionById(Long regionId) {
return regionRepository.findById(regionId)
.orElseThrow(() -> BaseException.type(RegionErrorCode.REGION_NOT_FOUND));
}

public PageResponseDto<SearchRegionDto> searchRegion(String keyword, Integer page){

String trimKeyword = keyword.trim();

Pageable pageable = PageRequest.of(page, 10);

return PageResponseDto.from(regionRepository.searchByKeywordIgnoreCase(trimKeyword, pageable)
.map(SearchRegionDto::from));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record UpdateUserInfoRequestDto(

Expand All @@ -21,8 +22,8 @@ public record UpdateUserInfoRequestDto(
@Schema(description = "경력 사항", example = "요식업 2년차")
String career,

@NotBlank
@Schema(description = "거주지", example = "서울시 강서구")
String region
@NotNull
@Schema(description = "거주지", example = "1")
Long regionId
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static GetUserInfoResponseDto of(User user, String imageUrl) {
.birthday(user.getBirthday().toString())
.phoneNumber(user.getPhoneNumber())
.career(user.getCareer())
.region(user.getRegion().getName())
.region(user.getRegion().getFullAddress())
.imageUrl(imageUrl)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void updateUserInfo(User user, UpdateUserInfoRequestDto request) {

userValidator.validateBirthday(userBirthday);

Region userRegion = regionQueryService.findRegionByName(request.region());
Region userRegion = regionQueryService.findRegionById(request.regionId());

user.update(request.name(), request.phoneNumber(), userBirthday, userRegion, request.career());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http,
"/webjars/**",
"/v3/api-docs/**",
"/v3/api-docs",
"/api/auth/reissue"
"/api/auth/reissue",
"/api/regions"
).permitAll()
.requestMatchers("/api/auth/google/login/complete").hasRole("TEMP_USER")
.requestMatchers("/admin/**").hasRole("ADMIN")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.dgu.returnwork.global.dto;

import org.springframework.data.domain.Page;

import java.util.List;

public record PageResponseDto<T>(
List<T> content,

int currentPage,

int totalPage,

long totalElements,

boolean hasNext
) {
public static <T> PageResponseDto<T> from(Page<T> page) {
return new PageResponseDto<>(
page.getContent(),
page.getNumber(),
page.getTotalPages(),
page.getTotalElements(),
page.hasNext()
);
}
}
9 changes: 7 additions & 2 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ spring:

jpa:
hibernate:
ddl-auto: update
ddl-auto: validate

data:
redis:
Expand All @@ -18,4 +18,9 @@ spring:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
min-idle: 0

flyway:
enabled : true
baseline-on-migrate: true
locations : classpath:db/migration
Loading