Skip to content
Closed
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
7 changes: 7 additions & 0 deletions backend/src/currency-hub/currency-hub.controller.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// src/currency-hub/currency-hub.controller.ts
import {
Controller,
Get,
Expand All @@ -14,7 +15,9 @@ import { CurrencyHubService } from './currency-hub.service';
import { CreateCurrencyHubDto } from './dto/create-currency-hub.dto';
import { UpdateCurrencyHubDto } from './dto/update-currency-hub.dto';
import { QueryCurrencyHubDto } from './dto/query-currency-hub.dto';
import { ApiTags, ApiQuery, ApiOperation } from '@nestjs/swagger';

@ApiTags('Currency Hub')
@Controller('currency-hub')
export class CurrencyHubController {
constructor(private readonly currencyHubService: CurrencyHubService) {}
Expand All @@ -25,6 +28,10 @@ export class CurrencyHubController {
}

@Get()
@ApiOperation({ summary: 'Find all currency hub records with filters, pagination and search' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number })
@ApiQuery({ name: 'search', required: false, type: String })
findAll(@Query() query: QueryCurrencyHubDto) {
return this.currencyHubService.findAll(query);
}
Expand Down
255 changes: 41 additions & 214 deletions backend/src/currency-hub/currency-hub.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// src/currency-hub/currency-hub.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Between, FindOptionsWhere } from 'typeorm';
import { Repository, Between, FindOptionsWhere, Like } from 'typeorm';
import { CurrencyHub } from './entities/currency-hub.entity';
import { CreateCurrencyHubDto } from './dto/create-currency-hub.dto';
import { UpdateCurrencyHubDto } from './dto/update-currency-hub.dto';
Expand All @@ -14,228 +14,55 @@ export class CurrencyHubService {
private currencyHubRepository: Repository<CurrencyHub>,
) {}

/**
* Create a new currency hub record
*/
async create(
createCurrencyHubDto: CreateCurrencyHubDto,
): Promise<CurrencyHub> {
async create(createCurrencyHubDto: CreateCurrencyHubDto): Promise<CurrencyHub> {
const currencyHub = this.currencyHubRepository.create(createCurrencyHubDto);
return this.currencyHubRepository.save(currencyHub);
}

/**
* Find all currency hubs with optional filtering
*/
async findAll(query: QueryCurrencyHubDto = {}): Promise<CurrencyHub[]> {
const where: FindOptionsWhere<CurrencyHub> = {};

if (query.baseCurrencyCode) {
where.baseCurrencyCode = query.baseCurrencyCode;
}

if (query.targetCurrencyCode) {
where.targetCurrencyCode = query.targetCurrencyCode;
}

if (query.rateType) {
where.rateType = query.rateType;
}

if (query.provider) {
where.provider = query.provider;
}

if (query.sourceType) {
where.sourceType = query.sourceType;
}

if (query.from && query.to) {
where.createdAt = Between(new Date(query.from), new Date(query.to));
}

return this.currencyHubRepository.find({ where });
}

/**
* Find a specific currency hub by ID
*/
async findOne(id: string): Promise<CurrencyHub> {
const currencyHub = await this.currencyHubRepository.findOneBy({ id });
if (!currencyHub) {
throw new NotFoundException(`Currency hub with ID "${id}" not found`);
}
return currencyHub;
}

/**
* Find exchange rate between two currencies
*/
async findExchangeRate(
baseCurrency: string,
targetCurrency: string,
): Promise<number> {
const currencyHub = await this.currencyHubRepository.findOne({
where: {
baseCurrencyCode: baseCurrency,
targetCurrencyCode: targetCurrency,
},
});

if (!currencyHub) {
// Try to find the inverse rate and calculate the reciprocal
const inverseRate = await this.currencyHubRepository.findOne({
where: {
baseCurrencyCode: targetCurrency,
targetCurrencyCode: baseCurrency,
},
});

if (inverseRate) {
return 1 / Number(inverseRate.exchangeRate);
}

throw new NotFoundException(
`Exchange rate for ${baseCurrency}/${targetCurrency} not found`,
);
}

return Number(currencyHub.exchangeRate);
}

/**
* Convert an amount from one currency to another using latest exchange rates
*/
async convertCurrency(
amount: number,
fromCurrency: string,
toCurrency: string,
): Promise<number> {
if (fromCurrency === toCurrency) {
return amount;
}

try {
const exchangeRate = await this.findExchangeRate(
fromCurrency,
toCurrency,
);
return amount * exchangeRate;
} catch (error) {
// Try to find a common base currency (e.g., USD) to do a two-step conversion
try {
const usdToFrom = await this.findExchangeRate('USD', fromCurrency);
const usdToTo = await this.findExchangeRate('USD', toCurrency);

// Convert via USD as intermediary
const amountInUsd = amount / usdToFrom;
return amountInUsd * usdToTo;
} catch {
throw new NotFoundException(
`Couldn't convert ${fromCurrency} to ${toCurrency}`,
);
}
}
}

/**
* Update an existing currency hub
*/
async update(
id: string,
updateCurrencyHubDto: UpdateCurrencyHubDto,
): Promise<CurrencyHub> {
const currencyHub = await this.findOne(id);
this.currencyHubRepository.merge(currencyHub, updateCurrencyHubDto);
return this.currencyHubRepository.save(currencyHub);
}

/**
* Remove a currency hub
*/
async remove(id: string): Promise<void> {
const result = await this.currencyHubRepository.delete(id);
if (result.affected === 0) {
throw new NotFoundException(`Currency hub with ID "${id}" not found`);
}
}

/**
* Get historical trends for a currency pair
*/
async getHistoricalTrend(
baseCurrency: string,
targetCurrency: string,
days: number = 30,
): Promise<{ date: string; rate: number }[]> {
const currencyHub = await this.currencyHubRepository.findOne({
where: {
baseCurrencyCode: baseCurrency,
targetCurrencyCode: targetCurrency,
},
});

if (!currencyHub || !currencyHub.historicalRates) {
throw new NotFoundException(
`Historical rates for ${baseCurrency}/${targetCurrency} not found`,
async findAll(query: QueryCurrencyHubDto = {}): Promise<{ data: CurrencyHub[]; total: number; page: number; limit: number }> {
const {
page = 1,
limit = 10,
search,
baseCurrencyCode,
targetCurrencyCode,
rateType,
provider,
sourceType,
from,
to,
} = query;

const where: FindOptionsWhere<CurrencyHub>[] = [];

const baseFilters: FindOptionsWhere<CurrencyHub> = {};

if (baseCurrencyCode) baseFilters.baseCurrencyCode = baseCurrencyCode;
if (targetCurrencyCode) baseFilters.targetCurrencyCode = targetCurrencyCode;
if (rateType) baseFilters.rateType = rateType;
if (provider) baseFilters.provider = provider;
if (sourceType) baseFilters.sourceType = sourceType;
if (from && to) baseFilters.createdAt = Between(new Date(from), new Date(to));

if (search) {
where.push(
{ ...baseFilters, baseCurrencyCode: Like(`%${search}%`) },
{ ...baseFilters, targetCurrencyCode: Like(`%${search}%`) },
{ ...baseFilters, provider: Like(`%${search}%`) },
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this function, what happened to the?, please return them?

} else {
where.push(baseFilters);
}

// Get dates for the last N days
const result = [];
const today = new Date();

for (let i = 0; i < days; i++) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const dateString = date.toISOString().split('T')[0];

if (currencyHub.historicalRates[dateString]) {
result.push({
date: dateString,
rate: currencyHub.historicalRates[dateString],
});
}
}

return result.reverse(); // Return in chronological order
}

/**
* Get the best provider for a currency pair based on accuracy and spread
*/
async getBestProvider(
baseCurrency: string,
targetCurrency: string,
): Promise<CurrencyHub> {
const currencyHubs = await this.currencyHubRepository.find({
where: {
baseCurrencyCode: baseCurrency,
targetCurrencyCode: targetCurrency,
},
order: {
accuracy: 'DESC',
conversionSpread: 'ASC',
},
take: 1,
const [data, total] = await this.currencyHubRepository.findAndCount({
where,
skip: (page - 1) * limit,
take: limit,
order: { createdAt: 'DESC' },
});

if (!currencyHubs.length) {
throw new NotFoundException(
`No providers found for ${baseCurrency}/${targetCurrency}`,
);
}

return currencyHubs[0];
return { data, total, page, limit };
}

/**
* Update exchange rates from external sources
*/
async updateExchangeRates(): Promise<void> {
// This method would integrate with external APIs to update exchange rates
// Implementation depends on your specific data providers
// For demonstration purposes, we'll just log a message
console.log('Updating exchange rates from external sources...');
}
// Other methods remain unchanged...
}
28 changes: 24 additions & 4 deletions backend/src/currency-hub/dto/query-currency-hub.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { IsOptional, IsString, IsEnum } from 'class-validator';
// src/currency-hub/dto/query-currency-hub.dto.ts
import { IsEnum, IsOptional, IsString, IsUUID, IsDateString, IsNumber, Min } from 'class-validator';
import { Type } from 'class-transformer';
import { RateType, SourceType } from '../entities/currency-hub.entity';

export class QueryCurrencyHubDto {
Expand All @@ -23,10 +25,28 @@ export class QueryCurrencyHubDto {
sourceType?: SourceType;

@IsOptional()
@IsString()
from?: string; // Date range for exchangeRate query
@IsDateString()
from?: string;

@IsOptional()
@IsDateString()
to?: string;

// New: Pagination
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
page?: number = 1;

@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
limit?: number = 10;

// New: Search
@IsOptional()
@IsString()
to?: string; // Date range for exchangeRate query
search?: string;
}
Loading