Skip to content

Commit

Permalink
Merge pull request #15 from study-hub-inu/dev
Browse files Browse the repository at this point in the history
[Feat] : BookMark CRD 구현
  • Loading branch information
elyudwo authored Sep 7, 2023
2 parents dbab77c + 9224f24 commit a99ab8d
Show file tree
Hide file tree
Showing 34 changed files with 536 additions and 103 deletions.
36 changes: 36 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
//querydsl 추가
buildscript {
dependencies {
classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10")
}
}

plugins {
id 'java'
id 'org.springframework.boot' version '2.7.14'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

apply plugin: "com.ewerk.gradle.plugins.querydsl"

group = 'kr.co.study-hub-appcenter'
version = '0.0.1-SNAPSHOT'

Expand Down Expand Up @@ -55,6 +64,33 @@ dependencies {
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// queryDsl
implementation 'com.querydsl:querydsl-jpa'
implementation 'com.querydsl:querydsl-apt'

}

//querydsl 추가
//def querydslDir = 'src/main/generated'
def querydslDir = "$buildDir/generated/querydsl"

querydsl {
library = "com.querydsl:querydsl-apt"
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main {
java {
srcDirs = ['src/main/java', querydslDir]
}
}
}
compileQuerydsl{
options.annotationProcessorPath = configurations.querydsl
}
configurations {
querydsl.extendsFrom compileClasspath
}

tasks.named('test') {
Expand Down
Binary file added img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package kr.co.studyhubinu.studyhubserver.bookmark.controller;

import io.swagger.v3.oas.annotations.Operation;
import kr.co.studyhubinu.studyhubserver.bookmark.dto.request.CreateBookMarkRequest;
import kr.co.studyhubinu.studyhubserver.bookmark.dto.response.FindBookMarkResponse;
import kr.co.studyhubinu.studyhubserver.bookmark.service.BookMarkService;
import kr.co.studyhubinu.studyhubserver.user.dto.data.UserId;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

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

private final BookMarkService bookMarkService;

@Operation(summary = "북마크 조회", description = "Http 헤더에 JWT accessToken 을 Json 형식으로 보내주시면 됩니다.")
@GetMapping("")
public ResponseEntity<Slice<FindBookMarkResponse>> findBookMark(UserId userId) {
return ResponseEntity.ok(bookMarkService.findBookMark(userId.getId()));
}

@Operation(summary = "북마크 저장", description = "Http 헤더에 JWT accessToken, 바디에 PostId를 Json 형식으로 보내주시면 됩니다.")
@PostMapping("")
public ResponseEntity<Void> saveBookMark(UserId userId, CreateBookMarkRequest request) {
bookMarkService.saveBookMark(userId.getId(), request);
return new ResponseEntity<>(HttpStatus.CREATED);
}

@Operation(summary = "북마크 삭제", description = "바디에 BookMarkId 를 Json 형식으로 보내주시면 됩니다.")
@DeleteMapping("/{bookMarkId}")
public ResponseEntity<Void> deleteBookMark(@PathVariable("bookMarkId") Long bookMarkId) {
bookMarkService.deleteBookMark(bookMarkId);
return ResponseEntity.noContent().build();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kr.co.studyhubinu.studyhubserver.bookmark.domain;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -21,4 +22,10 @@ public class BookMarkEntity {

@Column(name = "user_id")
private Long userId;

@Builder
public BookMarkEntity(Long postId, Long userId) {
this.postId = postId;
this.userId = userId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kr.co.studyhubinu.studyhubserver.bookmark.dto.request;


import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;

import javax.validation.constraints.NotBlank;

@Getter
public class CreateBookMarkRequest {

@Schema(description = "게시글 id", example = "1")
@NotBlank
private Long postId;

public CreateBookMarkRequest(Long postId) {
this.postId = postId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kr.co.studyhubinu.studyhubserver.bookmark.dto.response;

import lombok.Getter;

@Getter
public class FindBookMarkResponse {

private Long postId;

private String title;
private String content;
private int leftover;


public FindBookMarkResponse(Long postId, String title, String content, int leftover) {
this.postId = postId;
this.title = title;
this.content = content;
this.leftover = leftover;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kr.co.studyhubinu.studyhubserver.bookmark.repository;

import kr.co.studyhubinu.studyhubserver.bookmark.domain.BookMarkEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;


@Repository
public interface BookMarkRepository extends JpaRepository<BookMarkEntity, Long>, BookMarkRepositoryCustom {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package kr.co.studyhubinu.studyhubserver.bookmark.repository;

import kr.co.studyhubinu.studyhubserver.study.domain.StudyPostEntity;
import org.springframework.data.domain.Slice;

public interface BookMarkRepositoryCustom {

Slice<StudyPostEntity> findPostByBookMark(Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package kr.co.studyhubinu.studyhubserver.bookmark.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import kr.co.studyhubinu.studyhubserver.bookmark.domain.QBookMarkEntity;
import kr.co.studyhubinu.studyhubserver.study.domain.QStudyPostEntity;
import kr.co.studyhubinu.studyhubserver.study.domain.StudyPostEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Repository;

import java.util.List;

import static kr.co.studyhubinu.studyhubserver.bookmark.domain.QBookMarkEntity.*;
import static kr.co.studyhubinu.studyhubserver.study.domain.QStudyPostEntity.*;

@RequiredArgsConstructor
@Repository
public class BookMarkRepositoryCustomImpl implements BookMarkRepositoryCustom {

private final JPAQueryFactory jpaQueryFactory;

@Override
public Slice<StudyPostEntity> findPostByBookMark(Long userId) {
QBookMarkEntity bookMark = bookMarkEntity;
QStudyPostEntity post = studyPostEntity;

return (Slice<StudyPostEntity>) jpaQueryFactory
.select(post)
.from(post)
.join(bookMark)
.where(bookMark.userId.eq(userId), bookMark.postId.eq(post.id))
.limit(100);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package kr.co.studyhubinu.studyhubserver.bookmark.service;

import kr.co.studyhubinu.studyhubserver.bookmark.domain.BookMarkEntity;
import kr.co.studyhubinu.studyhubserver.bookmark.dto.request.CreateBookMarkRequest;
import kr.co.studyhubinu.studyhubserver.bookmark.dto.response.FindBookMarkResponse;
import kr.co.studyhubinu.studyhubserver.bookmark.repository.BookMarkRepository;
import kr.co.studyhubinu.studyhubserver.exception.bookmark.BookMarkNotFoundException;
import kr.co.studyhubinu.studyhubserver.study.domain.StudyPostEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.stream.Collectors;


@Service
@RequiredArgsConstructor
@Transactional
public class BookMarkService {

private final BookMarkRepository bookMarkRepository;

public void saveBookMark(Long id, CreateBookMarkRequest request) {
bookMarkRepository.save(BookMarkEntity.builder()
.userId(id)
.postId(request.getPostId())
.build());
}

public void deleteBookMark(Long bookMarkId) {
BookMarkEntity bookMark = bookMarkRepository.findById(bookMarkId).orElseThrow(BookMarkNotFoundException::new);
bookMarkRepository.delete(bookMark);
}

public Slice<FindBookMarkResponse> findBookMark(Long id) {
Slice<StudyPostEntity> postEntities = bookMarkRepository.findPostByBookMark(id);

Slice<FindBookMarkResponse> responses = (Slice<FindBookMarkResponse>) postEntities.stream()
.map(postEntity -> {
FindBookMarkResponse response = new FindBookMarkResponse(postEntity.getId(), postEntity.getTitle(), postEntity.getContent(), postEntity.getStudyPerson());
return response;
})
.collect(Collectors.toList());
return responses;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kr.co.studyhubinu.studyhubserver.config;

import kr.co.studyhubinu.studyhubserver.user.domain.QUserEntity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
Expand All @@ -19,8 +20,9 @@ public CorsFilter corsFilter() {
configuration.addAllowedMethod("*");
configuration.setAllowCredentials(true);

source.registerCorsConfiguration("/**", configuration);
source.registerCorsConfiguration("/**", configuration);

return new CorsFilter(source);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package kr.co.studyhubinu.studyhubserver.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Configuration
public class JPAConfig {

@PersistenceContext
private EntityManager entityManager;

@Bean
public JPAQueryFactory queryFactory() {
return new JPAQueryFactory(entityManager);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
// 시큐리티가 filter 가지고있는데 그 필터중에 BasicAuthenticationFilter 라는 것이 있다.
// 권한이나 인증이 필요한 특정 주소를 요청했을때 위 필터를 무조건 타게 되어있음
// 만약 권한이나 인증이 필요한 주소가 아니라면 이 필터를 안탄다.
@Slf4j
@Component
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

Expand All @@ -41,15 +40,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

if(isHeaderVerify(request)) {
String accessToken = request.getHeader(JwtProperties.ACCESS_HEADER_STRING);
String refreshToken = request.getHeader(JwtProperties.REFRESH_HEADER_STRING);

try {
PrincipalDetails principalDetails = jwtProvider.accessTokenVerify(accessToken);

Authentication authentication = new UsernamePasswordAuthenticationToken(principalDetails, null);
Authentication authentication = new UsernamePasswordAuthenticationToken(principalDetails, null, null);

SecurityContextHolder.getContext().setAuthentication(authentication);
} catch(TokenExpiredException e) {
// 에러 발생 시켜서 보내줌
throw new TokenNotFoundException();
}
}
Expand All @@ -60,6 +58,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
private boolean isHeaderVerify(HttpServletRequest request) {
String accessHeader = request.getHeader(JwtProperties.ACCESS_HEADER_STRING);

return accessHeader != null && !accessHeader.startsWith(JwtProperties.TOKEN_PREFIX);
if(accessHeader == null) {
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package kr.co.studyhubinu.studyhubserver.config.jwt;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

Expand All @@ -16,18 +15,11 @@ public class JwtController {

private final JwtProvider jwtProvider;

@GetMapping("/accessToken")
public ResponseEntity<?> accessTokenIssued(@RequestBody JwtDto jwtDto) {
String accessToken = jwtProvider.reissuedAccessToken(jwtDto);

return new ResponseEntity<>(accessToken, HttpStatus.OK);
}

@GetMapping("/refreshToken")
public ResponseEntity<?> refreshTokenIssued(@RequestBody JwtDto jwtDto) {
@PostMapping("/accessToken")
public ResponseEntity<JwtResponseDto> accessTokenIssued(@RequestBody JwtDto jwtDto) {
String accessToken = jwtProvider.reissuedAccessToken(jwtDto);
String refreshToken = jwtProvider.reissuedRefreshToken(jwtDto);

return new ResponseEntity<>(refreshToken, HttpStatus.OK);
return ResponseEntity.ok(new JwtResponseDto(accessToken, refreshToken));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
@Getter
public class JwtDto {

private Long id;
private String refreshToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

@Getter
public class JwtProperties {
public static final int EXPIRATION_TIME = 864000000; // 10일
public static final String TOKEN_PREFIX = "Bearer ";
public static final String ACCESS_HEADER_STRING = "ACCESS_TOKEN";
public static final String ACCESS_HEADER_STRING = "Authorization";
public static final String REFRESH_HEADER_STRING = "REFRESH_TOKEN";
}
Loading

0 comments on commit a99ab8d

Please sign in to comment.