From 81b70a72aca6dbc2aa8be8fe91f16ba363b01c62 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 5 Apr 2026 12:59:21 +0200 Subject: [PATCH] feat(18-01): add Zod schemas, types, and global items seed data - Add searchGlobalItemsSchema, linkItemSchema, updateProfileSchema to schemas.ts - Add isPublic field to createSetupSchema and updateSetupSchema - Add GlobalItem, ItemGlobalLink, SearchGlobalItems, LinkItem, UpdateProfile types - Create global-items-seed.json with 18 bikepacking gear items across 7 categories - Format fix in schema.ts (pre-existing biome formatting) --- src/db/global-items-seed.json | 146 ++++++++++++++++++++++++++++++++++ src/db/schema.ts | 4 +- src/shared/schemas.ts | 18 +++++ src/shared/types.ts | 12 +++ 4 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 src/db/global-items-seed.json diff --git a/src/db/global-items-seed.json b/src/db/global-items-seed.json new file mode 100644 index 0000000..2c82126 --- /dev/null +++ b/src/db/global-items-seed.json @@ -0,0 +1,146 @@ +[ + { + "brand": "Revelate Designs", + "model": "Terrapin System", + "category": "bags", + "weightGrams": 529, + "priceCents": 18500, + "description": "Waterproof saddle bag with 14L capacity, roll-top closure, and integrated Revelate seat bag mount." + }, + { + "brand": "Apidura", + "model": "Expedition Handlebar Pack", + "category": "bags", + "weightGrams": 300, + "priceCents": 16000, + "description": "14L waterproof handlebar roll bag with internal dry bag and accessory pocket." + }, + { + "brand": "Ortlieb", + "model": "Frame-Pack Toptube", + "category": "bags", + "weightGrams": 180, + "priceCents": 7500, + "description": "4L waterproof top-tube bag with magnetic closure and reflective details." + }, + { + "brand": "Revelate Designs", + "model": "Tangle Frame Bag", + "category": "bags", + "weightGrams": 170, + "priceCents": 13500, + "description": "Full-frame bag with water-resistant construction and multiple internal pockets." + }, + { + "brand": "Big Agnes", + "model": "Copper Spur HV UL1", + "category": "shelters", + "weightGrams": 879, + "priceCents": 42000, + "description": "Ultralight 1-person freestanding tent with high-volume hub design and DAC Featherlite poles." + }, + { + "brand": "Tarptent", + "model": "Protrail Li", + "category": "shelters", + "weightGrams": 454, + "priceCents": 35000, + "description": "Ultralight single-wall trekking pole shelter in Dyneema composite fabric." + }, + { + "brand": "Outdoor Research", + "model": "Helium Bivy", + "category": "shelters", + "weightGrams": 510, + "priceCents": 24900, + "description": "Waterproof breathable bivy sack with single-hoop pole and full-zip entry." + }, + { + "brand": "Sea to Summit", + "model": "Spark SP1", + "category": "sleep-systems", + "weightGrams": 375, + "priceCents": 28000, + "description": "Ultralight 850+ fill down sleeping bag rated to 40F/4C with Ultra-Dry Down." + }, + { + "brand": "Nemo", + "model": "Tensor Ultralight Insulated Regular", + "category": "sleep-systems", + "weightGrams": 425, + "priceCents": 18000, + "description": "3-inch thick insulated sleeping pad with R-value 4.2 and Spaceframe baffles." + }, + { + "brand": "Therm-a-Rest", + "model": "NeoAir XLite NXT", + "category": "sleep-systems", + "weightGrams": 354, + "priceCents": 22000, + "description": "Ultralight insulated air pad with R-value 4.5, Triangular Core Matrix, and WingLock valve." + }, + { + "brand": "MSR", + "model": "PocketRocket 2", + "category": "cooking", + "weightGrams": 73, + "priceCents": 5500, + "description": "Ultralight canister stove with adjustable flame control, boils 1L in 3.5 minutes." + }, + { + "brand": "Toaks", + "model": "Titanium 750ml Pot", + "category": "cooking", + "weightGrams": 103, + "priceCents": 3300, + "description": "Ultralight titanium pot with lid and foldable handles, 750ml capacity." + }, + { + "brand": "Katadyn", + "model": "BeFree 1.0L", + "category": "hydration", + "weightGrams": 59, + "priceCents": 4500, + "description": "Ultralight hollow fiber water filter with 0.1 micron filtration and 1L soft flask." + }, + { + "brand": "HydraPak", + "model": "Seeker 2L", + "category": "hydration", + "weightGrams": 73, + "priceCents": 1800, + "description": "Collapsible 2L water storage with wide mouth and compatible with Katadyn BeFree filter." + }, + { + "brand": "Nitecore", + "model": "NU25 UL", + "category": "lighting", + "weightGrams": 28, + "priceCents": 3600, + "description": "Ultralight USB-C rechargeable headlamp with 400 lumens max and red light mode." + }, + { + "brand": "Exposure Lights", + "model": "Revo Dynamo", + "category": "lighting", + "weightGrams": 130, + "priceCents": 22000, + "description": "Dynamo-powered front light with 800 lumens, built-in standlight, and USB charging output." + }, + { + "brand": "Surly", + "model": "24-Pack Rack", + "category": "racks", + "weightGrams": 750, + "priceCents": 10000, + "description": "Front rack for bikepacking with 24-pack platform, fits most forks with mid-blade eyelets." + }, + { + "brand": "Salsa", + "model": "Anything Cage HD", + "category": "accessories", + "weightGrams": 80, + "priceCents": 2500, + "description": "Heavy-duty bottle cage for oversized loads like dry bags and fuel canisters." + } +] diff --git a/src/db/schema.ts b/src/db/schema.ts index 24b66ca..b11d6bc 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -198,9 +198,7 @@ export const oauthCodes = pgTable("oauth_codes", { code: text("code").notNull().unique(), clientId: text("client_id").notNull(), codeChallenge: text("code_challenge").notNull(), - codeChallengeMethod: text("code_challenge_method") - .notNull() - .default("S256"), + codeChallengeMethod: text("code_challenge_method").notNull().default("S256"), redirectUri: text("redirect_uri").notNull(), expiresAt: timestamp("expires_at").notNull(), used: integer("used").notNull().default(0), diff --git a/src/shared/schemas.ts b/src/shared/schemas.ts index ed34157..0e68b9a 100644 --- a/src/shared/schemas.ts +++ b/src/shared/schemas.ts @@ -73,10 +73,12 @@ export const reorderCandidatesSchema = z.object({ // Setup schemas export const createSetupSchema = z.object({ name: z.string().min(1, "Setup name is required"), + isPublic: z.boolean().optional().default(false), }); export const updateSetupSchema = z.object({ name: z.string().min(1, "Setup name is required"), + isPublic: z.boolean().optional(), }); export const syncSetupItemsSchema = z.object({ @@ -89,3 +91,19 @@ export const classificationSchema = z.enum(["base", "worn", "consumable"]); export const updateClassificationSchema = z.object({ classification: classificationSchema, }); + +// Global item schemas +export const searchGlobalItemsSchema = z.object({ + q: z.string().optional(), +}); + +export const linkItemSchema = z.object({ + globalItemId: z.number().int().positive(), +}); + +// Profile schemas +export const updateProfileSchema = z.object({ + displayName: z.string().max(100).optional(), + avatarUrl: z.string().optional(), + bio: z.string().max(500).optional(), +}); diff --git a/src/shared/types.ts b/src/shared/types.ts index f96624e..3069dad 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,6 +1,8 @@ import type { z } from "zod"; import type { categories, + globalItems, + itemGlobalLinks, items, setupItems, setups, @@ -13,13 +15,16 @@ import type { createItemSchema, createSetupSchema, createThreadSchema, + linkItemSchema, reorderCandidatesSchema, resolveThreadSchema, + searchGlobalItemsSchema, syncSetupItemsSchema, updateCandidateSchema, updateCategorySchema, updateClassificationSchema, updateItemSchema, + updateProfileSchema, updateSetupSchema, updateThreadSchema, } from "./schemas.ts"; @@ -42,6 +47,11 @@ export type UpdateSetup = z.infer; export type SyncSetupItems = z.infer; export type UpdateClassification = z.infer; +// Global item types +export type SearchGlobalItems = z.infer; +export type LinkItem = z.infer; +export type UpdateProfile = z.infer; + // Types inferred from Drizzle schema export type Item = typeof items.$inferSelect; export type Category = typeof categories.$inferSelect; @@ -49,3 +59,5 @@ export type Thread = typeof threads.$inferSelect; 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;