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
22 changes: 22 additions & 0 deletions app/backend/src/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Controller, Get, Query } from "@nestjs/common";
import { AnalyticsService } from "./analytics.service";

@Controller("analytics")
export class AnalyticsController {
constructor(private readonly service: AnalyticsService) {}

@Get("dashboard")
async dashboard(@Query("userId") userId: string) {
return this.service.getDashboardData(userId);
}

@Get("time-series")
async timeSeries(@Query("userId") userId: string, @Query("interval") interval: "day" | "week" | "month") {
return this.service.getTimeSeriesData(userId, interval);
}

@Get("export")
async export(@Query("userId") userId: string, @Query("format") format: "csv" | "pdf") {
return this.service.exportReport(userId, format);
}
}
32 changes: 32 additions & 0 deletions app/backend/src/analytics/analytics.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AppDataSource } from "../data-source";
import { Transaction } from "../entities/transaction.entity";

export const transactionRepo = AppDataSource.getRepository(Transaction);

export async function getTotalVolumeUSD(userId: string) {
return transactionRepo
.createQueryBuilder("t")
.select("SUM(t.amount_usd)", "total")
.where("t.userId = :userId", { userId })
.getRawOne();
}

export async function getAssetDistribution(userId: string) {
return transactionRepo
.createQueryBuilder("t")
.select("t.asset, SUM(t.amount_usd)", "total")
.where("t.userId = :userId", { userId })
.groupBy("t.asset")
.getRawMany();
}

export async function getTimeSeries(userId: string, interval: "day" | "week" | "month") {
return transactionRepo
.createQueryBuilder("t")
.select(`DATE_TRUNC('${interval}', t.createdAt)`, "period")
.addSelect("SUM(t.amount_usd)", "total")
.where("t.userId = :userId", { userId })
.groupBy("period")
.orderBy("period", "ASC")
.getRawMany();
}
21 changes: 21 additions & 0 deletions app/backend/src/analytics/analytics.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getTotalVolumeUSD, getAssetDistribution, getTimeSeries } from "./analytics.repository";
import { exportToCSV } from "../utils/csv-export";
import { exportToPDF } from "../utils/pdf-export";

export class AnalyticsService {
async getDashboardData(userId: string) {
const totalVolume = await getTotalVolumeUSD(userId);
const distribution = await getAssetDistribution(userId);
return { totalVolume, distribution };
}

async getTimeSeriesData(userId: string, interval: "day" | "week" | "month") {
return getTimeSeries(userId, interval);
}

async exportReport(userId: string, format: "csv" | "pdf") {
const data = await this.getDashboardData(userId);
if (format === "csv") return exportToCSV(data);
if (format === "pdf") return exportToPDF(data);
}
}
26 changes: 26 additions & 0 deletions app/backend/src/analytics/tests/analytics.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AnalyticsService } from "../analytics.service";

describe("AnalyticsService", () => {
const service = new AnalyticsService();

it("should return dashboard data", async () => {
const data = await service.getDashboardData("user1");
expect(data).toHaveProperty("totalVolume");
expect(data).toHaveProperty("distribution");
});

it("should return time series data", async () => {
const series = await service.getTimeSeriesData("user1", "day");
expect(Array.isArray(series)).toBe(true);
});

it("should export CSV", async () => {
const csv = await service.exportReport("user1", "csv");
expect(typeof csv).toBe("string");
});

it("should export PDF", async () => {
const pdf = await service.exportReport("user1", "pdf");
expect(typeof pdf).toBe("string");
});
});
6 changes: 6 additions & 0 deletions app/backend/src/utils/csv-export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Parser } from "json2csv";

export function exportToCSV(data: any) {

Check failure on line 3 in app/backend/src/utils/csv-export.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Unexpected any. Specify a different type
const parser = new Parser();
return parser.parse(data);
}
14 changes: 14 additions & 0 deletions app/backend/src/utils/pdf-export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import PDFDocument from "pdfkit";

export function exportToPDF(data: any) {

Check failure on line 3 in app/backend/src/utils/pdf-export.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Unexpected any. Specify a different type
const doc = new PDFDocument();
let buffers: Buffer[] = [];

Check failure on line 5 in app/backend/src/utils/pdf-export.ts

View workflow job for this annotation

GitHub Actions / Build and Test

'buffers' is never reassigned. Use 'const' instead
doc.on("data", buffers.push.bind(buffers));
doc.on("end", () => Buffer.concat(buffers));

doc.text("Financial Report");
doc.text(JSON.stringify(data, null, 2));
doc.end();

return Buffer.concat(buffers).toString("base64");
}
Loading