Skip to content

Commit

Permalink
Merge pull request #105 from Review-zip/dev
Browse files Browse the repository at this point in the history
[Feat] ์นด์นด์˜ค ์†Œ์…œ๋กœ๊ทธ์ธ
  • Loading branch information
hsuush authored Jan 29, 2024
2 parents 06b3e2d + f0786dd commit d62828b
Show file tree
Hide file tree
Showing 22 changed files with 382 additions and 30 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'io.jsonwebtoken:jjwt:0.9.1' // ์ž๋ฐ” jwt ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
implementation 'javax.xml.bind:jaxb-api:2.3.1' // XML ๋ฌธ์„œ์™€ Java ๊ฐ์ฒด ๊ฐ„ ๋งคํ•‘์„ ์ž๋™ํ™”
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' // aws s3

Expand Down
2 changes: 1 addition & 1 deletion config
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import com.example.ReviewZIP.domain.user.Users;
import com.example.ReviewZIP.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.*;

@Entity
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "follows")
public class Follows extends BaseEntity {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.example.ReviewZIP.domain.follow;

import com.example.ReviewZIP.global.response.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/v1/follows")
public class FollowsController {
private final FollowsService followsService;

@PostMapping("/users/{userId}")
@Operation(summary = "์œ ์ € ํŒ”๋กœ์šฐ ํ•˜๊ธฐ API",description = "์œ ์ €์˜ id๋ฅผ ๋ฐ›์•„ ํ•ด๋‹น ์œ ์ € ํŒ”๋กœ์šฐ")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200",description = "OK, ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER404", description = "ํŒ”๋กœ์šฐ ํ•  ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค",content = @Content(schema = @Schema(implementation = ApiResponse.class))),
})
@Parameters({
@Parameter(name = "userId", description = "ํŒ”๋กœ์šฐํ•  ์œ ์ €์˜ ์•„์ด๋””"),
})
public ApiResponse<Void> follow(@PathVariable(name="userId") Long userId) {
Follows follows = followsService.createFollowing(userId);

return ApiResponse.onSuccess(null);
}

