feat(19-01): update Zod schemas, types, and seed script for reference model

- Add globalItemId and purchasePriceCents to createItemSchema
- Add globalItemId to createCandidateSchema
- Add tags param to searchGlobalItemsSchema
- Remove linkItemSchema from schemas and types
- Replace ItemGlobalLink with Tag and GlobalItemTag types
- Convert seedGlobalItems to async, add seedTags with 30 curated tags
This commit is contained in:
2026-04-05 20:27:51 +02:00
parent 5df513c138
commit e9baa8d7e0
3 changed files with 67 additions and 21 deletions

View File

@@ -1,27 +1,73 @@
import seedData from "./global-items-seed.json";
import { db as prodDb } from "./index.ts";
import { globalItems } from "./schema.ts";
import { globalItems, tags } from "./schema.ts";
type Db = typeof prodDb;
const SEED_TAGS = [
"handlebar-bag",
"framebag",
"saddlebag",
"top-tube-bag",
"stem-bag",
"fork-bag",
"hip-pack",
"backpack",
"tent",
"bivy",
"tarp",
"hammock",
"sleeping-bag",
"sleeping-pad",
"quilt",
"pillow",
"stove",
"cookware",
"water-filter",
"water-bottle",
"headlamp",
"bike-light",
"ultralight",
"waterproof",
"budget",
"premium",
"bikepacking",
"hiking",
"camping",
"touring",
];
/**
* Seed curated tags for outdoor/adventure gear.
* Idempotent: skips if any tags already exist.
*/
export async function seedTags(db: Db = prodDb) {
const existing = await db.select().from(tags).limit(1);
if (existing.length > 0) return;
for (const name of SEED_TAGS) {
await db.insert(tags).values({ name });
}
}
/**
* Seed the global items table with initial bikepacking gear data.
* Idempotent: skips if any rows already exist.
*/
export function seedGlobalItems(db: Db = prodDb) {
const existing = db.select().from(globalItems).limit(1).all();
export async function seedGlobalItems(db: Db = prodDb) {
const existing = await db.select().from(globalItems).limit(1);
if (existing.length > 0) return;
for (const item of seedData) {
db.insert(globalItems)
.values({
brand: item.brand,
model: item.model,
category: item.category ?? null,
weightGrams: item.weightGrams ?? null,
priceCents: item.priceCents ?? null,
description: item.description ?? null,
})
.run();
await db.insert(globalItems).values({
brand: item.brand,
model: item.model,
category: item.category ?? null,
weightGrams: item.weightGrams ?? null,
priceCents: item.priceCents ?? null,
description: item.description ?? null,
});
}
await seedTags(db);
}

View File

@@ -10,6 +10,8 @@ export const createItemSchema = z.object({
imageFilename: z.string().optional(),
imageSourceUrl: z.string().url().optional().or(z.literal("")),
quantity: z.number().int().positive().optional(),
globalItemId: z.number().int().positive().optional(),
purchasePriceCents: z.number().int().nonnegative().optional(),
});
export const updateItemSchema = createItemSchema.partial().extend({
@@ -58,6 +60,7 @@ export const createCandidateSchema = z.object({
status: candidateStatusSchema.optional(),
pros: z.string().optional(),
cons: z.string().optional(),
globalItemId: z.number().int().positive().optional(),
});
export const updateCandidateSchema = createCandidateSchema.partial();
@@ -95,10 +98,7 @@ export const updateClassificationSchema = z.object({
// Global item schemas
export const searchGlobalItemsSchema = z.object({
q: z.string().optional(),
});
export const linkItemSchema = z.object({
globalItemId: z.number().int().positive(),
tags: z.string().optional(),
});
// Profile schemas

View File

@@ -2,10 +2,11 @@ import type { z } from "zod";
import type {
categories,
globalItems,
itemGlobalLinks,
globalItemTags,
items,
setupItems,
setups,
tags,
threadCandidates,
threads,
} from "../db/schema.ts";
@@ -15,7 +16,6 @@ import type {
createItemSchema,
createSetupSchema,
createThreadSchema,
linkItemSchema,
reorderCandidatesSchema,
resolveThreadSchema,
searchGlobalItemsSchema,
@@ -49,7 +49,6 @@ export type UpdateClassification = z.infer<typeof updateClassificationSchema>;
// Global item types
export type SearchGlobalItems = z.infer<typeof searchGlobalItemsSchema>;
export type LinkItem = z.infer<typeof linkItemSchema>;
export type UpdateProfile = z.infer<typeof updateProfileSchema>;
// Types inferred from Drizzle schema
@@ -60,4 +59,5 @@ export type ThreadCandidate = typeof threadCandidates.$inferSelect;
export type Setup = typeof setups.$inferSelect;
export type SetupItem = typeof setupItems.$inferSelect;
export type GlobalItem = typeof globalItems.$inferSelect;
export type ItemGlobalLink = typeof itemGlobalLinks.$inferSelect;
export type Tag = typeof tags.$inferSelect;
export type GlobalItemTag = typeof globalItemTags.$inferSelect;