feat: seed manufacturers list, update seedGlobalItems to resolve by name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 16:16:52 +02:00
parent ec27df1d0f
commit f868bbdecf

View File

@@ -1,81 +1,59 @@
import seedData from "./global-items-seed.json"; import seedData from "./global-items-seed.json";
import { db as prodDb } from "./index.ts"; import { db as prodDb } from "./index.ts";
import { globalItems, tags } from "./schema.ts"; import { globalItems, manufacturers, tags } from "./schema.ts";
type Db = typeof prodDb; type Db = typeof prodDb;
const SEED_TAGS = [ export const SEED_MANUFACTURERS = [
// Hobby / activity tags (used by onboarding hobby picker) { name: "Revelate Designs", slug: "revelate-designs", website: "https://revelatedesigns.com", country: "US", tier: 1 },
"bikepacking", { name: "Apidura", slug: "apidura", website: "https://apidura.com", country: "GB", tier: 1 },
"cycling", { name: "Ortlieb", slug: "ortlieb", website: "https://ortlieb.com", country: "DE", tier: 1 },
"hiking", { name: "Big Agnes", slug: "big-agnes", website: "https://bigagnes.com", country: "US", tier: 1 },
"backpacking", { name: "Tarptent", slug: "tarptent", website: "https://tarptent.com", country: "US", tier: 1 },
"camping", { name: "Zpacks", slug: "zpacks", website: "https://zpacks.com", country: "US", tier: 1 },
"climbing", { name: "Sea to Summit", slug: "sea-to-summit", website: "https://seatosummit.com", country: "AU", tier: 1 },
"mountaineering", { name: "Western Mountaineering", slug: "western-mountaineering", website: "https://westernmountaineering.com", country: "US", tier: 1 },
"road-cycling", { name: "MSR", slug: "msr", website: "https://msrgear.com", country: "US", tier: 1 },
"gravel", { name: "BioLite", slug: "biolite", website: "https://bioliteenergy.com", country: "US", tier: 1 },
"running", { name: "Petzl", slug: "petzl", website: "https://petzl.com", country: "FR", tier: 1 },
"trail-running", { name: "Black Diamond", slug: "black-diamond", website: "https://blackdiamondequipment.com", country: "US", tier: 1 },
// Bag types { name: "Garmin", slug: "garmin", website: "https://garmin.com", country: "US", tier: 1 },
"handlebar-bag", { name: "Wahoo", slug: "wahoo", website: "https://wahoofitness.com", country: "US", tier: 1 },
"framebag", { name: "Sawyer", slug: "sawyer", website: "https://sawyerproducts.com", country: "US", tier: 1 },
"saddlebag", { name: "Canyon", slug: "canyon", website: "https://canyon.com", country: "DE", tier: 1 },
"top-tube-bag", { name: "Specialized", slug: "specialized", website: "https://specialized.com", country: "US", tier: 1 },
"stem-bag", { name: "Trek", slug: "trek", website: "https://trekbikes.com", country: "US", tier: 1 },
"fork-bag", { name: "Salsa Cycles", slug: "salsa-cycles", website: "https://salsacycles.com", country: "US", tier: 1 },
"feed-bag", { name: "Surly", slug: "surly", website: "https://surlybikes.com", country: "US", tier: 1 },
"dry-bag",
"stuff-sack",
// Bike bags (parent)
"bike-bag",
// Shelter
"tent",
"bivy",
"tarp",
"hammock",
// Sleep system
"sleeping-bag",
"sleeping-pad",
"quilt",
"pillow",
// Cooking
"stove",
"cookware",
"mug",
"utensils",
// Water
"water-filter",
"water-bottle",
// Lighting
"headlamp",
"bike-light",
"lantern",
// Navigation & electronics
"gps",
"bike-computer",
"power-bank",
"solar-panel",
// Tools & repair
"multi-tool",
"pump",
"repair-kit",
"lock",
// Clothing
"rain-jacket",
"base-layer",
"gloves",
"shoe",
]; ];
/** const SEED_TAGS = [
* Seed curated tags for outdoor/adventure gear. "bikepacking", "cycling", "hiking", "backpacking", "camping", "climbing",
* Idempotent: inserts only tags that don't already exist. "mountaineering", "road-cycling", "gravel", "running", "trail-running",
*/ "handlebar-bag", "framebag", "saddlebag", "top-tube-bag", "stem-bag",
"fork-bag", "feed-bag", "dry-bag", "stuff-sack", "bike-bag",
"tent", "bivy", "tarp", "hammock",
"sleeping-bag", "sleeping-pad", "quilt", "pillow",
"stove", "cookware", "mug", "utensils",
"water-filter", "water-bottle",
"headlamp", "bike-light", "lantern",
"gps", "bike-computer", "power-bank", "solar-panel",
"multi-tool", "pump", "repair-kit", "lock",
"rain-jacket", "base-layer", "gloves", "shoe",
];
export async function seedManufacturers(db: Db = prodDb) {
for (const m of SEED_MANUFACTURERS) {
await db
.insert(manufacturers)
.values(m)
.onConflictDoNothing();
}
}
export async function seedTags(db: Db = prodDb) { export async function seedTags(db: Db = prodDb) {
const existing = await db.select().from(tags); const existing = await db.select().from(tags);
const existingNames = new Set(existing.map((t) => t.name)); const existingNames = new Set(existing.map((t) => t.name));
for (const name of SEED_TAGS) { for (const name of SEED_TAGS) {
if (!existingNames.has(name)) { if (!existingNames.has(name)) {
await db.insert(tags).values({ name }); await db.insert(tags).values({ name });
@@ -83,17 +61,21 @@ export async function seedTags(db: Db = prodDb) {
} }
} }
/**
* Seed the global items table with initial bikepacking gear data.
* Idempotent: skips if any rows already exist.
*/
export async function seedGlobalItems(db: Db = prodDb) { export async function seedGlobalItems(db: Db = prodDb) {
await seedManufacturers(db);
const existing = await db.select().from(globalItems).limit(1); const existing = await db.select().from(globalItems).limit(1);
if (existing.length > 0) return; if (existing.length > 0) return;
const allManufacturers = await db.select().from(manufacturers);
const mfByName = new Map(allManufacturers.map((m) => [m.name, m.id]));
for (const item of seedData) { for (const item of seedData) {
const manufacturerId = mfByName.get(item.brand);
if (!manufacturerId) continue;
await db.insert(globalItems).values({ await db.insert(globalItems).values({
brand: item.brand, manufacturerId,
model: item.model, model: item.model,
category: item.category ?? null, category: item.category ?? null,
weightGrams: item.weightGrams ?? null, weightGrams: item.weightGrams ?? null,