diff --git a/umc9th/.gitignore b/umc9th/.gitignore index c2065bc..5fc642b 100644 --- a/umc9th/.gitignore +++ b/umc9th/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +# Spring Boot application configuration +application.yml \ No newline at end of file diff --git a/umc9th/build.gradle b/umc9th/build.gradle index c663a55..a1c7908 100644 --- a/umc9th/build.gradle +++ b/umc9th/build.gradle @@ -35,8 +35,41 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // QueryDSL : OpenFeign + implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0" + implementation "io.github.openfeign.querydsl:querydsl-core:7.0" + annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13' + + // Validation + implementation 'org.springframework.boot:spring-boot-starter-validation' } tasks.named('test') { useJUnitPlatform() } + +// QueryDSL 관련 설정 +// generated/querydsl 폴더 생성 & 삽입 +def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile + +// 소스 세트에 생성 경로 추가 (구체적인 경로 지정) +sourceSets { + main.java.srcDirs += [ querydslDir ] +} + +// 컴파일 시 생성 경로 지정 +tasks.withType(JavaCompile).configureEach { + options.generatedSourceOutputDirectory.set(querydslDir) +} + +// clean 태스크에 생성 폴더 삭제 로직 추가 +clean.doLast { + file(querydslDir).deleteDir() +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/controller/MemberController.java b/umc9th/src/main/java/com/example/umc9th/domain/member/controller/MemberController.java new file mode 100644 index 0000000..a67f97c --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/controller/MemberController.java @@ -0,0 +1,29 @@ +package com.example.umc9th.domain.member.controller; + +import com.example.umc9th.domain.member.dto.MemberReqDTO; +import com.example.umc9th.domain.member.dto.MemberResDTO; +import com.example.umc9th.domain.member.exception.code.MemberSuccessCode; +import com.example.umc9th.domain.member.service.command.MemberCommandService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class MemberController { + + private final MemberCommandService memberCommandService; + + // 회원가입 + @PostMapping("/sign-up") + public ApiResponse signUp( + @RequestBody @Valid MemberReqDTO.JoinDTO dto + ){ + return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberCommandService.signup(dto)); + } + + +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/converter/MemberConverter.java b/umc9th/src/main/java/com/example/umc9th/domain/member/converter/MemberConverter.java new file mode 100644 index 0000000..ec9676d --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/converter/MemberConverter.java @@ -0,0 +1,31 @@ +package com.example.umc9th.domain.member.converter; + +import com.example.umc9th.domain.member.dto.MemberReqDTO; +import com.example.umc9th.domain.member.dto.MemberResDTO; +import com.example.umc9th.domain.member.entity.Member; + +public class MemberConverter { + + // Entity -> DTO + public static MemberResDTO.JoinDTO toJoinDTO( + Member member + ){ + return MemberResDTO.JoinDTO.builder() + .memberId(member.getId()) + .createAt(member.getCreatedAt()) + .build(); + } + + // DTO -> Entity + public static Member toMember(MemberReqDTO.JoinDTO dto) { + return Member.builder() + .name(dto.name()) + .birth(dto.birth()) + .address(dto.address()) + .gender(dto.gender()) + .email(dto.email()) + .password(dto.password()) + .phone(dto.phone()) + .build(); + } +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/dto/MemberReqDTO.java b/umc9th/src/main/java/com/example/umc9th/domain/member/dto/MemberReqDTO.java new file mode 100644 index 0000000..4589e75 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/dto/MemberReqDTO.java @@ -0,0 +1,22 @@ +package com.example.umc9th.domain.member.dto; + +import com.example.umc9th.domain.member.enums.Gender; +import com.example.umc9th.global.annotation.ExistFoods; + +import java.time.LocalDate; +import java.util.List; + +public class MemberReqDTO { + + public record JoinDTO( + String name, + Gender gender, + LocalDate birth, + String address, + String email, + String password, + String phone, + @ExistFoods + List preferCategory + ){} +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/dto/MemberResDTO.java b/umc9th/src/main/java/com/example/umc9th/domain/member/dto/MemberResDTO.java new file mode 100644 index 0000000..9bc5c5e --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/dto/MemberResDTO.java @@ -0,0 +1,14 @@ +package com.example.umc9th.domain.member.dto; + +import lombok.Builder; + +import java.time.LocalDateTime; + +public class MemberResDTO { + + @Builder + public record JoinDTO( + Long memberId, + LocalDateTime createAt + ){} +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/entity/Interest.java b/umc9th/src/main/java/com/example/umc9th/domain/member/entity/Interest.java index 2701268..4a83acd 100644 --- a/umc9th/src/main/java/com/example/umc9th/domain/member/entity/Interest.java +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/entity/Interest.java @@ -22,9 +22,9 @@ public class Interest { @Enumerated(EnumType.STRING) private FoodName name; - //연관 관계 - @OneToMany(mappedBy = "interest") - private List memberInterests = new ArrayList<>(); +// //연관 관계 +// @OneToMany(mappedBy = "interest") +// private List memberInterests = new ArrayList<>(); } diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/entity/Member.java b/umc9th/src/main/java/com/example/umc9th/domain/member/entity/Member.java index e6387f3..c3dd01f 100644 --- a/umc9th/src/main/java/com/example/umc9th/domain/member/entity/Member.java +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/entity/Member.java @@ -2,6 +2,7 @@ import com.example.umc9th.domain.member.enums.Gender; import com.example.umc9th.global.entity.BaseEntity; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.*; import java.time.LocalDate; @@ -47,11 +48,13 @@ public class Member extends BaseEntity { @Column(name = "status") private boolean status; - //연관 관계 - @OneToMany(mappedBy = "member") - private List memberInterests = new ArrayList<>(); +// //연관 관계 +// @OneToMany(mappedBy = "member") +// @JsonIgnore +// private List memberInterests = new ArrayList<>(); - @OneToMany(mappedBy = "member") - private List memberTerms = new ArrayList<>(); +// @OneToMany(mappedBy = "member") +// @JsonIgnore +// private List memberTerms = new ArrayList<>(); } diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/enums/FoodName.java b/umc9th/src/main/java/com/example/umc9th/domain/member/enums/FoodName.java index 701fc3d..a73d87e 100644 --- a/umc9th/src/main/java/com/example/umc9th/domain/member/enums/FoodName.java +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/enums/FoodName.java @@ -1,5 +1,5 @@ package com.example.umc9th.domain.member.enums; public enum FoodName { - A, B, C, D, E, F; + KOREAN, JAPAN, CHINA; } diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/exception/InterestException.java b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/InterestException.java new file mode 100644 index 0000000..d9e83c2 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/InterestException.java @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.member.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class InterestException extends GeneralException { + public InterestException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/exception/MemberException.java b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/MemberException.java new file mode 100644 index 0000000..43f63f3 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/MemberException.java @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.member.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class MemberException extends GeneralException { + public MemberException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/InterestErrorCode.java b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/InterestErrorCode.java new file mode 100644 index 0000000..e3bd901 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/InterestErrorCode.java @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.member.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum InterestErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "INTERSET404_1", + "해당 관심음식을 찾지 못했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/InterestSuccessCode.java b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/InterestSuccessCode.java new file mode 100644 index 0000000..c905377 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/InterestSuccessCode.java @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.member.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum InterestSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.OK, + "INTEREST200_1", + "성공적으로 관심음식를 조회했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/MemberErrorCode.java b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/MemberErrorCode.java new file mode 100644 index 0000000..64645ae --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/MemberErrorCode.java @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.member.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MemberErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "MEMBER404_1", + "해당 사용자를 찾지 못했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/MemberSuccessCode.java b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/MemberSuccessCode.java new file mode 100644 index 0000000..5b1e0e0 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/exception/code/MemberSuccessCode.java @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.member.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MemberSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.OK, + "MEMBER200_1", + "성공적으로 사용자를 조회했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/repository/InterestRepository.java b/umc9th/src/main/java/com/example/umc9th/domain/member/repository/InterestRepository.java new file mode 100644 index 0000000..57bc460 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/repository/InterestRepository.java @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.member.repository; + +import com.example.umc9th.domain.member.entity.Interest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface InterestRepository extends JpaRepository { +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/repository/MemberInterestRepository.java b/umc9th/src/main/java/com/example/umc9th/domain/member/repository/MemberInterestRepository.java new file mode 100644 index 0000000..9b2bddd --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/repository/MemberInterestRepository.java @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.member.repository; + +import com.example.umc9th.domain.member.entity.MemberInterest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberInterestRepository extends JpaRepository { +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/repository/MemberRepository.java b/umc9th/src/main/java/com/example/umc9th/domain/member/repository/MemberRepository.java new file mode 100644 index 0000000..a9a3f27 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/repository/MemberRepository.java @@ -0,0 +1,9 @@ +package com.example.umc9th.domain.member.repository; + +import com.example.umc9th.domain.member.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + Optional findByIdAndStatusIsTrue(Long memberId); +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/service/command/MemberCommandService.java b/umc9th/src/main/java/com/example/umc9th/domain/member/service/command/MemberCommandService.java new file mode 100644 index 0000000..03ec6f2 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/service/command/MemberCommandService.java @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.member.service.command; + +import com.example.umc9th.domain.member.dto.MemberReqDTO; +import com.example.umc9th.domain.member.dto.MemberResDTO; + +public interface MemberCommandService { + public MemberResDTO.JoinDTO signup( + MemberReqDTO.JoinDTO dto + ); +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/service/command/MemberCommandServiceImpl.java b/umc9th/src/main/java/com/example/umc9th/domain/member/service/command/MemberCommandServiceImpl.java new file mode 100644 index 0000000..3793a11 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/service/command/MemberCommandServiceImpl.java @@ -0,0 +1,70 @@ +package com.example.umc9th.domain.member.service.command; + +import com.example.umc9th.domain.member.converter.MemberConverter; +import com.example.umc9th.domain.member.dto.MemberReqDTO; +import com.example.umc9th.domain.member.dto.MemberResDTO; +import com.example.umc9th.domain.member.entity.Interest; +import com.example.umc9th.domain.member.entity.Member; +import com.example.umc9th.domain.member.entity.MemberInterest; +import com.example.umc9th.domain.member.exception.InterestException; +import com.example.umc9th.domain.member.exception.code.InterestErrorCode; +import com.example.umc9th.domain.member.repository.InterestRepository; +import com.example.umc9th.domain.member.repository.MemberInterestRepository; +import com.example.umc9th.domain.member.repository.MemberRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class MemberCommandServiceImpl implements MemberCommandService{ + + private final MemberRepository memberRepository; + private final MemberInterestRepository memberInterestRepository; + private final InterestRepository interestRepository; + + // 회원가입 + @Override + @Transactional + public MemberResDTO.JoinDTO signup( + MemberReqDTO.JoinDTO dto + ){ + //사용자 생성 + Member member = MemberConverter.toMember(dto); + //DB 적용 + memberRepository.save(member); + + //선호 음식 여부 존재 확인 + if(dto.preferCategory().size() > 0){ + List memberInterestList = new ArrayList<>(); + + // 선호 음식 ID별 조회 + for (Long id : dto.preferCategory()){ + + // 음식 존재 여부 검증 + Interest interest = interestRepository.findById(id) + .orElseThrow(() -> new InterestException(InterestErrorCode.NOT_FOUND)); + + // MemberFood 엔티티 생성 (컨버터 사용해야 함) + MemberInterest memberFood = MemberInterest.builder() + .member(member) + .interest(interest) + .build(); + + // 사용자 - 음식 (선호 음식) 추가 + memberInterestList.add(memberFood); + } + + // 모든 관심 음식 추가: DB 적용 + memberInterestRepository.saveAll(memberInterestList); + + + } + // 응답 DTO 생성 + return MemberConverter.toJoinDTO(member); + + } +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/service/query/MemberQueryService.java b/umc9th/src/main/java/com/example/umc9th/domain/member/service/query/MemberQueryService.java new file mode 100644 index 0000000..286df25 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/service/query/MemberQueryService.java @@ -0,0 +1,4 @@ +package com.example.umc9th.domain.member.service.query; + +public class MemberQueryService { +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/member/service/query/MemberQueryServiceImpl.java b/umc9th/src/main/java/com/example/umc9th/domain/member/service/query/MemberQueryServiceImpl.java new file mode 100644 index 0000000..7ce41c8 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/member/service/query/MemberQueryServiceImpl.java @@ -0,0 +1,4 @@ +package com.example.umc9th.domain.member.service.query; + +public class MemberQueryServiceImpl { +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java new file mode 100644 index 0000000..c1961bd --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java @@ -0,0 +1,50 @@ +package com.example.umc9th.domain.mission.controller; + +import com.example.umc9th.domain.mission.dto.MissionReqDTO; +import com.example.umc9th.domain.mission.dto.MissionResDTO; +import com.example.umc9th.domain.mission.exception.code.MissionSuccessCode; +import com.example.umc9th.domain.mission.service.MissionService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/missions") +public class MissionController { + + private final MissionService missionService; + + // 미션 도전하기 + @PostMapping("/challenge") + public ApiResponse challengeMission( + @RequestBody MissionReqDTO.ChallengeDTO dto) { + + MissionResDTO.ChallengeDTO result = + missionService.challengeMission(dto.memberId(), dto.missionId(), dto.deadline()); + + return ApiResponse.onSuccess(MissionSuccessCode.CREATE, result); + } + + // 내 미션 목록 조회 (진행중 + 성공) + @GetMapping("/my/{memberId}") + public ApiResponse> getMyMissions(@PathVariable Long memberId) { + return ApiResponse.onSuccess( + MissionSuccessCode.OK, + missionService.getMyMissions(memberId) + ); + } + + // 미션 성공 처리 + @PostMapping("/success") + public ApiResponse successMission( + @RequestBody MissionReqDTO.SuccessDTO dto) { + + MissionResDTO.SuccessDTO result = + missionService.successMission(dto.memberId(), dto.userMissionId()); + + return ApiResponse.onSuccess(MissionSuccessCode.SUCCESS, result); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java new file mode 100644 index 0000000..cde1c51 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java @@ -0,0 +1,49 @@ +package com.example.umc9th.domain.mission.converter; + +import com.example.umc9th.domain.member.entity.Member; +import com.example.umc9th.domain.mission.dto.MissionResDTO; +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.mission.entity.UserMission; + +import java.time.LocalDate; + +public class MissionConverter { + + // UserMission 생성 (도전하기) + public static UserMission toUserMission(Member member, Mission mission, LocalDate deadline) { + return UserMission.builder() + .member(member) + .mission(mission) + .deadline(deadline) + .status(true) // 도전 시작 → 진행중 + .build(); + } + + // 도전 응답 DTO + public static MissionResDTO.ChallengeDTO toChallengeDTO(UserMission userMission) { + return MissionResDTO.ChallengeDTO.builder() + .userMissionId(userMission.getId()) + .deadline(userMission.getDeadline()) + .build(); + } + + // 내 미션 목록 DTO + public static MissionResDTO.MyMissionDTO toMyMissionDTO(UserMission um) { + return MissionResDTO.MyMissionDTO.builder() + .userMissionId(um.getId()) + .missionId(um.getMission().getId()) + .missionStatus(um.isStatus() ? "CHALLENGING" : "SUCCESS") + .point(um.getMission().getPoint()) + .storeName(um.getMission().getStore().getName()) + .deadline(um.getDeadline()) + .build(); + } + + // 미션 성공 응답 DTO + public static MissionResDTO.SuccessDTO toSuccessDTO(UserMission um) { + return MissionResDTO.SuccessDTO.builder() + .userMissionId(um.getId()) + .missionStatus("SUCCESS") + .build(); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/HomeMissionDto.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/HomeMissionDto.java new file mode 100644 index 0000000..b76afc8 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/HomeMissionDto.java @@ -0,0 +1,22 @@ +package com.example.umc9th.domain.mission.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * 홈 화면에서 지역별 미참여 미션을 조회할 때 사용되는 DTO + * MissionRepository의 JPQL DTO Projection에서 사용됨. + */ +@Getter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class HomeMissionDto { + + private String region; // 사용자가 선택한 지역명 + private Long missionId; // 미션 ID + private String storeName; // 가게 이름 + private int point; // 미션 포인트 +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/MissionReqDTO.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/MissionReqDTO.java new file mode 100644 index 0000000..bb96078 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/MissionReqDTO.java @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.mission.dto; + +import java.time.LocalDate; + +public class MissionReqDTO { + + // 미션 도전하기 요청 + public record ChallengeDTO( + Long memberId, + Long missionId, + LocalDate deadline // null이면 서비스에서 기본값(예: +7일) 설정 + ) {} + + // 미션 성공 처리 요청 + public record SuccessDTO( + Long memberId, + Long userMissionId // 도전한 user_mission PK + ) {} +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/MissionResDTO.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/MissionResDTO.java new file mode 100644 index 0000000..a73748a --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/dto/MissionResDTO.java @@ -0,0 +1,41 @@ +package com.example.umc9th.domain.mission.dto; + +import lombok.Builder; + +import java.time.LocalDate; + +public class MissionResDTO { + + // 미션 도전 성공 응답 + @Builder + public record ChallengeDTO( + Long userMissionId, + LocalDate deadline + ) {} + + // 내 미션 목록 (진행중/성공) + @Builder + public record MyMissionDTO( + Long userMissionId, + Long missionId, + String missionStatus, // "CHALLENGING" 또는 "SUCCESS" + int point, + String storeName, + LocalDate deadline + ) {} + + // 미션 단일 정보 (필요하면 사용) + @Builder + public record MissionDTO( + Long missionId, + int point, + boolean status + ) {} + + // 미션 성공 처리 응답 + @Builder + public record SuccessDTO( + Long userMissionId, + String missionStatus // 항상 "SUCCESS" + ) {} +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/entity/UserMission.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/entity/UserMission.java index d596315..e3de7fa 100644 --- a/umc9th/src/main/java/com/example/umc9th/domain/mission/entity/UserMission.java +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/entity/UserMission.java @@ -22,6 +22,8 @@ public class UserMission extends BaseEntity { @Column(name = "deadline", nullable = false) private LocalDate deadline; + // true = 진행중(CHALLENGING) + // false = 성공(SUCCESS) @Column(name = "status", nullable = false) private boolean status; @@ -33,4 +35,9 @@ public class UserMission extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "mission_id", nullable = false) private Mission mission; + + // ==== 비즈니스 메서드 ==== + public void complete() { + this.status = false; + } } diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/MissionException.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/MissionException.java new file mode 100644 index 0000000..222838a --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/MissionException.java @@ -0,0 +1,15 @@ +package com.example.umc9th.domain.mission.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; + +@Getter +public class MissionException extends RuntimeException { + + private final BaseErrorCode errorCode; + + public MissionException(BaseErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/code/MissionErrorCode.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/code/MissionErrorCode.java new file mode 100644 index 0000000..251ba56 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/code/MissionErrorCode.java @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.mission.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MissionErrorCode implements BaseErrorCode { + + // 미션 조회 오류 + NOT_FOUND(HttpStatus.NOT_FOUND, "MISSION_404", "해당 미션을 찾을 수 없습니다."), + + // 이미 도전 중 + ALREADY_CHALLENGING(HttpStatus.CONFLICT, "MISSION_409", "이미 도전 중인 미션입니다."), + + // 유저 미션 조회 오류 + USER_MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "MISSION_404_U", "해당 유저의 미션 정보를 찾을 수 없습니다."), + + // 미션 비활성화 상태 + MISSION_DISABLED(HttpStatus.BAD_REQUEST, "MISSION_400", "활성화되지 않은 미션입니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/code/MissionSuccessCode.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/code/MissionSuccessCode.java new file mode 100644 index 0000000..48bf48f --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/exception/code/MissionSuccessCode.java @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.mission.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MissionSuccessCode implements BaseSuccessCode { + + OK(HttpStatus.OK, "MISSION_200", "미션 조회 성공"), + CREATE(HttpStatus.CREATED, "MISSION_201", "미션 도전 성공"), + SUCCESS(HttpStatus.OK, "MISSION_200_S", "미션 성공 처리 완료"); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java new file mode 100644 index 0000000..2830c23 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java @@ -0,0 +1,37 @@ +package com.example.umc9th.domain.mission.repository; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.mission.dto.HomeMissionDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.*; +import org.springframework.data.repository.query.Param; + +public interface MissionRepository extends JpaRepository { + + @Query("SELECT COUNT(um) FROM UserMission um WHERE um.member.id = :memberId AND um.status = true") + long countPerformedMissions(@Param("memberId") Long memberId); + + @Query(""" + SELECT new com.example.umc9th.domain.mission.dto.HomeMissionDto( + :region, + m.id, + s.name, + m.point + ) + FROM Mission m + JOIN m.store s + WHERE s.address LIKE CONCAT(:region, '%') + AND NOT EXISTS ( + SELECT 1 FROM UserMission um + WHERE um.mission.id = m.id + AND um.member.id = :memberId + ) + ORDER BY m.id DESC + """) + Page findUnjoinedMissions( + @Param("region") String region, + @Param("memberId") Long memberId, + Pageable pageable + ); +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/repository/UserMissionRepository.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/repository/UserMissionRepository.java new file mode 100644 index 0000000..b0ec599 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/repository/UserMissionRepository.java @@ -0,0 +1,21 @@ +package com.example.umc9th.domain.mission.repository; + +import com.example.umc9th.domain.member.entity.Member; +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.mission.entity.UserMission; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface UserMissionRepository extends JpaRepository { + + List findByMemberIdAndStatusInOrderByDeadlineDesc(Long memberId, List statuses); + + boolean existsByMemberAndMission(Member member, Mission mission); + + List findByMemberId(Long memberId); + + // 미션 성공 처리 시: 해당 회원의 해당 user_mission만 조회 + Optional findByIdAndMemberId(Long id, Long memberId); +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java b/umc9th/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java new file mode 100644 index 0000000..6c7e429 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java @@ -0,0 +1,95 @@ +package com.example.umc9th.domain.mission.service; + +import com.example.umc9th.domain.member.entity.Member; +import com.example.umc9th.domain.member.exception.MemberException; +import com.example.umc9th.domain.member.exception.code.MemberErrorCode; +import com.example.umc9th.domain.member.repository.MemberRepository; +import com.example.umc9th.domain.mission.converter.MissionConverter; +import com.example.umc9th.domain.mission.dto.MissionResDTO; +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.mission.entity.UserMission; +import com.example.umc9th.domain.mission.exception.MissionException; +import com.example.umc9th.domain.mission.exception.code.MissionErrorCode; +import com.example.umc9th.domain.mission.repository.MissionRepository; +import com.example.umc9th.domain.mission.repository.UserMissionRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class MissionService { + + private final MemberRepository memberRepository; + private final MissionRepository missionRepository; + private final UserMissionRepository userMissionRepository; + + /** + * 미션 도전하기 + */ + @Transactional + public MissionResDTO.ChallengeDTO challengeMission(Long memberId, Long missionId, LocalDate deadline) { + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + Mission mission = missionRepository.findById(missionId) + .orElseThrow(() -> new MissionException(MissionErrorCode.NOT_FOUND)); + + // 이미 도전 중인지 체크 + if (userMissionRepository.existsByMemberAndMission(member, mission)) { + throw new MissionException(MissionErrorCode.ALREADY_CHALLENGING); + } + + // 마감 기한 기본값 설정 (null이면 +7일) + LocalDate finalDeadline = (deadline != null) ? deadline : LocalDate.now().plusDays(7); + + // UserMission 생성 및 저장 + UserMission userMission = MissionConverter.toUserMission(member, mission, finalDeadline); + userMissionRepository.save(userMission); + + return MissionConverter.toChallengeDTO(userMission); + } + + /** + * 내 미션 목록 조회 (진행중 + 성공) + */ + @Transactional + public List getMyMissions(Long memberId) { + + // 유저 유효성 검사 + memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + List list = userMissionRepository.findByMemberId(memberId); + + return list.stream() + .map(MissionConverter::toMyMissionDTO) + .toList(); + } + + /** + * 미션 성공 처리 + */ + @Transactional + public MissionResDTO.SuccessDTO successMission(Long memberId, Long userMissionId) { + + // 해당 회원의 user_mission인지 검증 + UserMission userMission = userMissionRepository.findByIdAndMemberId(userMissionId, memberId) + .orElseThrow(() -> new MissionException(MissionErrorCode.USER_MISSION_NOT_FOUND)); + + // 이미 성공 상태면 그냥 둠 (idempotent) + if (!userMission.isStatus()) { + // 이미 SUCCESS 상태 → 그대로 응답 + return MissionConverter.toSuccessDTO(userMission); + } + + // 진행중 → 성공 처리 + userMission.complete(); // status = false + + return MissionConverter.toSuccessDTO(userMission); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java b/umc9th/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java new file mode 100644 index 0000000..daefe51 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java @@ -0,0 +1,48 @@ +package com.example.umc9th.domain.review.controller; + +import com.example.umc9th.domain.review.dto.ReviewReqDTO; +import com.example.umc9th.domain.review.dto.ReviewResDTO; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.domain.review.exception.code.ReviewSuccessCode; +import com.example.umc9th.domain.review.service.ReviewService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.GeneralSuccessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +@RestController +@RequestMapping("/api/reviews") +@RequiredArgsConstructor +public class ReviewController { + + private final ReviewService reviewService; + + //테스트 + @GetMapping("/search") + public ApiResponse> Search(@RequestParam String query, @RequestParam String type) { //query : "안암동", type:"location" + List reviews = reviewService.searchReview(query, type); + return ApiResponse.onSuccess(GeneralSuccessCode.SUCCESS, reviews); + } + + //미션 : 내가 직접 작성한 리뷰 조회 (가게명 / 별점 필터링) + @GetMapping("/my") + public ApiResponse> My(@RequestParam(required = false) String storeName, //아무 값도 안 들어올 수 있음 + @RequestParam(required = false) Integer star) { + long memberId = 1; + List reviews = reviewService.getMyReviews(memberId, storeName, star); + return ApiResponse.onSuccess(GeneralSuccessCode.SUCCESS, reviews); + + } + + @PostMapping + public ApiResponse createReview( + @RequestBody ReviewReqDTO.CreateDTO dto + ){ + ReviewResDTO.CreateDTO result = reviewService.createReview(dto); + return ApiResponse.onSuccess(ReviewSuccessCode.CREATE, result); + } + +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java b/umc9th/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java new file mode 100644 index 0000000..8a5ec83 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.review.converter; + +import com.example.umc9th.domain.member.entity.Member; +import com.example.umc9th.domain.review.dto.ReviewReqDTO; +import com.example.umc9th.domain.review.dto.ReviewResDTO; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.domain.store.entity.Store; + +import java.time.LocalDate; + +public class ReviewConverter { + + public static Review toEntity(ReviewReqDTO.CreateDTO dto, Member member, Store store) { + return Review.builder() + .star(dto.star()) + .content(dto.content()) + .imageUrl(dto.imageUrl()) + .member(member) + .store(store) + .status(true) + .build(); + } + + public static ReviewResDTO.CreateDTO toCreateDTO(Review review) { + return ReviewResDTO.CreateDTO.builder() + .reviewId(review.getId()) + .createdAt(LocalDate.from(review.getCreatedAt())) + .build(); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/dto/ReviewReqDTO.java b/umc9th/src/main/java/com/example/umc9th/domain/review/dto/ReviewReqDTO.java new file mode 100644 index 0000000..1d34903 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/dto/ReviewReqDTO.java @@ -0,0 +1,14 @@ +package com.example.umc9th.domain.review.dto; + +import java.time.LocalDate; + +public class ReviewReqDTO { + + public record CreateDTO( + Long memberId, + Long storeId, + Float star, + String content, + String imageUrl + ) {} +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/dto/ReviewResDTO.java b/umc9th/src/main/java/com/example/umc9th/domain/review/dto/ReviewResDTO.java new file mode 100644 index 0000000..ba16496 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/dto/ReviewResDTO.java @@ -0,0 +1,14 @@ +package com.example.umc9th.domain.review.dto; + +import lombok.Builder; + +import java.time.LocalDate; + +public class ReviewResDTO { + + @Builder + public record CreateDTO( + Long reviewId, + LocalDate createdAt + ) {} +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java b/umc9th/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java new file mode 100644 index 0000000..afde6b0 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.review.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class ReviewException extends GeneralException { + public ReviewException(BaseErrorCode code) { + super(code); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java b/umc9th/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java new file mode 100644 index 0000000..bdd86b2 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.review.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ReviewErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "STORE404_1", + "해당 리뷰를 찾지 못했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; + } diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewSuccessCode.java b/umc9th/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewSuccessCode.java new file mode 100644 index 0000000..efdc9c0 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewSuccessCode.java @@ -0,0 +1,24 @@ +package com.example.umc9th.domain.review.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ReviewSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.OK, + "INTEREST200_1", + "성공적으로 리뷰를 조회했습니다."), + + CREATE(HttpStatus.CREATED, + "REVIEW200_2", + "리뷰 작성 성공") + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewQueryDsl.java b/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewQueryDsl.java new file mode 100644 index 0000000..e11daa5 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewQueryDsl.java @@ -0,0 +1,9 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.entity.Review; +import com.querydsl.core.types.Predicate; +import java.util.List; + +public interface ReviewQueryDsl { + List searchReview(Predicate predicate); //동적 쿼리를 위해 Predicate 객체 사용 +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewQueryDslImpl.java b/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewQueryDslImpl.java new file mode 100644 index 0000000..72bcdc1 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewQueryDslImpl.java @@ -0,0 +1,33 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.entity.QReview; +import com.example.umc9th.domain.review.entity.Review; +import com.querydsl.core.types.Predicate; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@RequiredArgsConstructor +public class ReviewQueryDslImpl implements ReviewQueryDsl { + + private final EntityManager em; //데이터베이스와 통신할 때 사용하는 핵심 인터페이스 + + @Override + public List searchReview(Predicate predicate) { + //JPA 세팅 + JPAQueryFactory queryFactory = new JPAQueryFactory(em); + + //Q클래스 선언 + QReview review = QReview.review; + + return queryFactory + .selectFrom(review) + .leftJoin(review.store).fetchJoin() // 리뷰와 가게를 한 번에 로드 + .leftJoin(review.member).fetchJoin() // 리뷰와 회원을 한 번에 로드 + .where(predicate) + .orderBy(review.createdAt.desc()) // 최신순 정렬 + .fetch(); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java b/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java new file mode 100644 index 0000000..b555573 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java @@ -0,0 +1,25 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.entity.Review; +import org.springframework.data.jpa.repository.*; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReviewRepository extends JpaRepository, ReviewQueryDsl { + + @Modifying + @Query( + value = "INSERT INTO review (member_id, store_id, star, content, created_at) " + + "VALUES (:memberId, :storeId, :star, :content, NOW())", + nativeQuery = true + ) + void insertReview( + @Param("memberId") Long memberId, + @Param("storeId") Long storeId, + @Param("star") int star, + @Param("content") String content + ); + + +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java b/umc9th/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java new file mode 100644 index 0000000..b7f6b2e --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java @@ -0,0 +1,98 @@ +package com.example.umc9th.domain.review.service; + +import com.example.umc9th.domain.member.entity.Member; +import com.example.umc9th.domain.member.exception.MemberException; +import com.example.umc9th.domain.member.exception.code.MemberErrorCode; +import com.example.umc9th.domain.member.repository.MemberRepository; +import com.example.umc9th.domain.review.converter.ReviewConverter; +import com.example.umc9th.domain.review.dto.ReviewReqDTO; +import com.example.umc9th.domain.review.dto.ReviewResDTO; +import com.example.umc9th.domain.review.entity.QReview; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.domain.review.repository.ReviewRepository; +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.store.exception.StoreException; +import com.example.umc9th.domain.store.exception.code.StoreErrorCode; +import com.example.umc9th.domain.store.repository.StoreRepository; +import com.querydsl.core.BooleanBuilder; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ReviewService { + + private final ReviewRepository reviewRepository; + private final MemberRepository memberRepository; + private final StoreRepository storeRepository; + + public String queryTest(String name) { + QReview review = QReview.review; + BooleanBuilder builder = new BooleanBuilder(); + + if (name != null) { + builder.and(review.member.name.eq(name)); + } + + List reviewList = reviewRepository.searchReview(builder); + return reviewList.toString(); + } + + public List searchReview(String query, String type) { + QReview review = QReview.review; + BooleanBuilder builder = new BooleanBuilder(); + + if ("location".equals(type)) { + builder.and(review.store.address.contains(query)); + } else if ("star".equals(type)) { + builder.and(review.star.goe(Integer.parseInt(query))); + } else if ("both".equals(type)) { + String[] parts = query.split("&"); + builder.and(review.store.address.contains(parts[0])); + builder.and(review.star.goe(Integer.parseInt(parts[1]))); + } + + return reviewRepository.searchReview(builder); + } + + //미션 + public List getMyReviews(long memberId, String storeName, Integer star) { + QReview review = QReview.review; + BooleanBuilder builder = new BooleanBuilder(); + + // 로그인 사용자 조건 + builder.and(review.member.id.eq(memberId)); + + // 가게명 필터 + if (storeName != null && !storeName.isEmpty()) { + builder.and(review.store.name.containsIgnoreCase(storeName)); + } + + // 별점 필터 + if (star != null) { + builder.and(review.star.goe(star)); + } + + return reviewRepository.searchReview(builder); + } + + //리뷰 생성 + @Transactional + public ReviewResDTO.CreateDTO createReview(ReviewReqDTO.CreateDTO dto) { + + Member member = memberRepository.findById(dto.memberId()) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + Store store = storeRepository.findById(dto.storeId()) + .orElseThrow(() -> new StoreException(StoreErrorCode.NOT_FOUND)); + + Review review = ReviewConverter.toEntity(dto, member, store); + reviewRepository.save(review); + + return ReviewConverter.toCreateDTO(review); + } +} + diff --git a/umc9th/src/main/java/com/example/umc9th/domain/store/entity/Store.java b/umc9th/src/main/java/com/example/umc9th/domain/store/entity/Store.java index 4a183e2..77c5c8d 100644 --- a/umc9th/src/main/java/com/example/umc9th/domain/store/entity/Store.java +++ b/umc9th/src/main/java/com/example/umc9th/domain/store/entity/Store.java @@ -3,6 +3,7 @@ import com.example.umc9th.domain.mission.entity.Mission; import com.example.umc9th.domain.review.entity.Review; import com.example.umc9th.global.entity.BaseEntity; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.*; @@ -41,8 +42,10 @@ public class Store extends BaseEntity { // 연관 관계 @OneToMany(mappedBy = "store") + @JsonIgnore private List reviews = new ArrayList<>(); @OneToMany(mappedBy = "store") + @JsonIgnore private List missions = new ArrayList<>(); } diff --git a/umc9th/src/main/java/com/example/umc9th/domain/store/exception/StoreException.java b/umc9th/src/main/java/com/example/umc9th/domain/store/exception/StoreException.java new file mode 100644 index 0000000..a6e94bc --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/store/exception/StoreException.java @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.store.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class StoreException extends GeneralException { + public StoreException(BaseErrorCode code) { + super(code); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/store/exception/code/StoreErrorCode.java b/umc9th/src/main/java/com/example/umc9th/domain/store/exception/code/StoreErrorCode.java new file mode 100644 index 0000000..b7a6d8b --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/store/exception/code/StoreErrorCode.java @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.store.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum StoreErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "STORE404_1", + "해당 식당을 찾지 못했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; + } diff --git a/umc9th/src/main/java/com/example/umc9th/domain/store/exception/code/StoreSuccessCode.java b/umc9th/src/main/java/com/example/umc9th/domain/store/exception/code/StoreSuccessCode.java new file mode 100644 index 0000000..bcb92e2 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/store/exception/code/StoreSuccessCode.java @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.store.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum StoreSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.OK, + "INTEREST200_1", + "성공적으로 식당을 조회했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/store/repository/StoreRepository.java b/umc9th/src/main/java/com/example/umc9th/domain/store/repository/StoreRepository.java new file mode 100644 index 0000000..11861a5 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/store/repository/StoreRepository.java @@ -0,0 +1,9 @@ +package com.example.umc9th.domain.store.repository; + +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.domain.store.entity.Store; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +public interface StoreRepository extends JpaRepository { +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java b/umc9th/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java new file mode 100644 index 0000000..af81183 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java @@ -0,0 +1,24 @@ +package com.example.umc9th.domain.test.converter; + +import com.example.umc9th.domain.test.dto.res.TestResDTO; + +public class TestConverter { + + // 객체 -> DTO + public static TestResDTO.Testing toTestingDTO( + String testing + ) { + return TestResDTO.Testing.builder() + .testString(testing) + .build(); + } + + // 객체 -> DTO + public static TestResDTO.Exception toExceptionDTO( + String testing + ){ + return TestResDTO.Exception.builder() + .testString(testing) + .build(); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java b/umc9th/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java new file mode 100644 index 0000000..9f13f52 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.test.dto.res; + +import lombok.Builder; +import lombok.Getter; + +public class TestResDTO { + + @Builder + @Getter + public static class Testing { + private String testString; + } + + @Builder + @Getter + public static class Exception { + private String testString; + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/domain/test/exception/TestException.java b/umc9th/src/main/java/com/example/umc9th/domain/test/exception/TestException.java new file mode 100644 index 0000000..3507b6c --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/test/exception/TestException.java @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.test.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class TestException extends GeneralException { + public TestException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java b/umc9th/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java new file mode 100644 index 0000000..7264f21 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.test.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum TestErrorCode implements BaseErrorCode { + + // For test + TEST_EXCEPTION(HttpStatus.BAD_REQUEST, "TEST400_1", "이거는 테스트"), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/global/annotation/ExistFoods.java b/umc9th/src/main/java/com/example/umc9th/global/annotation/ExistFoods.java new file mode 100644 index 0000000..01c1172 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/annotation/ExistFoods.java @@ -0,0 +1,26 @@ +package com.example.umc9th.global.annotation; + +import com.example.umc9th.global.validator.FoodExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +// +@Documented +@Constraint(validatedBy = FoodExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistFoods { + //여기서 디폴트 메시지를 설정합니다. + String message() default "해당 음식이 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java new file mode 100644 index 0000000..809ea7c --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java @@ -0,0 +1,36 @@ +package com.example.umc9th.global.apiPayload; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + + @JsonProperty("code") + private final String code; + + @JsonProperty("message") + private final String message; + + @JsonProperty("result") + private T result; + + // 성공한 경우 (result 포함) + public static ApiResponse onSuccess(BaseSuccessCode code, T result) { + return new ApiResponse<>(true, code.getCode(), code.getMessage(), result); + } + + // 실패한 경우 (result 포함) + public static ApiResponse onFailure(BaseErrorCode code, T result) { + return new ApiResponse<>(false, code.getCode(), code.getMessage(), result); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java new file mode 100644 index 0000000..3830b84 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,10 @@ +package com.example.umc9th.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseErrorCode { + + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java new file mode 100644 index 0000000..2924540 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java @@ -0,0 +1,10 @@ +package com.example.umc9th.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseSuccessCode { + + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java new file mode 100644 index 0000000..3410535 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java @@ -0,0 +1,35 @@ +package com.example.umc9th.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralErrorCode implements BaseErrorCode{ + + BAD_REQUEST(HttpStatus.BAD_REQUEST, + "COMMON400_1", + "잘못된 요청입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, + "AUTH401_1", + "인증이 필요합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, + "AUTH403_1", + "요청이 거부되었습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, + "COMMON404_1", + "요청한 리소스를 찾을 수 없습니다."), + + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, + "COMMON500_1", + "예기치 않은 서버 에러가 발생했습니다."), + + VALID_FAIL(HttpStatus.BAD_REQUEST, + "COMMON400_2", + "요청 값이 유효하지 않습니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java new file mode 100644 index 0000000..aea35aa --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java @@ -0,0 +1,17 @@ +package com.example.umc9th.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralSuccessCode implements BaseSuccessCode { + + SUCCESS(HttpStatus.OK, "COMMON200", "요청이 성공적으로 처리되었습니다."), + REVIEW_LIST_SUCCESS(HttpStatus.OK, "REVIEW200", "내가 작성한 리뷰 조회"), + ; + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java new file mode 100644 index 0000000..420007c --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java @@ -0,0 +1,13 @@ +package com.example.umc9th.global.apiPayload.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException { + + private final BaseErrorCode errorCode; + +} diff --git a/umc9th/src/main/java/com/example/umc9th/global/apiPayload/exception/GlobalExceptionHandler.java b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..fcf2a44 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/exception/GlobalExceptionHandler.java @@ -0,0 +1,68 @@ +package com.example.umc9th.global.apiPayload.exception; + +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.code.GeneralErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 비즈니스 로직에서 발생시키는 Custom Exception 처리 + */ + @ExceptionHandler(GeneralException.class) + public ResponseEntity> handleGeneralException(GeneralException e) { + + BaseErrorCode errorCode = e.getErrorCode(); + + log.warn("[GeneralException] {} - {}", errorCode.getCode(), errorCode.getMessage()); + + return ResponseEntity + .status(errorCode.getStatus()) + .body(ApiResponse.onFailure(errorCode, null)); + } + + /** + * 잘못된 입력 예외 처리 + */ + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgument(IllegalArgumentException e) { + + log.warn("[IllegalArgumentException] {}", e.getMessage()); + + return ResponseEntity + .status(GeneralErrorCode.BAD_REQUEST.getStatus()) + .body(ApiResponse.onFailure(GeneralErrorCode.BAD_REQUEST, null)); + } + + /** + * 인증 실패 예외 처리 (예: JWT 검증 실패) + */ + @ExceptionHandler(SecurityException.class) + public ResponseEntity> handleSecurityException(SecurityException e) { + + log.warn("[SecurityException] {}", e.getMessage()); + + return ResponseEntity + .status(GeneralErrorCode.UNAUTHORIZED.getStatus()) + .body(ApiResponse.onFailure(GeneralErrorCode.UNAUTHORIZED, null)); + } + + /** + * 예상하지 못한 모든 서버 오류 처리 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity> handleUnexpectedException(Exception e) { + + log.error("[UnexpectedException]", e); + + return ResponseEntity + .status(GeneralErrorCode.INTERNAL_SERVER_ERROR.getStatus()) + .body(ApiResponse.onFailure(GeneralErrorCode.INTERNAL_SERVER_ERROR, null)); + } +} diff --git a/umc9th/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java new file mode 100644 index 0000000..7512f95 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java @@ -0,0 +1,65 @@ +package com.example.umc9th.global.apiPayload.handler; + +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.code.GeneralErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class GeneralExceptionAdvice { + + // 애플리케이션에서 발생하는 커스텀 예외를 처리 + @ExceptionHandler(GeneralException.class) + public ResponseEntity> handleException( + GeneralException ex + ) { + + return ResponseEntity.status(ex.getErrorCode().getStatus()) + .body(ApiResponse.onFailure( + ex.getErrorCode(), + null + ) + ); + } + + // 그 외의 정의되지 않은 모든 예외 처리 + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException( + Exception ex + ) { + + BaseErrorCode code = GeneralErrorCode.INTERNAL_SERVER_ERROR; + return ResponseEntity.status(code.getStatus()) + .body(ApiResponse.onFailure( + code, + ex.getMessage() + ) + ); + } + + // 컨트롤러 메서드에서 @Valid 어노테이션을 사용하여 DTO의 유효성 검사를 수행 + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ResponseEntity>> handleMethodArgumentNotValidException( + MethodArgumentNotValidException ex + ) { + // 검사에 실패한 필드와 그에 대한 메시지를 저장하는 Map + Map errors = new HashMap<>(); + ex.getBindingResult().getFieldErrors().forEach(error -> + errors.put(error.getField(), error.getDefaultMessage()) + ); + + GeneralErrorCode code = GeneralErrorCode.VALID_FAIL; + ApiResponse> errorResponse = ApiResponse.onFailure(code, errors); + + // 에러 코드, 메시지와 함께 errors를 반환 + return ResponseEntity.status(code.getStatus()).body(errorResponse); + } + +} diff --git a/umc9th/src/main/java/com/example/umc9th/global/config/SwaggerConfig.java b/umc9th/src/main/java/com/example/umc9th/global/config/SwaggerConfig.java new file mode 100644 index 0000000..e3e85a2 --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/config/SwaggerConfig.java @@ -0,0 +1,37 @@ +package com.example.umc9th.global.config; + + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI swagger() { + Info info = new Info().title("UMC9th").description("Spring Boot").version("0.0.1"); + + // JWT 토큰 헤더 방식 + String securityScheme = "JWT TOKEN"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityScheme); + + Components components = new Components() + .addSecuritySchemes(securityScheme, new SecurityScheme() + .name(securityScheme) + .type(SecurityScheme.Type.HTTP) + .scheme("Bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .info(info) + .addServersItem(new Server().url("/")) + .addSecurityItem(securityRequirement) + .components(components); + } +} \ No newline at end of file diff --git a/umc9th/src/main/java/com/example/umc9th/global/validator/FoodExistValidator.java b/umc9th/src/main/java/com/example/umc9th/global/validator/FoodExistValidator.java new file mode 100644 index 0000000..57324cf --- /dev/null +++ b/umc9th/src/main/java/com/example/umc9th/global/validator/FoodExistValidator.java @@ -0,0 +1,33 @@ +package com.example.umc9th.global.validator; + +import com.example.umc9th.domain.member.exception.code.InterestErrorCode; +import com.example.umc9th.domain.member.repository.InterestRepository; +import com.example.umc9th.global.annotation.ExistFoods; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class FoodExistValidator implements ConstraintValidator> { + + private final InterestRepository interestRepository; + + @Override + public boolean isValid(List values, ConstraintValidatorContext context) { + boolean isValid = values.stream() + .allMatch(value -> interestRepository.existsById(value)); + + if (!isValid) { + // 이 부분에서 아까 디폴트 메시지를 초기화 시키고, 새로운 메시지로 덮어씌우게 됩니다. + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(InterestErrorCode.NOT_FOUND.getMessage()).addConstraintViolation(); + } + + return isValid; + + } +} \ No newline at end of file diff --git a/umc9th/src/main/resources/application.yml b/umc9th/src/main/resources/application.yml index d820e86..87375cb 100644 --- a/umc9th/src/main/resources/application.yml +++ b/umc9th/src/main/resources/application.yml @@ -13,7 +13,8 @@ spring: database-platform: org.hibernate.dialect.MySQLDialect # Hibernate에서 사용할 MySQL 방언(dialect) 설정 show-sql: true # 실행된 SQL 쿼리를 콘솔에 출력할지 여부 설정 hibernate: - ddl-auto: create # 애플리케이션 실행 시 데이터베이스 스키마의 상태를 설정 + ddl-auto: update # 애플리케이션 실행 시 데이터베이스 스키마의 상태를 설정 properties: hibernate: - format_sql: true # 출력되는 SQL 쿼리를 보기 좋게 포맷팅 \ No newline at end of file + format_sql: true # 출력되는 SQL 쿼리를 보기 좋게 포맷팅 + diff --git a/umc9th/src/test/java/com/example/umc9th/domain/review/ReviewRepositoryTest.java b/umc9th/src/test/java/com/example/umc9th/domain/review/ReviewRepositoryTest.java new file mode 100644 index 0000000..3310b6d --- /dev/null +++ b/umc9th/src/test/java/com/example/umc9th/domain/review/ReviewRepositoryTest.java @@ -0,0 +1,40 @@ +package com.example.umc9th.domain.review; + +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.domain.review.repository.ReviewRepository; +import com.querydsl.core.BooleanBuilder; +import com.example.umc9th.domain.review.entity.QReview; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@SpringBootTest +@Transactional +public class ReviewRepositoryTest { + + @Autowired + private ReviewRepository reviewRepository; + + @Test + void testSearchReview_ByStoreName() { + // given + QReview review = QReview.review; + BooleanBuilder builder = new BooleanBuilder(); + + builder.and(review.store.name.containsIgnoreCase("반이학생마라탕마라반")); + + // when + List results = reviewRepository.searchReview(builder); + + // then + System.out.println("✅ 검색 결과 개수: " + results.size()); + for (Review r : results) { + System.out.println("가게명: " + r.getStore().getName() + + ", 별점: " + r.getStar() + + ", 작성자: " + r.getMember().getName()); + } + } +} diff --git a/week0/ERD.vuerd.json b/week0/ERD.vuerd.json deleted file mode 100644 index 9b9623c..0000000 --- a/week0/ERD.vuerd.json +++ /dev/null @@ -1,2342 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/dineug/erd-editor/main/json-schema/schema.json", - "version": "3.0.0", - "settings": { - "width": 2000, - "height": 2000, - "scrollTop": -263.7869, - "scrollLeft": -267.5756, - "zoomLevel": 0.52, - "show": 431, - "database": 4, - "databaseName": "", - "canvasType": "ERD", - "language": 1, - "tableNameCase": 4, - "columnNameCase": 2, - "bracketType": 1, - "relationshipDataTypeSync": true, - "relationshipOptimization": false, - "columnOrder": [ - 1, - 2, - 4, - 8, - 16, - 32, - 64 - ], - "maxWidthComment": -1, - "ignoreSaveSettings": 0 - }, - "doc": { - "tableIds": [ - "2y1bvJOIG1e0BeEWgxI08", - "rtTGV1n8fXrv3EOos4XcJ", - "C70HRfoZAc7T12El2mwAl", - "NA3wwYX9_yPbc5rR8NQP3", - "ETcc-k8p-2Xj7Avc_QuIL", - "YsXMe4aWI4vSU7M4Pdeny", - "ORir_zY3zWs8fj9eKIhij", - "EPYnTEbnzHcSM-TV7Yogy" - ], - "relationshipIds": [ - "fr8aQL5wJeCHd6nA3FkpV", - "vUfGlcmJ3e4oTKMnvlBzJ", - "SsosDuomeYItbtek60tL8", - "Dbft5I8MTyNYVBBE8bJi3", - "27fFwlvcry7s3zwMedMPL", - "mokzjvYQM38mTea-1naJe", - "leblF8kHmiRMhcnCVAceD", - "onHzSgg1u3-Nk4tYVf7t0", - "zdTWn5Wpu4-atC3FVpk1E" - ], - "indexIds": [], - "memoIds": [] - }, - "collections": { - "tableEntities": { - "2y1bvJOIG1e0BeEWgxI08": { - "id": "2y1bvJOIG1e0BeEWgxI08", - "name": "user", - "comment": "사용자", - "columnIds": [ - "vDFt6LsnU_OsODOI3QFtU", - "BfvVdK0SF5_S-F3PvFVzl", - "KpiXQMwL5ze-vJKrQlQ1V", - "CPEv0NYYbfZ99wRzuH59U", - "ONrE7jtU9NhI2LDhx3Eu5", - "oU52cpVBEP62Bd-YEUSBd", - "ZRG-tqrVHD7qvwbIsCxij", - "LDPp74sjuq_edOeSxvkCj", - "-gL9Tv6k4tzEhExARRMVF", - "rAo8blJvJWiSMdcXxPEQm", - "AdD_6uo7QsyR4vZ5mWieR", - "sbMtd5RQq7uaV0Cru8P_v" - ], - "seqColumnIds": [ - "WkqAgrjlXhLG2sEbIA5US", - "vDFt6LsnU_OsODOI3QFtU", - "BfvVdK0SF5_S-F3PvFVzl", - "KpiXQMwL5ze-vJKrQlQ1V", - "CPEv0NYYbfZ99wRzuH59U", - "ONrE7jtU9NhI2LDhx3Eu5", - "oU52cpVBEP62Bd-YEUSBd", - "ZRG-tqrVHD7qvwbIsCxij", - "LDPp74sjuq_edOeSxvkCj", - "-gL9Tv6k4tzEhExARRMVF", - "rAo8blJvJWiSMdcXxPEQm", - "AdD_6uo7QsyR4vZ5mWieR", - "sbMtd5RQq7uaV0Cru8P_v" - ], - "ui": { - "x": 212.9919, - "y": 486.4899, - "zIndex": 2, - "widthName": 60, - "widthComment": 60, - "color": "" - }, - "meta": { - "updateAt": 1758003899942, - "createAt": 1758002169271 - } - }, - "rtTGV1n8fXrv3EOos4XcJ": { - "id": "rtTGV1n8fXrv3EOos4XcJ", - "name": "interest", - "comment": "선호조사", - "columnIds": [ - "aNu4U_tr1NuWc674pipto", - "K_vOBQsbyMWekqQ_MEI4N" - ], - "seqColumnIds": [ - "aNu4U_tr1NuWc674pipto", - "K_vOBQsbyMWekqQ_MEI4N" - ], - "ui": { - "x": 1246.7656, - "y": 203.1501, - "zIndex": 10, - "widthName": 60, - "widthComment": 60, - "color": "" - }, - "meta": { - "updateAt": 1758005023914, - "createAt": 1758002208352 - } - }, - "C70HRfoZAc7T12El2mwAl": { - "id": "C70HRfoZAc7T12El2mwAl", - "name": "user_interest", - "comment": "유저_선호도", - "columnIds": [ - "ZjOGl86CJEvJ0RrhbbwU6", - "mO1A7j7Qok_C85BipXICi", - "TOi3d5nz-qA4ggbf5SGaA" - ], - "seqColumnIds": [ - "DTmy4dcbiGAdTi4A-XWI2", - "YMASBpKizoBjilyztmjJw", - "mHW9ecFk_GQMdjVjAyLpl", - "TuJoJOEFzpUIH-vMsLceS", - "ZjOGl86CJEvJ0RrhbbwU6", - "6yNKlUgOR6xxUcTHjIBha", - "ZOfk9HJZaFLK5-NEKO8vl", - "3Uwxxt6EGW-qJqorWfAJw", - "ZbsPFDxV-m07EPay0qcI2", - "lRREo8Fk9Tkbs54oI_Cdg", - "VThr2Y2U3_ZJAmivzYUhu", - "mO1A7j7Qok_C85BipXICi", - "TOi3d5nz-qA4ggbf5SGaA" - ], - "ui": { - "x": 689.4692, - "y": 190.8081, - "zIndex": 11, - "widthName": 69, - "widthComment": 67, - "color": "" - }, - "meta": { - "updateAt": 1758005001026, - "createAt": 1758002209969 - } - }, - "NA3wwYX9_yPbc5rR8NQP3": { - "id": "NA3wwYX9_yPbc5rR8NQP3", - "name": "store", - "comment": "가게", - "columnIds": [ - "F_CCSEveysTRYLn8lx8gE", - "GnAxf5fMaPNYtyzoiYMA-", - "chq0SKqnzdsl00Sjku66M", - "CXpU5qG_eglJfOISVq81C", - "4Zf3gYPB2YGTzKHUoubEC", - "WAkzAlU4FeofJm5HCmtbA", - "Z8tbMfid3UrIRtl_5g6Gf", - "zBPannWM-vuszorqrrPRy", - "Uyl-wmz-YUPjS_-t71ciN" - ], - "seqColumnIds": [ - "F_CCSEveysTRYLn8lx8gE", - "V8nBlqc3TJAqAXnaYYz1C", - "GnAxf5fMaPNYtyzoiYMA-", - "chq0SKqnzdsl00Sjku66M", - "CXpU5qG_eglJfOISVq81C", - "4Zf3gYPB2YGTzKHUoubEC", - "WAkzAlU4FeofJm5HCmtbA", - "Z8tbMfid3UrIRtl_5g6Gf", - "zBPannWM-vuszorqrrPRy", - "Uyl-wmz-YUPjS_-t71ciN", - "uM2ODWrSWgyAfDgK5Acu0" - ], - "ui": { - "x": 1246.1474, - "y": 487.642, - "zIndex": 12, - "widthName": 60, - "widthComment": 60, - "color": "" - }, - "meta": { - "updateAt": 1758005005400, - "createAt": 1758002211392 - } - }, - "ETcc-k8p-2Xj7Avc_QuIL": { - "id": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "review", - "comment": "리뷰", - "columnIds": [ - "PMYbggYiPE_sMz3U_2LqK", - "2JRKA_JcUA-Bcyt770hvg", - "YxWKD6oE6Vx5FQS9ihF1j", - "TADzLT5Iy1-DnwdSdWGW5", - "8riY_zkqWhXWBs1nJq-ov", - "RZ6HLhbxxbfiqm-zqJylb", - "iLB48NnJniXTBCzu9DLXn", - "cbAXCG_peFxJDjmzh-Q-8", - "z_xv-ZhRyFXARZdrqLhG8" - ], - "seqColumnIds": [ - "PMYbggYiPE_sMz3U_2LqK", - "2JRKA_JcUA-Bcyt770hvg", - "6QIpc602j9EoJGfHiqBDe", - "6HKr1XtX6o2r29AAttbG0", - "YxWKD6oE6Vx5FQS9ihF1j", - "TADzLT5Iy1-DnwdSdWGW5", - "8riY_zkqWhXWBs1nJq-ov", - "RZ6HLhbxxbfiqm-zqJylb", - "iLB48NnJniXTBCzu9DLXn", - "cbAXCG_peFxJDjmzh-Q-8", - "z_xv-ZhRyFXARZdrqLhG8", - "6KXjwhh7zBv5U9ju-mYIk" - ], - "ui": { - "x": 743.7147, - "y": 492.0608, - "zIndex": 13, - "widthName": 60, - "widthComment": 60, - "color": "" - }, - "meta": { - "updateAt": 1758005004123, - "createAt": 1758002211983 - } - }, - "YsXMe4aWI4vSU7M4Pdeny": { - "id": "YsXMe4aWI4vSU7M4Pdeny", - "name": "user_misson", - "comment": "유저+미션 정보", - "columnIds": [ - "w4-iEtBZVv4oZMo29nEVr", - "qyv68yagM3C9_IGyT2VLt", - "DpzdVYsCL9lkCfts-ehwz", - "P5y2uZFuvYjeM7xY-aY56", - "p5gVywEZV25DrR5jz4fkH", - "S2qqHB5cNDTVqitPQMpQ1" - ], - "seqColumnIds": [ - "w4-iEtBZVv4oZMo29nEVr", - "VEnb5JFzd_h3CPZ0KYP49", - "JtA1FPZRI3bkk9CFQzhXP", - "qyv68yagM3C9_IGyT2VLt", - "DpzdVYsCL9lkCfts-ehwz", - "P5y2uZFuvYjeM7xY-aY56", - "p5gVywEZV25DrR5jz4fkH", - "S2qqHB5cNDTVqitPQMpQ1" - ], - "ui": { - "x": 205.9873, - "y": 937.228, - "zIndex": 14, - "widthName": 67, - "widthComment": 85, - "color": "" - }, - "meta": { - "updateAt": 1758004995283, - "createAt": 1758002212209 - } - }, - "ORir_zY3zWs8fj9eKIhij": { - "id": "ORir_zY3zWs8fj9eKIhij", - "name": "mission", - "comment": "미션", - "columnIds": [ - "5rnWTeGw4icmvmMekej_n", - "kFwVTJyPh2e9OaCMgJwgf", - "8hkYv9EPb5YN4pbtA3lAP", - "xpF9-6BHmY2hAjnGZtJIs", - "xLjxhMuIUPvpQGztC2-uP", - "ucsLuC5D65W0EisT2Jycw" - ], - "seqColumnIds": [ - "5rnWTeGw4icmvmMekej_n", - "kFwVTJyPh2e9OaCMgJwgf", - "MDJpiqCun3ipr8-5x2Y1j", - "8hkYv9EPb5YN4pbtA3lAP", - "KINewFTpc2Rbnmxv9IuPN", - "xpF9-6BHmY2hAjnGZtJIs", - "xLjxhMuIUPvpQGztC2-uP", - "ucsLuC5D65W0EisT2Jycw" - ], - "ui": { - "x": 889.8041, - "y": 952.9649, - "zIndex": 15, - "widthName": 60, - "widthComment": 60, - "color": "" - }, - "meta": { - "updateAt": 1758004989746, - "createAt": 1758002213387 - } - }, - "EPYnTEbnzHcSM-TV7Yogy": { - "id": "EPYnTEbnzHcSM-TV7Yogy", - "name": "alam", - "comment": "알람", - "columnIds": [ - "4djG9DS3q5mf3YpwU2HxJ", - "nbxToDPDboiyNNJkXi3gK", - "8AHxK4llaRij7qCl2APTB", - "SRBlLiACwF7a2KELunbz9", - "-d9Y5vU42vkDQB2q9tUd0", - "btij5fWDwHCTeHvbkXJGC", - "b5UklzcMtcMi11RLRp0Tz", - "CCq1c0NyEngIoj0AErl8d" - ], - "seqColumnIds": [ - "4djG9DS3q5mf3YpwU2HxJ", - "nbxToDPDboiyNNJkXi3gK", - "8AHxK4llaRij7qCl2APTB", - "SRBlLiACwF7a2KELunbz9", - "-d9Y5vU42vkDQB2q9tUd0", - "btij5fWDwHCTeHvbkXJGC", - "b5UklzcMtcMi11RLRp0Tz", - "CCq1c0NyEngIoj0AErl8d" - ], - "ui": { - "x": 119.2208, - "y": 96.1056, - "zIndex": 539, - "widthName": 60, - "widthComment": 60, - "color": "" - }, - "meta": { - "updateAt": 1758004999298, - "createAt": 1758004663649 - } - } - }, - "tableColumnEntities": { - "vDFt6LsnU_OsODOI3QFtU": { - "id": "vDFt6LsnU_OsODOI3QFtU", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002565024, - "createAt": 1758002176062 - } - }, - "BfvVdK0SF5_S-F3PvFVzl": { - "id": "BfvVdK0SF5_S-F3PvFVzl", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "name", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002411607, - "createAt": 1758002176997 - } - }, - "KpiXQMwL5ze-vJKrQlQ1V": { - "id": "KpiXQMwL5ze-vJKrQlQ1V", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "sex", - "comment": "", - "dataType": "INT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002410593, - "createAt": 1758002177165 - } - }, - "CPEv0NYYbfZ99wRzuH59U": { - "id": "CPEv0NYYbfZ99wRzuH59U", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "birthday", - "comment": "", - "dataType": "DATE", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002434300, - "createAt": 1758002177388 - } - }, - "ONrE7jtU9NhI2LDhx3Eu5": { - "id": "ONrE7jtU9NhI2LDhx3Eu5", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "adress", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002451752, - "createAt": 1758002276724 - } - }, - "oU52cpVBEP62Bd-YEUSBd": { - "id": "oU52cpVBEP62Bd-YEUSBd", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "email", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002471108, - "createAt": 1758002328551 - } - }, - "ZRG-tqrVHD7qvwbIsCxij": { - "id": "ZRG-tqrVHD7qvwbIsCxij", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "password", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002464744, - "createAt": 1758002332815 - } - }, - "LDPp74sjuq_edOeSxvkCj": { - "id": "LDPp74sjuq_edOeSxvkCj", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "phone", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002474331, - "createAt": 1758002333325 - } - }, - "-gL9Tv6k4tzEhExARRMVF": { - "id": "-gL9Tv6k4tzEhExARRMVF", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "point", - "comment": "", - "dataType": "int", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002477308, - "createAt": 1758002348699 - } - }, - "rAo8blJvJWiSMdcXxPEQm": { - "id": "rAo8blJvJWiSMdcXxPEQm", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "createAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002483210, - "createAt": 1758002348910 - } - }, - "AdD_6uo7QsyR4vZ5mWieR": { - "id": "AdD_6uo7QsyR4vZ5mWieR", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "upateAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002487099, - "createAt": 1758002386257 - } - }, - "sbMtd5RQq7uaV0Cru8P_v": { - "id": "sbMtd5RQq7uaV0Cru8P_v", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "status", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002609142, - "createAt": 1758002386845 - } - }, - "MDJpiqCun3ipr8-5x2Y1j": { - "id": "MDJpiqCun3ipr8-5x2Y1j", - "tableId": "ORir_zY3zWs8fj9eKIhij", - "name": "store_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002553538, - "createAt": 1758002518872 - } - }, - "8hkYv9EPb5YN4pbtA3lAP": { - "id": "8hkYv9EPb5YN4pbtA3lAP", - "tableId": "ORir_zY3zWs8fj9eKIhij", - "name": "point", - "comment": "", - "dataType": "int", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002614654, - "createAt": 1758002520501 - } - }, - "xpF9-6BHmY2hAjnGZtJIs": { - "id": "xpF9-6BHmY2hAjnGZtJIs", - "tableId": "ORir_zY3zWs8fj9eKIhij", - "name": "createAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002586510, - "createAt": 1758002527502 - } - }, - "xLjxhMuIUPvpQGztC2-uP": { - "id": "xLjxhMuIUPvpQGztC2-uP", - "tableId": "ORir_zY3zWs8fj9eKIhij", - "name": "upateAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002590031, - "createAt": 1758002527844 - } - }, - "ucsLuC5D65W0EisT2Jycw": { - "id": "ucsLuC5D65W0EisT2Jycw", - "tableId": "ORir_zY3zWs8fj9eKIhij", - "name": "status", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002621934, - "createAt": 1758002528147 - } - }, - "VEnb5JFzd_h3CPZ0KYP49": { - "id": "VEnb5JFzd_h3CPZ0KYP49", - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "name": "user_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002675364, - "createAt": 1758002647784 - } - }, - "JtA1FPZRI3bkk9CFQzhXP": { - "id": "JtA1FPZRI3bkk9CFQzhXP", - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "name": "mission_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002760013, - "createAt": 1758002648236 - } - }, - "P5y2uZFuvYjeM7xY-aY56": { - "id": "P5y2uZFuvYjeM7xY-aY56", - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "name": "upateAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002802948, - "createAt": 1758002654537 - } - }, - "p5gVywEZV25DrR5jz4fkH": { - "id": "p5gVywEZV25DrR5jz4fkH", - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "name": "createAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002795917, - "createAt": 1758002656058 - } - }, - "5rnWTeGw4icmvmMekej_n": { - "id": "5rnWTeGw4icmvmMekej_n", - "tableId": "ORir_zY3zWs8fj9eKIhij", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003598435, - "createAt": 1758002688468 - } - }, - "S2qqHB5cNDTVqitPQMpQ1": { - "id": "S2qqHB5cNDTVqitPQMpQ1", - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "name": "status", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002798902, - "createAt": 1758002792542 - } - }, - "PMYbggYiPE_sMz3U_2LqK": { - "id": "PMYbggYiPE_sMz3U_2LqK", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003592824, - "createAt": 1758002822923 - } - }, - "6HKr1XtX6o2r29AAttbG0": { - "id": "6HKr1XtX6o2r29AAttbG0", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "store_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002851602, - "createAt": 1758002834332 - } - }, - "TADzLT5Iy1-DnwdSdWGW5": { - "id": "TADzLT5Iy1-DnwdSdWGW5", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "star", - "comment": "", - "dataType": "FLOAT", - "default": "0", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002870541, - "createAt": 1758002852579 - } - }, - "8riY_zkqWhXWBs1nJq-ov": { - "id": "8riY_zkqWhXWBs1nJq-ov", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "content", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002885701, - "createAt": 1758002874603 - } - }, - "RZ6HLhbxxbfiqm-zqJylb": { - "id": "RZ6HLhbxxbfiqm-zqJylb", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "images", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002922594, - "createAt": 1758002889030 - } - }, - "iLB48NnJniXTBCzu9DLXn": { - "id": "iLB48NnJniXTBCzu9DLXn", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "createAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002934078, - "createAt": 1758002923693 - } - }, - "cbAXCG_peFxJDjmzh-Q-8": { - "id": "cbAXCG_peFxJDjmzh-Q-8", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "updatedAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002941910, - "createAt": 1758002935672 - } - }, - "z_xv-ZhRyFXARZdrqLhG8": { - "id": "z_xv-ZhRyFXARZdrqLhG8", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "status", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758002953050, - "createAt": 1758002943155 - } - }, - "F_CCSEveysTRYLn8lx8gE": { - "id": "F_CCSEveysTRYLn8lx8gE", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003595545, - "createAt": 1758002973996 - } - }, - "chq0SKqnzdsl00Sjku66M": { - "id": "chq0SKqnzdsl00Sjku66M", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "name", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003009284, - "createAt": 1758003002273 - } - }, - "CXpU5qG_eglJfOISVq81C": { - "id": "CXpU5qG_eglJfOISVq81C", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "address", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003029776, - "createAt": 1758003011207 - } - }, - "4Zf3gYPB2YGTzKHUoubEC": { - "id": "4Zf3gYPB2YGTzKHUoubEC", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "boss", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003042039, - "createAt": 1758003031164 - } - }, - "WAkzAlU4FeofJm5HCmtbA": { - "id": "WAkzAlU4FeofJm5HCmtbA", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "store_number", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 75, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003058857, - "createAt": 1758003044580 - } - }, - "Z8tbMfid3UrIRtl_5g6Gf": { - "id": "Z8tbMfid3UrIRtl_5g6Gf", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "createdAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003067406, - "createAt": 1758003060401 - } - }, - "zBPannWM-vuszorqrrPRy": { - "id": "zBPannWM-vuszorqrrPRy", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "updatedAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003077323, - "createAt": 1758003068601 - } - }, - "Uyl-wmz-YUPjS_-t71ciN": { - "id": "Uyl-wmz-YUPjS_-t71ciN", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "status", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003084382, - "createAt": 1758003078444 - } - }, - "YMASBpKizoBjilyztmjJw": { - "id": "YMASBpKizoBjilyztmjJw", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "user_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003283691, - "createAt": 1758003272191 - } - }, - "TuJoJOEFzpUIH-vMsLceS": { - "id": "TuJoJOEFzpUIH-vMsLceS", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "interest_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003294806, - "createAt": 1758003284595 - } - }, - "ZjOGl86CJEvJ0RrhbbwU6": { - "id": "ZjOGl86CJEvJ0RrhbbwU6", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "", - "comment": "", - "dataType": "", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003295578, - "createAt": 1758003295578 - } - }, - "aNu4U_tr1NuWc674pipto": { - "id": "aNu4U_tr1NuWc674pipto", - "tableId": "rtTGV1n8fXrv3EOos4XcJ", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003590493, - "createAt": 1758003330679 - } - }, - "K_vOBQsbyMWekqQ_MEI4N": { - "id": "K_vOBQsbyMWekqQ_MEI4N", - "tableId": "rtTGV1n8fXrv3EOos4XcJ", - "name": "name", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003368416, - "createAt": 1758003357168 - } - }, - "KINewFTpc2Rbnmxv9IuPN": { - "id": "KINewFTpc2Rbnmxv9IuPN", - "tableId": "ORir_zY3zWs8fj9eKIhij", - "name": "deadLine", - "comment": "", - "dataType": "INT", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003461287, - "createAt": 1758003449850 - } - }, - "6QIpc602j9EoJGfHiqBDe": { - "id": "6QIpc602j9EoJGfHiqBDe", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "user_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003558685, - "createAt": 1758003542692 - } - }, - "DpzdVYsCL9lkCfts-ehwz": { - "id": "DpzdVYsCL9lkCfts-ehwz", - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "name": "deadLine", - "comment": "", - "dataType": "INT", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003574822, - "createAt": 1758003574821 - } - }, - "V8nBlqc3TJAqAXnaYYz1C": { - "id": "V8nBlqc3TJAqAXnaYYz1C", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "categori_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003691494, - "createAt": 1758003673432 - } - }, - "6yNKlUgOR6xxUcTHjIBha": { - "id": "6yNKlUgOR6xxUcTHjIBha", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "", - "comment": "", - "dataType": "", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003706143, - "createAt": 1758003706143 - } - }, - "ZOfk9HJZaFLK5-NEKO8vl": { - "id": "ZOfk9HJZaFLK5-NEKO8vl", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003750071, - "createAt": 1758003750070 - } - }, - "3Uwxxt6EGW-qJqorWfAJw": { - "id": "3Uwxxt6EGW-qJqorWfAJw", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "", - "comment": "", - "dataType": "", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003866240, - "createAt": 1758003866240 - } - }, - "WkqAgrjlXhLG2sEbIA5US": { - "id": "WkqAgrjlXhLG2sEbIA5US", - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "name": "", - "comment": "", - "dataType": "", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003867276, - "createAt": 1758003867276 - } - }, - "DTmy4dcbiGAdTi4A-XWI2": { - "id": "DTmy4dcbiGAdTi4A-XWI2", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "user_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003910732, - "createAt": 1758003899738 - } - }, - "mHW9ecFk_GQMdjVjAyLpl": { - "id": "mHW9ecFk_GQMdjVjAyLpl", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "interset_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758003982598, - "createAt": 1758003967414 - } - }, - "2JRKA_JcUA-Bcyt770hvg": { - "id": "2JRKA_JcUA-Bcyt770hvg", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "user_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004080945, - "createAt": 1758004034426 - } - }, - "uM2ODWrSWgyAfDgK5Acu0": { - "id": "uM2ODWrSWgyAfDgK5Acu0", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004056510, - "createAt": 1758004056509 - } - }, - "ZbsPFDxV-m07EPay0qcI2": { - "id": "ZbsPFDxV-m07EPay0qcI2", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004190658, - "createAt": 1758004190658 - } - }, - "lRREo8Fk9Tkbs54oI_Cdg": { - "id": "lRREo8Fk9Tkbs54oI_Cdg", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004206973, - "createAt": 1758004206973 - } - }, - "VThr2Y2U3_ZJAmivzYUhu": { - "id": "VThr2Y2U3_ZJAmivzYUhu", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004221323, - "createAt": 1758004221323 - } - }, - "6KXjwhh7zBv5U9ju-mYIk": { - "id": "6KXjwhh7zBv5U9ju-mYIk", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004261136, - "createAt": 1758004261136 - } - }, - "mO1A7j7Qok_C85BipXICi": { - "id": "mO1A7j7Qok_C85BipXICi", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "user_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004527788, - "createAt": 1758004410855 - } - }, - "TOi3d5nz-qA4ggbf5SGaA": { - "id": "TOi3d5nz-qA4ggbf5SGaA", - "tableId": "C70HRfoZAc7T12El2mwAl", - "name": "interest_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004538380, - "createAt": 1758004421840 - } - }, - "YxWKD6oE6Vx5FQS9ihF1j": { - "id": "YxWKD6oE6Vx5FQS9ihF1j", - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "name": "store_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004551595, - "createAt": 1758004441852 - } - }, - "kFwVTJyPh2e9OaCMgJwgf": { - "id": "kFwVTJyPh2e9OaCMgJwgf", - "tableId": "ORir_zY3zWs8fj9eKIhij", - "name": "sotre_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004563363, - "createAt": 1758004489596 - } - }, - "w4-iEtBZVv4oZMo29nEVr": { - "id": "w4-iEtBZVv4oZMo29nEVr", - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "name": "user_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004573431, - "createAt": 1758004509769 - } - }, - "qyv68yagM3C9_IGyT2VLt": { - "id": "qyv68yagM3C9_IGyT2VLt", - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "name": "mission_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004578883, - "createAt": 1758004517394 - } - }, - "4djG9DS3q5mf3YpwU2HxJ": { - "id": "4djG9DS3q5mf3YpwU2HxJ", - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004789027, - "createAt": 1758004685866 - } - }, - "8AHxK4llaRij7qCl2APTB": { - "id": "8AHxK4llaRij7qCl2APTB", - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "name": "content", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004760600, - "createAt": 1758004748516 - } - }, - "SRBlLiACwF7a2KELunbz9": { - "id": "SRBlLiACwF7a2KELunbz9", - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "name": "dtype", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004786975, - "createAt": 1758004761964 - } - }, - "-d9Y5vU42vkDQB2q9tUd0": { - "id": "-d9Y5vU42vkDQB2q9tUd0", - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "name": "is_confirmed", - "comment": "", - "dataType": "BOOLEAN", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 69, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004819783, - "createAt": 1758004803972 - } - }, - "btij5fWDwHCTeHvbkXJGC": { - "id": "btij5fWDwHCTeHvbkXJGC", - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "name": "createdAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004834316, - "createAt": 1758004821577 - } - }, - "b5UklzcMtcMi11RLRp0Tz": { - "id": "b5UklzcMtcMi11RLRp0Tz", - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "name": "updatedAt", - "comment": "", - "dataType": "DATETIME", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004844394, - "createAt": 1758004838567 - } - }, - "nbxToDPDboiyNNJkXi3gK": { - "id": "nbxToDPDboiyNNJkXi3gK", - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "name": "title", - "comment": "", - "dataType": "VARCHAR", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004859027, - "createAt": 1758004845381 - } - }, - "CCq1c0NyEngIoj0AErl8d": { - "id": "CCq1c0NyEngIoj0AErl8d", - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "name": "id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004886190, - "createAt": 1758004886190 - } - }, - "GnAxf5fMaPNYtyzoiYMA-": { - "id": "GnAxf5fMaPNYtyzoiYMA-", - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "name": "categori_id", - "comment": "", - "dataType": "BIGINT", - "default": "", - "options": 8, - "ui": { - "keys": 2, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1758004970506, - "createAt": 1758004959005 - } - } - }, - "relationshipEntities": { - "xK2wIzn8YvFuChzw0Dagu": { - "id": "xK2wIzn8YvFuChzw0Dagu", - "identification": false, - "relationshipType": 8, - "startRelationshipType": 2, - "start": { - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "columnIds": [ - "vDFt6LsnU_OsODOI3QFtU" - ], - "x": 409.8077, - "y": 211.3846, - "direction": 4 - }, - "end": { - "tableId": "C70HRfoZAc7T12El2mwAl", - "columnIds": [ - "ZOfk9HJZaFLK5-NEKO8vl" - ], - "x": 424.5, - "y": 172, - "direction": 8 - }, - "meta": { - "updateAt": 1758003750071, - "createAt": 1758003750071 - } - }, - "CsYdwbemu7dScsssDAGrm": { - "id": "CsYdwbemu7dScsssDAGrm", - "identification": false, - "relationshipType": 16, - "startRelationshipType": 2, - "start": { - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "columnIds": [ - "vDFt6LsnU_OsODOI3QFtU" - ], - "x": 304.2419, - "y": 486.4899, - "direction": 4 - }, - "end": { - "tableId": "C70HRfoZAc7T12El2mwAl", - "columnIds": [ - "DTmy4dcbiGAdTi4A-XWI2" - ], - "x": 305.1974, - "y": 281.6316, - "direction": 8 - }, - "meta": { - "updateAt": 1758003899738, - "createAt": 1758003899738 - } - }, - "SCM3Y82XG6UqMZaqywoaC": { - "id": "SCM3Y82XG6UqMZaqywoaC", - "identification": false, - "relationshipType": 16, - "startRelationshipType": 2, - "start": { - "tableId": "rtTGV1n8fXrv3EOos4XcJ", - "columnIds": [ - "aNu4U_tr1NuWc674pipto" - ], - "x": 821.6032, - "y": 187.4332, - "direction": 1 - }, - "end": { - "tableId": "C70HRfoZAc7T12El2mwAl", - "columnIds": [ - "mHW9ecFk_GQMdjVjAyLpl" - ], - "x": 578.9474, - "y": 193.6316, - "direction": 2 - }, - "meta": { - "updateAt": 1758003967414, - "createAt": 1758003967414 - } - }, - "fr8aQL5wJeCHd6nA3FkpV": { - "id": "fr8aQL5wJeCHd6nA3FkpV", - "identification": false, - "relationshipType": 4, - "startRelationshipType": 2, - "start": { - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "columnIds": [ - "vDFt6LsnU_OsODOI3QFtU" - ], - "x": 577.9919, - "y": 658.4899, - "direction": 2 - }, - "end": { - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "columnIds": [ - "2JRKA_JcUA-Bcyt770hvg" - ], - "x": 743.7147, - "y": 628.0608, - "direction": 1 - }, - "meta": { - "updateAt": 1758004034426, - "createAt": 1758004034426 - } - }, - "tZExJN6wzYQQN6Tca1jgN": { - "id": "tZExJN6wzYQQN6Tca1jgN", - "identification": false, - "relationshipType": 8, - "startRelationshipType": 2, - "start": { - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "columnIds": [ - "PMYbggYiPE_sMz3U_2LqK" - ], - "x": 1241.6559, - "y": 648.0608, - "direction": 2 - }, - "end": { - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "columnIds": [ - "uM2ODWrSWgyAfDgK5Acu0" - ], - "x": 1510.0089, - "y": 659.2758, - "direction": 1 - }, - "meta": { - "updateAt": 1758004056510, - "createAt": 1758004056510 - } - }, - "gp8EmzSFJVNx_IWEC5SS_": { - "id": "gp8EmzSFJVNx_IWEC5SS_", - "identification": false, - "relationshipType": 8, - "startRelationshipType": 2, - "start": { - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "columnIds": [ - "vDFt6LsnU_OsODOI3QFtU" - ], - "x": 395.4919, - "y": 486.4899, - "direction": 4 - }, - "end": { - "tableId": "C70HRfoZAc7T12El2mwAl", - "columnIds": [ - "ZbsPFDxV-m07EPay0qcI2" - ], - "x": 396.4474, - "y": 257.6316, - "direction": 8 - }, - "meta": { - "updateAt": 1758004190658, - "createAt": 1758004190658 - } - }, - "pAmwkZWnp6pSWMBQJSz0W": { - "id": "pAmwkZWnp6pSWMBQJSz0W", - "identification": false, - "relationshipType": 8, - "startRelationshipType": 2, - "start": { - "tableId": "rtTGV1n8fXrv3EOos4XcJ", - "columnIds": [ - "aNu4U_tr1NuWc674pipto" - ], - "x": 821.6032, - "y": 213.4332, - "direction": 1 - }, - "end": { - "tableId": "C70HRfoZAc7T12El2mwAl", - "columnIds": [ - "lRREo8Fk9Tkbs54oI_Cdg" - ], - "x": 578.9474, - "y": 243.6316, - "direction": 2 - }, - "meta": { - "updateAt": 1758004206973, - "createAt": 1758004206973 - } - }, - "X4sknonb_IgA-_vhYKmss": { - "id": "X4sknonb_IgA-_vhYKmss", - "identification": false, - "relationshipType": 8, - "startRelationshipType": 2, - "start": { - "tableId": "rtTGV1n8fXrv3EOos4XcJ", - "columnIds": [ - "aNu4U_tr1NuWc674pipto" - ], - "x": 842.6559, - "y": 196.6438, - "direction": 1 - }, - "end": { - "tableId": "C70HRfoZAc7T12El2mwAl", - "columnIds": [ - "VThr2Y2U3_ZJAmivzYUhu" - ], - "x": 578.9474, - "y": 181.6316, - "direction": 2 - }, - "meta": { - "updateAt": 1758004221323, - "createAt": 1758004221323 - } - }, - "fi6hg4FJxXwO9G-Q4dkuQ": { - "id": "fi6hg4FJxXwO9G-Q4dkuQ", - "identification": false, - "relationshipType": 16, - "startRelationshipType": 2, - "start": { - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "columnIds": [ - "F_CCSEveysTRYLn8lx8gE" - ], - "x": 1457.3773, - "y": 592.4335, - "direction": 1 - }, - "end": { - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "columnIds": [ - "6KXjwhh7zBv5U9ju-mYIk" - ], - "x": 1241.6559, - "y": 592.0608, - "direction": 2 - }, - "meta": { - "updateAt": 1758004261137, - "createAt": 1758004261137 - } - }, - "vUfGlcmJ3e4oTKMnvlBzJ": { - "id": "vUfGlcmJ3e4oTKMnvlBzJ", - "identification": false, - "relationshipType": 16, - "startRelationshipType": 2, - "start": { - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "columnIds": [ - "vDFt6LsnU_OsODOI3QFtU" - ], - "x": 486.7419, - "y": 486.4899, - "direction": 4 - }, - "end": { - "tableId": "C70HRfoZAc7T12El2mwAl", - "columnIds": [ - "mO1A7j7Qok_C85BipXICi" - ], - "x": 689.4692, - "y": 254.8081, - "direction": 1 - }, - "meta": { - "updateAt": 1758004410855, - "createAt": 1758004410855 - } - }, - "SsosDuomeYItbtek60tL8": { - "id": "SsosDuomeYItbtek60tL8", - "identification": false, - "relationshipType": 16, - "startRelationshipType": 2, - "start": { - "tableId": "rtTGV1n8fXrv3EOos4XcJ", - "columnIds": [ - "aNu4U_tr1NuWc674pipto" - ], - "x": 1246.7656, - "y": 255.1501, - "direction": 1 - }, - "end": { - "tableId": "C70HRfoZAc7T12El2mwAl", - "columnIds": [ - "TOi3d5nz-qA4ggbf5SGaA" - ], - "x": 1054.4692, - "y": 254.8081, - "direction": 2 - }, - "meta": { - "updateAt": 1758004421840, - "createAt": 1758004421840 - } - }, - "Dbft5I8MTyNYVBBE8bJi3": { - "id": "Dbft5I8MTyNYVBBE8bJi3", - "identification": false, - "relationshipType": 4, - "startRelationshipType": 2, - "start": { - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "columnIds": [ - "F_CCSEveysTRYLn8lx8gE" - ], - "x": 1246.1474, - "y": 623.642, - "direction": 1 - }, - "end": { - "tableId": "ETcc-k8p-2Xj7Avc_QuIL", - "columnIds": [ - "YxWKD6oE6Vx5FQS9ihF1j" - ], - "x": 1108.7147, - "y": 628.0608, - "direction": 2 - }, - "meta": { - "updateAt": 1758004441852, - "createAt": 1758004441852 - } - }, - "27fFwlvcry7s3zwMedMPL": { - "id": "27fFwlvcry7s3zwMedMPL", - "identification": false, - "relationshipType": 4, - "startRelationshipType": 2, - "start": { - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "columnIds": [ - "F_CCSEveysTRYLn8lx8gE" - ], - "x": 1436.1474, - "y": 759.642, - "direction": 8 - }, - "end": { - "tableId": "ORir_zY3zWs8fj9eKIhij", - "columnIds": [ - "kFwVTJyPh2e9OaCMgJwgf" - ], - "x": 1254.8040999999998, - "y": 1052.9649, - "direction": 2 - }, - "meta": { - "updateAt": 1758004489596, - "createAt": 1758004489596 - } - }, - "mokzjvYQM38mTea-1naJe": { - "id": "mokzjvYQM38mTea-1naJe", - "identification": false, - "relationshipType": 16, - "startRelationshipType": 2, - "start": { - "tableId": "ORir_zY3zWs8fj9eKIhij", - "columnIds": [ - "5rnWTeGw4icmvmMekej_n" - ], - "x": 889.8041, - "y": 1052.9649, - "direction": 1 - }, - "end": { - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "columnIds": [ - "w4-iEtBZVv4oZMo29nEVr" - ], - "x": 570.9873, - "y": 1037.228, - "direction": 2 - }, - "meta": { - "updateAt": 1758004509769, - "createAt": 1758004509769 - } - }, - "leblF8kHmiRMhcnCVAceD": { - "id": "leblF8kHmiRMhcnCVAceD", - "identification": false, - "relationshipType": 16, - "startRelationshipType": 2, - "start": { - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "columnIds": [ - "vDFt6LsnU_OsODOI3QFtU" - ], - "x": 395.4919, - "y": 830.4899, - "direction": 8 - }, - "end": { - "tableId": "YsXMe4aWI4vSU7M4Pdeny", - "columnIds": [ - "qyv68yagM3C9_IGyT2VLt" - ], - "x": 388.4873, - "y": 937.228, - "direction": 4 - }, - "meta": { - "updateAt": 1758004517394, - "createAt": 1758004517394 - } - }, - "onHzSgg1u3-Nk4tYVf7t0": { - "id": "onHzSgg1u3-Nk4tYVf7t0", - "identification": false, - "relationshipType": 4, - "startRelationshipType": 2, - "start": { - "tableId": "2y1bvJOIG1e0BeEWgxI08", - "columnIds": [ - "vDFt6LsnU_OsODOI3QFtU" - ], - "x": 304.2419, - "y": 486.4899, - "direction": 4 - }, - "end": { - "tableId": "EPYnTEbnzHcSM-TV7Yogy", - "columnIds": [ - "CCq1c0NyEngIoj0AErl8d" - ], - "x": 306.2208, - "y": 344.1056, - "direction": 8 - }, - "meta": { - "updateAt": 1758004886190, - "createAt": 1758004886190 - } - }, - "zdTWn5Wpu4-atC3FVpk1E": { - "id": "zdTWn5Wpu4-atC3FVpk1E", - "identification": false, - "relationshipType": 8, - "startRelationshipType": 2, - "start": { - "tableId": "rtTGV1n8fXrv3EOos4XcJ", - "columnIds": [ - "aNu4U_tr1NuWc674pipto" - ], - "x": 1429.2656, - "y": 307.1501, - "direction": 8 - }, - "end": { - "tableId": "NA3wwYX9_yPbc5rR8NQP3", - "columnIds": [ - "GnAxf5fMaPNYtyzoiYMA-" - ], - "x": 1436.1474, - "y": 487.642, - "direction": 4 - }, - "meta": { - "updateAt": 1758004959005, - "createAt": 1758004959005 - } - } - }, - "indexEntities": {}, - "indexColumnEntities": {}, - "memoEntities": {} - } -} \ No newline at end of file