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:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user