Browse Source

Merge remote-tracking branch 'origin/main' into main

xrh 3 weeks ago
parent
commit
a73846dfc4

+ 0 - 10
bex-cloud-staking-api/src/main/resources/application.yml

@@ -6,16 +6,6 @@ spring:
     name: bex-cloud-staking
   profiles:
     active: ${PROFILE:dev}
-  # gRPC 客户端通道配置:资产服务(:9093) / 行情服务(:9094)
-  grpc:
-    client:
-      channels:
-        asset-service:
-          address: 'static://localhost:9093'
-          negotiation-type: plaintext
-        market-service:
-          address: 'static://localhost:9094'
-          negotiation-type: plaintext
   config:
     import:
       - nacos:${spring.application.name}-${spring.profiles.active}.yml?group=DEFAULT_GROUP&refresh=true

+ 6 - 0
bex-cloud-staking-service/src/main/java/com/bex/staking/grpc/StakingGrpcClientConfig.java

@@ -2,6 +2,7 @@ package com.bex.staking.grpc;
 
 import com.bex.proto.asset.AssetServiceGrpc;
 import com.bex.proto.market.MarketServiceGrpc;
+import com.bex.proto.user.UserServiceGrpc;
 import io.grpc.Channel;
 import io.grpc.ClientInterceptor;
 import java.util.concurrent.ConcurrentHashMap;
@@ -65,6 +66,11 @@ public class StakingGrpcClientConfig {
         return MarketServiceGrpc.newBlockingStub(getOrCreateChannel(channels, "market-service"));
     }
 
