Skip to content

Commit

Permalink
Merge pull request #11 from Hey-Porori/faet/post-basic
Browse files Browse the repository at this point in the history
중고거래 게시글 관련 APIs
  • Loading branch information
isExample authored Sep 30, 2023
2 parents fbaf567 + f19943d commit 72d1fcd
Show file tree
Hide file tree
Showing 26 changed files with 655 additions and 12 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'mysql:mysql-connector-java:8.0.25'
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'
implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebClient
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' // S3
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class TransactionApplication {

static {
System.setProperty("com.amazonaws.sdk.disableEc2Metadata", "true"); // S3 예외발생 방지
}
public static void main(String[] args) {
SpringApplication.run(TransactionApplication.class, args);
}
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/HeyPorori/transaction/config/AmazonS3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package HeyPorori.transaction.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class AmazonS3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;

@Value("${cloud.aws.region.static}")
private String region;

@Bean
@Primary
public BasicAWSCredentials awsCredentialsProvider(){
return new BasicAWSCredentials(accessKey, secretKey);
}

@Bean
public AmazonS3 amazonS3Client() {
return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentialsProvider()))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class BaseException extends Exception {
@NoArgsConstructor
public class BaseException extends RuntimeException {
private BaseResponseStatus status;
}
2 changes: 2 additions & 0 deletions src/main/java/HeyPorori/transaction/config/BaseResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import static HeyPorori.transaction.config.BaseResponseStatus.SUCCESS;

@Getter
@AllArgsConstructor
@NoArgsConstructor
@JsonPropertyOrder({"statusCode", "message", "data"})
public class BaseResponse<T> {
private String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
@Getter
public enum BaseResponseStatus {
/* 요청 성공 */
SUCCESS(true, 1000, "요청에 성공하였습니다.")
SUCCESS(true, 1000, "요청에 성공하였습니다."),

/* 요청 실패 */
USER_INVALID_RESPONSE(false, 1050, "User 서비스로부터 올바른 응답을 받지 못하였습니다."),
INVALID_JWT(false, 1051, "잘못된 JWT 토큰입니다."),
INVALID_CATEGORY(false, 3001, "유효하지 않은 카테고리입니다."),
POST_NOT_FOUND(false, 3010, "존재하지 않는 게시글입니다."),
INVALID_POST_OWNER(false, 3011, "게시글 작성자가 아닙니다.")

;

private final boolean isSuccess;
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/HeyPorori/transaction/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package HeyPorori.transaction.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@SecurityScheme(
type = SecuritySchemeType.APIKEY, in = SecuritySchemeIn.HEADER,
name = "Authorization", description = "Authorization"
)
@OpenAPIDefinition(security = { @SecurityRequirement(name = "Authorization") })
public class SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/HeyPorori/transaction/config/WebClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package HeyPorori.transaction.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {
@Value("${service.user}")
private String userUrl;
@Bean
public ReactorResourceFactory reactorResourceFactory(){
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setUseGlobalResources(false);
return factory;
}

@Bean
public WebClient webClient(){
return WebClient.builder()
.baseUrl(userUrl)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,95 @@

import HeyPorori.transaction.config.BaseException;
import HeyPorori.transaction.config.BaseResponse;
import HeyPorori.transaction.config.BaseResponseStatus;
import HeyPorori.transaction.dto.*;
import HeyPorori.transaction.service.AmazonS3Service;
import HeyPorori.transaction.service.TransactionService;
import HeyPorori.transaction.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@RequiredArgsConstructor
@Tag(name = "중고거래", description = "중고거래 관련 API 입니다.")
@RestController
@RequestMapping("/api/transactions")
public class TransactionController {
private final TransactionService transactionService;
private final UserService userService;
private final AmazonS3Service amazonS3Service;

// 테스트용 APIs
@Operation(summary = "Swagger UI 테스트용 메서드", description = "프로젝트 초기 Swagger UI 정상작동을 확인하기 위한 메서드입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@GetMapping("/test")
public BaseResponse<String> testSwagger() {
return new BaseResponse<>("테스트 성공");
public BaseResponse<String> testSwagger() { return new BaseResponse<>("테스트 성공"); }

@Operation(summary = "User 서버 테스트용 메서드", description = "User 서버와의 통신이 정상작동하는지 확인하기 위한 메서드입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@PostMapping("/testUser")
public BaseResponse<Long> testUserService(@RequestHeader("Authorization") String token) {
userService.sendTestJwtRequest(token);
return new BaseResponse<>(userService.getUserId(token));
}

// 실사용 APIs
@Operation(summary = "중고거래 게시글 작성 API", description = "중고거래 서비스의 거래 게시글을 작성하기 위한 API입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@PostMapping("/post")
public BaseResponse<BaseResponseStatus> createPost(@RequestHeader("Authorization") String token, @RequestBody @Valid CreatePostReq postReq) {
transactionService.createPost(token, postReq);
return new BaseResponse<>(BaseResponseStatus.SUCCESS);
}

@Operation(summary = "Pre-Signed Url 발급 API", description = "AWS S3 이미지 업로드 권한을 요청하기 위한 API입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@GetMapping("/url")
public BaseResponse<PreSignedUrlRes> getPreSignedUrl(@RequestHeader("Authorization") String token) throws BaseException {
return new BaseResponse<>(amazonS3Service.getPreSignedUrl());
}

@Operation(summary = "중고거래 게시글 목록 조회 API", description = "중고거래 서비스의 거래 게시글 목록을 조회하기 위한 API입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@GetMapping("/post")
public BaseResponse<List<PostsRes>> getAllPostByCategory(String category) throws BaseException {
return new BaseResponse<>(transactionService.findAllPostByCategory(category));
}

@Operation(summary = "중고거래 게시글 상세 조회 API", description = "중고거래 서비스의 거래 게시글 상세정보를 조회하기 위한 API입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@GetMapping("/post/{transactionId}")
public BaseResponse<PostDetailRes> getPostDetail(@RequestHeader("Authorization") String token, @PathVariable Long transactionId) throws BaseException {
return new BaseResponse<>(transactionService.getPostDetail(token, transactionId));
}

@Operation(summary = "중고거래 게시글 삭제 API", description = "중고거래 서비스의 거래 게시글을 삭제하기 위한 API입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@DeleteMapping("/post/{transactionId}")
public BaseResponse<String> deletePost(@RequestHeader("Authorization") String token, @PathVariable Long transactionId) throws BaseException {
transactionService.deletePost(token, transactionId);
return new BaseResponse<>("게시글이 삭제되었습니다.");
}

@Operation(summary = "중고거래 게시글 추천 API", description = "중고거래 서비스의 거래 게시글을 추천하거나 추천 취소하기 위한 API입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@PostMapping("/recommend")
public BaseResponse<String> recommendPost(@RequestHeader("Authorization") String token, @RequestBody RecommendPostReq req) throws BaseException {
transactionService.recommendPost(token, req);
return new BaseResponse<>("게시글을 추천했습니다.");
}

@Operation(summary = "중고거래 게시글 검색 API", description = "중고거래 서비스의 거래 게시글을 검색하기 위한 API입니다.")
@ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class)))
@GetMapping("/search/{keyword}")
public BaseResponse<List<PostsRes>> recommendPost(@PathVariable String keyword) throws BaseException {
return new BaseResponse<>(transactionService.findAllPostByKeyword(keyword));
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/HeyPorori/transaction/domain/Category.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package HeyPorori.transaction.domain;

import HeyPorori.transaction.config.BaseException;
import HeyPorori.transaction.config.BaseResponseStatus;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.Getter;

import java.util.Arrays;
import java.util.stream.Stream;

@Getter
public enum Category {
ELECTRONICS("전자제품", "스마트폰, 컴퓨터, 태블릿, TV, 오디오 기기 등"),
Expand All @@ -20,4 +26,10 @@ public enum Category {
this.name = name;
this.description = description;
}

public static Category parsing(String inputValue) {
return Arrays.stream(Category.values())
.filter(r -> r.name.equals(inputValue))
.findAny().orElse(null);
}
}
23 changes: 22 additions & 1 deletion src/main/java/HeyPorori/transaction/domain/Recommend.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package HeyPorori.transaction.domain;

import HeyPorori.transaction.config.BaseTimeEntity;
import HeyPorori.transaction.dto.CreatePostReq;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

Expand All @@ -10,6 +14,8 @@
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "recommend")
@DynamicInsert
@DynamicUpdate
public class Recommend extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -23,9 +29,24 @@ public class Recommend extends BaseTimeEntity {
@Column(name = "user_id", nullable = false)
private Long userId;

@ColumnDefault("'ACTIVE'")
private String status;

@Builder
public Recommend(Transaction transactionId, Long userId){
public Recommend(Transaction transactionId, Long userId, String status){
this.transactionId = transactionId;
this.userId = userId;
this.status = status;
}

public static Recommend toEntity(Transaction transaction, Long userId){
return Recommend.builder()
.transactionId(transaction)
.userId(userId)
.build();
}

public void changeStatus(String status) {
this.status = status;
}
}
17 changes: 16 additions & 1 deletion src/main/java/HeyPorori/transaction/domain/Transaction.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package HeyPorori.transaction.domain;

import HeyPorori.transaction.config.BaseTimeEntity;
import HeyPorori.transaction.dto.CreatePostReq;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;
Expand Down Expand Up @@ -44,7 +45,8 @@ public class Transaction extends BaseTimeEntity {
@Column(nullable = false)
private Double longitude;

@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
@Column(nullable = true)
private Category category;

@ColumnDefault("'ACTIVE'")
Expand All @@ -69,6 +71,19 @@ public Transaction(Long userId, String title, String content, int recommend, Str
this.status = status;
}

public static Transaction toEntity(CreatePostReq postReq, Long userId){
return Transaction.builder()
.userId(userId)
.title(postReq.getTitle())
.content(postReq.getContent())
.recommend(0)
.address("필동")
.latitude(0.0)
.longitude(0.0)
.category(Category.parsing(postReq.getCategory()))
.build();
}

public void changeStatus(String status) {
this.status = status;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,11 @@ public TransactionAttach(Transaction transactionId, String imageUrl){
this.transactionId = transactionId;
this.imageUrl = imageUrl;
}

public static TransactionAttach toEntity(Transaction transaction, String imageUrl){
return TransactionAttach.builder()
.transactionId(transaction)
.imageUrl(imageUrl)
.build();
}
}
Loading

0 comments on commit 72d1fcd

Please sign in to comment.