Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Follow / Subscribe Portfolio #92

Merged
merged 6 commits into from
Jun 23, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import com.pda.apiutils.ApiUtils;
import com.pda.apiutils.GlobalResponse;
import com.pda.creditapplication.controller.dto.req.TransferRequest;
import com.pda.creditapplication.controller.dto.req.WithdrawRequest;
import com.pda.creditapplication.service.CreditService;
import com.pda.creditapplication.service.dto.req.SetAmountServiceRequest;
import com.pda.creditapplication.service.dto.req.TransferServiceRequest;
import com.pda.tofinsecurity.user.AuthUser;
import com.pda.tofinsecurity.user.AuthUserInfo;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -14,7 +16,6 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -33,8 +34,7 @@ public class CreditController {
@PostMapping("/withdraw")
@Operation(summary = "ν¬λ ˆλ”§ 인좜", description = "ν¬λ ˆλ”§ 인좜 API",
security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponse(responseCode = "201", description = "성곡")
@ResponseStatus(HttpStatus.CREATED)
@ApiResponse(responseCode = "200", description = "성곡")
public GlobalResponse<Void> withdraw(@AuthUser AuthUserInfo userInfo, @Valid @RequestBody WithdrawRequest request) {
creditService.withdraw(SetAmountServiceRequest.builder()
.userId(userInfo.getId())
Expand All @@ -51,4 +51,18 @@ public GlobalResponse<Void> withdraw(@AuthUser AuthUserInfo userInfo, @Valid @Re
public GlobalResponse<Long> getCredit(@AuthUser AuthUserInfo userInfo) {
return ApiUtils.success("ν¬λ ˆλ”§ 보유 ν˜„ν™© 쑰회", creditService.getAmount(userInfo.getId()));
}

@PostMapping("/transfer")
@Operation(summary = "ν¬λ ˆλ”§ 이체", description = "ν¬λ ˆλ”§ 이체 API",
security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponse(responseCode = "200", description = "성곡")
public GlobalResponse<Void> transfer(@AuthUser AuthUserInfo userInfo, @Valid @RequestBody TransferRequest request) {
creditService.transfer(TransferServiceRequest.builder()
.fromUserId(userInfo.getId())
.toUserId(request.getToUserId())
.amount(request.getAmount())
.transactionDateTime(request.getTransactionDateTime())
.build());
return ApiUtils.success("인좜 성곡");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.pda.creditapplication.controller.dto.req;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.PastOrPresent;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Schema(description = "ν¬λ ˆλ”§ 전솑 Request")
public class TransferRequest {
@Schema(description = "전솑 λͺ©μ μ§€ μœ μ €", example = "1")
private Long toUserId;
@Min(1)
@Schema(description = "차감앑(μ–‘μˆ˜λ§Œ κ°€λŠ₯)", example = "100", requiredMode = Schema.RequiredMode.REQUIRED)
private Long amount;
@PastOrPresent
@Schema(description = "차감 λ°œμƒ λ‚ μ§œ/μ‹œκ°„", example = "2024-06-19T10:41:04.172+09:00", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime transactionDateTime;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.pda.creditapplication.repository.CreditStore;
import com.pda.creditapplication.repository.CreditStoreRepository;
import com.pda.creditapplication.service.dto.req.SetAmountServiceRequest;
import com.pda.creditapplication.service.dto.req.TransferServiceRequest;
import com.pda.exceptionhandler.exceptions.BadRequestException;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Async;
Expand Down Expand Up @@ -56,6 +57,20 @@ public void withdraw(final SetAmountServiceRequest request) {
}

@Transactional
public void transfer(final TransferServiceRequest request) {
withdraw(SetAmountServiceRequest.builder()
.amount(request.getAmount())
.transactionDateTime(LocalDateTime.now())
.userId(request.getFromUserId())
.build());

deposit(SetAmountServiceRequest.builder()
.amount(request.getAmount())
.transactionDateTime(LocalDateTime.now())
.userId(request.getToUserId())
.build());
}

public Long getAmount(final Long userId) {
return storeRepository.findByUserId(userId)
.orElseThrow(() -> new BadRequestException("μœ μ €κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")).getAmount();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.pda.creditapplication.service.dto.req;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;

@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class TransferServiceRequest {
private Long fromUserId;
private Long toUserId;
private Long amount;
private LocalDateTime transactionDateTime;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ public SecurityRequestMatcherChain securityRequestMatcherChain() {
.add(SecurityRequestMatcher.hasRoleOf(UserRole.ADMIN, "/test"));
securityRequestMatcherChain
.add(SecurityRequestMatcher.hasAnyRolesOf(List.of(UserRole.NORMAL, UserRole.FINFLUENCER),
"/users/assets", "/users/public-options", "/users/detail-info", "/users/tendency", "/users/profile"));
"/users/assets", "/users/public-options", "/users/detail-info",
"/users/tendency", "/users/profile"));
securityRequestMatcherChain
.add(SecurityRequestMatcher.hasAnyRolesOf(List.of(UserRole.NORMAL, UserRole.FINFLUENCER),
HttpMethod.POST, "/users/{id:[0-9]+}/portfolios"));
securityRequestMatcherChain
.add(SecurityRequestMatcher.authenticatedOf(HttpMethod.POST, "/users/{id:[0-9]+}/follow"));
securityRequestMatcherChain
.add(SecurityRequestMatcher.authenticatedOf("/users/{id:[0-9]+}/portfolios"));
securityRequestMatcherChain
.add(SecurityRequestMatcher.hasRoleOf(UserRole.NORMAL, HttpMethod.PUT, "/finfluencer"));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.pda.userapplication.domains;

import com.pda.userapplication.domains.vo.UserId;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class PortfolioSubscribeLog {
private Long id;
private Integer period;
private UserId fromUserId;
private UserId toUserId;
private LocalDateTime startAt;

public boolean isSubscribed() {
LocalDateTime now = LocalDateTime.now();

return now.isBefore(startAt.plusDays(period*7));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.pda.userapplication.inadapters.controllers;

import com.pda.apiutils.ApiUtils;
import com.pda.apiutils.GlobalResponse;
import com.pda.tofinsecurity.user.AuthUser;
import com.pda.tofinsecurity.user.AuthUserInfo;
import com.pda.userapplication.services.in.PortfolioUseCase;
import com.pda.userapplication.services.in.dto.req.PortfolioSubscribeServiceRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Portfolio", description = "포트폴리였 API")
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class PortfolioController {
private final PortfolioUseCase portfolioUseCase;

@PostMapping("/{id}/portfolios")
@Operation(summary = "포트폴리였 ꡬ독", description = "μžκΈ°μžμ‹  or λˆμ—†μŒ or 이미 ꡬ독 쀑 => X ",
security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponse(responseCode = "201", description = "성곡")
@ResponseStatus(HttpStatus.CREATED)
public GlobalResponse<Void> subscribe(@AuthUser AuthUserInfo authUser, @PathVariable Long id) {
portfolioUseCase.subscribe(PortfolioSubscribeServiceRequest.builder()
.myId(authUser.getId())
.toId(id)
.token(authUser.getToken())
.build());
return ApiUtils.success("ꡬ독 μ™„λ£Œ");
}

@GetMapping("/{id}/portfolios")
@Operation(summary = "포트폴리였 쑰회", description = "포트폴리였 쑰회 API",
security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponse(responseCode = "201", description = "성곡")
public GlobalResponse<Void> getPortfolios(@AuthUser AuthUserInfo authUser, @PathVariable Long id) {
portfolioUseCase.getPortfolios(authUser.getId(), id);
return ApiUtils.success("포트폴리였 쑰회");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,6 @@ public GlobalResponse<GetUserPagingResponse> searchByNickname(
.build()));
}

@GetMapping("/{id}/portfolios")
@Operation(summary = "μœ μ € 포트폴리였 쑰회", description = "μœ μ € 포트폴리였 쑰회 -> 인증 ν•„μˆ˜ μ•„λ‹˜",
security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponse(responseCode = "200", description = "성곡")
public GlobalResponse<Void> getPortfolios(@AuthUser AuthUserInfo authUser, @PathVariable("id") Long id) {
return ApiUtils.success("μœ μ € 포트폴리였 쑰회 μ™„λ£Œ");
}

@GetMapping("/products")
@Operation(summary = "μœ μ € 보유 μƒν’ˆ 쑰회", description = "μœ μ € 본인의 보유 μƒν’ˆ 쑰회",
security = @SecurityRequirement(name = "bearerAuth"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.pda.userapplication.domains.NormalUser;
import com.pda.userapplication.services.out.CreditOutputPort;
import com.pda.userapplication.services.out.GetAssetsOutputPort;
import com.pda.userapplication.services.out.dto.req.TransferCreditRequest;
import com.pda.userapplication.services.out.dto.res.AssetInfoResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -50,6 +51,7 @@ public AssetInfoResponse getAssets(NormalUser normalUser) {
return mono.block().getData();
}

// TODO: 이거 아직 μ•ˆμ”€
@Override
public AssetInfoResponse getAssetsExcludePortfolio(NormalUser normalUser) {
AssetInfoResponse result = webClient.get().uri(uriBuilder -> uriBuilder
Expand All @@ -73,7 +75,7 @@ public AssetInfoResponse getAssetsExcludePortfolio(NormalUser normalUser) {
}

@Override
public boolean consumeCredit(Long amount, String token) {
public void consumeCredit(Long amount, String token) {
Map<String, Object> body = new HashMap<>();
body.put("amount", amount);
body.put("transactionDateTime", LocalDateTime.now());
Expand All @@ -89,7 +91,25 @@ public boolean consumeCredit(Long amount, String token) {

return response.bodyToMono(new ParameterizedTypeReference<GlobalResponse<Void>>() {});
}).block();
}

return true;
@Override
public void transferCredit(TransferCreditRequest request) {
Map<String, Object> body = new HashMap<>();
body.put("amount", request.getAmount());
body.put("transactionDateTime", LocalDateTime.now());
body.put("toUserId", request.getToUserId().toLong());

webClient.post().uri(creditUrl+"/credit/transfer")
.header("Authorization", String.format("Bearer %s", request.getToken()))
.body(BodyInserters.fromValue(body))
.exchangeToMono(response -> {
if (!response.statusCode().is2xxSuccessful()) {
log.error("Credit Server Exception: " + response.statusCode());
throw new BadRequestException("ν¬λ ˆλ”§ 이체 μ‹€νŒ¨");
}

return response.bodyToMono(new ParameterizedTypeReference<GlobalResponse<Void>>() {});
}).block();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.pda.userapplication.outadapters.jpa.entities.portfolio;

import com.pda.userapplication.outadapters.jpa.entities.BaseTimeEntity;
import com.pda.userapplication.outadapters.jpa.entities.user.UserEntity;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

@Entity
@Table(name = "SubscribeLog")
@Getter
@NoArgsConstructor
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@DynamicInsert
@DynamicUpdate
public class SubscribeLogEntity extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@ColumnDefault("5")
@Comment("ꡬ독 κΈ°κ°„(μ£Όλ‹¨μœ„ 5μ£Ό 톡일)")
@Column(name = "period", nullable = false, updatable = false)
private Integer period;

@ManyToOne
@JoinColumn(name = "from_user_id", nullable = false,
foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private UserEntity fromUser;

@ManyToOne
@JoinColumn(name = "to_user_id", nullable = false,
foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private UserEntity toUser;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.pda.userapplication.outadapters.jpa.entities.portfolio;

import com.pda.userapplication.domains.PortfolioSubscribeLog;
import com.pda.userapplication.domains.vo.UserId;
import com.pda.userapplication.outadapters.jpa.entities.user.UserEntity;
import com.pda.userapplication.services.out.PortfolioSubscribeOutputPort;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class SubscribeLogJpaAdapter implements PortfolioSubscribeOutputPort {
private final SubscribeLogRepository subscribeLogRepository;

@Override
public PortfolioSubscribeLog subscribe(UserId fromUserId, UserId toUserId) {
return toDomain(subscribeLogRepository.save(SubscribeLogEntity.builder()
.fromUser(UserEntity.builder().id(fromUserId.toLong()).build())
.toUser(UserEntity.builder().id(toUserId.toLong()).build())
.period(5) // 5주둜 톡일이닀.
.build()), fromUserId, toUserId);
}

@Override
public List<PortfolioSubscribeLog> findSubscribeLogsBy(UserId fromUserId, UserId toUserId) {
return subscribeLogRepository.findByFromUserAndToUserOrderByIdDesc(UserEntity.builder().id(fromUserId.toLong()).build(),
UserEntity.builder().id(toUserId.toLong()).build())
.stream().map(sub -> toDomain(sub, fromUserId, toUserId))
.toList();
}

private PortfolioSubscribeLog toDomain(final SubscribeLogEntity subscribeLogEntity, UserId fromUserId, UserId toUserId) {
return PortfolioSubscribeLog.builder()
.id(subscribeLogEntity.getId())
.period(subscribeLogEntity.getPeriod())
.startAt(subscribeLogEntity.getCreatedAt())
.fromUserId(fromUserId)
.toUserId(toUserId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.pda.userapplication.outadapters.jpa.entities.portfolio;

import com.pda.userapplication.outadapters.jpa.entities.user.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface SubscribeLogRepository extends JpaRepository<SubscribeLogEntity, Long> {
List<SubscribeLogEntity> findByFromUserAndToUserOrderByIdDesc(UserEntity fromUser, UserEntity toUser);
}
Loading