Skip to content
Open
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
@@ -0,0 +1,34 @@
-- CreateTable
CREATE TABLE "public"."Wallet" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"totalValue" DOUBLE PRECISION NOT NULL DEFAULT 0,
"totalProfit" DOUBLE PRECISION NOT NULL DEFAULT 0,
"rentability" DOUBLE PRECISION NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Wallet_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "public"."WalletItem" (
"id" TEXT NOT NULL,
"walletId" TEXT NOT NULL,
"assetId" TEXT NOT NULL,
"quantity" DOUBLE PRECISION NOT NULL,
"avgBuyPrice" DOUBLE PRECISION NOT NULL,
"currentPrice" DOUBLE PRECISION NOT NULL,
"profit" DOUBLE PRECISION NOT NULL,

CONSTRAINT "WalletItem_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Wallet_userId_key" ON "public"."Wallet"("userId");

-- AddForeignKey
ALTER TABLE "public"."Wallet" ADD CONSTRAINT "Wallet_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."WalletItem" ADD CONSTRAINT "WalletItem_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "public"."Wallet"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
27 changes: 26 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,29 @@ model User {
role String @default("user")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
wallet Wallet?
}

model Wallet {
id String @id @default(uuid())
userId String @unique
totalValue Float @default(0)
totalProfit Float @default(0)
rentability Float @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
items WalletItem[]
user User @relation(fields: [userId], references: [id])
}

model WalletItem {
id String @id @default(uuid())
walletId String
assetId String
quantity Float
avgBuyPrice Float
currentPrice Float
profit Float
wallet Wallet @relation(fields: [walletId], references: [id])
// assets Assets @relation(fields: [assetId], references: [id])
}
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { AssetsModule } from "./assets/assets.module";
import { UsersModule } from "./users/users.module";
import { AuthModule } from "./auth/auth.module";
import { ConfigModule } from "@nestjs/config";
import { WalletModule } from "./wallet/wallet.module";

@Module({
imports: [
AssetsModule,
UsersModule,
AuthModule,
WalletModule,
ConfigModule.forRoot({
isGlobal: true,
}),
Expand Down
25 changes: 25 additions & 0 deletions src/wallet/tests/wallet.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { WalletService } from '../wallet.service';
import { PrismaService } from '../../common/prisma.service';

describe('WalletService', () => {
let service: WalletService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [WalletService, PrismaService],
}).compile();

service = module.get<WalletService>(WalletService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});

it('should return wallet summary', async () => {
const summary = await service.getWalletSummary('test-user');
expect(summary).toHaveProperty('totalValue');
expect(summary).toHaveProperty('totalProfit');
});
});
17 changes: 17 additions & 0 deletions src/wallet/wallet.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Controller, Get, Query } from '@nestjs/common';
import { WalletService } from './wallet.service';

@Controller('wallet')
export class WalletController {
constructor(private readonly walletService: WalletService) {}

@Get()
async getWallet(@Query('userId') userId: string) {
return this.walletService.getWallet(userId);
}

@Get('summary')
async getWalletSummary(@Query('userId') userId: string) {
return this.walletService.getWalletSummary(userId);
}
}
10 changes: 10 additions & 0 deletions src/wallet/wallet.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { WalletService } from './wallet.service';
import { WalletController } from './wallet.controller';
import { PrismaService } from '../common/prisma.service';

@Module({
providers: [WalletService, PrismaService],
controllers: [WalletController],
})
export class WalletModule {}
66 changes: 66 additions & 0 deletions src/wallet/wallet.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../common/prisma.service';

@Injectable()
export class WalletService {
constructor(private prisma: PrismaService) {}

// Retorna ativos detalhados na carteira do usuário
async getWallet(userId: string) {
// const orders = await this.prisma.order.findMany({
// where: { userId },
// include: { asset: true },
// });

const orders = [
{ assetId: '1', quantity: 10, price: 50, type: 'BUY', asset: { symbol: 'TEC11', name: 'Tesouro', currentPrice: 55 } },
{ assetId: '2', quantity: 5, price: 100, type: 'BUY', asset: { symbol: 'FINV3', name: 'Financeira', currentPrice: 95 } },
];

const grouped = new Map<string, any>();

for (const order of orders) {
const { assetId, asset, quantity, price, type } = order;
const sign = type === 'BUY' ? 1 : -1;
const entry = grouped.get(assetId) || {
assetId,
symbol: asset.symbol,
name: asset.name,
quantity: 0,
totalSpent: 0,
avgBuyPrice: 0,
currentPrice: asset.currentPrice,
};

entry.quantity += quantity * sign;
entry.totalSpent += price * quantity * (type === 'BUY' ? 1 : -1);
entry.avgBuyPrice = entry.totalSpent / entry.quantity;
grouped.set(assetId, entry);
}

const walletItems = Array.from(grouped.values()).map((item) => {
const profit = (item.currentPrice - item.avgBuyPrice) * item.quantity;
return { ...item, profit };
});

return walletItems;
}

// Resumo da carteira
async getWalletSummary(userId: string) {
const wallet = await this.getWallet(userId);
const totalValue = wallet.reduce(
(acc, a) => acc + a.currentPrice * a.quantity,
0,
);
const totalProfit = wallet.reduce((acc, a) => acc + a.profit, 0);
const rentability = totalProfit / (totalValue - totalProfit || 1);

return {
totalValue,
totalProfit,
rentability,
assetsCount: wallet.length,
};
}
}
25 changes: 0 additions & 25 deletions test/app.e2e-spec.ts

This file was deleted.

33 changes: 0 additions & 33 deletions test/db.e2e-spec.ts

This file was deleted.

33 changes: 33 additions & 0 deletions test/wallet.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { AppModule } from '../src/app.module';

describe('Wallet (e2e)', () => {
let app: INestApplication;

beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
//aqui desabilitamos o JwtStrategy só para o teste
.overrideProvider('JwtStrategy')
.useValue({})
.compile();

app = moduleFixture.createNestApplication();
await app.init();
});

it("/wallet (GET)", async () => {
const res = await request(app.getHttpServer())
.get("/wallet")
.query({ userId: "admin-001" });
expect(res.status).toBe(200);
expect(res.body).toBeDefined();
});

afterAll(async () => {
await app.close();
});
});