Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
098a8c2
Update README.md
junyi04 Oct 3, 2025
5200ce0
Merge branch 'ApptiveDev:main' into main
junyi04 Oct 5, 2025
3d54a1f
chore: add JPA and Lombok dependencies
junyi04 Oct 5, 2025
2ded9e9
feat(docs): add PostEntity and explanation
junyi04 Oct 5, 2025
2592017
feat(docs): add PostRepository and explanation
junyi04 Oct 5, 2025
a869462
feat(docs): add PostService and explanation
junyi04 Oct 5, 2025
9346fa9
feat(docs): add PostController and explanation
junyi04 Oct 5, 2025
e227126
feat: create templates/list.html (/posts/new)
junyi04 Oct 5, 2025
96b956f
feat: create templates/form.html (/posts)
junyi04 Oct 5, 2025
845bb2d
feat: create templates/detail.html (/posts/{id}/delete)
junyi04 Oct 5, 2025
eb68811
chore: update H2 database and JPA configuration
junyi04 Oct 5, 2025
8000aaf
fix: correct typo in list.html&detail.html th name
junyi04 Oct 5, 2025
7437ff5
feat: add createdAt, updatedAt fields and connect CRUD
junyi04 Oct 7, 2025
bbc2f3d
feat: implement edit feature with update API and edit.html
junyi04 Oct 7, 2025
cc40f17
feat: 댓글 기능 로직 구현 완료
junyi04 Nov 13, 2025
f955163
feat: detail 화면에 댓글 기능 추가
junyi04 Nov 13, 2025
a06835e
refactor: Entity를 DTO 쓰는 방향으로 수정
junyi04 Nov 13, 2025
f090e61
feat: week-2 브랜치
junyi04 Nov 13, 2025
6bb0f9d
feat: MemberEntity 생성 및 연관관계 추가
junyi04 Nov 13, 2025
7fff80a
feat: MemberService 비지니스 로직 추가
junyi04 Nov 13, 2025
c2cd66e
feat: 좋아요 기능 생성 및 연결
junyi04 Nov 14, 2025
5bef8a8
feat: member 전체 조회 기능 추가
junyi04 Nov 14, 2025
fed4d15
feat: 멤버 목록 및 게시글 목록 연결 완료
junyi04 Nov 14, 2025
eeaf7d7
feat: week3 과제
junyi04 Nov 14, 2025
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# backend-study-sns

강준이
12 changes: 10 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description = 'Demo project for Spring Boot'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
languageVersion = JavaLanguageVersion.of(17)
}
}

Expand All @@ -19,8 +19,16 @@ repositories {
}

dependencies {
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'com.mysql:mysql-connector-j:8.2.0' // MySQL
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/example/devSns/DevSnsApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ public static void main(String[] args) {
SpringApplication.run(DevSnsApplication.class, args);
}

}
}
24 changes: 24 additions & 0 deletions src/main/java/com/example/devSns/config/DataInit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.devSns.config;

import com.example.devSns.entity.MemberEntity;
import com.example.devSns.repository.MemberRepository;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

