feat(backend): implement REST API for races calendar
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
This commit is contained in:
74
backend/src/seed.ts
Normal file
74
backend/src/seed.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user