From 46686e0f177d3ac95ed5ee9c479999b2ceac63f3 Mon Sep 17 00:00:00 2001 From: okjodom Date: Mon, 30 Dec 2024 17:59:07 +0300 Subject: [PATCH] feat: update bitsacco shares --- apps/api/src/shares/shares.controller.ts | 22 ++++--- apps/api/src/shares/shares.service.ts | 5 ++ apps/shares/src/shares.controller.ts | 6 ++ apps/shares/src/shares.service.ts | 44 +++++++++---- libs/common/src/dto/shares.dto.ts | 82 ++++++++++++++++++++++-- libs/common/src/types/proto/shares.ts | 22 +++++++ proto/shares.proto | 16 +++++ 7 files changed, 169 insertions(+), 28 deletions(-) diff --git a/apps/api/src/shares/shares.controller.ts b/apps/api/src/shares/shares.controller.ts index 525510e..63f95b5 100644 --- a/apps/api/src/shares/shares.controller.ts +++ b/apps/api/src/shares/shares.controller.ts @@ -2,17 +2,10 @@ import { OfferSharesDto, SubscribeSharesDto, TransferSharesDto, + UpdateSharesDto, } from '@bitsacco/common'; -import { - Body, - Controller, - Get, - Logger, - Param, - Post, - Query, -} from '@nestjs/common'; -import { ApiOperation, ApiBody, ApiQuery, ApiParam } from '@nestjs/swagger'; +import { Body, Controller, Get, Logger, Param, Post } from '@nestjs/common'; +import { ApiOperation, ApiBody, ApiParam } from '@nestjs/swagger'; import { SharesService } from './shares.service'; @Controller('shares') @@ -56,6 +49,15 @@ export class SharesController { return this.sharesService.transferShares(req); } + @Post('update') + @ApiOperation({ summary: 'Update Bitsacco shares' }) + @ApiBody({ + type: UpdateSharesDto, + }) + updateShares(@Body() req: UpdateSharesDto) { + return this.sharesService.updateShares(req); + } + @Get('transactions') @ApiOperation({ summary: 'List all Bitsacco share transactions' }) allSharesTransactions() { diff --git a/apps/api/src/shares/shares.service.ts b/apps/api/src/shares/shares.service.ts index f217160..46262a9 100644 --- a/apps/api/src/shares/shares.service.ts +++ b/apps/api/src/shares/shares.service.ts @@ -6,6 +6,7 @@ import { SubscribeSharesDto, TransferSharesDto, UserSharesDto, + UpdateSharesDto, } from '@bitsacco/common'; import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { type ClientGrpc } from '@nestjs/microservices'; @@ -37,6 +38,10 @@ export class SharesService implements OnModuleInit { return this.client.transferShares(req); } + updateShares(req: UpdateSharesDto) { + return this.client.updateShares(req); + } + userSharesTransactions(req: UserSharesDto) { return this.client.userSharesTransactions(req); } diff --git a/apps/shares/src/shares.controller.ts b/apps/shares/src/shares.controller.ts index ed2f196..3ef0374 100644 --- a/apps/shares/src/shares.controller.ts +++ b/apps/shares/src/shares.controller.ts @@ -6,6 +6,7 @@ import { SharesServiceControllerMethods, SubscribeSharesDto, TransferSharesDto, + UpdateSharesDto, UserTxsRequestDto, } from '@bitsacco/common'; import { SharesService } from './shares.service'; @@ -35,6 +36,11 @@ export class SharesController { return this.sharesService.transferShares(request); } + @GrpcMethod() + updateShares(request: UpdateSharesDto) { + return this.sharesService.updateShares(request); + } + @GrpcMethod() userSharesTransactions(request: UserTxsRequestDto) { return this.sharesService.userSharesTransactions(request); diff --git a/apps/shares/src/shares.service.ts b/apps/shares/src/shares.service.ts index 4954da0..0dca9c6 100644 --- a/apps/shares/src/shares.service.ts +++ b/apps/shares/src/shares.service.ts @@ -7,6 +7,7 @@ import { SharesTxStatus, SubscribeSharesDto, TransferSharesDto, + UpdateSharesDto, UserSharesDto, UserShareTxsResponse, } from '@bitsacco/common'; @@ -90,10 +91,6 @@ export class SharesService { sharesId, ...transfer }: TransferSharesDto): Promise { - // const originShares = await this.userShareTransactions({ - // userId: fromUserId, - // }); - const originShares = await this.shares.findOne({ _id: sharesId }); if (originShares.status !== SharesTxStatus.COMPLETE) { @@ -104,29 +101,48 @@ export class SharesService { throw new Error('Not enough shares to transfer'); } - await this.shares.findOneAndUpdate( - { _id: sharesId }, - { - $set: { - quantity: originShares.quantity - transfer.quantity, - updatedAt: Date.now(), - transfer, - }, + // Update origin shares quantity, and record transfer metadata + await this.updateShares({ + sharesId, + updates: { + quantity: originShares.quantity - transfer.quantity, + transfer, }, - ); + }); // Assign shares to the recipient await this.shares.create({ userId: transfer.toUserId, offerId: originShares.offerId, quantity: transfer.quantity, - transfer, status: SharesTxStatus.COMPLETE, + transfer, }); return this.userSharesTransactions({ userId: transfer.fromUserId }); } + async updateShares({ + sharesId, + updates, + }: UpdateSharesDto): Promise { + const originShares = await this.shares.findOne({ _id: sharesId }); + const { quantity, status, transfer, offerId } = updates; + + let { userId } = await this.shares.findOneAndUpdate( + { _id: sharesId }, + { + quantity: quantity ?? originShares.quantity, + status: status ?? originShares.status, + transfer: transfer ?? originShares.transfer, + offerId: offerId ?? originShares.offerId, + updatedAt: Date.now(), + }, + ); + + return this.userSharesTransactions({ userId }); + } + async userSharesTransactions({ userId, }: UserSharesDto): Promise { diff --git a/libs/common/src/dto/shares.dto.ts b/libs/common/src/dto/shares.dto.ts index 279c8cd..4001e3a 100644 --- a/libs/common/src/dto/shares.dto.ts +++ b/libs/common/src/dto/shares.dto.ts @@ -5,11 +5,19 @@ import { IsPositive, IsNumber, IsDateString, + IsEnum, + IsOptional, + ValidateNested, + IsNotEmptyObject, } from 'class-validator'; import { OfferSharesRequest, + SharesTxStatus, + SharesTxTransferMeta, + SharesTxUpdates, SubscribeSharesRequest, TransferSharesRequest, + UpdateSharesRequest, UserSharesTxsRequest, } from '@bitsacco/common'; import { ApiProperty } from '@nestjs/swagger'; @@ -24,13 +32,19 @@ export class OfferSharesDto implements OfferSharesRequest { @IsNotEmpty() @IsDateString() @Type(() => String) - @ApiProperty({ description: 'Start date of availability (ISO 8601 format)', example: '2024-12-30T12:19:04.077Z' }) + @ApiProperty({ + description: 'Start date of availability (ISO 8601 format)', + example: '2024-12-30T12:19:04.077Z', + }) availableFrom: string; @IsNotEmpty() @IsDateString() @Type(() => String) - @ApiProperty({ description: 'End date of availability (ISO 8601 format)', example: '2025-12-30T12:19:04.077Z' }) + @ApiProperty({ + description: 'End date of availability (ISO 8601 format)', + example: '2025-12-30T12:19:04.077Z', + }) availableTo?: string; } @@ -50,7 +64,7 @@ export class SubscribeSharesDto implements SubscribeSharesRequest { @IsPositive() @IsNumber() @Type(() => Number) - @ApiProperty() + @ApiProperty({ description: 'Amount of shares to subscribe', example: 21 }) quantity: number; } @@ -76,10 +90,70 @@ export class TransferSharesDto implements TransferSharesRequest { @IsPositive() @IsNumber() @Type(() => Number) - @ApiProperty() + @ApiProperty({ description: 'Amount of shares to transfer', example: 10 }) + quantity: number; +} + +class SharesTxTransferMetaDto implements SharesTxTransferMeta { + @IsNotEmpty() + @IsString() + @Type(() => String) + @ApiProperty({ example: '7b158dfd-cb98-40b1-9ed2-a13006a9f670' }) + fromUserId: string; + + @IsNotEmpty() + @IsString() + @Type(() => String) + @ApiProperty({ example: '8b158dfd-cb98-40b1-9ed2-a13006a9f671' }) + toUserId: string; + + @IsPositive() + @IsNumber() + @Type(() => Number) + @ApiProperty({ description: 'Amount of shares transferred', example: 10 }) quantity: number; } +class SharesTxUpdatesDto implements SharesTxUpdates { + @IsOptional() + @IsPositive() + @IsNumber() + @Type(() => Number) + @ApiProperty() + quantity?: number; + + @IsOptional() + @IsEnum(SharesTxStatus) + @ApiProperty({ enum: SharesTxStatus }) + status?: SharesTxStatus; + + @IsOptional() + @ValidateNested() + @Type(() => SharesTxTransferMetaDto) + @ApiProperty({ type: SharesTxTransferMetaDto }) + transfer?: SharesTxTransferMeta; + + @IsOptional() + @IsString() + @Type(() => String) + @ApiProperty({ example: '3e158dfd-cb98-40b1-9ed2-a13006a9f430' }) + offerId?: string; +} + +export class UpdateSharesDto implements UpdateSharesRequest { + @IsNotEmpty() + @IsString() + @Type(() => String) + @ApiProperty({ example: '3e158dfd-cb98-40b1-9ed2-a13006a9f430' }) + sharesId: string; + + @IsNotEmptyObject() + @ValidateNested() + @Type(() => SharesTxUpdatesDto) + @ApiProperty({ type: SharesTxUpdatesDto }) + updates: SharesTxUpdatesDto; +} + export class UserSharesDto implements UserSharesTxsRequest { @IsNotEmpty() @IsString() diff --git a/libs/common/src/types/proto/shares.ts b/libs/common/src/types/proto/shares.ts index 0023537..5594942 100644 --- a/libs/common/src/types/proto/shares.ts +++ b/libs/common/src/types/proto/shares.ts @@ -83,6 +83,18 @@ export interface TransferSharesRequest { quantity: number; } +export interface UpdateSharesRequest { + sharesId: string; + updates: SharesTxUpdates | undefined; +} + +export interface SharesTxUpdates { + quantity?: number | undefined; + status?: SharesTxStatus | undefined; + transfer?: SharesTxTransferMeta | undefined; + offerId?: string | undefined; +} + export interface UserSharesTxsRequest { userId: string; } @@ -112,6 +124,8 @@ export interface SharesServiceClient { request: TransferSharesRequest, ): Observable; + updateShares(request: UpdateSharesRequest): Observable; + userSharesTransactions( request: UserSharesTxsRequest, ): Observable; @@ -142,6 +156,13 @@ export interface SharesServiceController { | Observable | UserShareTxsResponse; + updateShares( + request: UpdateSharesRequest, + ): + | Promise + | Observable + | UserShareTxsResponse; + userSharesTransactions( request: UserSharesTxsRequest, ): @@ -164,6 +185,7 @@ export function SharesServiceControllerMethods() { 'getSharesOffers', 'subscribeShares', 'transferShares', + 'updateShares', 'userSharesTransactions', 'allSharesTransactions', ]; diff --git a/proto/shares.proto b/proto/shares.proto index 814a14c..5454f51 100644 --- a/proto/shares.proto +++ b/proto/shares.proto @@ -9,6 +9,7 @@ service SharesService { rpc GetSharesOffers(lib.Empty) returns (AllSharesOffers); rpc SubscribeShares(SubscribeSharesRequest) returns (UserShareTxsResponse); rpc TransferShares(TransferSharesRequest) returns (UserShareTxsResponse); + rpc UpdateShares(UpdateSharesRequest) returns (UserShareTxsResponse); rpc UserSharesTransactions(UserSharesTxsRequest) returns (UserShareTxsResponse); rpc AllSharesTransactions(lib.Empty) returns (AllSharesTxsResponse); } @@ -101,6 +102,21 @@ message TransferSharesRequest { int32 quantity = 4; } +message UpdateSharesRequest { + string shares_id = 1; + SharesTxUpdates updates = 2; +} + +message SharesTxUpdates { + optional int32 quantity = 1; + + optional SharesTxStatus status = 2; + + optional SharesTxTransferMeta transfer = 3; + + optional string offer_id = 4; +} + message UserSharesTxsRequest { string user_id = 1; }