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
25 changes: 10 additions & 15 deletions backend/src/controllers/v1/disputes.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import { Request, Response, NextFunction } from "express";
import { Dispute, DisputeStatus } from "../../types/contract";
import { freshnessService } from "../../services/freshnessService";
import { labelRecord } from "../../services/versioningService";
import { Dispute } from "../../types/contract";
import { applyCacheHeaders, CC_NO_STORE } from "../../middleware/cache-headers";
import { parsePaginationParams, PaginationError, applyPagination } from "../../utils/pagination";

export const MOCK_DISPUTES: Dispute[] = [
labelRecord<Omit<Dispute, "contract_version" | "event_schema_version" | "indexed_at">>({
id: "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
invoice_id: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
initiator: "GA...BUYER",
reason: "Goods not delivered as per description",
status: DisputeStatus.UnderReview,
created_at: Math.floor(Date.now() / 1000) - 86400,
}),
];
import { derivedTableStore } from "../../services/replayService";

export const getDisputes = async (
req: Request,
Expand All @@ -25,7 +13,7 @@ export const getDisputes = async (
const params = parsePaginationParams(req.query);
const { id: invoice_id } = req.params;

let filtered = [...MOCK_DISPUTES];
let filtered = await listIndexedDisputes();
if (invoice_id) {
filtered = filtered.filter((d) => d.invoice_id === invoice_id);
}
Expand All @@ -41,3 +29,10 @@ export const getDisputes = async (
next(error);
}
};

async function listIndexedDisputes(): Promise<Dispute[]> {
if (!derivedTableStore.listDisputes) {
return [];
}
return (await derivedTableStore.listDisputes()) as Dispute[];
}
95 changes: 81 additions & 14 deletions backend/src/controllers/v1/portfolio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import {
PaginationError,
} from "../../utils/pagination";
import { applyCacheHeaders, CC_SHORT } from "../../middleware/cache-headers";
import {
Bid,
BidStatus,
Invoice,
InvoiceStatus,
} from "../../types/contract";
import { derivedTableStore } from "../../services/replayService";
import { invoiceStore } from "../../services/invoiceStore";

export interface PortfolioEntry {
id: string;
Expand All @@ -16,18 +24,6 @@ export interface PortfolioEntry {
invested_at: number;
}

export const MOCK_PORTFOLIO: PortfolioEntry[] = [
{
id: "0xport001",
investor: "GA...ABC",
invoice_id: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
invested_amount: "950000000",
expected_return: "50000000",
status: "Active",
invested_at: Math.floor(Date.now() / 1000) - 3600,
},
];

export const getPortfolio = async (
req: Request,
res: Response,
Expand All @@ -43,8 +39,8 @@ export const getPortfolio = async (
});
}

const filtered = MOCK_PORTFOLIO.filter((p) => p.investor === investor);
const result = applyPagination(filtered, "invested_at", params);
const entries = await getPortfolioEntries(investor);
const result = applyPagination(entries, "invested_at", params);

if (applyCacheHeaders(req, res, { cacheControl: CC_SHORT, body: result })) {
res.status(304).end();
Expand All @@ -60,3 +56,74 @@ export const getPortfolio = async (
next(error);
}
};

async function getPortfolioEntries(investor: string): Promise<PortfolioEntry[]> {
const [bids, invoices] = await Promise.all([
listIndexedBids(),
listIndexedInvoices(),
]);
const invoicesById = new Map(invoices.map((invoice) => [invoice.id, invoice]));

return bids
.filter((bid) => bid.investor === investor)
.filter((bid) => isPortfolioBidStatus(bid.status as BidStatus))
.map((bid) => {
const invoice = invoicesById.get(bid.invoice_id);
return {
id: bid.bid_id,
investor: bid.investor,
invoice_id: bid.invoice_id,
invested_amount: bid.bid_amount,
expected_return: bid.expected_return,
status: mapPortfolioStatus(bid.status as BidStatus, invoice?.status),
invested_at: Number(bid.timestamp ?? 0),
};
});
}

async function listIndexedBids(): Promise<Bid[]> {
if (!derivedTableStore.listBids) {
return [];
}
return (await derivedTableStore.listBids()) as Bid[];
}

async function listIndexedInvoices(): Promise<Invoice[]> {
if (derivedTableStore.listInvoices) {
const indexed = (await derivedTableStore.listInvoices()) as Invoice[];
if (indexed.length > 0) return indexed;
}

try {
return invoiceStore.findInvoices();
} catch (err: any) {
if (
process.env.NODE_ENV === "test" &&
/no such table/i.test(String(err?.message ?? ""))
) {
return [];
}
throw err;
}
}

function isPortfolioBidStatus(status: BidStatus): boolean {
return status === BidStatus.Accepted;
}

function mapPortfolioStatus(
bidStatus: BidStatus,
invoiceStatus?: InvoiceStatus,
): PortfolioEntry["status"] {
if (invoiceStatus === InvoiceStatus.Paid) return "Completed";
if (invoiceStatus === InvoiceStatus.Defaulted) return "Defaulted";
if (
invoiceStatus === InvoiceStatus.Cancelled ||
bidStatus === BidStatus.Cancelled ||
bidStatus === BidStatus.Withdrawn ||
bidStatus === BidStatus.Expired
) {
return "Refunded";
}
return "Active";
}
22 changes: 21 additions & 1 deletion backend/src/services/derivedTableStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ export class InMemoryDerivedTableStore implements DerivedTableStore {
return Array.from(this.tables.invoices.values());
}

async listBids(): Promise<any[]> {
return Array.from(this.tables.bids.values());
}

async listDisputes(): Promise<any[]> {
return Array.from(this.tables.disputes.values());
}

async getBid(id: string): Promise<any | null> {
return this.tables.bids.get(id) || null;
}
Expand Down Expand Up @@ -417,9 +425,21 @@ export class FileSystemDerivedTableStore implements DerivedTableStore {
}

async listInvoices(): Promise<any[]> {
return this.listRecords("invoices");
}

async listBids(): Promise<any[]> {
return this.listRecords("bids");
}

async listDisputes(): Promise<any[]> {
return this.listRecords("disputes");
}

private async listRecords(table: keyof typeof this.tablesFiles): Promise<any[]> {
const fs = require("fs").promises;
const path = require("path");
const filePath = path.join(this.dataDir, this.tablesFiles.invoices);
const filePath = path.join(this.dataDir, this.tablesFiles[table]);
try {
const data = await fs.readFile(filePath, "utf8");
const lines = data.trim().split("\n").filter((l: string) => l.length > 0);
Expand Down
2 changes: 1 addition & 1 deletion backend/src/tests/openapi-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const KNOWN_SETTLEMENT_ID = "0xsettle123";
*/
const UNKNOWN_SETTLEMENT_ID = "0xdeadbeef";

/** The investor value used in MOCK_PORTFOLIO (controllers/v1/portfolio.ts). */
/** Investor value used for portfolio contract-shape requests. */
const KNOWN_INVESTOR = "GA...ABC";

// ─── Schema helpers ──────────────────────────────────────────────────────────
Expand Down
Loading