feat: migrate setup visibility from boolean to three-tier system

Replace isPublic boolean with visibility enum (private/link/public) across
the full stack. Add shares table to schema for future share link support.
Update all services, routes, schemas, hooks, components, and tests.

Plan: 32-01 (Setup Sharing System - Schema Migration)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 17:55:46 +02:00
parent 727abf1528
commit edc9793c2d
20 changed files with 1556 additions and 81 deletions

View File

@@ -857,7 +857,7 @@ export const DEV_THREADS = [
export const DEV_SETUPS = [
{
name: "Weekend Overnighter",
isPublic: true,
visibility: "public" as const,
items: [
{ userItemIndex: 0, classification: "base" }, // Terrapin saddle bag
{ userItemIndex: 3, classification: "base" }, // X-Mid 1
@@ -871,7 +871,7 @@ export const DEV_SETUPS = [
},
{
name: "Ultra-Light Day Ride",
isPublic: false,
visibility: "private" as const,
items: [
{ userItemIndex: 2, classification: "base" }, // Top tube pack
{ userItemIndex: 7, classification: "worn" }, // Nitecore NU25

View File

@@ -252,7 +252,7 @@ async function seedDevData(database: Db = db) {
.values({
name: setupDef.name,
userId,
isPublic: setupDef.isPublic,
visibility: setupDef.visibility,
})
.returning();
if (!setup) throw new Error(`Failed to insert setup: ${setupDef.name}`);

View File

@@ -1,5 +1,4 @@
import {
boolean,
doublePrecision,
integer,
pgTable,
@@ -121,7 +120,7 @@ export const setups = pgTable("setups", {
userId: integer("user_id")
.notNull()
.references(() => users.id),
isPublic: boolean("is_public").notNull().default(false),
visibility: text("visibility").notNull().default("private"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
@@ -139,6 +138,21 @@ export const setupItems = pgTable("setup_items", {
classification: text("classification").notNull().default("base"),
});
// ── Shares ─────────────────────────────────────────────────────────
export const shares = pgTable("shares", {
id: serial("id").primaryKey(),
setupId: integer("setup_id")
.notNull()
.references(() => setups.id, { onDelete: "cascade" }),
token: text("token").notNull().unique(),
permission: text("permission").notNull().default("read"),
expiresAt: timestamp("expires_at"),
userId: integer("user_id").references(() => users.id),
createdAt: timestamp("created_at").defaultNow().notNull(),
revokedAt: timestamp("revoked_at"),
});
// ── Global Items ────────────────────────────────────────────────────
export const globalItems = pgTable(