Function bodies 642 total
load_json function · python · L37-L40 (4 LOC)analyze.py
def load_json(filename):
path = os.path.join(BASE_DIR, filename)
with open(path) as f:
return json.load(f)analyze_warehouse function · python · L43-L92 (50 LOC)analyze.py
def analyze_warehouse(data):
"""Analyze warehouse stock data for all metals."""
metals_of_interest = ["Gold", "Silver", "Copper", "Platinum", "Palladium", "Aluminum", "Zinc"]
summary = {}
for metal in metals_of_interest:
if metal not in data:
continue
entry = data[metal]
totals = entry["totals"]
registered = totals["registered"]
eligible = totals["eligible"]
total = totals["total"]
reg_pct = (registered / total * 100) if total > 0 else 0
# Top depositories by total holdings
deps_sorted = sorted(entry["depositories"], key=lambda d: d["total"], reverse=True)
top_deps = [
{"name": d["name"], "registered": d["registered"],
"eligible": d["eligible"], "total": d["total"]}
for d in deps_sorted[:5]
]
summary[metal] = {
"registered": round(registered, 2),
"eligible": round(eligible, 2),
"total": roanalyze_deliveries function · python · L95-L209 (115 LOC)analyze.py
def analyze_deliveries(mtd_data, daily_data, ytd_data, warehouse_data):
"""Analyze delivery activity: daily rates, MTD totals, delivery-to-inventory ratios."""
# ── Collect MTD totals ──────────────────────────────────────────────
# Best source: delivery_daily (most recent date) has month_to_date field
mtd_totals = {} # metal -> contracts delivered MTD
# From delivery_daily.json
for d in daily_data.get("deliveries", []):
metal = d["metal"]
mtd_totals[metal] = d["month_to_date"]
# Fill in from delivery_mtd.json for metals not in daily
for c in mtd_data.get("contracts", []):
metal = c["metal"]
if metal not in mtd_totals:
mtd_totals[metal] = c["total_cumulative"]
# ── Compute daily velocity from MTD data ──────────────────────────
daily_rates = {}
for c in mtd_data.get("contracts", []):
metal = c["metal"]
daily_entries = c.get("daily_data", [])
if not daily_entries:
analyze_market_structure function · python · L212-L256 (45 LOC)analyze.py
def analyze_market_structure(volume_data, bulletin_data, warehouse_data):
"""Analyze open interest, volume/OI ratios, paper-to-physical ratios."""
results = {}
# Map symbols to metals
symbol_to_metal = {v["symbol"]: k for k, v in CONTRACT_SIZES.items()}
# Use volume_summary for OI and volume
for product in volume_data.get("products", []):
sym = product["symbol"]
if sym not in symbol_to_metal:
continue
metal = symbol_to_metal[sym]
spec = CONTRACT_SIZES[metal]
oi = product["open_interest"]
vol = product["total_volume"]
oi_change = product["oi_change"]
vol_oi_ratio = round(vol / oi, 3) if oi > 0 else 0
# Paper-to-physical ratio
paper_claims = oi * spec["size"] # in contract units (oz, lbs)
conv = WAREHOUSE_UNIT_CONVERSIONS.get(metal, 1.0)
registered = warehouse_data.get(metal, {}).get("totals", {}).get("registered", 0)
registered_converted = regiassess_risk function · python · L259-L342 (84 LOC)analyze.py
def assess_risk(warehouse_summary, delivery_analysis, market_structure):
"""Generate risk assessment with coverage ratios, alerts, and overall risk level."""
alerts = []
metal_risks = {}
for metal in CONTRACT_SIZES:
risk_factors = []
# ── Coverage ratio ────────────────────────────────────────────
coverage_days = None
if metal in delivery_analysis["daily_velocity"] and metal in delivery_analysis["delivery_to_inventory"]:
avg_daily = delivery_analysis["daily_velocity"][metal]["avg_daily_contracts"]
spec = CONTRACT_SIZES[metal]
conv = WAREHOUSE_UNIT_CONVERSIONS.get(metal, 1.0)
di = delivery_analysis["delivery_to_inventory"].get(metal, {})
registered_inv = di.get("registered_inventory", 0)
if avg_daily > 0 and registered_inv > 0:
daily_physical = avg_daily * spec["size"]
coverage_days = round(registered_inv / daily_physical, 1)
generate_key_findings function · python · L345-L443 (99 LOC)analyze.py
def generate_key_findings(warehouse_summary, delivery_analysis, market_structure, risk_assessment):
"""Generate a list of notable observations."""
findings = []
# ── Warehouse observations ────────────────────────────────────────
for metal, ws in warehouse_summary["per_metal"].items():
if ws["registered_pct"] < 30:
findings.append(
f"{metal}: Only {ws['registered_pct']}% of warehouse stock is registered "
f"(available for delivery), indicating most metal is in eligible (private) storage."
)
elif ws["registered_pct"] > 70:
findings.append(
f"{metal}: {ws['registered_pct']}% of warehouse stock is registered, "
f"suggesting strong delivery availability."
)
# ── Zinc special note ─────────────────────────────────────────────
if "Zinc" in warehouse_summary["per_metal"]:
zn = warehouse_summary["per_metal"]["Zinc"]
if zn["regmain function · python · L446-L575 (130 LOC)analyze.py
def main():
print("Loading data files...")
warehouse_data = load_json("data.json")
mtd_data = load_json("delivery_mtd.json")
daily_data = load_json("delivery_daily.json")
ytd_data = load_json("delivery_ytd.json")
volume_data = load_json("volume_summary.json")
bulletin_data = load_json("bulletin.json")
print("All 6 data files loaded successfully.\n")
# ── 1. Warehouse Analysis ─────────────────────────────────────────
print("=" * 70)
print("WAREHOUSE STOCK ANALYSIS")
print("=" * 70)
warehouse_summary = analyze_warehouse(warehouse_data)
for metal, ws in warehouse_summary["per_metal"].items():
print(f"\n{metal}:")
print(f" Registered: {ws['registered']:>15,.2f} ({ws['registered_pct']:.1f}%)")
print(f" Eligible: {ws['eligible']:>15,.2f} ({ws['eligible_pct']:.1f}%)")
print(f" Total: {ws['total']:>15,.2f}")
print(f" Top depository: {ws['top_depositories'][0]['name']} ({ws['top_deposAbout: code-quality intelligence by Repobility · https://repobility.com
About function · typescript · L25-L111 (87 LOC)app/about/page.tsx
export default function About() {
const sections = [
{
title: 'What is Heavy Metal Stats?',
content: 'Heavy Metal Stats is a free, independent dashboard that tracks COMEX warehouse inventory data for precious and base metals. We aggregate publicly available data from CME Group and present it in a clean, accessible format — making it easy to monitor gold, silver, copper, platinum, palladium, and aluminum warehouse stocks at a glance.',
},
{
title: 'Our Mission',
content: 'Our mission is to make COMEX data accessible and understandable for everyone — from retail investors and researchers to metals enthusiasts and market watchers. The raw data published by CME Group can be difficult to navigate and interpret. Heavy Metal Stats transforms that data into clear visuals, meaningful ratios, and actionable insights, all completely free of charge.',
},
{
title: 'What Data We Track',
content: 'We track a comprehensive set of COMEX metrics isValidEmail function · typescript · L6-L8 (3 LOC)app/api/auth/register/route.ts
function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}POST function · typescript · L10-L78 (69 LOC)app/api/auth/register/route.ts
export async function POST(request: Request) {
// Rate limit: 5 registration attempts per IP per 15 minutes
const ip = getClientIp(request);
const { limited } = rateLimit(`register:${ip}`, 5, 15 * 60 * 1000);
if (limited) {
return NextResponse.json(
{ error: 'Too many registration attempts. Please try again later.' },
{ status: 429 },
);
}
try {
const body = await request.json();
const { email, password, displayName } = body;
if (!email || !isValidEmail(email)) {
return NextResponse.json({ error: 'Please enter a valid email address.' }, { status: 400 });
}
if (!password || password.length < 8) {
return NextResponse.json({ error: 'Password must be at least 8 characters.' }, { status: 400 });
}
if (!displayName || displayName.trim().length < 2) {
return NextResponse.json({ error: 'Display name must be at least 2 characters.' }, { status: 400 });
}
if (displayName.trim().length > 30) {
return NexGET function · typescript · L7-L79 (73 LOC)app/api/blob/previous/route.ts
export async function GET(request: NextRequest) {
try {
if (!BLOB_READ_WRITE_TOKEN) {
return NextResponse.json(
{ error: 'Blob token not configured' },
{ status: 500 }
);
}
// Get today's date in YYYY-MM-DD format
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
// List all available blobs
const { blobs } = await list({
prefix: 'comex-data/',
token: BLOB_READ_WRITE_TOKEN,
});
if (blobs.length === 0) {
return NextResponse.json(
{ error: 'No historical data available' },
{ status: 404 }
);
}
// Find the most recent date that's not today
const dates = blobs
.map(blob => blob.pathname.replace('comex-data/', '').replace('.json', ''))
.filter(date => date < todayStr)
.sort()
.reverse();
if (dates.length === 0) {
return NextResponse.json(
{ error: 'No previous day data available' },
{ status: 4GET function · typescript · L8-L55 (48 LOC)app/api/blob/route.ts
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const date = searchParams.get('date'); // Format: YYYY-MM-DD
if (!date) {
return NextResponse.json(
{ error: 'Date parameter is required' },
{ status: 400 }
);
}
if (!BLOB_READ_WRITE_TOKEN) {
return NextResponse.json(
{ error: 'Blob token not configured' },
{ status: 500 }
);
}
const blobName = `comex-data/${date}.json`;
try {
const blob = await head(blobName, {
token: BLOB_READ_WRITE_TOKEN,
});
// Fetch the blob content
const response = await fetch(blob.url);
const data = await response.json();
return NextResponse.json(data);
} catch (error: unknown) {
if (error && typeof error === 'object' && 'status' in error && (error as { status: number }).status === 404) {
return NextResponse.json(
{ error: 'DaPOST function · typescript · L58-L102 (45 LOC)app/api/blob/route.ts
export async function POST(request: NextRequest) {
if (!isAuthorized(request)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
if (!BLOB_READ_WRITE_TOKEN) {
return NextResponse.json(
{ error: 'Blob token not configured' },
{ status: 500 }
);
}
const body = await request.json();
const { date, data } = body; // date: YYYY-MM-DD, data: WarehouseStocksData
if (!date || !data) {
return NextResponse.json(
{ error: 'Date and data are required' },
{ status: 400 }
);
}
const blobName = `comex-data/${date}.json`;
const jsonData = JSON.stringify(data, null, 2);
const blob = await put(blobName, jsonData, {
access: 'public',
token: BLOB_READ_WRITE_TOKEN,
contentType: 'application/json',
});
return NextResponse.json({
success: true,
url: blob.url,
pathname: blob.pathname,
});
} catch (error: unknown) {
consolLIST function · typescript · L105-L136 (32 LOC)app/api/blob/route.ts
export async function LIST(request: NextRequest) {
if (!isAuthorized(request)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
if (!BLOB_READ_WRITE_TOKEN) {
return NextResponse.json(
{ error: 'Blob token not configured' },
{ status: 500 }
);
}
const { blobs } = await list({
prefix: 'comex-data/',
token: BLOB_READ_WRITE_TOKEN,
});
const dates = blobs
.map(blob => blob.pathname.replace('comex-data/', '').replace('.json', ''))
.sort()
.reverse(); // Most recent first
return NextResponse.json({ dates });
} catch (error: unknown) {
console.error('Error listing blobs:', error);
return NextResponse.json(
{ error: 'Failed to list data' },
{ status: 500 }
);
}
}isDatabaseConfigured function · typescript · L15-L17 (3 LOC)app/api/bulletin/history/route.ts
function isDatabaseConfigured(): boolean {
return !!process.env.DATABASE_URL;
}Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
getDb function · typescript · L19-L24 (6 LOC)app/api/bulletin/history/route.ts
function getDb() {
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL not configured');
}
return neon(process.env.DATABASE_URL);
}GET function · typescript · L27-L88 (62 LOC)app/api/bulletin/history/route.ts
export async function GET(request: NextRequest) {
if (!isDatabaseConfigured()) {
return NextResponse.json(
{ error: 'Database not configured', message: 'Set DATABASE_URL environment variable' },
{ status: 503 }
);
}
try {
const sql = getDb();
const searchParams = request.nextUrl.searchParams;
const symbol = searchParams.get('symbol') || 'GC';
const days = parseInt(searchParams.get('days') || '30');
// Validate inputs
const validSymbols = ['GC', '1OZ', 'SI', 'SIL', 'HG', 'PL', 'PA', 'ALI'];
if (!validSymbols.includes(symbol.toUpperCase())) {
return NextResponse.json(
{ error: 'Invalid symbol', validSymbols },
{ status: 400 }
);
}
const maxDays = Math.min(days, 365); // Limit to 1 year
const result = await sql`
SELECT
date,
symbol,
front_month_settle,
total_volume,
total_open_interest,
total_oi_change
FROM bulletin_snapshots
isDatabaseConfigured function · typescript · L27-L29 (3 LOC)app/api/bulletin/previous/route.ts
function isDatabaseConfigured(): boolean {
return !!process.env.DATABASE_URL;
}getDb function · typescript · L31-L36 (6 LOC)app/api/bulletin/previous/route.ts
function getDb() {
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL not configured');
}
return neon(process.env.DATABASE_URL);
}rowToProduct function · typescript · L38-L49 (12 LOC)app/api/bulletin/previous/route.ts
function rowToProduct(row: BulletinRow): ProductData {
return {
symbol: row.symbol,
name: row.product_name || row.symbol,
totalVolume: parseInt(String(row.total_volume)) || 0,
totalOpenInterest: parseInt(String(row.total_open_interest)) || 0,
totalOiChange: parseInt(String(row.total_oi_change)) || 0,
frontMonth: row.front_month || '',
frontMonthSettle: parseFloat(String(row.front_month_settle)) || 0,
frontMonthChange: parseFloat(String(row.front_month_change)) || 0,
};
}GET function · typescript · L64-L158 (95 LOC)app/api/bulletin/previous/route.ts
export async function GET(request: NextRequest) {
if (!isDatabaseConfigured()) {
return NextResponse.json(
{ error: 'Database not configured', message: 'Set DATABASE_URL environment variable' },
{ status: 503 }
);
}
try {
const sql = getDb();
const currentDate = request.nextUrl.searchParams.get('currentDate');
let previousDate: string | null = null;
let previousProducts: Record<string, ProductData> = {};
if (currentDate) {
// Find the most recent bulletin date strictly before the current date
const dateResult = await sql`
SELECT DISTINCT date
FROM bulletin_snapshots
WHERE date < ${currentDate}::date
ORDER BY date DESC
LIMIT 1
`;
if (dateResult.length > 0) {
previousDate = dateResult[0].date instanceof Date
? dateResult[0].date.toISOString().split('T')[0]
: String(dateResult[0].date).split('T')[0];
}
} else {
// No currentDate provisDatabaseConfigured function · typescript · L38-L40 (3 LOC)app/api/bulletin/route.ts
function isDatabaseConfigured(): boolean {
return !!process.env.DATABASE_URL;
}getDb function · typescript · L42-L47 (6 LOC)app/api/bulletin/route.ts
function getDb() {
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL not configured');
}
return neon(process.env.DATABASE_URL);
}Repobility · severity-and-effort ranking · https://repobility.com
POST function · typescript · L51-L123 (73 LOC)app/api/bulletin/route.ts
export async function POST(request: NextRequest) {
// Auth check: only allow authorized callers to write bulletin data
const authHeader = request.headers.get('authorization');
const cronSecret = process.env.CRON_SECRET;
if (!cronSecret || authHeader !== `Bearer ${cronSecret}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
if (!isDatabaseConfigured()) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
try {
const sql = getDb();
const body: BulletinPayload = await request.json();
if (!body.parsed_date || !body.products?.length) {
return NextResponse.json(
{ error: 'Missing parsed_date or products' },
{ status: 400 }
);
}
let saved = 0;
for (const product of body.products) {
const front = product.contracts?.[0] ?? null;
await sql`
INSERT INTO bulletin_snapshots (
date, symbol, product_name,
GET function · typescript · L126-L194 (69 LOC)app/api/bulletin/route.ts
export async function GET() {
if (!isDatabaseConfigured()) {
return NextResponse.json(
{ error: 'Database not configured', message: 'Set DATABASE_URL environment variable' },
{ status: 503 }
);
}
try {
const sql = getDb();
// Get the most recent bulletin date
const dateResult = await sql`
SELECT DISTINCT date
FROM bulletin_snapshots
ORDER BY date DESC
LIMIT 1
`;
if (dateResult.length === 0) {
return NextResponse.json(
{ error: 'No bulletin data available' },
{ status: 404 }
);
}
const latestDate = dateResult[0].date;
// Fetch all products for that date
const dataResult = await sql`
SELECT
date,
symbol,
product_name,
total_volume,
total_open_interest,
total_oi_change,
front_month,
front_month_settle,
front_month_change
FROM bulletin_snapshots
WHERE date = ${latestDate}
ORDERisDatabaseConfigured function · typescript · L16-L18 (3 LOC)app/api/bulletin/summary/route.ts
function isDatabaseConfigured(): boolean {
return !!process.env.DATABASE_URL;
}getDb function · typescript · L20-L25 (6 LOC)app/api/bulletin/summary/route.ts
function getDb() {
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL not configured');
}
return neon(process.env.DATABASE_URL);
}GET function · typescript · L28-L118 (91 LOC)app/api/bulletin/summary/route.ts
export async function GET() {
if (!isDatabaseConfigured()) {
return NextResponse.json(
{ error: 'Database not configured', message: 'Set DATABASE_URL environment variable' },
{ status: 503 }
);
}
try {
const sql = getDb();
// Get the most recent bulletin date
const result = await sql`
SELECT
date,
symbol,
front_month_settle,
front_month_change,
total_volume,
total_open_interest
FROM bulletin_snapshots
WHERE date = (SELECT MAX(date) FROM bulletin_snapshots)
ORDER BY symbol
`;
if (result.length === 0) {
return NextResponse.json(
{ error: 'No bulletin data available' },
{ status: 404 }
);
}
// Build summary object
const summary = {
date: result[0].date,
gold: { settle: 0, change: 0, volume: 0, oi: 0 },
silver: { settle: 0, change: 0, volume: 0, oi: 0 },
copper: { settle: 0, change: 0, volume: 0, oi: 0 },
fetchYahooChart function · typescript · L18-L23 (6 LOC)app/api/cron/prices/route.ts
async function fetchYahooChart(symbol: string): Promise<{
price: number;
prevClose: number;
timestamp: number;
marketState: string;
} | null> {GET function · typescript · L86-L178 (93 LOC)app/api/cron/prices/route.ts
export async function GET(request: Request) {
if (!isAuthorized(request)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
if (!isDatabaseAvailable()) {
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
}
try {
// Fetch all futures prices in parallel
const futuresEntries = Object.entries(FUTURES_SYMBOLS);
const futuresResults = await Promise.all(
futuresEntries.map(async ([metal, { symbol, unit }]) => {
const data = await fetchYahooChart(symbol);
if (!data) return [metal, null] as const;
const change = data.price - data.prevClose;
const changePercent = data.prevClose > 0 ? (change / data.prevClose) * 100 : 0;
return [metal, {
price: Math.round(data.price * 100) / 100,
change: Math.round(change * 100) / 100,
changePercent: Math.round(changePercent * 100) / 100,
timestamp: data.timestamp,
marketState: data.maGET function · typescript · L5-L71 (67 LOC)app/api/db/previous/route.ts
export async function GET() {
try {
// Get today's date
const today = new Date();
today.setHours(0, 0, 0, 0);
const todayStr = today.toISOString().split('T')[0];
// Query for the most recent date before today
const result = await sql`
SELECT DISTINCT date
FROM warehouse_snapshots
WHERE date < ${todayStr}
ORDER BY date DESC
LIMIT 1
`;
if (result.length === 0) {
return NextResponse.json(
{ error: 'No previous day data available' },
{ status: 404 }
);
}
const previousDate = (result as Record<string, unknown>[])[0].date;
// Fetch all metals for that date
const dataResult = await sql`
SELECT
metal,
report_date,
activity_date,
registered,
eligible,
total
FROM warehouse_snapshots
WHERE date = ${previousDate}
ORDER BY metal
`;
// Transform to match WarehouseStocksData structure
const data: Record<stCitation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
POST function · typescript · L6-L111 (106 LOC)app/api/db/save/route.ts
export async function POST(request: NextRequest) {
if (!isAuthorized(request)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const body = await request.json();
const { date, data } = body; // date: YYYY-MM-DD, data: WarehouseStocksData
if (!date || !data) {
return NextResponse.json(
{ error: 'Date and data are required' },
{ status: 400 }
);
}
// Validate date format
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(date)) {
return NextResponse.json(
{ error: 'Invalid date format. Use YYYY-MM-DD' },
{ status: 400 }
);
}
// Insert or update data for each metal
const metals = Object.keys(data);
let inserted = 0;
let updated = 0;
for (const metal of metals) {
const metalData = data[metal];
if (!metalData || !metalData.totals) {
continue;
}
try {
// Use INSERT ... ON CONFLICT to handltoDateString function · typescript · L23-L36 (14 LOC)app/api/delivery/history/route.ts
function toDateString(val: unknown): string {
if (!val) return '';
if (val instanceof Date) return val.toISOString().split('T')[0];
const s = String(val);
// Already YYYY-MM-DD
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s;
// ISO datetime like "2026-02-05T00:00:00.000Z"
const isoMatch = s.match(/^(\d{4}-\d{2}-\d{2})T/);
if (isoMatch) return isoMatch[1];
// Try parsing as Date (handles "Thu Feb 05 2026 ..." etc.)
const d = new Date(s);
if (!isNaN(d.getTime())) return d.toISOString().split('T')[0];
return s;
}isDatabaseConfigured function · typescript · L38-L40 (3 LOC)app/api/delivery/history/route.ts
function isDatabaseConfigured(): boolean {
return !!process.env.DATABASE_URL;
}getDb function · typescript · L42-L47 (6 LOC)app/api/delivery/history/route.ts
function getDb() {
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL not configured');
}
return neon(process.env.DATABASE_URL);
}GET function · typescript · L54-L155 (102 LOC)app/api/delivery/history/route.ts
export async function GET(request: NextRequest) {
if (!isDatabaseConfigured()) {
return NextResponse.json(
{ error: 'Database not configured', message: 'Set DATABASE_URL environment variable' },
{ status: 503 }
);
}
try {
const sql = getDb();
const searchParams = request.nextUrl.searchParams;
const metal = searchParams.get('metal') || 'Gold';
const parsedDays = parseInt(searchParams.get('days') || '30');
const days = Math.max(1, Math.min(isNaN(parsedDays) ? 30 : parsedDays, 730));
const aggregate = searchParams.get('aggregate') || 'daily';
// Validate metal
const validMetals = ['Gold', 'Silver', 'Copper', 'Aluminum', 'Platinum', 'Palladium'];
if (!validMetals.includes(metal)) {
return NextResponse.json(
{ error: 'Invalid metal', validMetals },
{ status: 400 }
);
}
if (aggregate === 'monthly') {
// Monthly aggregation: get the maximum month_to_date per month per metal
// ThisDELETE function · typescript · L6-L36 (31 LOC)app/api/developer/keys/[id]/route.ts
export async function DELETE(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ success: false, error: 'Authentication required' }, { status: 401 });
}
if (!isDatabaseAvailable()) {
return NextResponse.json({ success: false, error: 'Service unavailable' }, { status: 503 });
}
try {
const { id } = await params;
const keyId = parseInt(id, 10);
if (isNaN(keyId)) {
return NextResponse.json({ success: false, error: 'Invalid key ID' }, { status: 400 });
}
const revoked = await revokeApiKey(keyId, Number(session.user.id));
if (!revoked) {
return NextResponse.json({ success: false, error: 'Key not found or already revoked' }, { status: 404 });
}
return NextResponse.json({ success: true, message: 'API key revoked' });
} catch (error) {
console.error('Error revoking API key:', error);
return NextResponse.json({ sGET function · typescript · L7-L36 (30 LOC)app/api/developer/keys/route.ts
export async function GET() {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ success: false, error: 'Authentication required' }, { status: 401 });
}
if (!isDatabaseAvailable()) {
return NextResponse.json({ success: false, error: 'Service unavailable' }, { status: 503 });
}
try {
const keys = await getUserApiKeys(Number(session.user.id));
return NextResponse.json({
success: true,
keys: keys.map(k => ({
id: k.id,
prefix: k.key_prefix,
name: k.name,
tier: k.tier,
active: k.active,
createdAt: k.created_at,
lastUsedAt: k.last_used_at,
revokedAt: k.revoked_at,
})),
});
} catch (error) {
console.error('Error listing API keys:', error);
return NextResponse.json({ success: false, error: 'Failed to list keys' }, { status: 500 });
}
}POST function · typescript · L39-L91 (53 LOC)app/api/developer/keys/route.ts
export async function POST(request: Request) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ success: false, error: 'Authentication required' }, { status: 401 });
}
if (!isDatabaseAvailable()) {
return NextResponse.json({ success: false, error: 'Service unavailable' }, { status: 503 });
}
try {
const userId = Number(session.user.id);
// Limit to 5 active keys per user
const existing = await getUserApiKeys(userId);
const activeCount = existing.filter(k => k.active).length;
if (activeCount >= 5) {
return NextResponse.json(
{ success: false, error: 'Maximum of 5 active API keys allowed. Revoke an existing key first.' },
{ status: 400 }
);
}
let name = 'Default';
try {
const body = await request.json();
if (body.name && typeof body.name === 'string') {
name = body.name.slice(0, 100);
}
} catch {
// No body or invalid JSON is fine, use About: code-quality intelligence by Repobility · https://repobility.com
GET function · typescript · L6-L39 (34 LOC)app/api/developer/usage/route.ts
export async function GET(request: Request) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ success: false, error: 'Authentication required' }, { status: 401 });
}
if (!isDatabaseAvailable()) {
return NextResponse.json({ success: false, error: 'Service unavailable' }, { status: 503 });
}
try {
const { searchParams } = new URL(request.url);
const days = Math.min(parseInt(searchParams.get('days') || '30', 10), 90);
const keys = await getUserApiKeys(Number(session.user.id));
const activeKeys = keys.filter(k => k.active);
const usage: Record<string, { keyName: string; prefix: string; daily: { date: string; count: number }[] }> = {};
for (const key of activeKeys) {
const daily = await getApiKeyUsage(key.id, days);
usage[key.id] = {
keyName: key.name,
prefix: key.key_prefix,
daily,
};
}
return NextResponse.json({ success: true, days, usage });
} catch (erGET function · typescript · L6-L133 (128 LOC)app/api/forecast/accuracy/route.ts
export async function GET() {
if (!isDatabaseAvailable()) {
return NextResponse.json({ success: true, source: 'none', data: null });
}
try {
const rows = await sql`
SELECT metal, forecast_date, direction, outcome, confidence, composite_score,
price_at_forecast, actual_price_5d, actual_change_pct_5d, actual_direction,
price_in_5d_range, forecast_error_pct_5d, eval_notes
FROM forecast_snapshots
WHERE outcome IS NOT NULL AND outcome != 'PENDING'
ORDER BY forecast_date DESC, metal
`;
const pending = await sql`
SELECT COUNT(*) as cnt FROM forecast_snapshots WHERE outcome = 'PENDING' OR outcome IS NULL
`;
const metals: Record<string, {
total_forecasts: number;
correct: number;
incorrect: number;
neutral_hit: number;
neutral_miss: number;
pending: number;
hit_rate: number | null;
in_range: number;
out_range: number;
}> = {};
let totalCorrect =GET function · typescript · L10-L54 (45 LOC)app/api/forecast/[metal]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ metal: string }> }
) {
const { metal } = await params;
// Normalize: capitalize first letter
const normalized = metal.charAt(0).toUpperCase() + metal.slice(1).toLowerCase();
if (!VALID_METALS.includes(normalized)) {
return NextResponse.json(
{ success: false, error: `Invalid metal: ${metal}`, validMetals: VALID_METALS },
{ status: 400 }
);
}
try {
const filePath = path.join(process.cwd(), 'public', 'forecast.json');
const raw = await fs.readFile(filePath, 'utf-8');
const data = JSON.parse(raw);
const metalForecast = data.metals?.[normalized];
if (!metalForecast) {
return NextResponse.json(
{ success: false, error: `No forecast available for ${normalized}` },
{ status: 404 }
);
}
return NextResponse.json({
success: true,
metal: normalized,
generated_at: data.generated_at,
model_version: dGET function · typescript · L8-L26 (19 LOC)app/api/forecast/route.ts
export async function GET() {
try {
const filePath = path.join(process.cwd(), 'public', 'forecast.json');
const raw = await fs.readFile(filePath, 'utf-8');
const data = JSON.parse(raw);
return NextResponse.json({
success: true,
data,
});
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : 'Unknown error';
console.error('Error reading forecast data:', msg);
return NextResponse.json(
{ success: false, error: 'Forecast data not available' },
{ status: 503 }
);
}
}GET function · typescript · L7-L25 (19 LOC)app/api/forecast-sd/route.ts
export async function GET() {
try {
const filePath = path.join(process.cwd(), 'public', 'forecast_sd.json');
const raw = await fs.readFile(filePath, 'utf-8');
const data = JSON.parse(raw);
return NextResponse.json({
success: true,
data,
});
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : 'Unknown error';
console.error('Error reading S&D forecast data:', msg);
return NextResponse.json(
{ success: false, error: 'S&D forecast data not available' },
{ status: 503 }
);
}
}GET function · typescript · L9-L24 (16 LOC)app/api/forecast/tracking/route.ts
export async function GET(request: NextRequest) {
if (!isDatabaseAvailable()) {
return NextResponse.json({ success: false, error: 'Database not available' }, { status: 503 });
}
const metal = request.nextUrl.searchParams.get('metal') || 'Gold';
const days = parseInt(request.nextUrl.searchParams.get('days') || '30');
try {
const tracking = await getForecastPriceTracking(metal, days);
return NextResponse.json({ success: true, metal, tracking });
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : 'Unknown error';
return NextResponse.json({ success: false, error: msg }, { status: 500 });
}
}POST function · typescript · L27-L74 (48 LOC)app/api/forecast/tracking/route.ts
export async function POST(request: NextRequest) {
if (!isDatabaseAvailable()) {
return NextResponse.json({ success: false, error: 'Database not available' }, { status: 503 });
}
try {
const body = await request.json();
const livePrices: Record<string, number> = body.prices || {};
const today = new Date().toISOString().split('T')[0];
// Get recent non-neutral forecasts
const recentForecasts = await sql`
SELECT metal, forecast_date, direction, price_at_forecast
FROM forecast_snapshots
WHERE forecast_date >= CURRENT_DATE - 30
AND direction != 'NEUTRAL'
ORDER BY forecast_date DESC
`;
let tracked = 0;
for (const row of recentForecasts as { metal: string; forecast_date: string; direction: string; price_at_forecast: number }[]) {
const livePrice = livePrices[row.metal];
if (!livePrice || Number(row.price_at_forecast) <= 0) continue;
const forecastDate = typeof row.forecast_date === 'string'
POST function · typescript · L5-L63 (59 LOC)app/api/forum/thread/route.ts
export async function POST(request: Request) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: 'You must be signed in.' }, { status: 401 });
}
if (!isDatabaseAvailable()) {
return NextResponse.json({ error: 'Service temporarily unavailable.' }, { status: 503 });
}
const { categorySlug, title, body } = await request.json();
if (!categorySlug || !title?.trim() || !body?.trim()) {
return NextResponse.json({ error: 'Category, title, and body are required.' }, { status: 400 });
}
if (title.trim().length > 200) {
return NextResponse.json({ error: 'Title must be 200 characters or fewer.' }, { status: 400 });
}
const authorId = parseInt(session.user.id, 10);
if (isNaN(authorId)) {
return NextResponse.json({ error: 'Invalid user session.' }, { status: 401 });
}
const category = await sql`
SELECT id FROM forum_categories WHERE slug = ${categorySlug} LHi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
GET function · typescript · L16-L47 (32 LOC)app/api/futures/route.ts
export async function GET() {
try {
if (!isDatabaseAvailable()) {
return NextResponse.json(
{ success: false, error: 'Database not available' },
{ status: 503 },
);
}
const cached = await getCachedPrices('futures');
if (!cached) {
return NextResponse.json(
{ success: false, error: 'No cached data yet — cron has not run' },
{ status: 503 },
);
}
const { futures, fetched_at } = cached.data as { futures: Record<string, FuturesPrice | null>; fetched_at: string };
return NextResponse.json(
{ success: true, futures, fetched_at },
{
headers: {
'Cache-Control': 'public, s-maxage=10, stale-while-revalidate=60',
},
},
);
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : 'Unknown error';
return NextResponse.json({ success: false, error: msg }, { status: 500 });
}
}POST function · typescript · L12-L33 (22 LOC)app/api/indexnow/route.ts
export async function POST(request: NextRequest) {
const authHeader = request.headers.get('authorization');
const expectedKey = process.env.INDEX_NOW_API;
if (!expectedKey || authHeader !== `Bearer ${expectedKey}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
let urls: string[];
try {
const body = await request.json().catch(() => null);
urls = body?.urls && Array.isArray(body.urls) && body.urls.length > 0
? body.urls
: getAllSiteUrls();
} catch {
urls = getAllSiteUrls();
}
const result = await submitToIndexNow(urls);
return NextResponse.json(result, { status: result.success ? 200 : 502 });
}ApiEndpoints function · typescript · L71-L400 (330 LOC)app/api-info/page.tsx
export default function ApiEndpoints() {
return (
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 flex flex-col">
{/* Header */}
<div className="border-b border-slate-200 dark:border-slate-800 bg-white/50 dark:bg-black/30">
<div className="max-w-4xl mx-auto px-6 py-4">
<Link
href="/"
className="inline-flex items-center gap-2 text-sm font-medium text-slate-500 hover:text-slate-900 dark:hover:text-white transition-colors"
>
<ArrowLeft className="w-4 h-4" />
Back to Dashboard
</Link>
</div>
</div>
{/* Main Content */}
<main className="flex-1 w-full px-6 py-12 flex flex-col items-center">
{/* Title */}
<h1 className="text-4xl sm:text-5xl font-black tracking-tight text-slate-900 dark:text-white mb-3 text-center">
API & Data Sources
</h1>
<p className="text-lg text-slate-500 dark:text-slate-400 mb-10 textpage 1 / 13next ›