Skip to content
Merged
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
4 changes: 0 additions & 4 deletions cookies.txt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,49 @@

import com.back.domain.post.post.entity.Post;
import com.back.domain.post.post.entity.PostImage;
import java.util.List;
import com.back.domain.post.post.enums.PostStatus;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@Builder
public class MyHistoryPostItemDto {
private Long id;
private Long postId;
private String categoryName;
private String userNickName;
private String title;
private String content;
private List<String> imageUrls;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private PostStatus status;
private Integer likeCount;
private Integer commentCount;
private Integer viewCount;

public static MyHistoryPostItemDto from(Post p) {
String categoryName = p.getCategory() != null ? p.getCategory().getName() : null;
String userNickName = p.getUser() != null ? p.getUser().getNickname() : null;
return MyHistoryPostItemDto.builder()
.id(p.getId())
.postId(p.getId())
.categoryName(categoryName)
.userNickName(userNickName)
.title(p.getTitle())
.content(p.getContent())
.imageUrls(p.getImages().stream()
.map(PostImage::getUrl)
.toList())
.map(PostImage::getUrl)
.toList())
.createdAt(p.getCreatedAt())
.updatedAt(p.getUpdatedAt())
.status(p.getStatus())
.likeCount(p.getLikeCount())
.commentCount(p.getCommentCount())
.viewCount(p.getViewCount())
.build();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import com.back.domain.post.comment.entity.Comment;
import com.back.domain.post.comment.enums.CommentStatus;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.back.domain.post.post.dto.request.PostCreateRequestDto;
import com.back.domain.post.post.dto.request.PostSortScrollRequestDto;
import com.back.domain.post.post.dto.request.PostUpdateRequestDto;
import com.back.domain.post.post.dto.response.PostLikeResponseDto;
import com.back.domain.post.post.dto.response.PostResponseDto;
import com.back.domain.post.post.service.PostService;
import com.back.global.rsData.RsData;
Expand Down Expand Up @@ -114,10 +115,9 @@ public RsData<Void> deletePost(
*/
@PostMapping("/{postId}/like")
@Operation(summary = "게시글 추천")
public RsData<Void> toggleLike(
public RsData<PostLikeResponseDto> toggleLike(
@PathVariable Long postId
) {
postService.toggleLike(postId);
return RsData.successOf(null); // code=200, message="success"
return RsData.successOf(postService.toggleLike(postId)); // code=200, message="success"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.back.domain.post.post.dto.response;

import com.back.domain.post.post.entity.PostLike;
import com.back.domain.post.post.enums.PostLikeStatus;

public record PostLikeResponseDto(
PostLikeStatus status
) {

public PostLikeResponseDto(PostLike postLike) {
this(
postLike.getStatus()
);
}
}
23 changes: 14 additions & 9 deletions src/main/java/com/back/domain/post/post/service/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.back.domain.post.post.dto.request.PostCreateRequestDto;
import com.back.domain.post.post.dto.request.PostSortScrollRequestDto;
import com.back.domain.post.post.dto.request.PostUpdateRequestDto;
import com.back.domain.post.post.dto.response.PostLikeResponseDto;
import com.back.domain.post.post.dto.response.PostResponseDto;
import com.back.domain.post.post.entity.Post;
import com.back.domain.post.post.entity.PostImage;
Expand Down Expand Up @@ -233,7 +234,7 @@ public void deletePost(Long postId) {

// 게시글 추천(좋아요) 토글 로직
@Transactional
public void toggleLike(Long postId) {
public PostLikeResponseDto toggleLike(Long postId) {
User user = rq.getActor(); // 현재 로그인한 사용자

Post post = postRepository.findById(postId)
Expand All @@ -248,6 +249,8 @@ public void toggleLike(Long postId) {
post.decreaseLikeCount();
// 활동 점수: 추천 취소 시 -0.1
abvScoreService.revokeForLike(user.getId());

return new PostLikeResponseDto(existingLike.get().getStatus());
} else {
// 추천 추가
PostLike postLike = PostLike.builder()
Expand All @@ -259,15 +262,17 @@ public void toggleLike(Long postId) {
post.increaseLikeCount();
// 활동 점수: 추천 추가 시 +0.1
abvScoreService.awardForLike(user.getId());
}

// 게시글 작성자에게 알림 전송
notificationService.sendNotification(
post.getUser(),
post,
NotificationType.LIKE,
user.getNickname() + " 님이 추천을 남겼습니다."
);
// 게시글 작성자에게 알림 전송
notificationService.sendNotification(
post.getUser(),
post,
NotificationType.LIKE,
user.getNickname() + " 님이 추천을 남겼습니다."
);

return new PostLikeResponseDto(postLike.getStatus());
}
}

// 태그 추가 메서드
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.back.domain.user.controller;

import com.back.domain.user.dto.RefreshTokenResDto;
import com.back.domain.user.dto.UserMeResDto;
import com.back.domain.user.service.UserAuthService;
import com.back.global.rsData.RsData;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -11,6 +12,7 @@
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand Down Expand Up @@ -50,4 +52,16 @@ public RsData<Void> logout(HttpServletRequest request, HttpServletResponse respo
userAuthService.logout(request, response);
return RsData.of(200, "로그아웃되었습니다.");
}

@Operation(summary = "현재 로그인한 유저 정보 조회", description = "세션 유효성 검증 및 사용자 정보 반환")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "인증된 유저 정보 반환 성공"),
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자"),
@ApiResponse(responseCode = "500", description = "서버 내부 오류")
})
@GetMapping("/me")
public RsData<UserMeResDto> getCurrentUser() {
UserMeResDto userInfo = userAuthService.getCurrentUser();
return RsData.of(200, "인증된 유저 정보 반환 성공", userInfo);
}
}
28 changes: 28 additions & 0 deletions src/main/java/com/back/domain/user/dto/UserMeResDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.back.domain.user.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class UserMeResDto {
@JsonProperty("user")
private final UserInfo user;

@Getter
@Builder
public static class UserInfo {
private final String id;
private final String email;
private final String nickname;

@JsonProperty("is_first_login")
private final Boolean isFirstLogin;

@JsonProperty("abv_degree")
private final Double abvDegree;

private final String provider;
}
}
69 changes: 68 additions & 1 deletion src/main/java/com/back/domain/user/service/UserAuthService.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.back.domain.user.service;

import com.back.domain.user.dto.RefreshTokenResDto;
import com.back.domain.user.dto.UserMeResDto;
import com.back.domain.user.entity.User;
import com.back.domain.user.repository.UserRepository;
import com.back.global.exception.ServiceException;
import com.back.global.jwt.JwtUtil;
import com.back.global.jwt.refreshToken.entity.RefreshToken;
import com.back.global.jwt.refreshToken.repository.RefreshTokenRepository;
import com.back.global.jwt.refreshToken.service.RefreshTokenService;
import com.back.global.rq.Rq;
import com.back.global.rsData.RsData;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -20,6 +22,8 @@
import java.util.Optional;
import java.util.Set;

import static org.springframework.security.core.context.SecurityContextHolder.*;

@Slf4j
@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -76,6 +80,7 @@ public class UserAuthService {
private final UserRepository userRepository;
private final RefreshTokenService refreshTokenService;
private final RefreshTokenRepository refreshTokenRepository;
private final Rq rq;

//OAuth 관련

Expand Down Expand Up @@ -196,19 +201,81 @@ public RefreshTokenResDto refreshTokens(HttpServletRequest request, HttpServletR

//토큰 끊기면서 OAuth 자동 로그아웃
public void logout(HttpServletRequest request, HttpServletResponse response) {
// 1. RefreshToken DB에서 삭제
String refreshToken = jwtUtil.getRefreshTokenFromCookie(request);

if (refreshToken != null) {
refreshTokenService.revokeToken(refreshToken);
}

// 2. JWT 쿠키 삭제
jwtUtil.removeAccessTokenCookie(response);
jwtUtil.removeRefreshTokenCookie(response);

// 3. Spring Security 세션 무효화 (Redis 포함)
try {
if (request.getSession(false) != null) {
request.getSession().invalidate();
log.debug("세션 무효화");
}
} catch (IllegalStateException e) {
log.debug("세션이 이미 무효화되어 있음");
}

// 4. SecurityContext 클리어
clearContext();

log.info("로그아웃 완료 - JWT, 세션, SecurityContext 모두 정리됨");
}

@Transactional
public void setFirstLoginFalse(Long id) {
Optional<User> userOpt = userRepository.findById(id);
userOpt.ifPresent(user -> user.setFirstLogin(false));
}

// 현재 로그인한 사용자 정보 조회 (세션 검증용)
public UserMeResDto getCurrentUser() {
try {
User actor = rq.getActor();

if (actor == null) {
log.debug("인증되지 않은 사용자");
throw new ServiceException(401, "인증되지 않은 사용자");
}

Optional<User> userOpt = userRepository.findById(actor.getId());
if (userOpt.isEmpty()) {
log.warn("사용자 ID {}를 DB에서 찾을 수 없음 (토큰은 유효하나 사용자 삭제됨)", actor.getId());
throw new ServiceException(401, "인증되지 않은 사용자");
}

User user = userOpt.get();
String provider = extractProvider(user.getOauthId());

return UserMeResDto.builder()
.user(UserMeResDto.UserInfo.builder()
.id(user.getId().toString())
.email(user.getEmail())
.nickname(user.getNickname())
.isFirstLogin(user.isFirstLogin())
.abvDegree(user.getAbvDegree())
.provider(provider)
.build())
.build();

} catch (ServiceException e) {
throw e;
} catch (Exception e) {
log.error("사용자 정보 조회 중 서버 오류 발생: {}", e.getMessage(), e);
throw new ServiceException(500, "서버 내부 오류");
}
}

private String extractProvider(String oauthId) {
if (oauthId == null || oauthId.isBlank()) {
return "unknown";
}
String[] parts = oauthId.split("_", 2);
return parts.length > 0 ? parts[0] : "unknown";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "refresh_tokens")
@Getter
@Setter
Expand All @@ -20,24 +23,33 @@ public class RefreshToken {
@Column(nullable = false)
private Long userId;

@Column(nullable = false)
@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;

@Column(nullable = false)
private LocalDateTime expiresAt;

@Column(nullable = false)
private LocalDateTime lastUsedAt;

public static RefreshToken create(String token, Long userId, long ttlSeconds) {
LocalDateTime now = LocalDateTime.now();
return RefreshToken.builder()
.token(token)
.userId(userId)
.createdAt(now)
.lastUsedAt(now)
.expiresAt(now.plusSeconds(ttlSeconds))
.build();
}

public boolean isExpired() {
return LocalDateTime.now().isAfter(this.expiresAt);
}

public boolean isIdleExpired(long idleTimeoutHours) {
return LocalDateTime.now().isAfter(this.lastUsedAt.plusMinutes(idleTimeoutHours));

}

}
Loading