Skip to content
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
16 changes: 14 additions & 2 deletions src/modules/ownership/ownership.controllers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AsyncController } from '../../types/auth.types';
import { OwnershipQuerySchema } from './ownership.schemas';
import { fetchOwnership } from './ownership.service';
import { calculateTotalPortfolioValue } from './ownership.utils';
import { sendSuccess, sendValidationError } from '../../utils/api-response.utils';

export const httpGetOwnership: AsyncController = async (req, res, next) => {
Expand All @@ -13,8 +14,19 @@ export const httpGetOwnership: AsyncController = async (req, res, next) => {
})));
}

const ownership = await fetchOwnership(parsed.data);
sendSuccess(res, ownership);
const records = await fetchOwnership(parsed.data);
const holdings = records.map(record => ({
id: record.id,
ownerAddress: record.ownerAddress,
creatorId: record.creatorId,
balance: record.balance.toString(),
currentPrice: '0',
updatedAt: record.updatedAt,
}));
sendSuccess(res, {
holdings,
total_portfolio_value: calculateTotalPortfolioValue(holdings),
});
} catch (error) {
next(error);
}
Expand Down
16 changes: 15 additions & 1 deletion src/modules/ownership/ownership.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,22 @@ export const OwnershipItemSchema = z.object({
id: z.string(),
ownerAddress: z.string(),
creatorId: z.string(),
balance: z.string(), // Decimal is returned as string in Prisma Json/JsonValue but as Decimal object in real types. For API, string is safer.
balance: z.string(),
updatedAt: z.date(),
});

export const HoldingItemSchema = z.object({
id: z.string(),
ownerAddress: z.string(),
creatorId: z.string(),
balance: z.string(),
currentPrice: z.string(),
updatedAt: z.date(),
});

export const HoldingsResponseSchema = z.object({
holdings: z.array(HoldingItemSchema),
total_portfolio_value: z.string(),
});

export const OwnershipResponseSchema = z.array(OwnershipItemSchema);
39 changes: 39 additions & 0 deletions src/modules/ownership/ownership.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { calculateTotalPortfolioValue, HoldingEntry } from './ownership.utils';

describe('calculateTotalPortfolioValue', () => {
it('returns correct sum for a single holding', () => {
const entries: HoldingEntry[] = [
{ balance: '100', currentPrice: '5.50' },
];
expect(calculateTotalPortfolioValue(entries)).toBe('550');
});

it('returns correct sum for multiple holdings', () => {
const entries: HoldingEntry[] = [
{ balance: '100', currentPrice: '5.50' },
{ balance: '200', currentPrice: '10.00' },
{ balance: '50', currentPrice: '2.00' },
];
expect(calculateTotalPortfolioValue(entries)).toBe('2650');
});

it('returns zero when all prices are zero', () => {
const entries: HoldingEntry[] = [
{ balance: '100', currentPrice: '0' },
{ balance: '200', currentPrice: '0' },
];
expect(calculateTotalPortfolioValue(entries)).toBe('0');
});

it('returns zero when all balances are zero', () => {
const entries: HoldingEntry[] = [
{ balance: '0', currentPrice: '5.50' },
{ balance: '0', currentPrice: '10.00' },
];
expect(calculateTotalPortfolioValue(entries)).toBe('0');
});

it('returns zero for an empty array', () => {
expect(calculateTotalPortfolioValue([])).toBe('0');
});
});
12 changes: 12 additions & 0 deletions src/modules/ownership/ownership.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface HoldingEntry {
balance: string;
currentPrice: string;
}

export function calculateTotalPortfolioValue(entries: HoldingEntry[]): string {
const total = entries.reduce(
(sum, entry) => sum + Number(entry.balance) * Number(entry.currentPrice),
0,
);
return String(total);
}
Loading