+    @Bean
+    UserServiceGrpc.UserServiceBlockingStub userStub(GrpcChannelFactory channels) {
+        return UserServiceGrpc.newBlockingStub(getOrCreateChannel(channels, "user-service"));
+    }
+
     /**
      * 全局 gRPC 客户端 Deadline 拦截器。
      *

+ 420 - 0
bex-cloud-staking-service/src/main/java/com/bex/staking/grpc/StakingGrpcService.java

@@ -0,0 +1,420 @@
+package com.bex.staking.grpc;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.bex.proto.staking.*;
+import com.bex.proto.user.*;
+import com.bex.staking.entity.StakingOrder;
+import com.bex.staking.entity.StakingProduct;
+import com.bex.staking.enums.ProductStatus;
+import com.bex.staking.mapper.StakingOrderMapper;
+import com.bex.staking.mapper.StakingProductMapper;
+import com.bex.staking.util.ProductDisplayMapper;
+import io.grpc.stub.StreamObserver;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StakingGrpcService extends StakingServiceGrpc.StakingServiceImplBase {
+
+    private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    private final StakingProductMapper stakingProductMapper;
+    private final StakingOrderMapper stakingOrderMapper;
+    private final UserServiceGrpc.UserServiceBlockingStub userStub;
+
+    @Override
+    public void listProducts(ListProductsRequest request, StreamObserver<ListProductsResponse> responseObserver) {
+        try {
+            int page = request.getPage() > 0 ? request.getPage() : 1;
+            int size = request.getSize() > 0 ? request.getSize() : 20;
+            if (size > 100) size = 100;
+
+            Page<StakingProduct> pageParam = new Page<>(page, size);
+            LambdaQueryWrapper<StakingProduct> wrapper = new LambdaQueryWrapper<>();
+
+            if (request.hasStatus() && StringUtils.isNotBlank(request.getStatus())) {
+                wrapper.eq(StakingProduct::getStatus, request.getStatus());
+            }
+            if (request.hasProductType() && StringUtils.isNotBlank(request.getProductType())) {
+                wrapper.eq(StakingProduct::getProductType, request.getProductType());
+            }
+            if (request.hasKeyword() && StringUtils.isNotBlank(request.getKeyword())) {
+                wrapper.like(StakingProduct::getTitle, request.getKeyword());
+            }
+            if (ObjectUtils.isNotEmpty(request.getTitle())) {
+                wrapper.like(StakingProduct::getTitle, request.getTitle());
+            }
+            wrapper.orderByDesc(StakingProduct::getCreatedAt);
+
+            Page<StakingProduct> result = stakingProductMapper.selectPage(pageParam, wrapper);
+
+            ListProductsResponse.Builder respBuilder = ListProductsResponse.newBuilder()
+                    .setTotal(result.getTotal())
+                    .setPage(page)
+                    .setSize(size);
+
+            for (StakingProduct p : result.getRecords()) {
+                String s = ProductDisplayMapper.displayStatus(ProductStatus.fromValue(p.getStatus()), p.getSaleStartTime(), p.getSaleEndTime(), System.currentTimeMillis());
+                p.setDisplayStatus(s);
+                respBuilder.addProducts(toProductInfo(p));
+            }
+
+            responseObserver.onNext(respBuilder.build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            log.error("listProducts gRPC error: {}", e.getMessage(), e);
+            responseObserver.onError(io.grpc.Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException());
+        }
+    }
+
+    @Override
+    public void getProduct(GetProductRequest request, StreamObserver<GetProductResponse> responseObserver) {
+        try {
+            StakingProduct product = stakingProductMapper.selectById(request.getProductId());
+            if (product == null) {
+                responseObserver.onError(io.grpc.Status.NOT_FOUND
+                        .withDescription("Product not found: " + request.getProductId())
+                        .asRuntimeException());
+                return;
+            }
+            responseObserver.onNext(GetProductResponse.newBuilder()
+                    .setProduct(toProductInfo(product))
+                    .build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            log.error("getProduct gRPC error: {}", e.getMessage(), e);
+            responseObserver.onError(io.grpc.Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException());
+        }
+    }
+
+    @Override
+    public void createProduct(CreateProductRequest request, StreamObserver<CreateProductResponse> responseObserver) {
+        try {
+
+            Long exeists = stakingProductMapper.selectCount(new LambdaQueryWrapper<StakingProduct>().eq(StakingProduct::getProductSn, request.getProductSn()));
+            if (exeists != 0L) {
+                responseObserver.onNext(CreateProductResponse.newBuilder().setSuccess(false).setMessage("该产品已存在").build());
+                responseObserver.onCompleted();
+                return;
+            }
+
+            StakingProduct product = new StakingProduct();
+            product.setProductSn(request.getProductSn());
+            product.setProductType(request.getProductType());
+            product.setTitle(request.getTitle());
+            product.setApr(new java.math.BigDecimal(request.getApr()));
+            product.setMinAmount(new java.math.BigDecimal(request.getMinAmount()));
+            product.setMaxAmount(new java.math.BigDecimal(request.getMaxAmount()));
+            product.setTotalCapacity(new java.math.BigDecimal(request.getTotalCapacity()));
+            product.setStatus(request.getStatus());
+            product.setRaisedAmount(java.math.BigDecimal.ZERO);
+            product.setDeleted(0);
+            product.setVersion(0);
+            product.setDisplayStatus("可质押");
+            product.setCreatedAt(LocalDateTime.now());
+            product.setUpdatedAt(LocalDateTime.now());
+            product.setIsRecommend(request.getIsRecommend());
+            product.setIsShowTwap(request.getIsShowTwap());
+            product.setSubMode(request.getSubMode());
+            product.setInterestType(request.getInterestType());
+            product.setApr(new BigDecimal(request.getApr()));
+            product.setPeriodRate(new BigDecimal(request.getPeriodRate()));
+            product.setFixedInterestAmount(new BigDecimal(request.getFixedInterestAmount()));
+            product.setLockDays(request.getLockDays());
+            product.setRedeemType(request.getRedeemType());
+            product.setDeflationRate(new BigDecimal(request.getDeflationRate()));
+            product.setDisplayScale(request.getDisplayScale());
+            product.setSaleStartTime(request.getSaleStartTime());
+            product.setSaleEndTime(request.getSaleEndTime());
+            product.setLockStartTime(request.getLockStartTime());
+            product.setLockEndTime(request.getLockEndTime());
+
+            stakingProductMapper.insert(product);
+
+            log.info("Created staking product id={}, title={}", product.getId(), product.getTitle());
+
+            responseObserver.onNext(CreateProductResponse.newBuilder()
+                    .setSuccess(true)
+                    .setProductId(product.getId())
+                    .setMessage("ok")
+                    .build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            log.error("createProduct gRPC error: {}", e.getMessage(), e);
+            responseObserver.onNext(CreateProductResponse.newBuilder()
+                    .setSuccess(false)
+                    .setMessage(e.getMessage())
+                    .build());
+            responseObserver.onCompleted();
+        }
+    }
+
+    @Override
+    public void updateProduct(UpdateProductRequest request, StreamObserver<UpdateProductResponse> responseObserver) {
+        try {
+            StakingProduct product = stakingProductMapper.selectById(request.getId());
+            if (product == null) {
+                responseObserver.onNext(UpdateProductResponse.newBuilder()
+                        .setSuccess(false)
+                        .setMessage("Product not found")
+                        .build());
+                responseObserver.onCompleted();
+                return;
+            }
+
+            if (ObjectUtils.isNotEmpty(request.getTitle())) product.setTitle(request.getTitle());
+            if (ObjectUtils.isNotEmpty(request.getApr())) product.setApr(new java.math.BigDecimal(request.getApr()));
+            if (ObjectUtils.isNotEmpty(request.getMinAmount())) product.setMinAmount(new java.math.BigDecimal(request.getMinAmount()));
+            if (ObjectUtils.isNotEmpty(request.getMaxAmount())) product.setMaxAmount(new java.math.BigDecimal(request.getMaxAmount()));
+            if (ObjectUtils.isNotEmpty(request.getTotalCapacity())) product.setTotalCapacity(new java.math.BigDecimal(request.getTotalCapacity()));
+            if (ObjectUtils.isNotEmpty(request.getStatus())) product.setStatus(request.getStatus());
+            if (ObjectUtils.isNotEmpty(request.getIsRecommend())) product.setIsRecommend(request.getIsRecommend());
+            if (ObjectUtils.isNotEmpty(request.getIsShowTwap())) product.setIsShowTwap(request.getIsShowTwap());
+            if (ObjectUtils.isNotEmpty(request.getSubMode())) product.setSubMode(request.getSubMode());
+            if (ObjectUtils.isNotEmpty(request.getInterestType())) product.setInterestType(request.getInterestType());
+            if (ObjectUtils.isNotEmpty(request.getPeriodRate())) product.setPeriodRate(new BigDecimal(request.getPeriodRate()));
+            if (ObjectUtils.isNotEmpty(request.getFixedInterestAmount())) product.setFixedInterestAmount(new BigDecimal(request.getFixedInterestAmount()));
+            if (ObjectUtils.isNotEmpty(request.getLockDays())) product.setLockDays(request.getLockDays());
+            if (ObjectUtils.isNotEmpty(request.getRedeemType())) product.setRedeemType(request.getRedeemType());
+            if (ObjectUtils.isNotEmpty(request.getDeflationRate())) product.setDeflationRate(new BigDecimal(request.getDeflationRate()));
+            if (ObjectUtils.isNotEmpty(request.getDisplayScale())) product.setDisplayScale(request.getDisplayScale());
+            if (ObjectUtils.isNotEmpty(request.getSaleStartTime())) product.setSaleStartTime(request.getSaleStartTime());
+            if (ObjectUtils.isNotEmpty(request.getSaleEndTime())) product.setSaleEndTime(request.getSaleEndTime());
+            if (ObjectUtils.isNotEmpty(request.getLockStartTime())) product.setLockStartTime(request.getLockStartTime());
+            if (ObjectUtils.isNotEmpty(request.getLockEndTime())) product.setLockEndTime(request.getLockEndTime());
+
+            product.setUpdatedAt(LocalDateTime.now());
+            stakingProductMapper.updateById(product);
+
+            log.info("Updated staking product id={}", product.getId());
+
+            responseObserver.onNext(UpdateProductResponse.newBuilder()
+                    .setSuccess(true)
+                    .setMessage("ok")
+                    .build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            log.error("updateProduct gRPC error: {}", e.getMessage(), e);
+            responseObserver.onNext(UpdateProductResponse.newBuilder()
+                    .setSuccess(false)
+                    .setMessage(e.getMessage())
+                    .build());
+            responseObserver.onCompleted();
+        }
+    }
+
+    @Override
+    public void deleteProduct(DeleteProductRequest request, StreamObserver<DeleteProductResponse> responseObserver) {
+        try {
+            StakingProduct product = stakingProductMapper.selectById(request.getProductId());
+            if (product == null) {
+                responseObserver.onNext(DeleteProductResponse.newBuilder()
+                        .setSuccess(false)
+                        .setMessage("Product not found")
+                        .build());
+                responseObserver.onCompleted();
+                return;
+            }
+
+            if ("ACTIVE".equals(product.getStatus())) {
+                responseObserver.onNext(DeleteProductResponse.newBuilder()
+                        .setSuccess(false)
+                        .setMessage("ACTIVE status product cannot be deleted")
+                        .build());
+                responseObserver.onCompleted();
+                return;
+            }
+
+            stakingProductMapper.deleteById(request.getProductId());
+
+            log.info("Deleted staking product id={}, operatorId={}", request.getProductId(), request.getOperatorId());
+
+            responseObserver.onNext(DeleteProductResponse.newBuilder()
+                    .setSuccess(true)
+                    .setMessage("ok")
+                    .build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            log.error("deleteProduct gRPC error: {}", e.getMessage(), e);
+            responseObserver.onNext(DeleteProductResponse.newBuilder()
+                    .setSuccess(false)
+                    .setMessage(e.getMessage())
+                    .build());
+            responseObserver.onCompleted();
+        }
+    }
+
+    @Override
+    public void listOrders(ListOrdersRequest request, StreamObserver<ListOrdersResponse> responseObserver) {
+        try {
+
+            Long userId = 0L;
+            if (ObjectUtils.isNotEmpty(request.getUid())) {
+                GetUserByUidResponse userByUid = userStub.getUserByUid(GetUserByUidRequest.newBuilder().setUid(request.getUid()).build());
+                if (ObjectUtils.isNotEmpty(userByUid)) {
+                    userId = userByUid.getUserId();
+                }
+            }
+
+            int page = request.getPage() > 0 ? request.getPage() : 1;
+            int size = request.getSize() > 0 ? request.getSize() : 20;
+            if (size > 100) size = 100;
+
+            Page<StakingOrder> pageParam = new Page<>(page, size);
+            LambdaQueryWrapper<StakingOrder> wrapper = new LambdaQueryWrapper<>();
+
+            if (request.hasStatus() && StringUtils.isNotBlank(request.getStatus())) {
+                wrapper.eq(StakingOrder::getStatus, request.getStatus());
+            }
+            if (request.hasProductType() && StringUtils.isNotBlank(request.getProductType())) {
+                wrapper.eq(StakingOrder::getProductType, request.getProductType());
+            }
+            if (request.hasUserId() && request.getUserId() > 0) {
+                wrapper.eq(StakingOrder::getUserId, request.getUserId());
+            }
+            if (userId != 0) {
+                wrapper.eq(StakingOrder::getUserId, userId);
+            }
+            wrapper.orderByDesc(StakingOrder::getCreatedAt);
+
+            Page<StakingOrder> result = stakingOrderMapper.selectPage(pageParam, wrapper);
+
+            List<Long> userIds = result.getRecords().stream().map(StakingOrder::getUserId).distinct().collect(Collectors.toList());
+            GetUserByIdsResponse user = userStub.getUserByIds(GetUserByIdsRequest.newBuilder().addAllUserIds(userIds).build());
+            Map<Long, String> userMap = user.getUsersList().stream().collect(Collectors.toMap(UserInfo::getId, UserInfo::getUid));
+
+            ListOrdersResponse.Builder respBuilder = ListOrdersResponse.newBuilder()
+                    .setTotal(result.getTotal())
+                    .setPage(page)
+                    .setSize(size);
+
+            for (StakingOrder o : result.getRecords()) {
+                respBuilder.addOrders(toOrderInfo(o, userMap.get(o.getUserId())));
+            }
+
+            responseObserver.onNext(respBuilder.build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            log.error("listOrders gRPC error: {}", e.getMessage(), e);
+            responseObserver.onError(io.grpc.Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException());
+        }
+    }
+
+    @Override
+    public void getOrder(GetOrderRequest request, StreamObserver<GetOrderResponse> responseObserver) {
+        try {
+            StakingOrder order = stakingOrderMapper.selectById(request.getOrderId());
+            if (order == null) {
+                responseObserver.onError(io.grpc.Status.NOT_FOUND
+                        .withDescription("Order not found: " + request.getOrderId())
+                        .asRuntimeException());
+                return;
+            }
+            GetUserByIdResponse userById = userStub.getUserById(GetUserByIdRequest.newBuilder().setUserId(order.getUserId()).build());
+            responseObserver.onNext(GetOrderResponse.newBuilder()
+                    .setOrder(toOrderInfo(order, userById.getUid()))
+                    .build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            log.error("getOrder gRPC error: {}", e.getMessage(), e);
+            responseObserver.onError(io.grpc.Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException());
+        }
+    }
+
+    private StakingProductInfo toProductInfo(StakingProduct p) {
+        StakingProductInfo.Builder builder = StakingProductInfo.newBuilder()
+                .setId(p.getId() != null ? p.getId() : 0L)
+                .setProductSn(ObjectUtils.isNotEmpty(p.getProductSn()) ? p.getProductSn() : "")
+                .setTitle(ObjectUtils.isNotEmpty(p.getTitle()) ? p.getTitle() : "")
+                .setProductType(ObjectUtils.isNotEmpty(p.getProductType()) ? p.getProductType() : "")
+                .setTermDays(p.getLockDays() != null ? p.getLockDays() : 0)
+                .setApr(p.getApr() != null ? p.getApr().toPlainString() : "0")
+                .setPeriodRate(p.getPeriodRate() != null ? p.getPeriodRate().toPlainString() : "0")
+                .setInterestType(ObjectUtils.isNotEmpty(p.getInterestType()) ? p.getInterestType() : "")
+                .setFixedInterestAmount(p.getFixedInterestAmount() != null ? p.getFixedInterestAmount().toPlainString() : "0")
+                .setMinAmount(p.getMinAmount() != null ? p.getMinAmount().toPlainString() : "0")
+                .setMaxAmount(p.getMaxAmount() != null ? p.getMaxAmount().toPlainString() : "0")
+                .setTotalCapacity(p.getTotalCapacity() != null ? p.getTotalCapacity().toPlainString() : "0")
+                .setRaisedAmount(p.getRaisedAmount() != null ? p.getRaisedAmount().toPlainString() : "0")
+                .setStatus(ObjectUtils.isNotEmpty(p.getStatus()) ? p.getStatus() : "")
+                .setDisplayStatus(ObjectUtils.isNotEmpty(p.getDisplayStatus()) ? p.getDisplayStatus() : "")
+                .setIsRecommend(java.lang.Boolean.TRUE.equals(p.getIsRecommend()))
+                .setIsShowTwap(java.lang.Boolean.TRUE.equals(p.getIsShowTwap()))
+                .setSubMode(ObjectUtils.isNotEmpty(p.getSubMode()) ? p.getSubMode() : "")
+                .setTierConfig(ObjectUtils.isNotEmpty(p.getTierConfig()) ? p.getTierConfig() : "")
+                .setRedeemType(ObjectUtils.isNotEmpty(p.getRedeemType()) ? p.getRedeemType() : "")
+                .setDeflationRate(p.getDeflationRate() != null ? p.getDeflationRate().toPlainString() : "0")
+                .setDisplayScale(p.getDisplayScale() != null ? p.getDisplayScale() : 0)
+                .setLockDays(p.getLockDays() != null ? p.getLockDays() : 0)
+                .setSaleStartTime(p.getSaleStartTime() != null ? p.getSaleStartTime() : 0L)
+                .setSaleEndTime(p.getSaleEndTime() != null ? p.getSaleEndTime() : 0L)
+                .setLockStartTime(p.getLockStartTime() != null ? p.getLockStartTime() : 0L)
+                .setLockEndTime(p.getLockEndTime() != null ? p.getLockEndTime() : 0L)
+                .setVersion(p.getVersion() != null ? p.getVersion() : 0);
+
+        if (p.getCreatedAt() != null) {
+            builder.setCreatedAt(p.getCreatedAt().atZone(ZoneId.systemDefault()).format(FMT));
+        } else {
+            builder.setCreatedAt("");
+        }
+        if (p.getUpdatedAt() != null) {
+            builder.setUpdatedAt(p.getUpdatedAt().atZone(ZoneId.systemDefault()).format(FMT));
+        } else {
+            builder.setUpdatedAt("");
+        }
+
+        return builder.build();
+    }
+
+    private StakingOrderInfo toOrderInfo(StakingOrder o, String uid) {
+        StakingOrderInfo.Builder builder = StakingOrderInfo.newBuilder()
+                .setId(o.getId() != null ? o.getId() : 0L)
+                .setOrderNo(ObjectUtils.isNotEmpty(o.getOrderNo()) ? o.getOrderNo() : "")
+                .setUserId(o.getUserId() != null ? o.getUserId() : 0L)
+                .setProductId(o.getProductId() != null ? o.getProductId() : 0L)
+                .setProductType(ObjectUtils.isNotEmpty(o.getProductType()) ? o.getProductType() : "")
+                .setStakeAmount(o.getStakeAmount() != null ? o.getStakeAmount().toPlainString() : "0")
+                .setEntryPrice(o.getEntryPrice() != null ? o.getEntryPrice().toPlainString() : "0")
+                .setInitialValueUsdt(o.getInitialValueUsdt() != null ? o.getInitialValueUsdt().toPlainString() : "0")
+                .setCumulativeProfit(o.getCumulativeProfit() != null ? o.getCumulativeProfit().toPlainString() : "0")
+                .setPendingProfit(o.getPendingProfit() != null ? o.getPendingProfit().toPlainString() : "0")
+                .setClaimedProfit(o.getClaimedProfit() != null ? o.getClaimedProfit().toPlainString() : "0")
+                .setSettledDays(o.getSettledDays() != null ? o.getSettledDays() : 0)
+                .setStatus(ObjectUtils.isNotEmpty(o.getStatus()) ? o.getStatus() : "")
+                .setReturnPrincipal(o.getReturnPrincipal() != null ? o.getReturnPrincipal().toPlainString() : "0")
+                .setBurnAmount(o.getBurnAmount() != null ? o.getBurnAmount().toPlainString() : "0")
+                .setExpirePrice(o.getExpirePrice() != null ? o.getExpirePrice().toPlainString() : "0")
+                .setBurnTxHash(ObjectUtils.isNotEmpty(o.getBurnTxHash()) ? o.getBurnTxHash() : "")
+                .setLockStartTime(o.getLockStartTime() != null ? o.getLockStartTime() : 0L)
+                .setExpireTime(o.getExpireTime() != null ? o.getExpireTime() : 0L)
+                .setUid(uid)
+                .setVersion(o.getVersion() != null ? o.getVersion() : 0);
+
+        if (o.getCreatedAt() != null) {
+            builder.setCreatedAt(o.getCreatedAt().atZone(ZoneId.systemDefault()).format(FMT));
+        } else {
+            builder.setCreatedAt("");
+        }
+        if (o.getUpdatedAt() != null) {
+            builder.setUpdatedAt(o.getUpdatedAt().atZone(ZoneId.systemDefault()).format(FMT));
+        } else {
+            builder.setUpdatedAt("");
+        }
+
+        return builder.build();
+    }
+}