@DeleteMapping("/users/{userId}")
@Operation(summary = " ์œ ์ € ์–ธํŒ”๋กœ์šฐ ํ•˜๊ธฐ API",description = "์œ ์ €์˜ id๋ฅผ ๋ฐ›์•„ ํ•ด๋‹น ์œ ์ € ์–ธํŒ”๋กœ์šฐ")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200",description = "OK, ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "USER404", description = "์–ธํŒ”๋กœ์šฐ ํ•  ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค",content = @Content(schema = @Schema(implementation = ApiResponse.class))),
})
@Parameters({
@Parameter(name = "userId", description = "ํŒ”๋กœ์šฐ ์ทจ์†Œํ•  ์œ ์ €์˜ ์•„์ด๋””"),
})
public ApiResponse<Void> unfollowUser(@PathVariable(name="userId")Long userId){
followsService.unfollowUser(userId);

return ApiResponse.onSuccess(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.ReviewZIP.domain.follow;

import com.example.ReviewZIP.domain.user.Users;

public class FollowsConverter {
public static Follows toFollows(Users sender, Users receiver){
return Follows.builder()
.sender(sender)
.receiver(receiver)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;


@Repository
public interface FollowsRepository extends JpaRepository<Follows, Long> {
Page<Follows> findAllBySender(Users sender, PageRequest pageRequest);
Page<Follows> findAllByReceiver(Users user, PageRequest pageRequest);

Follows getBySenderAndReceiver(Users sender, Users receiver);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.example.ReviewZIP.domain.follow;


import com.example.ReviewZIP.domain.user.Users;
import com.example.ReviewZIP.domain.user.UsersRepository;
import com.example.ReviewZIP.global.response.code.resultCode.ErrorStatus;
import com.example.ReviewZIP.global.response.exception.handler.UsersHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class FollowsService {
private final FollowsRepository followsRepository;
private final UsersRepository usersRepository;


@Transactional
public Follows createFollowing(Long userId) {
Users sender = usersRepository.getById(1L);
Users receiver = usersRepository.findById(userId).orElseThrow(() -> new UsersHandler(ErrorStatus.USER_NOT_FOUND));

Follows newFollow = FollowsConverter.toFollows(sender, receiver);
return followsRepository.save(newFollow);
}

@Transactional
public void unfollowUser(Long userId){
Users sender = usersRepository.findById(1L).orElseThrow(()->new UsersHandler(ErrorStatus.USER_NOT_FOUND));
Users receiver = usersRepository.findById(userId).orElseThrow(()->new UsersHandler(ErrorStatus.USER_NOT_FOUND));

Follows unfollow = followsRepository.getBySenderAndReceiver(sender, receiver);

followsRepository.delete(unfollow);
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/example/ReviewZIP/domain/jwt/JwtProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.ReviewZIP.domain.jwt;

import lombok.Getter;
import lombok.Setter;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Getter
@Setter
@Component
@ConfigurationProperties("jwt")
public class JwtProperties {
@Value("${jwt.issuer}")
private String issuer;

@Value("${jwt.secretKey}")
private String secretKey;
}
54 changes: 54 additions & 0 deletions src/main/java/com/example/ReviewZIP/domain/jwt/TokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.example.ReviewZIP.domain.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Date;

@RequiredArgsConstructor
@Service
public class TokenProvider {
private final JwtProperties jwtProperties;

public String makeToken(Long userId){
Date now = new Date();
Date exp = new Date(now.getTime() + 3600000); //1์‹œ๊ฐ„ ๋งŒ๋ฃŒ ๊ธฐ์ค€
Claims claims = Jwts.claims();
claims.put("userid", userId);

return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer(jwtProperties.getIssuer())
.setIssuedAt(now)
.setExpiration(exp)
.setClaims(claims)
//.setSubject(userId.toString())
.signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey())
.compact();
}

public Long getUserId(String token){
Claims claims = Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token)
.getBody();
return claims.get("userId", Long.class);
}

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


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.ReviewZIP.domain.oauth;

import com.example.ReviewZIP.domain.jwt.TokenProvider;
import com.example.ReviewZIP.domain.oauth.dto.request.OauthRequestDto;
import com.example.ReviewZIP.global.response.ApiResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequiredArgsConstructor
@RequestMapping("/v1/oauth")
public class KakaoController {

private final OauthService oauthService;

// ์œ ์ € ๊ด€๋ จ token ๋ฐ›์€ ํ›„ ํ•ด๋‹น ์ •๋ณด๋กœ accessToken ๋ฐœ๊ธ‰
@PostMapping("/kakao")
public ApiResponse<Map<String, Object>> sendAccessToken(@RequestBody OauthRequestDto.kakaoTokenRequest request) throws JsonProcessingException {

List<String> kakaoUserInfoList = oauthService.getKakaoUserInfo(request);

return ApiResponse.onSuccess(oauthService.generateAccessToken(kakaoUserInfoList.get(0),kakaoUserInfoList.get(1) , kakaoUserInfoList.get(2)));

}
}
92 changes: 92 additions & 0 deletions src/main/java/com/example/ReviewZIP/domain/oauth/OauthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.example.ReviewZIP.domain.oauth;

import com.example.ReviewZIP.domain.jwt.TokenProvider;
import com.example.ReviewZIP.domain.oauth.dto.request.OauthRequestDto;
import com.example.ReviewZIP.domain.user.Users;
import com.example.ReviewZIP.domain.user.UsersRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.util.*;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OauthService {
private final UsersRepository usersRepository;
private final TokenProvider tokenProvider;

@Transactional
public Long createUser(String id, String nickname, String email){
Users newUser = Users.builder()
.social(id)
.nickname(nickname)
.name(nickname)
.email(email)
.build();
usersRepository.save(newUser);

return newUser.getId();
}

@Transactional
public Map<String, Object> generateAccessToken(String id, String nickname, String email){
boolean exist = usersRepository.existsBySocial(id);
Long userId;

if(!exist) {
// ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„๋•Œ -> ์œ ์ € ์ƒ์„ฑ
userId = createUser(id, nickname, email);
} else {
// ์กด์žฌํ•  ๊ฒฝ์šฐ ํ•ด๋‹น ์œ ์ €์˜ userId ๋ฐ˜ํ™˜
userId = usersRepository.getBySocial(id).getId();
}

String accessToken = tokenProvider.makeToken(userId);

Map<String, Object> map = new LinkedHashMap<>();
map.put("accessToken", accessToken);

return map;
}

public List<String> getKakaoUserInfo(OauthRequestDto.kakaoTokenRequest request) throws JsonProcessingException {
String token = request.getToken();

HttpHeaders headers = new HttpHeaders();
headers.set("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
headers.set("Authorization", "Bearer " + token);

HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest = new HttpEntity<>(null, headers);

RestTemplate restTemplate = new RestTemplate();

ResponseEntity<String> response = restTemplate.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.POST,
kakaoProfileRequest,
String.class
);

ObjectMapper objectMapper = new ObjectMapper();
String responseBody = response.getBody();
JsonNode jsonNode = objectMapper.readTree(responseBody);
String id = jsonNode.get("id").asText();
String nickname = jsonNode.get("properties").get("nickname").asText();
String email = jsonNode.get("kakao_account").get("email").asText();

List<String> kakaoUserInfoList = Arrays.asList(id, nickname, email);

return kakaoUserInfoList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.ReviewZIP.domain.oauth.dto.Response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;


public class OauthResponseDto {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.ReviewZIP.domain.oauth.dto.request;

import lombok.Getter;


public class OauthRequestDto {

@Getter
public static class kakaoTokenRequest{
String token;
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/example/ReviewZIP/domain/post/Posts.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.example.ReviewZIP.domain.store.Stores;
import com.example.ReviewZIP.domain.user.Users;
import com.example.ReviewZIP.global.entity.BaseEntity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -21,6 +22,7 @@
@NoArgsConstructor
@Table(name = "posts")
public class Posts extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class PostsController {
@Operation(summary = "ํŠน์ • ๊ฒŒ์‹œ๊ธ€์˜ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ API",description = "๊ฒŒ์‹œ๊ธ€์˜ id๋ฅผ ์ด์šฉํ•˜์—ฌ ์ƒ์„ธ์ •๋ณด ์ถœ๋ ฅ, UserInfoDto & ImageListDto & PostInfoDto ์ด์šฉ")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200",description = "OK, ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST401", description = "ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Œ",content = @Content(schema = @Schema(implementation = ApiResponse.class))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "POST401", description = "ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",content = @Content(schema = @Schema(implementation = ApiResponse.class))),
})
@Parameters({
@Parameter(name = "postId", description = "๊ฒŒ์‹œ๊ธ€์˜ ์•„์ด๋””"),
Expand Down
Loading

0 comments on commit d62828b

Please sign in to comment.