Skip to content

Commit 1b60355

Browse files
authored
Merge pull request #33 from CAPS-DGU/revert-29-feature/#28
Revert 29 feature/#28
2 parents 25afe21 + dd074b0 commit 1b60355

File tree

11 files changed

+391
-4
lines changed

11 files changed

+391
-4
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package kr.dgucaps.caps.domain.ledger.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.media.Content;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
7+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import jakarta.validation.Valid;
10+
import jakarta.validation.constraints.Min;
11+
import kr.dgucaps.caps.domain.ledger.dto.request.CreateOrModifyLedgerRequest;
12+
import kr.dgucaps.caps.domain.ledger.dto.response.LedgerListResponse;
13+
import kr.dgucaps.caps.domain.ledger.dto.response.LedgerResponse;
14+
import kr.dgucaps.caps.global.annotation.Auth;
15+
import kr.dgucaps.caps.global.common.SuccessResponse;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
@Tag(name = "Ledger", description = "장부 API")
20+
public interface LedgerApi {
21+
22+
@Operation(
23+
summary = "게시물 목록 조회",
24+
description = "게시물의 목록을 확인합니다"
25+
)
26+
@ApiResponses({
27+
@ApiResponse(responseCode = "200", description = "게시물 목록 조회 성공",
28+
content = @Content(mediaType = "application/json",
29+
schema = @Schema(implementation = LedgerListResponse.class)))
30+
})
31+
ResponseEntity<SuccessResponse<?>> getLedgersList(@RequestParam(value = "page", required = false, defaultValue = "1") @Valid @Min(1) Integer page);
32+
33+
@Operation(
34+
summary = "게시물 상세 조회",
35+
description = "게시물의 상세 내용을 확인합니다"
36+
)
37+
@ApiResponses({
38+
@ApiResponse(responseCode = "200", description = "게시물 조회 성공",
39+
content = @Content(mediaType = "application/json",
40+
schema = @Schema(implementation = LedgerResponse.class))),
41+
@ApiResponse(responseCode = "404", description = "존재하지 않는 장부 아이디")
42+
})
43+
ResponseEntity<SuccessResponse<?>> getSpecificLedger(@PathVariable("ledgerId") Long ledgerId);
44+
45+
@Operation(
46+
summary = "게시물 등록",
47+
description = "새로운 게시물을 장부 게시판에 등록합니다"
48+
)
49+
@ApiResponses({
50+
@ApiResponse(responseCode = "201", description = "게시물 작성 성공",
51+
content = @Content(mediaType = "application/json",
52+
schema = @Schema(implementation = LedgerResponse.class))),
53+
@ApiResponse(responseCode = "401", description = "게시물을 작성할 권한이 없음")
54+
})
55+
ResponseEntity<SuccessResponse<?>> createLedger(
56+
@Auth Long memberId,
57+
@RequestBody @Valid CreateOrModifyLedgerRequest request
58+
);
59+
60+
@Operation(
61+
summary = "게시물 수정",
62+
description = "기존 게시물의 내용을 수정합니다"
63+
)
64+
@ApiResponses({
65+
@ApiResponse(responseCode = "200", description = "게시물 수정 성공",
66+
content = @Content(mediaType = "application/json",
67+
schema = @Schema(implementation = LedgerResponse.class))),
68+
@ApiResponse(responseCode = "401", description = "게시물을 수정할 권한이 없음")
69+
})
70+
ResponseEntity<SuccessResponse<?>> modifyLedger(
71+
@Auth Long memberId,
72+
@PathVariable("ledgerId") Long ledgerId,
73+
@RequestBody @Valid CreateOrModifyLedgerRequest request
74+
);
75+
76+
@Operation(
77+
summary = "게시물 삭제",
78+
description = "기존 게시물을 장부 게시판에서 삭제합니다"
79+
)
80+
@ApiResponses({
81+
@ApiResponse(responseCode = "204", description = "게시물 삭제 성공 (본문 없음)"),
82+
@ApiResponse(responseCode = "401", description = "게시물을 삭제할 권한이 없음"),
83+
@ApiResponse(responseCode = "404", description = "해당 게시물이 존재하지 않음")
84+
})
85+
ResponseEntity<SuccessResponse<?>> deleteLedger(
86+
@Auth Long memberId,
87+
@PathVariable("ledgerId") Long ledgerId
88+
);
89+
90+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package kr.dgucaps.caps.domain.ledger.controller;
2+
3+
import jakarta.validation.Valid;
4+
import jakarta.validation.constraints.Min;
5+
import kr.dgucaps.caps.domain.ledger.dto.request.CreateOrModifyLedgerRequest;
6+
import kr.dgucaps.caps.domain.ledger.service.LedgerService;
7+
import kr.dgucaps.caps.global.annotation.Auth;
8+
import kr.dgucaps.caps.global.common.SuccessResponse;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.security.access.prepost.PreAuthorize;
12+
import org.springframework.validation.annotation.Validated;
13+
import org.springframework.web.bind.annotation.*;
14+
15+
@RestController
16+
@RequiredArgsConstructor
17+
@RequestMapping("/api/v1/ledgers")
18+
@Validated
19+
public class LedgerController implements LedgerApi {
20+
21+
private final LedgerService ledgerService;
22+
23+
@PreAuthorize("hasAnyRole('MEMBER', 'GRADUATE', 'COUNCIL', 'PRESIDENT', 'ADMIN')")
24+
@GetMapping
25+
public ResponseEntity<SuccessResponse<?>> getLedgersList(
26+
@RequestParam(value = "page", required = false, defaultValue = "1") @Valid @Min(1) Integer page) {
27+
return SuccessResponse.ok(ledgerService.getLedgersByPage(page-1));
28+
}
29+
30+
@PreAuthorize("hasAnyRole('MEMBER', 'GRADUATE', 'COUNCIL', 'PRESIDENT', 'ADMIN')")
31+
@GetMapping("/{ledgerId}")
32+
public ResponseEntity<SuccessResponse<?>> getSpecificLedger(@PathVariable("ledgerId") Long ledgerId) {
33+
return SuccessResponse.ok(ledgerService.getLedgerById(ledgerId));
34+
}
35+
36+
@PreAuthorize("hasAnyRole('COUNCIL', 'PRESIDENT', 'ADMIN')")
37+
@PostMapping
38+
public ResponseEntity<SuccessResponse<?>> createLedger(
39+
@Auth Long memberId,
40+
@RequestBody @Valid CreateOrModifyLedgerRequest request
41+
) {
42+
return SuccessResponse.created(ledgerService.createLedger(memberId, request));
43+
}
44+
45+
@PreAuthorize("hasAnyRole('COUNCIL', 'PRESIDENT', 'ADMIN')")
46+
@PatchMapping("/{ledgerId}")
47+
public ResponseEntity<SuccessResponse<?>> modifyLedger(
48+
@Auth Long memberId,
49+
@PathVariable("ledgerId") Long ledgerId,
50+
@RequestBody @Valid CreateOrModifyLedgerRequest request
51+
) {
52+
return SuccessResponse.ok(ledgerService.modifyLedger(ledgerId, memberId, request));
53+
}
54+
55+
@PreAuthorize("hasAnyRole('COUNCIL', 'PRESIDENT', 'ADMIN')")
56+
@DeleteMapping("/{ledgerId}")
57+
public ResponseEntity<SuccessResponse<?>> deleteLedger(
58+
@Auth Long memberId,
59+
@PathVariable("ledgerId") Long ledgerId
60+
) {
61+
ledgerService.deleteLedger(ledgerId, memberId);
62+
return ResponseEntity.noContent().build();
63+
}
64+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package kr.dgucaps.caps.domain.ledger.dto.request;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import kr.dgucaps.caps.domain.ledger.entity.Ledger;
5+
import kr.dgucaps.caps.domain.member.entity.Member;
6+
7+
public record CreateOrModifyLedgerRequest(
8+
@NotBlank String title,
9+
@NotBlank String content,
10+
String fileUrl,
11+
Boolean isPinned
12+
) {
13+
public Ledger toEntity(Member member) {
14+
return Ledger.builder()
15+
.member(member)
16+
.title(title)
17+
.content(content)
18+
.fileUrl(fileUrl)
19+
.isPinned(isPinned)
20+
.build();
21+
}
22+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package kr.dgucaps.caps.domain.ledger.dto.response;
2+
3+
import kr.dgucaps.caps.domain.dto.MemberSummary;
4+
import kr.dgucaps.caps.domain.ledger.entity.Ledger;
5+
import lombok.Builder;
6+
7+
import java.time.LocalDateTime;
8+
9+
@Builder
10+
public record LedgerListResponse(
11+
Long id,
12+
String title,
13+
MemberSummary member,
14+
LocalDateTime createdAt
15+
) {
16+
public static LedgerListResponse from(Ledger ledger) {
17+
return LedgerListResponse.builder()
18+
.id(ledger.getId())
19+
.title(ledger.getTitle())
20+
.member(MemberSummary.from(ledger.getMember()))
21+
.createdAt(ledger.getCreatedAt())
22+
.build();
23+
}
24+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package kr.dgucaps.caps.domain.ledger.dto.response;
2+
3+
import kr.dgucaps.caps.domain.dto.MemberSummary;
4+
import kr.dgucaps.caps.domain.ledger.entity.Ledger;
5+
import lombok.Builder;
6+
7+
import java.time.LocalDateTime;
8+
9+
@Builder
10+
public record LedgerResponse(
11+
Long id,
12+
String title,
13+
String content,
14+
String fileUrl,
15+
MemberSummary member,
16+
LocalDateTime createdAt,
17+
Boolean isPinned
18+
) {
19+
public static LedgerResponse from(Ledger ledger) {
20+
return LedgerResponse.builder()
21+
.id(ledger.getId())
22+
.title(ledger.getTitle())
23+
.content(ledger.getContent())
24+
.fileUrl(ledger.getFileUrl())
25+
.member(MemberSummary.from(ledger.getMember()))
26+
.createdAt(ledger.getCreatedAt())
27+
.isPinned(ledger.isPinned())
28+
.build();
29+
}
30+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package kr.dgucaps.caps.domain.ledger.entity;
2+
3+
import jakarta.persistence.*;
4+
import kr.dgucaps.caps.domain.common.entity.BaseTimeEntity;
5+
import kr.dgucaps.caps.domain.member.entity.Member;
6+
import lombok.AccessLevel;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
import org.hibernate.annotations.ColumnDefault;
11+
12+
@Entity
13+
@Getter
14+
@Table(name = "ledger")
15+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
16+
public class Ledger extends BaseTimeEntity {
17+
18+
@Id
19+
@GeneratedValue(strategy = GenerationType.IDENTITY)
20+
private Long id;
21+
22+
@ManyToOne(fetch = FetchType.LAZY)
23+
@JoinColumn(name = "member_id", nullable = false)
24+
private Member member;
25+
26+
@Column(nullable = false)
27+
private String title;
28+
29+
@Column(nullable = false, columnDefinition = "TEXT")
30+
private String content;
31+
32+
@Column(nullable = true, name = "file_url")
33+
private String fileUrl;
34+
35+
@Column(nullable = false, name = "view_count")
36+
private Integer viewCount = 0;
37+
38+
@Column(nullable = false, name = "is_pinned")
39+
@ColumnDefault("false")
40+
private boolean isPinned;
41+
42+
@Builder
43+
public Ledger(Member member, String title, String content, String fileUrl, Boolean isPinned) {
44+
this.member = member;
45+
this.title = title;
46+
this.content = content;
47+
this.fileUrl = fileUrl;
48+
this.viewCount = 0;
49+
if (isPinned != null) {
50+
this.isPinned = isPinned;
51+
}
52+
}
53+
54+
public void updateLedger(String title, String content, String fileUrl, Boolean isPinned) {
55+
this.title = title;
56+
this.content = content;
57+
if (fileUrl != null && !fileUrl.isBlank()) {
58+
this.fileUrl = fileUrl;
59+
}
60+
if (isPinned != null) {
61+
this.isPinned = isPinned;
62+
}
63+
}
64+
65+
public void increaseViewCount() {
66+
this.viewCount++;
67+
}
68+
69+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package kr.dgucaps.caps.domain.ledger.repository;
2+
3+
import kr.dgucaps.caps.domain.ledger.entity.Ledger;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface LedgerRepository extends JpaRepository<Ledger, Long> {
9+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package kr.dgucaps.caps.domain.ledger.service;
2+
3+
import kr.dgucaps.caps.domain.ledger.dto.request.CreateOrModifyLedgerRequest;
4+
import kr.dgucaps.caps.domain.ledger.dto.response.LedgerListResponse;
5+
import kr.dgucaps.caps.domain.ledger.dto.response.LedgerResponse;
6+
import kr.dgucaps.caps.domain.ledger.entity.Ledger;
7+
import kr.dgucaps.caps.domain.ledger.repository.LedgerRepository;
8+
import kr.dgucaps.caps.domain.member.entity.Member;
9+
import kr.dgucaps.caps.domain.member.repository.MemberRepository;
10+
import kr.dgucaps.caps.global.error.ErrorCode;
11+
import kr.dgucaps.caps.global.error.exception.EntityNotFoundException;
12+
import kr.dgucaps.caps.global.error.exception.ForbiddenException;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.data.domain.Page;
15+
import org.springframework.data.domain.PageRequest;
16+
import org.springframework.data.domain.Pageable;
17+
import org.springframework.data.domain.Sort;
18+
import org.springframework.stereotype.Service;
19+
import org.springframework.transaction.annotation.Transactional;
20+
21+
@Service
22+
@Transactional(readOnly = true)
23+
@RequiredArgsConstructor
24+
public class LedgerService {
25+
26+
private final LedgerRepository ledgerRepository;
27+
private final MemberRepository memberRepository;
28+
29+
public Page<LedgerListResponse> getLedgersByPage(int page) {
30+
Sort sort = Sort.by(Sort.Order.desc("isPinned"), Sort.Order.desc("createdAt"));
31+
Pageable pageable = PageRequest.of(page, 12, sort);
32+
Page<Ledger> ledgerPage = ledgerRepository.findAll(pageable);
33+
return ledgerPage.map(LedgerListResponse::from);
34+
}
35+
36+
@Transactional
37+
public LedgerResponse getLedgerById(Long ledgerId) {
38+
Ledger ledger = ledgerRepository.findById(ledgerId)
39+
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.LEDGER_NOT_FOUND));
40+
ledger.increaseViewCount();
41+
return LedgerResponse.from(ledger);
42+
}
43+
44+
@Transactional
45+
public LedgerResponse createLedger(Long memberId, CreateOrModifyLedgerRequest request) {
46+
Member member = memberRepository.findById(memberId)
47+
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.MEMBER_NOT_FOUND));
48+
Ledger ledger = ledgerRepository.save(request.toEntity(member));
49+
return LedgerResponse.from(ledger);
50+
}
51+
52+
@Transactional
53+
public LedgerResponse modifyLedger(Long ledgerId, Long memberId, CreateOrModifyLedgerRequest request) {
54+
Ledger ledger = ledgerRepository.findById(ledgerId)
55+
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.LEDGER_NOT_FOUND));
56+
57+
if (!ledger.getMember().getId().equals(memberId)) {
58+
throw new ForbiddenException(ErrorCode.FORBIDDEN);
59+
}
60+
61+
ledger.updateLedger(request.title(), request.content(), request.fileUrl(), request.isPinned());
62+
Ledger updatedLedger = ledgerRepository.save(ledger);
63+
return LedgerResponse.from(updatedLedger);
64+
}
65+
66+
@Transactional
67+
public void deleteLedger(Long ledgerId, Long memberId) {
68+
Ledger ledger = ledgerRepository.findById(ledgerId)
69+
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.LEDGER_NOT_FOUND));
70+
71+
if (!ledger.getMember().getId().equals(memberId)) {
72+
throw new ForbiddenException(ErrorCode.FORBIDDEN);
73+
}
74+
75+
ledgerRepository.deleteById(ledgerId);
76+
}
77+
78+
}

0 commit comments

Comments
 (0)