Express + TypeScript backend with PostgreSQL: CRUD endpoints for /races (GET list with year/month filters, GET by id, POST, PATCH, DELETE), health/readiness probes, SQL migration runner, seed script with upsert from CSV, camelCase/snake_case mapper, CORS, env validation, docker-compose, and API docs for frontend. Made-with: Cursor
75 lines
1.8 KiB
TypeScript
75 lines
1.8 KiB
TypeScript
import fs from "fs";
|
||
import path from "path";
|
||
import { parse } from "csv-parse/sync";
|
||
import { pool } from "./db";
|
||
|
||
interface CsvRow {
|
||
date: string;
|
||
month: string;
|
||
day: string;
|
||
event: string;
|
||
distance_km: string;
|
||
}
|
||
|
||
function slugify(text: string): string {
|
||
return text
|
||
.toLowerCase()
|
||
.replace(/[«»"]/g, "")
|
||
.replace(/[^a-zа-яё0-9]+/gi, "-")
|
||
.replace(/(^-|-$)/g, "")
|
||
.substring(0, 60);
|
||
}
|
||
|
||
function makeId(date: string, title: string): string {
|
||
return `${date}-${slugify(title)}`;
|
||
}
|
||
|
||
async function seed() {
|
||
const csvPath = path.resolve(__dirname, "../../import/races_2026_calendar.csv");
|
||
if (!fs.existsSync(csvPath)) {
|
||
console.error(`[seed] CSV not found: ${csvPath}`);
|
||
process.exit(1);
|
||
}
|
||
|
||
const raw = fs.readFileSync(csvPath, "utf-8");
|
||
const records: CsvRow[] = parse(raw, {
|
||
columns: true,
|
||
skip_empty_lines: true,
|
||
trim: true,
|
||
});
|
||
|
||
console.log(`[seed] Parsed ${records.length} rows from CSV`);
|
||
|
||
const client = await pool.connect();
|
||
try {
|
||
for (const row of records) {
|
||
const id = makeId(row.date, row.event);
|
||
const distanceKm = parseFloat(row.distance_km);
|
||
|
||
await client.query(
|
||
`INSERT INTO races (id, race_date, title, distance_km, status)
|
||
VALUES ($1, $2, $3, $4, $5)
|
||
ON CONFLICT (id) DO UPDATE SET
|
||
race_date = EXCLUDED.race_date,
|
||
title = EXCLUDED.title,
|
||
distance_km = EXCLUDED.distance_km,
|
||
status = EXCLUDED.status,
|
||
updated_at = NOW()`,
|
||
[id, row.date, row.event, distanceKm, "planned"],
|
||
);
|
||
|
||
console.log(`[seed] Upserted: ${id}`);
|
||
}
|
||
|
||
console.log("[seed] Done.");
|
||
} finally {
|
||
client.release();
|
||
await pool.end();
|
||
}
|
||
}
|
||
|
||
seed().catch((err) => {
|
||
console.error("[seed] FAILED:", err.message);
|
||
process.exit(1);
|
||
});
|