@Component
public class DataInit {

private final MemberRepository memberRepository;

public DataInit(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

@PostConstruct
public void init() {
// Member가 없으면 테스트용 계정 자동 생성
if (memberRepository.count() == 0) {
memberRepository.save(MemberEntity.create("tester", "tester@example.com"));
}
}
}
31 changes: 31 additions & 0 deletions src/main/java/com/example/devSns/controller/CommentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.example.devSns.controller;

import com.example.devSns.dto.CommentRequest;
import com.example.devSns.entity.CommentEntity;
import com.example.devSns.service.CommentService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/comments")
public class CommentController {
private final CommentService commentService;

public CommentController(CommentService commentService) {
this.commentService = commentService;
}

// 댓글 생성
@PostMapping("/{postId}")
public String createComment(@PathVariable Long postId, @ModelAttribute CommentRequest request) {
commentService.createComment(postId, request);
return "redirect:/posts/" + postId;
}

// 댓글 삭제
@PostMapping("/{commentId}/delete")
public String deleteComment(@PathVariable Long commentId, @RequestParam Long postId) {
commentService.deleteComment(commentId);
return "redirect:/posts/" + postId;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"redirect:/posts/" 와 같은 문자열들은 상수로 만들어 사용하시면, 추후에 링크가 변경되더라도 더 효율적으로 코드를 수정할 수 있을거 같아요

}
}
61 changes: 61 additions & 0 deletions src/main/java/com/example/devSns/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.example.devSns.controller;

import com.example.devSns.entity.MemberEntity;
import com.example.devSns.service.MemberService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RestController@controller의 차이는 무엇일까요?

@RequestMapping("/members")
public class MemberController {

private final MemberService memberService;

public MemberController(MemberService memberService) {
this.memberService = memberService;
}

// 전체 멤버 목록
@GetMapping
public String list(Model model) {
model.addAttribute("members", memberService.getAllMembers());
return "member-list";
}
Comment on lines +21 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메소드에서 model은 어떤 역할을 할까요?


// 멤버 상세 페이지
@GetMapping("/{id}")
public String detail(@PathVariable Long id, Model model) {
MemberEntity member = memberService.getMember(id);

model.addAttribute("member", member);
model.addAttribute("posts", member.getPosts());
model.addAttribute("comments", member.getComments());

return "member-detail";
}

// 멤버 생성
@GetMapping("/new")
public String newForm() {
return "member-form";
}

// 멤버 생성 처리
@PostMapping
public String create(@RequestParam String username,
@RequestParam String email) {

memberService.createMember(username, email);
return "redirect:/members";
}

// 멤버 검색
// /members/search?keyword=abc
@GetMapping("/search")
public String search(@RequestParam String keyword, Model model) {
model.addAttribute("members", memberService.search(keyword));
model.addAttribute("keyword", keyword);
return "member-search";
}
}
95 changes: 95 additions & 0 deletions src/main/java/com/example/devSns/controller/PostController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.example.devSns.controller;

import com.example.devSns.entity.MemberEntity;
import com.example.devSns.entity.PostEntity;
import com.example.devSns.repository.LikeRepository;
import com.example.devSns.repository.MemberRepository;
import com.example.devSns.service.PostService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/posts")
public class PostController {

private final PostService postService;
private final LikeRepository likeRepository;
private final MemberRepository memberRepository;

// 생성자
public PostController(PostService postService,
LikeRepository likeRepository,
MemberRepository memberRepository) {
this.postService = postService;
this.likeRepository = likeRepository;
this.memberRepository = memberRepository;
}

// HTTP GET /posts
@GetMapping
public String list(Model model) {
model.addAttribute("posts", postService.getAllPosts());
return "list";
}

// HTTP GET /posts/new
@GetMapping("/new")
public String form(Model model) {
model.addAttribute("post", new PostEntity());
return "form";
}

// HTTP POST /posts
@PostMapping
public String create(@ModelAttribute PostEntity postEntity) {
postService.createPost(postEntity);
return "redirect:/posts"; // 글 생성 후 돌아올 주소
}

// HTTP GET /posts/{id}
@GetMapping("/{id}")
public String detail(@PathVariable Long id, Model model) {
PostEntity postEntity = postService.getPost(id);

// 임시 멤버 아이디
Long memberId = 1L;
MemberEntity member = memberRepository.findById(memberId).orElseThrow();

// 좋아요 수
int likeCount = likeRepository.countByPostId(id);

// 좋아요 여부
boolean liked = likeRepository
.findByMemberAndPost(member, postEntity)
.isPresent();

model.addAttribute("post", postEntity);
model.addAttribute("likeCount", likeCount);
model.addAttribute("liked", liked);
model.addAttribute("memberId", memberId);
return "detail";
}

// HTTP GET /posts/{id}/edit
@GetMapping("/{id}/edit")
public String editForm(@PathVariable Long id, Model model) {
PostEntity postEntity = postService.getPost(id);
model.addAttribute("post", postEntity);
return "edit";
}

// HTTP POST /posts/{id}/update
@PostMapping("/{id}/update")
public String update(@PathVariable Long id, @ModelAttribute PostEntity updatedPost) {
postService.updatePost(id, updatedPost);
return "redirect:/posts/" + id; // 수정 후 돌아올 주소
}

// HTTP POST /posts/{id}/delete
@PostMapping("/{id}/delete")
public String delete(@PathVariable Long id) {
postService.deletePost(id);
return "redirect:/posts"; // 삭제 후 돌아올 주소
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/example/devSns/controller/explanation
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
요청을 받는 곳

PostController
Spring MVC 패턴에서 Controller 계층 역할
- 사용자의 요청을 받고 Service 계층을 호출해서 처리한 후, 결과를 View에 전달

@Controller
이 클래스는 웹 요청을 처리하는 컨트롤러임을 알려줌
- Model에 데이터를 담아 템플릿에 전달 (templates/list.html)

@RequestMapping
클래스 전체의 기본 URL 경로를 지정
- 이 컨트롤러 안의 모든 요청은 /posts로 시작
- /posts (게시글 목록), /posts/new (새 글 작성 폼)

@GetMapping, @PostMapping
HTTP 메서드 별 라우팅

@PathVariable, @ModelAttribute
URL 경로 변수 바인딩 / 폼 데이터를 객체에 바인딩

@RestController
문자열 반환이 뷰 이름이 아닌 JSON
- REST API 만들 때 사용
13 changes: 13 additions & 0 deletions src/main/java/com/example/devSns/dto/CommentRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.devSns.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CommentRequest {
private String username;
private String content;
}

// Controller는 CommentEntity를 직접 받지 않고 CommentRequest만 받게 됨
20 changes: 20 additions & 0 deletions src/main/java/com/example/devSns/dto/CommentResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.devSns.dto;

import lombok.Getter;

@Getter
public class CommentResponse {
private final Long id;
private final String username;
private final String content;
private final String createdAt;

public CommentResponse(Long id, String username, String content, String createdAt) {
this.id = id;
this.username = username;
this.content = content;
this.createdAt = createdAt;
}
}

// Entity를 외부로 그대로 내보내지 않고 DTO로 가공해서 반환
8 changes: 8 additions & 0 deletions src/main/java/com/example/devSns/dto/explanation
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
계층간 데이터 교환에 사용
- Entity 클래스를 보호
- 필요한 데이터만 선택적으로 담을 수 있음

분리해서 사용하는 이유
- Entity 객체의 변경을 피하기 위함
- 클라이언트와 통신하는 ResponseDTO나 RequestDTO는 요구사항에 따라 자주 변경
- 어떤 요청에서는 특정 값이 추가되거나 없을 수 있어서 분리해서 관리
57 changes: 57 additions & 0 deletions src/main/java/com/example/devSns/entity/CommentEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.example.devSns.entity;

import jakarta.persistence.*;
import lombok.Getter;

import java.time.LocalDateTime;

// DTO를 사용하고 Setter를 삭제 (안전)
@Entity
@Getter
public class CommentEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String content;
private String username;

private LocalDateTime createdAt;
private LocalDateTime updatedAt;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private MemberEntity member;

// Post와 N:1 관계
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private PostEntity postEntity;

// JPA 기본 생성자
protected CommentEntity() {}

// 정적 생성 메서드 > 엔터티 변경은 오직 메서드로
public static CommentEntity create(PostEntity postEntity, String username, String content) {
CommentEntity comment = new CommentEntity();
comment.postEntity = postEntity;
comment.username = username;
comment.content = content;
return comment;
}

public void updateContent(String content) {
this.content = content;
}

@PrePersist
public void onCreate() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}

@PreUpdate
public void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
Loading