Skip to content

Commit ddf2d4f

Browse files
authored
Merge pull request #29 from CAPS-DGU/feature/#28
Feature/#28
2 parents 73df9d7 + 16f93d2 commit ddf2d4f

13 files changed

Lines changed: 427 additions & 4 deletions

File tree

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ dependencies {
4141

4242
// redis
4343
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
44+
45+
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
4446
}
4547

4648
tasks.named('test') {
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+
}

0 commit comments

Comments
 (0)