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
5,690 changes: 2,074 additions & 3,616 deletions package-lock.json

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "tradeflow-api",
"version": "0.0.1",
"description": "TradeFlow API",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^10.4.22",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.4.22",
"@nestjs/platform-express": "^10.4.22",
"@nestjs/swagger": "^7.1.1",
"@nestjs/throttler": "^6.5.0",
"@nestjs/typeorm": "^10.0.2",
"@stellar/stellar-sdk": "^11.0.0",
"@types/jsonwebtoken": "^9.0.10",
"axios": "^1.13.6",
"class-validator": "^0.14.4",
"jsonwebtoken": "^9.0.3",
"pg": "^8.11.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2",
"swagger-ui-express": "^5.0.0",
"typeorm": "^0.3.28"
},
"devDependencies": {
"@nestjs/cli": "^11.0.16",
"@nestjs/testing": "^10.4.22",
"@types/express": "^5.0.6",
"@types/jest": "^30.0.0",
"@types/node": "^25.2.3",
"@types/pdf-parse": "^1.1.5",
"jest": "^30.2.0",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
}
}
6 changes: 6 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
import { Module } from '@nestjs/common';
import { TokensModule } from './tokens/tokens.module';

@Module({
imports: [TokensModule],
})
export class AppModule {}
4 changes: 1 addition & 3 deletions src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ export class AppService {
}

