Backoffice contact details, currency mgmt. updates

This commit is contained in:
Stephanos A
2026-07-11 12:16:05 +03:00
parent 3c3924b33f
commit 50ebe8ddda
14 changed files with 323 additions and 355 deletions

View File

@@ -0,0 +1,55 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, SetMetadata } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { IsEnum, IsNumber, IsOptional, IsString, Min } from 'class-validator';
import { Currency } from '@prisma/client';
import { CurrencyService } from './currency.service';
import { PassengerAdmin } from '../../common/passenger-guards';
class CreateRateDto {
@IsEnum(Currency) fromCurrency: Currency;
@IsEnum(Currency) toCurrency: Currency;
@IsNumber() @Min(0.000001) rate: number;
@IsOptional() @IsString() source?: string;
}
class UpdateRateDto {
@IsNumber() @Min(0.000001) rate: number;
@IsOptional() @IsString() source?: string;
}
@ApiTags('Currency')
@Controller('currencies')
export class CurrencyController {
constructor(private readonly currencyService: CurrencyService) {}
@Get()
@SetMetadata('isPublic', true)
@ApiOperation({ summary: 'List all exchange rates' })
listRates() {
return this.currencyService.listRates();
}
@Post()
@PassengerAdmin()
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Create exchange rate' })
create(@Body() dto: CreateRateDto) {
return this.currencyService.upsertRate(dto.fromCurrency, dto.toCurrency, dto.rate, undefined, dto.source ?? 'MANUAL');
}
@Patch(':id')
@PassengerAdmin()
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Update exchange rate by ID' })
update(@Param('id') id: string, @Body() dto: UpdateRateDto) {
return this.currencyService.updateRateById(id, dto.rate, dto.source ?? 'MANUAL');
}
@Delete(':id')
@PassengerAdmin()
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Delete exchange rate by ID' })
delete(@Param('id') id: string) {
return this.currencyService.deleteRate(id);
}
}

View File

@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { CurrencyService } from './currency.service';
import { CurrencyController } from './currency.controller';
import { PrismaModule } from '../../common/prisma.module';
@Module({
imports: [PrismaModule, HttpModule],
imports: [PrismaModule],
controllers: [CurrencyController],
providers: [CurrencyService],
exports: [CurrencyService],
})

View File

@@ -1,12 +1,4 @@
import {
Injectable,
Logger,
NotFoundException,
BadRequestException,
} from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';
import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { Currency } from '@prisma/client';
@@ -24,11 +16,7 @@ const CHARGE_CURRENCY_DECIMALS: Record<string, number> = {
export class CurrencyService {
private readonly logger = new Logger(CurrencyService.name);
constructor(
private readonly prisma: PrismaService,
private readonly httpService: HttpService,
private readonly configService: ConfigService,
) {}
constructor(private readonly prisma: PrismaService) {}
/**
* Converts a stored display-currency minor amount to the charge major amount
@@ -148,53 +136,6 @@ export class CurrencyService {
return Number(exchangeRate.rate);
}
async syncExchangeRates(): Promise<void> {
this.logger.log('Syncing exchange rates from central bank API');
const today = this.todayUtc();
// Fallback rates used when the API is unreachable
const fallbackRates = [
{ from: Currency.ETB, to: Currency.ETB, rate: 1.0 },
{ from: Currency.ETB, to: Currency.DJF, rate: 3.25 },
{ from: Currency.ETB, to: Currency.USD, rate: 0.018 },
{ from: Currency.DJF, to: Currency.ETB, rate: 0.3077 },
{ from: Currency.USD, to: Currency.ETB, rate: 55.56 },
];
const apiUrl = this.configService.get<string>('EXCHANGE_RATE_API_URL');
if (apiUrl) {
try {
const response = await firstValueFrom(
this.httpService.get<Record<string, number>>(apiUrl, { timeout: 5000 }),
);
// Expected response shape: { "ETB_DJF": 3.25, "ETB_USD": 0.018, ... }
const data = response.data;
const apiRates = [
{ from: Currency.ETB, to: Currency.ETB, rate: 1.0 },
{ from: Currency.ETB, to: Currency.DJF, rate: data['ETB_DJF'] ?? fallbackRates[1].rate },
{ from: Currency.ETB, to: Currency.USD, rate: data['ETB_USD'] ?? fallbackRates[2].rate },
{ from: Currency.DJF, to: Currency.ETB, rate: data['DJF_ETB'] ?? fallbackRates[3].rate },
{ from: Currency.USD, to: Currency.ETB, rate: data['USD_ETB'] ?? fallbackRates[4].rate },
];
for (const { from, to, rate } of apiRates) {
await this.upsertRate(from, to, rate, today, 'CENTRAL_BANK_API');
}
this.logger.log('Exchange rates synced from central bank API');
return;
} catch (err) {
this.logger.warn(
`Central bank API unreachable (${(err as Error).message}), falling back to configured rates`,
);
}
}
// Fallback: persist the static rates so the DB always has a current row
for (const { from, to, rate } of fallbackRates) {
await this.upsertRate(from, to, rate, today, 'FALLBACK');
}
this.logger.log('Exchange rates synced using fallback values');
}
async listRates() {
return this.prisma.currencyExchangeRate.findMany({
orderBy: [{ fromCurrency: 'asc' }, { toCurrency: 'asc' }, { effectiveDate: 'desc' }],