Skip to content

Commit 40f3079

Browse files
authored
✨ feat: 북마크 등록 및 취소 기능 개발 (#90)
1 parent 812651d commit 40f3079

File tree

7 files changed

+182
-0
lines changed

7 files changed

+182
-0
lines changed

http/test.http

+9
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ Content-Type: application/json
6565
DELETE http://localhost:8080/api/v1/announcements/1
6666
Authorization: Bearer {{masterToken}}
6767

68+
### 북마크 등록 및 취소
69+
POST http://localhost:8080/api/v1/me/announcement/0/bookmarked
70+
Authorization: Bearer {{matsterToken}}
71+
Content-Type: application/json
72+
73+
{
74+
"announcementId": 1
75+
}
76+
6877
### 보고서 작성
6978
POST http://localhost:8080/api/v1/reports
7079
Content-Type: application/json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.sponus.sponusbe.domain.bookmark.controller;
2+
3+
import org.springframework.web.bind.annotation.PostMapping;
4+
import org.springframework.web.bind.annotation.RequestBody;
5+
import org.springframework.web.bind.annotation.RequestMapping;
6+
import org.springframework.web.bind.annotation.RestController;
7+
8+
import com.sponus.sponusbe.auth.annotation.AuthOrganization;
9+
import com.sponus.sponusbe.domain.bookmark.dto.BookmarkToggleRequest;
10+
import com.sponus.sponusbe.domain.bookmark.dto.BookmarkToggleResponse;
11+
import com.sponus.sponusbe.domain.organization.entity.Organization;
12+
import com.sponus.sponusbe.domain.bookmark.service.BookmarkService;
13+
import com.sponus.sponusbe.global.common.ApiResponse;
14+
15+
import lombok.RequiredArgsConstructor;
16+
17+
@RestController
18+
@RequiredArgsConstructor
19+
@RequestMapping("/api/v1/me/announcement")
20+
public class BookmarkController {
21+
22+
private final BookmarkService bookmarkService;
23+
24+
@PostMapping("/{organizationId}/bookmarked")
25+
public ApiResponse<BookmarkToggleResponse> bookmarkToggle(
26+
@AuthOrganization Organization authOrganization,
27+
@RequestBody BookmarkToggleRequest request
28+
) {
29+
return ApiResponse.onSuccess(bookmarkService.bookmarkToggle(authOrganization, request));
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.sponus.sponusbe.domain.bookmark.dto;
2+
3+
import com.sponus.sponusbe.domain.announcement.entity.Announcement;
4+
import com.sponus.sponusbe.domain.bookmark.entity.Bookmark;
5+
import com.sponus.sponusbe.domain.organization.entity.Organization;
6+
7+
public record BookmarkToggleRequest(
8+
Long announcementId
9+
) {
10+
11+
public Bookmark toEntity(Organization organization, Announcement announcement) {
12+
return Bookmark.builder()
13+
.organization(organization)
14+
.announcement(announcement)
15+
.build();
16+
}
17+
}
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.sponus.sponusbe.domain.bookmark.dto;
2+
3+
import com.sponus.sponusbe.domain.bookmark.entity.Bookmark;
4+
5+
import lombok.Builder;
6+
7+
@Builder
8+
public record BookmarkToggleResponse(
9+
Long id,
10+
Long organizationId,
11+
Long announcementId,
12+
Boolean bookmarked
13+
) {
14+
15+
public static BookmarkToggleResponse from(Bookmark bookmark, boolean bookmarked) {
16+
return BookmarkToggleResponse.builder()
17+
.id(bookmark.getId())
18+
.organizationId(bookmark.getOrganization().getId())
19+
.announcementId(bookmark.getAnnouncement().getId())
20+
.bookmarked(bookmarked)
21+
.build();
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.sponus.sponusbe.domain.bookmark.entity;
2+
3+
import com.sponus.sponusbe.domain.announcement.entity.Announcement;
4+
import com.sponus.sponusbe.domain.organization.entity.Organization;
5+
import com.sponus.sponusbe.global.common.BaseEntity;
6+
7+
import jakarta.persistence.Column;
8+
import jakarta.persistence.ConstraintMode;
9+
import jakarta.persistence.Entity;
10+
import jakarta.persistence.FetchType;
11+
import jakarta.persistence.ForeignKey;
12+
import jakarta.persistence.GeneratedValue;
13+
import jakarta.persistence.GenerationType;
14+
import jakarta.persistence.Id;
15+
import jakarta.persistence.JoinColumn;
16+
import jakarta.persistence.ManyToOne;
17+
import jakarta.persistence.Table;
18+
import lombok.AccessLevel;
19+
import lombok.AllArgsConstructor;
20+
import lombok.Builder;
21+
import lombok.Getter;
22+
import lombok.NoArgsConstructor;
23+
24+
@Builder
25+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
26+
@AllArgsConstructor(access = AccessLevel.PRIVATE)
27+
@Getter
28+
@Entity
29+
@Table(name = "bookmark")
30+
public class Bookmark extends BaseEntity {
31+
32+
@Id
33+
@GeneratedValue(strategy = GenerationType.IDENTITY)
34+
@Column(name = "bookmark_id")
35+
private Long id;
36+
37+
@ManyToOne(fetch = FetchType.LAZY)
38+
@JoinColumn(name = "organization_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
39+
private Organization organization;
40+
41+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
42+
@JoinColumn(name = "announcement_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
43+
private Announcement announcement;
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.sponus.sponusbe.domain.bookmark.repository;
2+
3+
import java.util.Optional;
4+
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
import com.sponus.sponusbe.domain.announcement.entity.Announcement;
8+
import com.sponus.sponusbe.domain.bookmark.entity.Bookmark;
9+
import com.sponus.sponusbe.domain.organization.entity.Organization;
10+
11+
public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {
12+
13+
Optional<Bookmark> findByOrganizationAndAnnouncement(Organization organization, Announcement announcement);
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.sponus.sponusbe.domain.bookmark.service;
2+
3+
import static com.sponus.sponusbe.domain.announcement.exception.AnnouncementErrorCode.*;
4+
5+
import org.springframework.stereotype.Service;
6+
7+
import com.sponus.sponusbe.domain.announcement.entity.Announcement;
8+
import com.sponus.sponusbe.domain.announcement.exception.AnnouncementException;
9+
import com.sponus.sponusbe.domain.announcement.repository.AnnouncementRepository;
10+
import com.sponus.sponusbe.domain.bookmark.dto.BookmarkToggleRequest;
11+
import com.sponus.sponusbe.domain.bookmark.dto.BookmarkToggleResponse;
12+
import com.sponus.sponusbe.domain.bookmark.entity.Bookmark;
13+
import com.sponus.sponusbe.domain.organization.entity.Organization;
14+
import com.sponus.sponusbe.domain.bookmark.repository.BookmarkRepository;
15+
16+
import jakarta.transaction.Transactional;
17+
import lombok.RequiredArgsConstructor;
18+
import lombok.extern.slf4j.Slf4j;
19+
20+
@Slf4j
21+
@RequiredArgsConstructor
22+
@Service
23+
@Transactional
24+
public class BookmarkService {
25+
26+
private final BookmarkRepository bookmarkRepository;
27+
private final AnnouncementRepository announcementRepository;
28+
29+
public BookmarkToggleResponse bookmarkToggle(Organization organization, BookmarkToggleRequest request) {
30+
Announcement announcement = announcementRepository.findById(request.announcementId())
31+
.orElseThrow(() -> new AnnouncementException(ANNOUNCEMENT_NOT_FOUND));
32+
Bookmark existingBookmark = bookmarkRepository.findByOrganizationAndAnnouncement(organization, announcement)
33+
.orElse(null);
34+
35+
if (existingBookmark != null) {
36+
bookmarkRepository.delete(existingBookmark);
37+
return BookmarkToggleResponse.from(existingBookmark, false); // 이미 북마크가 되어있는 경우 취소
38+
} else {
39+
final Bookmark bookmark = bookmarkRepository.save(request.toEntity(organization, announcement));
40+
return BookmarkToggleResponse.from(bookmark, true); // 북마크가 안되어있는 경우 등록
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)