import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; import { authTables } from "@convex-dev/auth/server"; // Override the default users table to store NO personal data (GDPR compliance) // User profile info (name, email, etc.) must be fetched from Zitadel OIDC userinfo endpoint const minimalAuthTables = { ...authTables, // Override users table - only store what's required for auth to function users: defineTable({ // No name, email, image, or any PII stored // The auth system needs this table to exist but we strip all profile data }), }; export default defineSchema({ ...minimalAuthTables, // Card printings with perceptual hashes cards: defineTable({ scryfallId: v.string(), oracleId: v.string(), name: v.string(), setCode: v.string(), collectorNumber: v.string(), rarity: v.string(), artist: v.optional(v.string()), imageUri: v.optional(v.string()), hash: v.bytes(), // 24-byte perceptual hash hashVersion: v.number(), // Algorithm version for migrations updatedAt: v.number(), }) .index("by_scryfall", ["scryfallId"]) .index("by_oracle", ["oracleId"]) .index("by_name", ["name"]) .index("by_updated", ["updatedAt"]), // Oracle cards (abstract game cards) oracles: defineTable({ oracleId: v.string(), name: v.string(), manaCost: v.optional(v.string()), cmc: v.optional(v.number()), typeLine: v.optional(v.string()), oracleText: v.optional(v.string()), colors: v.optional(v.array(v.string())), colorIdentity: v.optional(v.array(v.string())), keywords: v.optional(v.array(v.string())), power: v.optional(v.string()), toughness: v.optional(v.string()), }) .index("by_oracle", ["oracleId"]) .index("by_name", ["name"]), // MTG sets sets: defineTable({ setId: v.string(), code: v.string(), name: v.string(), setType: v.optional(v.string()), releasedAt: v.optional(v.string()), cardCount: v.optional(v.number()), iconSvgUri: v.optional(v.string()), }) .index("by_set", ["setId"]) .index("by_code", ["code"]), // User collections collections: defineTable({ userId: v.id("users"), cardId: v.id("cards"), quantity: v.number(), isFoil: v.boolean(), addedAt: v.number(), }) .index("by_user", ["userId"]) .index("by_user_card", ["userId", "cardId", "isFoil"]), // Scan history scanHistory: defineTable({ userId: v.id("users"), cardId: v.optional(v.id("cards")), confidence: v.number(), scannedAt: v.number(), addedToCollection: v.boolean(), }).index("by_user", ["userId"]), // Sync metadata metadata: defineTable({ key: v.string(), value: v.string(), }).index("by_key", ["key"]), });