Files
novi-lessons/Les12-Tool-Calling/polderfest-demo/scripts/seed-polderfest.ts
2026-05-21 08:52:47 +02:00

273 lines
11 KiB
TypeScript

/**
* Polderfest 2027 — seed script
* ----------------------------------------------------------
* Genereert 500 fictieve bands en zet ze in je Supabase `bands` tabel.
* Run:
* 1. Zorg dat `bands` tabel bestaat (zie schema.sql)
* 2. Vul .env.local met:
* SUPABASE_URL=https://<project>.supabase.co
* SUPABASE_SERVICE_ROLE_KEY=<service role key>
* 3. npm i @supabase/supabase-js dotenv tsx --save-dev
* 4. npx tsx seed-polderfest.ts
*
* Service role key is bewust nodig — alleen voor lokaal seeden.
* NIET committen, NIET in client gebruiken.
*/
import { createClient } from "@supabase/supabase-js";
import dotenv from "dotenv";
// Laad .env.local (i.p.v. default .env)
dotenv.config({ path: ".env.local" });
const SUPABASE_URL =
process.env.NEXT_PUBLIC_SUPABASE_URL ?? process.env.SUPABASE_URL;
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
console.error(
"Ontbrekende env vars. Check .env.local:\n" +
" NEXT_PUBLIC_SUPABASE_URL=https://<project>.supabase.co\n" +
" SUPABASE_SERVICE_ROLE_KEY=<service role key>"
);
process.exit(1);
}
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
auth: { persistSession: false },
});
// ────────────────────────────────────────────────────────────
// Deterministische random (zodat seed reproduceerbaar is)
// ────────────────────────────────────────────────────────────
let seed = 42;
function rand() {
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280;
}
function pick<T>(arr: readonly T[]): T {
return arr[Math.floor(rand() * arr.length)];
}
function pickN<T>(arr: readonly T[], n: number): T[] {
const copy = [...arr];
const out: T[] = [];
for (let i = 0; i < n && copy.length; i++) {
out.push(copy.splice(Math.floor(rand() * copy.length), 1)[0]);
}
return out;
}
function range(min: number, max: number): number {
return Math.floor(rand() * (max - min + 1)) + min;
}
// ────────────────────────────────────────────────────────────
// Bouwstenen voor band-namen
// ────────────────────────────────────────────────────────────
const adjectives = [
"Lost", "Velvet", "Iron", "Neon", "Silent", "Wild", "Glass", "Paper", "Sleeping",
"Honest", "Crooked", "Bitter", "Sweet", "Drowsy", "Drowning", "Restless", "Sober",
"Midnight", "Morning", "Yellow", "Crimson", "Hollow", "Heavy", "Floating", "Slow",
"Burning", "Frozen", "Cardboard", "Plastic", "Analog", "Digital", "Forgotten",
] as const;
const nouns = [
"Tigers", "Wolves", "Horses", "Rabbits", "Mirrors", "Clouds", "Echoes", "Ghosts",
"Lights", "Roots", "Stones", "Foxes", "Riders", "Ships", "Tides", "Anchors",
"Maps", "Letters", "Postcards", "Radios", "Telegrams", "Diaries", "Highways",
"Cassettes", "Polaroids", "Cathedrals", "Stations", "Lanterns", "Compasses",
"Saturdays", "Tuesdays", "Mondays",
] as const;
const dutchPrefixes = [
"De", "Het", "Van der", "Polder", "Noord", "Zuid",
] as const;
const soloNamesFirst = [
"Sanne", "Joost", "Yara", "Lex", "Mila", "Tess", "Bram", "Lotte", "Ravi", "Imani",
"Marit", "Stijn", "Liva", "Noor", "Casper", "Anouk", "Mees", "Pien", "Daan", "Olivia",
"Niels", "Fenna", "Tygo", "Saar", "Cas", "Maud", "Roos", "Vince", "Lieke", "Floris",
] as const;
const soloNamesLast = [
"Van Dijk", "De Boer", "Visser", "Jansen", "Bakker", "Hendriks", "Mulder", "Smit",
"Peters", "De Vries", "Kuipers", "Brouwer", "Postma", "Hofman", "Van Loon",
] as const;
// ────────────────────────────────────────────────────────────
// Fest-velden
// ────────────────────────────────────────────────────────────
const genres = [
"Indie Rock", "Electronic", "Hip-Hop", "Jazz Fusion", "Folk", "Punk", "Soul",
"Ambient", "Disco-House", "Experimental", "Singer-Songwriter", "Synth-Pop",
"Garage Rock", "Neo-Soul", "Drum & Bass", "Afrobeat", "Dream Pop", "Post-Rock",
] as const;
const subGenresByGenre: Record<string, string[]> = {
"Indie Rock": ["Shoegaze", "Lo-Fi", "Math Rock", "Slowcore"],
"Electronic": ["Techno", "House", "IDM", "Glitch", "Trance"],
"Hip-Hop": ["Boom Bap", "Trap", "Lo-Fi", "Conscious"],
"Jazz Fusion": ["Funk Jazz", "Cosmic Jazz", "Nu-Jazz"],
"Folk": ["Anti-Folk", "Sea Shanty", "Modern Folk"],
"Punk": ["Post-Punk", "Hardcore", "Surf Punk"],
"Soul": ["Neo-Soul", "Northern Soul", "Funk"],
"Ambient": ["Drone", "New Age", "Field Recording"],
"Disco-House": ["Italo Disco", "Nu-Disco", "French House"],
"Experimental": ["Noise", "Sound Art", "Avantgarde"],
"Singer-Songwriter": ["Confessional", "Storytelling"],
"Synth-Pop": ["Vaporwave", "Italo", "Darkwave"],
"Garage Rock": ["Surf", "Power Pop"],
"Neo-Soul": ["Alt R&B", "Jazz-influenced"],
"Drum & Bass": ["Liquid", "Jungle", "Neurofunk"],
"Afrobeat": ["Afro-Fusion", "Highlife"],
"Dream Pop": ["Bedroom Pop", "Ethereal"],
"Post-Rock": ["Cinematic", "Math-influenced"],
};
const stages = [
"Main Stage", "Tent Stage", "Beach Stage", "Acoustic Bar", "Late Night Tent",
] as const;
const days = ["Vrijdag", "Zaterdag", "Zondag"] as const;
const timeSlots = [
"14:00", "15:30", "17:00", "18:30", "20:00", "21:30", "23:00", "00:30",
] as const;
const cities = [
"Amsterdam", "Rotterdam", "Utrecht", "Groningen", "Eindhoven", "Den Haag",
"Tilburg", "Maastricht", "Nijmegen", "Leeuwarden", "Arnhem", "Breda", "Haarlem",
"Zwolle", "Enschede", "Delft", "Den Bosch", "Apeldoorn",
] as const;
const tiers = ["headliner", "mid", "opener"] as const;
// ────────────────────────────────────────────────────────────
// Bio-fragmenten — combinatorisch zodat 500 bios uniek voelen
// ────────────────────────────────────────────────────────────
const bioOpenings = [
"Begonnen in een garage in",
"Ontstaan tijdens een blackout in",
"Een vriendengroep uit",
"Doorgebroken op het kleine podium van",
"Geboren uit een jam-sessie in",
"Een collectief van producers uit",
];
const bioMiddle = [
"experimenteert met analoge synths en gefluisterde lyrics",
"balanceert tussen melancholie en dansvloer-euforie",
"mixt traditionele samples met breakbeats",
"gebruikt veldopnames als ritmesectie",
"schrijft songs in Nederlands en Engels door elkaar",
"speelt instrumenten die ze grotendeels zelf hebben gebouwd",
"draait alleen optredens op locaties zonder Wi-Fi",
];
const bioEnding = [
"Debuut-EP verschijnt eind 2027.",
"Hun laatste album werd genomineerd voor de fictieve Edison Polder Award.",
"Polderfest is hun grootste festival tot nu toe.",
"Vorig jaar speelden ze nog in cafés, dit jaar op Stage B.",
"Spelen voor het eerst op een buitenpodium.",
"Beruchte live-show met 12 backing vocalists.",
];
// ────────────────────────────────────────────────────────────
// Namen genereren
// ────────────────────────────────────────────────────────────
function generateBandName(seedIdx: number): string {
const pattern = seedIdx % 4;
if (pattern === 0) {
return `${pick(adjectives)} ${pick(nouns)}`;
}
if (pattern === 1) {
return `${pick(dutchPrefixes)} ${pick(nouns)}`;
}
if (pattern === 2) {
return `${pick(soloNamesFirst)} ${pick(soloNamesLast)}`;
}
return `${pick(soloNamesFirst)} & The ${pick(nouns)}`;
}
function generateMembers(): string[] {
const count = range(1, 5);
const out: string[] = [];
for (let i = 0; i < count; i++) {
out.push(`${pick(soloNamesFirst)} ${pick(soloNamesLast)}`);
}
return out;
}
function generateBio(name: string): string {
return `${pick(bioOpenings)} ${pick(cities)}, ${name} ${pick(bioMiddle)}. ${pick(bioEnding)}`;
}
// ────────────────────────────────────────────────────────────
// Hoofdfunctie
// ────────────────────────────────────────────────────────────
async function runSeed() {
console.log("Genereren van 500 Polderfest bands...");
// Wipe bestaande data (optioneel)
await supabase.from("bands").delete().neq("id", 0);
const bands = [];
const usedNames = new Set<string>();
for (let i = 0; i < 500; i++) {
let name = generateBandName(i);
let attempts = 0;
while (usedNames.has(name) && attempts < 10) {
name = generateBandName(i + attempts * 7);
attempts++;
}
usedNames.add(name);
const genre = pick(genres);
const sub_genre = pick(subGenresByGenre[genre]);
const tier = pick(tiers);
const popularity = tier === "headliner" ? range(80, 100)
: tier === "mid" ? range(40, 79)
: range(10, 39);
const ticket_impact = tier === "headliner" ? range(25, 60)
: tier === "mid" ? range(5, 25)
: 0;
bands.push({
name,
genre,
sub_genre,
stage: pick(stages),
day: pick(days),
start_time: pick(timeSlots),
duration_min: tier === "headliner" ? range(75, 120)
: tier === "mid" ? range(45, 75)
: range(30, 45),
origin_city: pick(cities),
members: generateMembers(),
bio: generateBio(name),
tier,
popularity,
ticket_impact,
});
}
console.log("Schrijven naar Supabase in batches van 100...");
// Supabase insert in batches (single call van 500 kan timeouten)
for (let i = 0; i < bands.length; i += 100) {
const batch = bands.slice(i, i + 100);
const { error } = await supabase.from("bands").insert(batch);
if (error) {
console.error("Insert error op batch", i / 100, ":", error.message);
process.exit(1);
}
console.log(`${i + batch.length}/${bands.length}`);
}
console.log("Klaar! 500 Polderfest bands staan in Supabase.");
}
runSeed().catch((e) => {
console.error(e);
process.exit(1);
});