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 { db as prodDb } from "./index.ts";
import { globalItems, tags } from "./schema.ts";
import { globalItems, manufacturers, tags } from "./schema.ts";
type Db = typeof prodDb;
const SEED_TAGS = [
// Hobby / activity tags (used by onboarding hobby picker)
"bikepacking",
"cycling",
"hiking",
"backpacking",
"camping",
"climbing",
"mountaineering",
"road-cycling",
"gravel",
"running",
"trail-running",
// Bag types
"handlebar-bag",
"framebag",
"saddlebag",
"top-tube-bag",
"stem-bag",
"fork-bag",
"feed-bag",
"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",
export const SEED_MANUFACTURERS = [
{ name: "Revelate Designs", slug: "revelate-designs", website: "https://revelatedesigns.com", country: "US", tier: 1 },
{ name: "Apidura", slug: "apidura", website: "https://apidura.com", country: "GB", tier: 1 },
{ name: "Ortlieb", slug: "ortlieb", website: "https://ortlieb.com", country: "DE", tier: 1 },
{ name: "Big Agnes", slug: "big-agnes", website: "https://bigagnes.com", country: "US", tier: 1 },
{ name: "Tarptent", slug: "tarptent", website: "https://tarptent.com", country: "US", tier: 1 },
{ name: "Zpacks", slug: "zpacks", website: "https://zpacks.com", country: "US", tier: 1 },
{ name: "Sea to Summit", slug: "sea-to-summit", website: "https://seatosummit.com", country: "AU", tier: 1 },
{ name: "Western Mountaineering", slug: "western-mountaineering", website: "https://westernmountaineering.com", country: "US", tier: 1 },
{ name: "MSR", slug: "msr", website: "https://msrgear.com", country: "US", tier: 1 },
{ name: "BioLite", slug: "biolite", website: "https://bioliteenergy.com", country: "US", tier: 1 },
{ name: "Petzl", slug: "petzl", website: "https://petzl.com", country: "FR", tier: 1 },
{ name: "Black Diamond", slug: "black-diamond", website: "https://blackdiamondequipment.com", country: "US", tier: 1 },
{ name: "Garmin", slug: "garmin", website: "https://garmin.com", country: "US", tier: 1 },
{ name: "Wahoo", slug: "wahoo", website: "https://wahoofitness.com", country: "US", tier: 1 },
{ name: "Sawyer", slug: "sawyer", website: "https://sawyerproducts.com", country: "US", tier: 1 },
{ name: "Canyon", slug: "canyon", website: "https://canyon.com", country: "DE", tier: 1 },
{ name: "Specialized", slug: "specialized", website: "https://specialized.com", country: "US", tier: 1 },
{ name: "Trek", slug: "trek", website: "https://trekbikes.com", country: "US", tier: 1 },
{ name: "Salsa Cycles", slug: "salsa-cycles", website: "https://salsacycles.com", country: "US", tier: 1 },
{ name: "Surly", slug: "surly", website: "https://surlybikes.com", country: "US", tier: 1 },
];
/**
* Seed curated tags for outdoor/adventure gear.
* Idempotent: inserts only tags that don't already exist.
*/
const SEED_TAGS = [
"bikepacking", "cycling", "hiking", "backpacking", "camping", "climbing",
"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) {
const existing = await db.select().from(tags);
const existingNames = new Set(existing.map((t) => t.name));
for (const name of SEED_TAGS) {
if (!existingNames.has(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) {
await seedManufacturers(db);
const existing = await db.select().from(globalItems).limit(1);
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) {
const manufacturerId = mfByName.get(item.brand);
if (!manufacturerId) continue;
await db.insert(globalItems).values({
brand: item.brand,
manufacturerId,
model: item.model,
category: item.category ?? null,
weightGrams: item.weightGrams ?? null,