-
Notifications
You must be signed in to change notification settings - Fork 13
Feat/week 1 #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat/week 1 #9
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.example.devSns.common.config; | ||
|
|
||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.data.jpa.repository.config.EnableJpaAuditing; | ||
|
|
||
| @Configuration | ||
| @EnableJpaAuditing | ||
| public class JpaConfig {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.example.devSns.common.exception; | ||
|
|
||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
|
||
| @RestControllerAdvice | ||
| public class GlobalExceptionHandler { | ||
|
|
||
| @ExceptionHandler(IllegalArgumentException.class) | ||
| public ResponseEntity<?> handleIllegalArgument(IllegalArgumentException e) { | ||
| return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); | ||
| } | ||
|
|
||
| @ExceptionHandler(MethodArgumentNotValidException.class) | ||
| public ResponseEntity<?> handleValidation(MethodArgumentNotValidException e) { | ||
| String msg = e.getBindingResult().getFieldErrors().stream() | ||
| .map(err -> err.getField() + ": " + err.getDefaultMessage()) | ||
| .findFirst().orElse("Validation error"); | ||
| return ResponseEntity.badRequest().body(msg); | ||
|
Comment on lines
+17
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예외 처리까지 구성해주셨네요! 그런데 현재 방식에서는 검증 실패 내역 중 첫 번째만 반환하는 구성이라 추후 joining등을 사용해서 검증 실패 내용을 전부 알려주는 형식이 더 좋을 것 같습니다! |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // com.example.devSns.domain.post.Post | ||
| package com.example.devSns.domain.post; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.*; | ||
| import org.springframework.data.annotation.CreatedDate; | ||
| import org.springframework.data.annotation.LastModifiedDate; | ||
| import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
| import java.time.LocalDateTime; | ||
|
|
||
| @Entity @Table(name="posts") | ||
| @EntityListeners(AuditingEntityListener.class) | ||
| @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Setter를 최대한 지양하고 update 형식으로 객체에게 일을 시키는 관점으로 리팩토링 해 보시면 더 좋을 것 같습니다! |
||
| public class Post { | ||
| @Id @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
| @Column(nullable=false, length=1000) | ||
| private String content; | ||
| @Column(nullable=false) | ||
| private String username; | ||
| @Column(nullable=false) | ||
| private Integer likes; | ||
| @CreatedDate @Column(updatable=false) | ||
| private LocalDateTime createdAt; | ||
| @LastModifiedDate | ||
| private LocalDateTime updatedAt; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // com.example.devSns.domain.post.PostRepository | ||
| package com.example.devSns.domain.post; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| public interface PostRepository extends JpaRepository<Post, Long> {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package com.example.devSns.service.post; | ||
|
|
||
| import com.example.devSns.domain.post.Post; | ||
| import com.example.devSns.domain.post.PostRepository; | ||
| import com.example.devSns.web.post.dto.PostDtos.CreateReq; | ||
| import com.example.devSns.web.post.dto.PostDtos.UpdateReq; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class PostService { | ||
|
|
||
| private final PostRepository postRepository; | ||
|
|
||
| @Transactional | ||
| public Post create(CreateReq req) { | ||
| Post p = Post.builder() | ||
| .content(req.content) | ||
| .username(req.username) | ||
| .likes(0) | ||
| .build(); | ||
| return postRepository.save(p); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public Post get(Long id) { | ||
| return postRepository.findById(id) | ||
| .orElseThrow(() -> new IllegalArgumentException("Post not found: " + id)); | ||
| } | ||
|
Comment on lines
+19
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 읽기 전용 시 |
||
|
|
||
| @Transactional(readOnly = true) | ||
| public List<Post> list() { | ||
| return postRepository.findAll(); | ||
| } | ||
|
|
||
| @Transactional | ||
| public Post update(Long id, UpdateReq req) { | ||
| Post p = get(id); | ||
| p.setContent(req.content); | ||
| return p; | ||
| } | ||
|
|
||
| @Transactional | ||
| public void delete(Long id) { | ||
| Post p = get(id); | ||
| postRepository.delete(p); | ||
| } | ||
|
|
||
| @Transactional | ||
| public Post like(Long id) { | ||
| Post p = get(id); | ||
| p.setLikes(p.getLikes() + 1); | ||
| return p; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| package com.example.devSns.web.post; | ||
|
|
||
| import com.example.devSns.domain.post.Post; | ||
| import com.example.devSns.service.post.PostService; | ||
| import com.example.devSns.web.post.dto.PostDtos.CreateReq; | ||
| import com.example.devSns.web.post.dto.PostDtos.UpdateReq; | ||
| import com.example.devSns.web.post.dto.PostDtos.Res; | ||
|
|
||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/posts") | ||
| public class PostController { | ||
|
|
||
| private final PostService postService; | ||
|
|
||
| // Create | ||
| @PostMapping | ||
| public Res create(@Valid @RequestBody CreateReq req) { | ||
| return toRes(postService.create(req)); | ||
| } | ||
|
|
||
| // Read one | ||
| @GetMapping("/{id}") | ||
| public Res get(@PathVariable Long id) { | ||
| return toRes(postService.get(id)); | ||
| } | ||
|
|
||
| // Read all | ||
| @GetMapping | ||
|
Comment on lines
+23
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 Dto를 바로 반환하는 구조네요! 그런데 이 경우 서버에서 항상 기본값인 200 상태코드만 반환하게 됩니다. ResponseEntity를 적용해보시면 좋을 것 같습니다. |
||
| public List<Res> list() { | ||
| return postService.list().stream().map(this::toRes).toList(); | ||
| } | ||
|
|
||
| // Update | ||
| @PutMapping("/{id}") | ||
| public Res update(@PathVariable Long id, @Valid @RequestBody UpdateReq req) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Res라는 변수명은 나중에 다른 ResponseDto가 생기면 헷갈릴 수도 있을 것 같습니다! 의도가 드러나도록 조금 더 구체적으로 명명하면 더 좋을 것 같습니다 |
||
| return toRes(postService.update(id, req)); | ||
| } | ||
|
|
||
| // Delete | ||
| @DeleteMapping("/{id}") | ||
| public void delete(@PathVariable Long id) { | ||
| postService.delete(id); | ||
| } | ||
|
|
||
| // Like | ||
| @PostMapping("/{id}/like") | ||
| public Res like(@PathVariable Long id) { | ||
| return toRes(postService.like(id)); | ||
| } | ||
|
|
||
| private Res toRes(Post p) { | ||
| Res r = new Res(); | ||
| r.id = p.getId(); | ||
| r.content = p.getContent(); | ||
| r.username = p.getUsername(); | ||
| r.likes = p.getLikes(); | ||
| r.createdAt = p.getCreatedAt(); | ||
| r.updatedAt = p.getUpdatedAt(); | ||
| return r; | ||
| } | ||
|
Comment on lines
+58
to
+67
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이것도 조금 더 객체지향적으로 본다면 Res에 Post를 인자로 받는 생성자를 추가하고 Res 생성은 Res에만 위임하면 Controller 계층의 코드를 더 간결하게 구성할 수 있을 것 같습니다. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.example.devSns.web.post.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Size; | ||
| import java.time.LocalDateTime; | ||
|
|
||
| public class PostDtos { | ||
|
|
||
| public static class CreateReq { | ||
| @NotBlank @Size(max = 1000) | ||
| public String content; | ||
| @NotBlank | ||
| public String username; | ||
| } | ||
|
|
||
| public static class UpdateReq { | ||
| @NotBlank @Size(max = 1000) | ||
| public String content; | ||
| } | ||
|
|
||
| public static class Res { | ||
| public Long id; | ||
| public String content; | ||
| public String username; | ||
| public Integer likes; | ||
| public LocalDateTime createdAt; | ||
| public LocalDateTime updatedAt; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,11 @@ | ||
| spring.application.name=devSns | ||
| spring.datasource.url=jdbc:h2:mem:devsns;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE | ||
| spring.datasource.driverClassName=org.h2.Driver | ||
| spring.datasource.username=sa | ||
| spring.datasource.password= | ||
|
|
||
| spring.h2.console.enabled=true | ||
| spring.h2.console.path=/h2-console | ||
|
|
||
| spring.jpa.hibernate.ddl-auto=update | ||
| spring.jpa.show-sql=true | ||
| spring.jpa.properties.hibernate.format_sql=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h2 사용하실 때 더미 데이터 필요하시면 data.sql 파일에 작성하시고 application.properties에 더미데이터 생성 경로 적어두는 방식으로도 구성할 수 있습니다! 이러면 코드에 데이터 초기화 로직이 안 들어가도 돼서 깔끔하게 구성 가능합니다.