// POST: Create Invoice & Calculate Risk
processNewInvoice(data: any) {
processNewInvoice(data: CreateInvoiceDto): InvoiceDto {
const amount = Number(data.amount) || 0;
const date = data.date ? new Date(data.date) : new Date();

const riskScore = this.riskService.calculateScore(amount, date);
processNewInvoice(data: CreateInvoiceDto): InvoiceDto {
// Mock Risk Logic: Random Score 50-99
const riskScore = Math.floor(Math.random() * 50) + 50;

Expand Down
32 changes: 32 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

// Enable CORS
app.enableCors({
origin: true,
credentials: true,
});

// Global validation pipe
app.useGlobalPipes(new ValidationPipe());

// Swagger documentation
const config = new DocumentBuilder()
.setTitle('TradeFlow API')
.setDescription('TradeFlow API documentation')
.setVersion('1.0')
.build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

await app.listen(3000);
console.log('Application is running on: http://localhost:3000');
}

bootstrap();
42 changes: 0 additions & 42 deletions src/risk/risk.service.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,3 @@
import { Injectable } from '@nestjs/common';

@Injectable()
export class RiskService {
calculateScore(amount: number, date: Date): number {
let baseScore = 90; // Default score for amounts between 1000 and 10000

if (amount > 10000) {
baseScore = 80;
} else if (amount < 1000) {
baseScore = 95;
}

// Generate a deterministic hash based on amount and date
// to simulate a consistent "AI analysis" result for the exact same inputs
const seedString = `${amount}-${date.getTime()}`;
const hash = this.generateHash(seedString);

// Generate a pseudo-random variation between -5 and +5
const variation = (hash % 11) - 5;

let finalScore = baseScore + variation;

// Ensure score is strictly between 0 and 100
if (finalScore > 100) {
finalScore = 100;
} else if (finalScore < 0) {
finalScore = 0;
}

return finalScore;
}

private generateHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0; // Convert to 32bit integer
}
return Math.abs(hash);
}
import { Injectable, BadRequestException } from '@nestjs/common';

@Injectable()
Expand Down
27 changes: 23 additions & 4 deletions src/tokens/tokens.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
@ApiTags('Tokens')
@Controller('tokens')
export class TokensController {
private cachedTokens: string[] | null = null;
private cacheTimestamp: number = 0;
private readonly CACHE_DURATION_MS = 5 * 60 * 1000; // 5 minutes

@Get()
@ApiOperation({ summary: 'Search for tokens by symbol', description: 'Search for tokens using a symbol query parameter' })
Expand All @@ -30,16 +33,32 @@ export class TokensController {
};
}

// Simulate a search operation - in a real app this would query a database
const mockTokens = ['BTC', 'ETH', 'USDT', 'BNB', 'ADA', 'DOT', 'LINK', 'UNI'];
const filteredTokens = mockTokens.filter(token =>
// Check if cache is valid (exists and not expired)
const currentTime = Date.now();
const isCacheValid = this.cachedTokens !== null &&
(currentTime - this.cacheTimestamp) < this.CACHE_DURATION_MS;

let tokens: string[];

if (isCacheValid) {
// Use cached tokens
tokens = this.cachedTokens;
} else {
// Cache miss or expired - fetch fresh data
tokens = ['BTC', 'ETH', 'USDT', 'BNB', 'ADA', 'DOT', 'LINK', 'UNI']; // Simulate database query
this.cachedTokens = tokens;
this.cacheTimestamp = currentTime;
}

const filteredTokens = tokens.filter(token =>
token.toLowerCase().includes(searchQuery.toLowerCase())
);

return {
message: `Search results for: ${searchQuery}`,
searchQuery: searchQuery,
results: filteredTokens
results: filteredTokens,
cached: isCacheValid // Include cache status for debugging
};
}

Expand Down
121 changes: 121 additions & 0 deletions test-cache-standalone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Standalone test for the token cache implementation
// This simulates the caching logic without requiring the full NestJS server

class TokensController {
constructor() {
this.cachedTokens = null;
this.cacheTimestamp = 0;
this.CACHE_DURATION_MS = 5 * 60 * 1000; // 5 minutes
}

searchTokens(searchQuery) {
if (!searchQuery) {
return {
message: 'No search query provided',
searchQuery: '',
results: []
};
}

// Check if cache is valid (exists and not expired)
const currentTime = Date.now();
const isCacheValid = this.cachedTokens !== null &&
(currentTime - this.cacheTimestamp) < this.CACHE_DURATION_MS;

let tokens;

if (isCacheValid) {
// Use cached tokens
tokens = this.cachedTokens;
console.log('🎯 Cache HIT - Using cached tokens');
} else {
// Cache miss or expired - fetch fresh data
console.log('❌ Cache MISS - Fetching fresh data');
tokens = ['BTC', 'ETH', 'USDT', 'BNB', 'ADA', 'DOT', 'LINK', 'UNI']; // Simulate database query
this.cachedTokens = tokens;
this.cacheTimestamp = currentTime;
}

const filteredTokens = tokens.filter(token =>
token.toLowerCase().includes(searchQuery.toLowerCase())
);

return {
message: `Search results for: ${searchQuery}`,
searchQuery: searchQuery,
results: filteredTokens,
cached: isCacheValid
};
}

// Helper method to clear cache for testing
clearCache() {
this.cachedTokens = null;
this.cacheTimestamp = 0;
console.log('🧹 Cache cleared');
}

// Helper method to expire cache for testing
expireCache() {
this.cacheTimestamp = Date.now() - this.CACHE_DURATION_MS - 1000;
console.log('⏰ Cache expired');
}
}

// Test the caching implementation
async function testCacheImplementation() {
console.log('🚀 Testing Token Cache Implementation\n');

const controller = new TokensController();

console.log('1️⃣ First request (should be cache miss):');
const start1 = Date.now();
const result1 = controller.searchTokens('BT');
const end1 = Date.now();
console.log(` Response time: ${end1 - start1}ms`);
console.log(` Cached: ${result1.cached}`);
console.log(` Results: ${JSON.stringify(result1.results)}\n`);

console.log('2️⃣ Second request (should be cache hit):');
const start2 = Date.now();
const result2 = controller.searchTokens('BT');
const end2 = Date.now();
console.log(` Response time: ${end2 - start2}ms`);
console.log(` Cached: ${result2.cached}`);
console.log(` Results: ${JSON.stringify(result2.results)}\n`);

console.log('3️⃣ Third request with different search (should still be cache hit):');
const start3 = Date.now();
const result3 = controller.searchTokens('ET');
const end3 = Date.now();
console.log(` Response time: ${end3 - start3}ms`);
console.log(` Cached: ${result3.cached}`);
console.log(` Results: ${JSON.stringify(result3.results)}\n`);

console.log('4️⃣ Testing cache expiration:');
controller.expireCache();
const start4 = Date.now();
const result4 = controller.searchTokens('BT');
const end4 = Date.now();
console.log(` Response time: ${end4 - start4}ms`);
console.log(` Cached: ${result4.cached}`);
console.log(` Results: ${JSON.stringify(result4.results)}\n`);

console.log('5️⃣ Testing cache clear:');
controller.clearCache();
const start5 = Date.now();
const result5 = controller.searchTokens('BT');
const end5 = Date.now();
console.log(` Response time: ${end5 - start5}ms`);
console.log(` Cached: ${result5.cached}`);
console.log(` Results: ${JSON.stringify(result5.results)}\n`);

console.log('✅ Cache implementation test completed successfully!');
console.log('\n📊 Summary:');
console.log('- Cache miss requests take longer (simulating database query)');
console.log('- Cache hit requests are faster');
console.log('- Cache expires after 5 minutes');
console.log('- Cache can be cleared manually');
}

testCacheImplementation();
48 changes: 48 additions & 0 deletions test-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const axios = require('axios');

// Test the token endpoint caching
async function testTokenCache() {
const baseURL = 'http://localhost:3000';

console.log('Testing Token Cache Implementation...\n');

try {
// First request - should fetch from database/cache miss
console.log('1. First request (cache miss):');
const start1 = Date.now();
const response1 = await axios.get(`${baseURL}/tokens?search=BT`);
const end1 = Date.now();
console.log(` Response time: ${end1 - start1}ms`);
console.log(` Cached: ${response1.data.cached}`);
console.log(` Results: ${JSON.stringify(response1.data.results)}\n`);

// Second request - should use cache
console.log('2. Second request (cache hit):');
const start2 = Date.now();
const response2 = await axios.get(`${baseURL}/tokens?search=BT`);
const end2 = Date.now();
console.log(` Response time: ${end2 - start2}ms`);
console.log(` Cached: ${response2.data.cached}`);
console.log(` Results: ${JSON.stringify(response2.data.results)}\n`);

// Third request with different search - should still use cache
console.log('3. Third request (different search, cache hit):');
const start3 = Date.now();
const response3 = await axios.get(`${baseURL}/tokens?search=ET`);
const end3 = Date.now();
console.log(` Response time: ${end3 - start3}ms`);
console.log(` Cached: ${response3.data.cached}`);
console.log(` Results: ${JSON.stringify(response3.data.results)}\n`);

console.log('Cache test completed successfully!');

} catch (error) {
if (error.code === 'ECONNREFUSED') {
console.log('Server is not running. Please start the server first with: npm run start:dev');
} else {
console.log('Error:', error.message);
}
}
}

testTokenCache();
Loading