Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import org.hibernate.annotations.OnDeleteAction;

@Entity
@Table(name = "fieldBookmark")
@Table(name = "field_bookmark")
@Getter
@Setter
@NoArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
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.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

@Entity
@Table(name = "majorBookmark")
@Table(name = "major_bookmark")
@Getter
@Setter
@NoArgsConstructor
Expand All @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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", "찾으려는 모의고사가 존재하지 않습니다.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", "코인(토큰) 사용이 완료되었습니다.");
Expand Down
Original file line number Diff line number Diff line change
@@ -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<List<FieldDto>> getMyLikedFields(
@AuthenticationPrincipal CustomUserDetails customUserDetails
) {
User user = userService.findUserByProviderId(customUserDetails.getUsername());
List<FieldDto> 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<List<FieldDto>> getFields(
@AuthenticationPrincipal CustomUserDetails customUserDetails
) {
List<FieldDto> fields = fieldService.getAllFields();

return ApiResponse.onSuccess(SuccessCode.FIELD_LIST_VIEW_SUCCESS, fields);
}
}
Original file line number Diff line number Diff line change
@@ -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<Boolean> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Boolean> 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<Boolean> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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<FieldBookmark, Long> {

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<Object[]> findAllLikedFields(@Param("userId") String userId);
}
Original file line number Diff line number Diff line change
@@ -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<Field, Long> {
}
Original file line number Diff line number Diff line change
@@ -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<MajorBookmark, Long> {

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);
}
Original file line number Diff line number Diff line change
@@ -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<FieldDto> getAllLikedFields(User user) {
List<Object[]> 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<FieldDto> getAllFields() {
List<Field> fields = fieldRepository.findAll();

return fields.stream()
.map(f -> new FieldDto(f.getId(), f.getName()))
.toList();
}
}
Loading