From 341d147615456953ee1168dd4ec686b7c9be68ab Mon Sep 17 00:00:00 2001 From: MinJunKKang Date: Wed, 20 Aug 2025 20:50:16 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=EC=84=B1=EA=B3=B5,=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../likelion13th/canfly/global/api/ErrorCode.java | 9 +++++++++ .../likelion13th/canfly/global/api/SuccessCode.java | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/hackerthon/likelion13th/canfly/global/api/ErrorCode.java b/src/main/java/hackerthon/likelion13th/canfly/global/api/ErrorCode.java index 9d9b8ea..5d4fdf3 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/global/api/ErrorCode.java +++ b/src/main/java/hackerthon/likelion13th/canfly/global/api/ErrorCode.java @@ -23,6 +23,15 @@ public enum ErrorCode implements BaseCode { // 실패 TOKEN_NO_AUTH(HttpStatus.FORBIDDEN, "JWT_4033", "권한 정보가 없는 token입니다."), TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "JWT_4011", "token 유효기간이 만료되었습니다."), + // FIELD + FIELD_NOT_FOUND(HttpStatus.NOT_FOUND, "FIELD_4041", "일치하는 FIELD가 없습니다."), + + // Major + MAJOR_NOT_FOUND(HttpStatus.NOT_FOUND, "MAJOR_4041", "일치하는 MAJOR가 없습니다."), + + // University + UNIVERSITY_NOT_FOUND(HttpStatus.NOT_FOUND, "UNIVERSITY_4041", "일치하는 UNIVERSITY가 없습니다."), + // Mock MOCK_NOT_FOUND(HttpStatus.NOT_FOUND, "MOCK_4041", "찾으려는 모의고사가 존재하지 않습니다."); diff --git a/src/main/java/hackerthon/likelion13th/canfly/global/api/SuccessCode.java b/src/main/java/hackerthon/likelion13th/canfly/global/api/SuccessCode.java index 9b29899..618786d 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/global/api/SuccessCode.java +++ b/src/main/java/hackerthon/likelion13th/canfly/global/api/SuccessCode.java @@ -37,6 +37,11 @@ public enum SuccessCode implements BaseCode { // 성공 // 형이 추가한 코드 MAJORSYNC_BULK_SUCCESS(HttpStatus.OK, "Sync_2012", "DB 동기화가 완료되었습니다."), + FIELD_LIKE_SUCCESS(HttpStatus.OK, "FIELD_MARK_2001", "계열 북마크 성공"), + MAJOR_LIKE_SUCCESS(HttpStatus.OK, "MAJOR_MARK_2001", "전공 북마크 성공"), + MAJOR_UNIV_LIKE_SUCCESS(HttpStatus.OK, "MAJOR_UNIV_MARK_2001", "전공+대학 북마크 성공"), + FIELD_LIKED_LIST_VIEW_SUCCESS(HttpStatus.OK, "FIELD_2006", "로그인 사용자가 북마크한 모든 계열명을 반환합니다."), + FIELD_LIST_VIEW_SUCCESS(HttpStatus.OK, "FIELD_2007", "계열 전체 조회 완료."), // 내가 추가한 코드 TOKEN_PROCESS_SUCCESS(HttpStatus.OK, "TOKEN_2001", "코인(토큰) 사용이 완료되었습니다."); From 4e62b58f1dc0bf00f475bcdcd4f0644c066ef2a4 Mon Sep 17 00:00:00 2001 From: MinJunKKang Date: Wed, 20 Aug 2025 20:50:50 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=ED=95=9C=20?= =?UTF-8?q?=EA=B3=84=EC=97=B4=20=EB=B0=8F=20=EC=A0=84=EC=B2=B4=20=EA=B3=84?= =?UTF-8?q?=EC=97=B4=20=EC=A1=B0=ED=9A=8C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/controller/FieldController.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/controller/FieldController.java b/src/main/java/hackerthon/likelion13th/canfly/search/controller/FieldController.java index 397613d..97624bd 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/search/controller/FieldController.java +++ b/src/main/java/hackerthon/likelion13th/canfly/search/controller/FieldController.java @@ -1,4 +1,59 @@ package hackerthon.likelion13th.canfly.search.controller; +import hackerthon.likelion13th.canfly.domain.user.User; +import hackerthon.likelion13th.canfly.global.api.ApiResponse; +import hackerthon.likelion13th.canfly.global.api.SuccessCode; +import hackerthon.likelion13th.canfly.login.auth.mapper.CustomUserDetails; +import hackerthon.likelion13th.canfly.login.service.UserService; +import hackerthon.likelion13th.canfly.search.dto.FieldDto; +import hackerthon.likelion13th.canfly.search.repository.FieldBookmarkRepository; +import hackerthon.likelion13th.canfly.search.service.FieldService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "계열 조회", description = "계열 관련 API") +@RestController +@RequestMapping("/field") +@RequiredArgsConstructor public class FieldController { + private final UserService userService; + private final FieldService fieldService; + private final FieldBookmarkRepository fieldBookmarkRepository; + + @Operation(summary = "내가 좋아요한 계열 전체", description = "로그인 사용자가 북마크한 모든 계열을 반환합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses. + ApiResponse(responseCode = "FIELD_2006", description = "내 북마크 계열 전체 조회 완료") + }) + @GetMapping("/like") + public ApiResponse> getMyLikedFields( + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + User user = userService.findUserByProviderId(customUserDetails.getUsername()); + List fields = fieldService.getAllLikedFields(user); + + return ApiResponse.onSuccess(SuccessCode.FIELD_LIKED_LIST_VIEW_SUCCESS, fields); + } + + @Operation(summary = "계열 전체", description = "모든 계열을 반환합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses. + ApiResponse(responseCode = "FIELD_2007", description = "계열 전체 조회 완료") + }) + @GetMapping("/all") + public ApiResponse> getFields( + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + List fields = fieldService.getAllFields(); + + return ApiResponse.onSuccess(SuccessCode.FIELD_LIST_VIEW_SUCCESS, fields); + } } From 83bae9e0f7a9ccbd7a8320ff8961c4f161a93ec2 Mon Sep 17 00:00:00 2001 From: MinJunKKang Date: Wed, 20 Aug 2025 20:51:09 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=EC=A0=84=EA=B3=B5=20=EB=B0=8F=20=EC=A0=84?= =?UTF-8?q?=EA=B3=B5+=EB=8C=80=ED=95=99=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MajorMarkController.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/hackerthon/likelion13th/canfly/search/controller/MajorMarkController.java diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/controller/MajorMarkController.java b/src/main/java/hackerthon/likelion13th/canfly/search/controller/MajorMarkController.java new file mode 100644 index 0000000..bff548d --- /dev/null +++ b/src/main/java/hackerthon/likelion13th/canfly/search/controller/MajorMarkController.java @@ -0,0 +1,53 @@ +package hackerthon.likelion13th.canfly.search.controller; + +import hackerthon.likelion13th.canfly.domain.user.User; +import hackerthon.likelion13th.canfly.global.api.ApiResponse; +import hackerthon.likelion13th.canfly.global.api.SuccessCode; +import hackerthon.likelion13th.canfly.login.auth.mapper.CustomUserDetails; +import hackerthon.likelion13th.canfly.login.service.UserService; +import hackerthon.likelion13th.canfly.search.service.MajorService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "전공 및 전공-대학 북마크", description = "전공 북마크 컨트롤러입니다") +@RestController +@RequestMapping("/major/{majorId}/like") +@RequiredArgsConstructor +public class MajorMarkController { + private final UserService userService; + private final MajorService majorService; + + @Operation(summary = "전공 북마크", description = "전공에 북마크를 추가하거나 취소하는 api.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "MAJOR_MARK_2001", description = "계열 북마크 성공") + }) + @PostMapping("/toggle") + public ApiResponse toggleLike( + @PathVariable(name = "majorId") Long majorId, + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + User user = userService.findUserByProviderId(customUserDetails.getUsername()); + majorService.toggleMajorLike(majorId, user); + + return ApiResponse.onSuccess(SuccessCode.MAJOR_LIKE_SUCCESS, true); + } + + @Operation(summary = "전공+대학 북마크 토글", description = "전공/대학 조합 북마크를 토글합니다.") + @PostMapping("/univ/{univId}/toggle") + public ApiResponse toggleLikeWithUniv( + @PathVariable Long majorId, + @PathVariable Long univId, + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + User user = userService.findUserByProviderId(customUserDetails.getUsername()); + majorService.toggleMajorUnivLike(majorId, univId, user); + return ApiResponse.onSuccess(SuccessCode.MAJOR_UNIV_LIKE_SUCCESS, true); + } +} From 82cd43867a3ab7d6b33289ab78e0ed1057c0d7f1 Mon Sep 17 00:00:00 2001 From: MinJunKKang Date: Wed, 20 Aug 2025 20:52:39 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Domain=EC=97=90=EC=84=9C=20=EC=8A=A4?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=81=AC=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=84=A4=EC=A0=95=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../likelion13th/canfly/domain/field/FieldBookmark.java | 2 +- .../likelion13th/canfly/domain/major/MajorBookmark.java | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/hackerthon/likelion13th/canfly/domain/field/FieldBookmark.java b/src/main/java/hackerthon/likelion13th/canfly/domain/field/FieldBookmark.java index da94d3f..21ebf30 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/domain/field/FieldBookmark.java +++ b/src/main/java/hackerthon/likelion13th/canfly/domain/field/FieldBookmark.java @@ -8,7 +8,7 @@ import org.hibernate.annotations.OnDeleteAction; @Entity -@Table(name = "fieldBookmark") +@Table(name = "field_bookmark") @Getter @Setter @NoArgsConstructor diff --git a/src/main/java/hackerthon/likelion13th/canfly/domain/major/MajorBookmark.java b/src/main/java/hackerthon/likelion13th/canfly/domain/major/MajorBookmark.java index f2b2525..8331552 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/domain/major/MajorBookmark.java +++ b/src/main/java/hackerthon/likelion13th/canfly/domain/major/MajorBookmark.java @@ -1,7 +1,7 @@ package hackerthon.likelion13th.canfly.domain.major; import hackerthon.likelion13th.canfly.domain.entity.BaseEntity; -import hackerthon.likelion13th.canfly.domain.field.Field; +import hackerthon.likelion13th.canfly.domain.university.University; import hackerthon.likelion13th.canfly.domain.user.User; import jakarta.persistence.*; import lombok.*; @@ -9,7 +9,7 @@ import org.hibernate.annotations.OnDeleteAction; @Entity -@Table(name = "majorBookmark") +@Table(name = "major_bookmark") @Getter @Setter @NoArgsConstructor @@ -30,4 +30,9 @@ public class MajorBookmark extends BaseEntity { @JoinColumn(name = "uid", nullable = false) @OnDelete(action = OnDeleteAction.CASCADE) private User user; + + @ManyToOne(fetch = FetchType.LAZY, optional = true) + @JoinColumn(name = "univ_id", nullable = true) + @OnDelete(action = OnDeleteAction.CASCADE) + private University university; } From 6f4f1c86cf4634e199a86b94ec12bcccb4216443 Mon Sep 17 00:00:00 2001 From: MinJunKKang Date: Wed, 20 Aug 2025 20:53:19 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=EA=B2=8C=EC=97=B4=20=EB=B6=81=EB=A7=88?= =?UTF-8?q?=ED=81=AC=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FieldMarkController.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/java/hackerthon/likelion13th/canfly/search/controller/FieldMarkController.java diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/controller/FieldMarkController.java b/src/main/java/hackerthon/likelion13th/canfly/search/controller/FieldMarkController.java new file mode 100644 index 0000000..7d3470a --- /dev/null +++ b/src/main/java/hackerthon/likelion13th/canfly/search/controller/FieldMarkController.java @@ -0,0 +1,41 @@ +package hackerthon.likelion13th.canfly.search.controller; + +import hackerthon.likelion13th.canfly.domain.user.User; +import hackerthon.likelion13th.canfly.global.api.ApiResponse; +import hackerthon.likelion13th.canfly.global.api.SuccessCode; +import hackerthon.likelion13th.canfly.login.auth.mapper.CustomUserDetails; +import hackerthon.likelion13th.canfly.login.service.UserService; +import hackerthon.likelion13th.canfly.search.service.FieldService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "계열 북마크", description = "계열 북마크 컨트롤러입니다") +@RestController +@RequestMapping("/field/{fieldId}/like") +@RequiredArgsConstructor +public class FieldMarkController { + private final UserService userService; + private final FieldService fieldService; + + @Operation(summary = "계열 북마크", description = "계열에 북마크를 추가하거나 취소하는 api.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "FIELD_MARK_2001", description = "계열 북마크 성공") + }) + @PostMapping("/toggle") + public ApiResponse toggleLike( + @PathVariable(name = "fieldId") Long fieldId, + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + User user = userService.findUserByProviderId(customUserDetails.getUsername()); + fieldService.toggleFieldLike(fieldId, user); + + return ApiResponse.onSuccess(SuccessCode.FIELD_LIKE_SUCCESS, true); + } +} From 18b62cd56357916c8631073c04c7d963eeaf6cac Mon Sep 17 00:00:00 2001 From: MinJunKKang Date: Wed, 20 Aug 2025 20:53:35 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC,=20=EB=94=94=ED=8B=B0=EC=98=A4,=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canfly/search/dto/FieldDto.java | 13 ++++- .../repository/FieldBookmarkRepository.java | 30 ++++++++++- .../search/repository/FieldRepository.java | 5 +- .../repository/MajorBookmarkRepository.java | 37 ++++++++++++- .../canfly/search/service/FieldService.java | 54 +++++++++++++++++++ .../canfly/search/service/MajorService.java | 50 +++++++++++++++++ 6 files changed, 185 insertions(+), 4 deletions(-) diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/dto/FieldDto.java b/src/main/java/hackerthon/likelion13th/canfly/search/dto/FieldDto.java index ceb8452..75df024 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/search/dto/FieldDto.java +++ b/src/main/java/hackerthon/likelion13th/canfly/search/dto/FieldDto.java @@ -1,4 +1,15 @@ package hackerthon.likelion13th.canfly.search.dto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder public class FieldDto { -} + private Long id; // field_id + private String name; // field_name +} \ No newline at end of file diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/repository/FieldBookmarkRepository.java b/src/main/java/hackerthon/likelion13th/canfly/search/repository/FieldBookmarkRepository.java index a792a84..52c33c1 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/search/repository/FieldBookmarkRepository.java +++ b/src/main/java/hackerthon/likelion13th/canfly/search/repository/FieldBookmarkRepository.java @@ -1,4 +1,32 @@ package hackerthon.likelion13th.canfly.search.repository; -public interface FieldBookmarkRepository { +import hackerthon.likelion13th.canfly.domain.field.Field; +import hackerthon.likelion13th.canfly.domain.field.FieldBookmark; +import hackerthon.likelion13th.canfly.domain.user.User; +import io.lettuce.core.dynamic.annotation.Param; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface FieldBookmarkRepository extends JpaRepository { + + boolean existsByUserAndField(User user, Field field); + + void deleteByUserAndField(User user, Field field); + + @Modifying + @Query(value = "INSERT INTO field_bookmark (uid, f_id) VALUES (:userId, :fieldId) " + + "ON DUPLICATE KEY UPDATE fb_id = LAST_INSERT_ID(fb_id)", nativeQuery = true) + void insertOrUpdateFieldMark(@Param("userId") String userId, @Param("fieldId") Long fieldId); + + @Query(value = """ + SELECT f.f_id AS id, f.f_name AS name + FROM field_bookmark fb + JOIN field f ON fb.f_id = f.f_id + WHERE fb.uid = :userId + ORDER BY fb.created_at DESC, fb.fb_id DESC + """, nativeQuery = true) + List findAllLikedFields(@Param("userId") String userId); } diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/repository/FieldRepository.java b/src/main/java/hackerthon/likelion13th/canfly/search/repository/FieldRepository.java index cda739e..d3b86a9 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/search/repository/FieldRepository.java +++ b/src/main/java/hackerthon/likelion13th/canfly/search/repository/FieldRepository.java @@ -1,4 +1,7 @@ package hackerthon.likelion13th.canfly.search.repository; -public interface FieldRepository { +import hackerthon.likelion13th.canfly.domain.field.Field; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FieldRepository extends JpaRepository { } diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/repository/MajorBookmarkRepository.java b/src/main/java/hackerthon/likelion13th/canfly/search/repository/MajorBookmarkRepository.java index 5af6ada..6f65824 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/search/repository/MajorBookmarkRepository.java +++ b/src/main/java/hackerthon/likelion13th/canfly/search/repository/MajorBookmarkRepository.java @@ -1,4 +1,39 @@ package hackerthon.likelion13th.canfly.search.repository; -public interface MajorBookmarkRepository { +import hackerthon.likelion13th.canfly.domain.major.Major; +import hackerthon.likelion13th.canfly.domain.major.MajorBookmark; +import hackerthon.likelion13th.canfly.domain.university.University; +import hackerthon.likelion13th.canfly.domain.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +public interface MajorBookmarkRepository extends JpaRepository { + + boolean existsByUserAndMajor(User user, Major major); + + void deleteByUserAndMajor(User user, Major major); + + boolean existsByUserAndMajorAndUniversity(User user, Major major, University university); + + void deleteByUserAndMajorAndUniversity(User user, Major major, University university); + + @Modifying + @Query(value = """ + INSERT INTO major_bookmark (uid, m_id, univ_id) + VALUES (:userId, :majorId, NULL) + ON DUPLICATE KEY UPDATE mb_id = LAST_INSERT_ID(mb_id) + """, nativeQuery = true) + void upsertMajorOnly(@org.springframework.data.repository.query.Param("userId") String userId, + @org.springframework.data.repository.query.Param("majorId") Long majorId); + + @Modifying + @Query(value = """ + INSERT INTO major_bookmark (uid, m_id, univ_id) + VALUES (:userId, :majorId, :univId) + ON DUPLICATE KEY UPDATE mb_id = LAST_INSERT_ID(mb_id) + """, nativeQuery = true) + void upsertMajorWithUniv(@org.springframework.data.repository.query.Param("userId") String userId, + @org.springframework.data.repository.query.Param("majorId") Long majorId, + @org.springframework.data.repository.query.Param("univId") Long univId); } diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/service/FieldService.java b/src/main/java/hackerthon/likelion13th/canfly/search/service/FieldService.java index c6d3da6..fe46a73 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/search/service/FieldService.java +++ b/src/main/java/hackerthon/likelion13th/canfly/search/service/FieldService.java @@ -1,4 +1,58 @@ package hackerthon.likelion13th.canfly.search.service; +import hackerthon.likelion13th.canfly.domain.field.Field; +import hackerthon.likelion13th.canfly.domain.user.User; +import hackerthon.likelion13th.canfly.global.api.ErrorCode; +import hackerthon.likelion13th.canfly.global.exception.GeneralException; +import hackerthon.likelion13th.canfly.search.dto.FieldDto; +import hackerthon.likelion13th.canfly.search.repository.FieldBookmarkRepository; +import hackerthon.likelion13th.canfly.search.repository.FieldRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor public class FieldService { + private final FieldBookmarkRepository fieldBookmarkRepository; + private final FieldRepository fieldRepository; + + @Transactional + public Field toggleFieldLike(Long fieldId, User user) { + Field field = fieldRepository.findById(fieldId) + .orElseThrow(() -> new GeneralException(ErrorCode.FIELD_NOT_FOUND)); + + boolean isMarked = fieldBookmarkRepository.existsByUserAndField(user, field); + + if (isMarked) { + fieldBookmarkRepository.deleteByUserAndField(user, field); + } else { + fieldBookmarkRepository.insertOrUpdateFieldMark(user.getUid(), field.getId()); // 좋아요 추가 (중복 삽입 방지) + } + + return field; + } + + public List getAllLikedFields(User user) { + List raw = fieldBookmarkRepository.findAllLikedFields(user.getUid()); + + return raw.stream() + .map(row -> new FieldDto( + ((Number) row[0]).longValue(), // f_id + (String) row[1] // f_name + )) + .toList(); + } + + public List getAllFields() { + List fields = fieldRepository.findAll(); + + return fields.stream() + .map(f -> new FieldDto(f.getId(), f.getName())) + .toList(); + } } diff --git a/src/main/java/hackerthon/likelion13th/canfly/search/service/MajorService.java b/src/main/java/hackerthon/likelion13th/canfly/search/service/MajorService.java index 44a255c..5b53957 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/search/service/MajorService.java +++ b/src/main/java/hackerthon/likelion13th/canfly/search/service/MajorService.java @@ -1,4 +1,54 @@ package hackerthon.likelion13th.canfly.search.service; +import hackerthon.likelion13th.canfly.domain.major.Major; +import hackerthon.likelion13th.canfly.domain.university.University; +import hackerthon.likelion13th.canfly.domain.user.User; +import hackerthon.likelion13th.canfly.global.api.ErrorCode; +import hackerthon.likelion13th.canfly.global.exception.GeneralException; +import hackerthon.likelion13th.canfly.search.repository.MajorBookmarkRepository; +import hackerthon.likelion13th.canfly.search.repository.MajorRepository; +import hackerthon.likelion13th.canfly.university.repository.UniversityRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor public class MajorService { + private final MajorBookmarkRepository majorBookmarkRepository; + private final MajorRepository majorRepository; + private final UniversityRepository universityRepository; + + @Transactional + public Major toggleMajorLike(Long majorId, User user) { + Major major = majorRepository.findById(majorId) + .orElseThrow(() -> new GeneralException(ErrorCode.MAJOR_NOT_FOUND)); + + boolean isMarked = majorBookmarkRepository.existsByUserAndMajor(user, major); + + if (isMarked) { + majorBookmarkRepository.deleteByUserAndMajor(user, major); + } else { + majorBookmarkRepository.upsertMajorOnly(user.getUid(), major.getId()); // 좋아요 추가 (중복 삽입 방지) + } + + return major; + } + + @Transactional + public void toggleMajorUnivLike(Long majorId, Long univId, User user) { + Major major = majorRepository.findById(majorId) + .orElseThrow(() -> new GeneralException(ErrorCode.MAJOR_NOT_FOUND)); + University univ = universityRepository.findById(univId) + .orElseThrow(() -> new GeneralException(ErrorCode.UNIVERSITY_NOT_FOUND)); + + boolean exists = majorBookmarkRepository.existsByUserAndMajorAndUniversity(user, major, univ); + if (exists) { + majorBookmarkRepository.deleteByUserAndMajorAndUniversity(user, major, univ); + } else { + majorBookmarkRepository.upsertMajorWithUniv(user.getUid(), major.getId(), univ.getId()); + } + } }