Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ out/

### VS Code ###
.vscode/

.env
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-security'

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JSON 파싱용

implementation 'io.github.cdimascio:dotenv-java:3.2.0'



}

tasks.named('test') {
Expand Down
30 changes: 15 additions & 15 deletions src/main/java/com/example/devSns/Comment/CommentController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.example.devSns.Comment.Dto.CreateCommentDto;
import com.example.devSns.Comment.Dto.UpdateCommentDto;

import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
Expand All @@ -12,6 +11,7 @@
import java.util.List;

@RestController
@RequestMapping("/api")
public class CommentController {

private final CommentService commentService;
Expand All @@ -20,7 +20,7 @@ public CommentController(CommentService commentService) {
this.commentService = commentService;
}

@PostMapping("posts/{post_id}/comments")
@PostMapping("/posts/{post_id}/comments")
public ResponseEntity<?> postComment(
@Valid
@RequestBody CreateCommentDto dto,
Expand All @@ -30,13 +30,13 @@ public ResponseEntity<?> postComment(
commentService.createComment(post_id, dto);
return ResponseEntity.status(HttpStatus.CREATED).build();
}catch (EntityNotFoundException e){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
return ResponseEntity.notFound().build();
}


}

@PostMapping("posts/{post_id}/comments/{comment_id}")
@PostMapping("/posts/{post_id}/comments/{comment_id}")
public ResponseEntity<?> postReplyComment(
@Valid
@RequestBody CreateCommentDto dto,
Expand All @@ -47,24 +47,24 @@ public ResponseEntity<?> postReplyComment(
commentService.createReplyComment(post_id, comment_id, dto);
return ResponseEntity.status(HttpStatus.CREATED).build();
}catch (EntityNotFoundException e){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
return ResponseEntity.notFound().build();
}
}

@GetMapping("posts/{post_id}/comments")
@GetMapping("/posts/{post_id}/comments")
public ResponseEntity<List<Comment>> getComments(@PathVariable("post_id") Long post_id) {
return ResponseEntity.status(HttpStatus.OK).body(commentService.getAllComments(post_id));
return ResponseEntity.ok().body(commentService.getAllComments(post_id));

}
@GetMapping("posts/{post_id}/comments/{comment_id}")
@GetMapping("/posts/{post_id}/comments/{comment_id}")
public ResponseEntity<Comment> getCommentsByPostId(
@PathVariable("post_id") Long post_id,
@PathVariable("comment_id") Long comment_id) {
try{
return ResponseEntity.status(HttpStatus.OK).body(commentService.getCommentById(post_id, comment_id));
return ResponseEntity.ok(commentService.getCommentById(post_id, comment_id));

}catch (EntityNotFoundException e){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
return ResponseEntity.notFound().build();
Comment on lines -64 to +67
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

피드백 반영 좋습니다!

}
}

Expand All @@ -75,21 +75,21 @@ public ResponseEntity<Comment> updateComment(
@Valid
@RequestBody UpdateCommentDto dto) {
try{
return ResponseEntity.status(HttpStatus.OK).body(commentService.updateComment(comment_id, dto));
return ResponseEntity.ok().body(commentService.updateComment(comment_id, dto));
}catch (EntityNotFoundException e){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
return ResponseEntity.notFound().build();
}


}
@DeleteMapping("comments/{comment_id}")
@DeleteMapping("/comments/{comment_id}")
public ResponseEntity<?> deleteComment(@PathVariable("comment_id") Long comment_id) {
try{
commentService.deleteCommentById(comment_id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
return ResponseEntity.noContent().build();
}
catch (EntityNotFoundException e){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
return ResponseEntity.notFound().build();
}
}

Expand Down
20 changes: 10 additions & 10 deletions src/main/java/com/example/devSns/Heart/Heart.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(
name = "heart",
indexes = {
@Index(name ="idx_heart_post_liked", columnList = "post_id , like_status")
}
)
Comment on lines +14 to +19
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이 부분 생각을 좀 더 해봤는데, Heart 엔티티의 liked속성이 false라면 튜플이 존재하지 않는 상태랑 차이가 없을 것 같아요 그리고 index의 경우 속성이 중복되지 않을수록 이점이 큰데 현재는 like속성도 사실상 boolean이라 상태가 2가지밖에 없네요 그래서 제 생각에는 index를 아예 없애버리고 heart 객체가 존재 -> 좋아요, heart 객체가 없음 -> 좋아요 x 이렇게 구성하면 토글 로직도 간단하게 떨어질 것 같아요

public class Heart {

@Id
Expand All @@ -29,21 +35,15 @@ public class Heart {
// 리포지터리에 메소드로 계산하면 되겠지?

@Column(name ="like_status", nullable = false)
@Enumerated(EnumType.STRING)
private LikeStatus like;
private boolean liked;

public Heart(Post post, Member member, LikeStatus heart) {
public Heart(Post post, Member member, boolean like) {
this.post = post;
this.member = member;
this.like = heart;
this.liked = like;
}

public void toggleLike() {

if (like == LikeStatus.LIKE) {
like = LikeStatus.NONE;
}else if(like == LikeStatus.NONE){
like = LikeStatus.LIKE;
}
this.liked = !this.liked;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ public interface HeartRepository extends JpaRepository<Heart, Long> {

Boolean existsByPostIdAndMemberId(Long postId, Long memberId);
Optional<Heart> findByPostIdAndMemberId(Long postId, Long memberId);
long countByPostIdAndLike(Long postId, LikeStatus like);
long countByPostIdAndLiked(Long postId, Boolean Liked);
}
53 changes: 53 additions & 0 deletions src/main/java/com/example/devSns/Jwt/JwtUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.devSns.Jwt;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtUtil {


private final String secret;// 실제 프로젝트에서는 환경변수로 관리
private final long EXPIRATION = 1000L * 60 * 60; // 1시간

public JwtUtil(@Value("${jwt.secret}") String secret) {
this.secret = secret;
}
Comment on lines +14 to +19
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

잘 구성해주셨네요! 다만 현재 구조에서는 1시간마다 재인증 해야 하는 구조라 리프레시 토큰도 추후에 도입해보면 좋을 것 같습니다!

public String createToken(Long memberId) {
Date now = new Date();
Date expireTime = new Date(now.getTime() + EXPIRATION);

return Jwts.builder()
.setSubject(String.valueOf(memberId))
.setIssuedAt(now)
.setExpiration(expireTime)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}

public boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}

public Long extractMemberId(String token) {
return Long.valueOf(
Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject()
);
}
}

27 changes: 27 additions & 0 deletions src/main/java/com/example/devSns/Login/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.devSns.Login;

import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class LoginController {

private final LoginService loginService;

@PostMapping("/login")
public String login(@RequestBody LoginRequest request) {
return loginService.login(request.getEmail(), request.getPassword());
}

@Data
static class LoginRequest {
private String email;
private String password;
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/example/devSns/Login/LoginService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.devSns.Login;

import com.example.devSns.Jwt.JwtUtil;
import com.example.devSns.Member.Member;
import com.example.devSns.Member.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class LoginService {

private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;

public String login(String email, String rawPassword) {

Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 이메일입니다."));

if (!passwordEncoder.matches(rawPassword, member.getPassword())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}

// JWT 발급
return jwtUtil.createToken(member.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,29 @@

import com.example.devSns.Member.Gender;
import com.example.devSns.Member.Member;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.*;

public record SignMemberRequestDto(

@NotBlank(message = "공백 또는 null 값은 허용하지 않습니다")
@NotBlank(message = "이름은 공백 또는 null 값은 허용하지 않습니다")
String nickname,

@NotBlank(message = "공백 또는 null 값은 허용하지 않습니다")
@NotBlank(message = "이메일은 공백 또는 null 값은 허용하지 않습니다")
@Email(message ="올바른 이메일 양식이 아닙니다")
String email,

@NotBlank(message = "공백 또는 null 값은 허용하지 않습니다")
@NotBlank(message = "비밀번호는 공백 또는 null 값은 허용하지 않습니다")
Comment on lines +12 to +16
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

좋습니다! message 옵션 상세히 적어둬야 나중에 프론트분들과 협업할 때 좋은 것 같더라고요

@Size(min = 8, max =16, message = "비밀번호는 8자 이상 16자 이하로 설정해주세요")
@Pattern(
regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]+$",
message = "영어, 숫자, 특수문자를 각각 최소 1개 이상 포함해야 합니다"
)
String password,

@NotNull(message = "공백 또는 null 값은 허용하지 않습니다")
@NotNull(message = "성별은 공백 또는 null 값은 허용하지 않습니다")
Gender gender,

@NotNull(message = "공백 또는 null 값은 허용하지 않습니다")
@NotNull(message = "나이는 공백 또는 null 값은 허용하지 않습니다")
Integer age

) {
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/example/devSns/Member/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class MemberController {

private final MemberService memberService;
Expand All @@ -22,15 +22,15 @@ public ResponseEntity<GetMemberResponseDto> createMember(
@Valid
@RequestBody SignMemberRequestDto dto) {

return ResponseEntity.status(HttpStatus.CREATED).body(memberService.createMember(dto));
return ResponseEntity.status(201).body(memberService.createMember(dto));
}

// 특정 멤버 검색하기
@GetMapping("/members/{member_id}")
public ResponseEntity<GetMemberResponseDto> getMember(
@PathVariable(name ="member_id") Long memberId
) {
return ResponseEntity.status(200).body(memberService.getMemberById(memberId));
return ResponseEntity.ok().body(memberService.getMemberById(memberId));
}

// like_status toggle
Expand All @@ -52,7 +52,7 @@ public ResponseEntity<?> convertLike(
public ResponseEntity<GetMemberPostAndCommentResponseDto> getMemberPostAndComment(
@PathVariable(name = "member_id") Long memberId
){
return ResponseEntity.status(200).body(memberService.getMemberPostAndComment(memberId));
return ResponseEntity.ok().body(memberService.getMemberPostAndComment(memberId));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {
import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
Loading