Migrate from .NET MAUI to Expo + Convex
Complete rewrite of Scry using TypeScript stack:
- Expo/React Native with Expo Router (file-based routing)
- Convex backend (serverless functions + real-time database)
- Adaptive camera system (expo-camera in Expo Go, Vision Camera in production)
- React Native Skia + fast-opencv for image processing
- GDPR-compliant auth setup with Zitadel OIDC (pending configuration)
Key features:
- Card recognition pipeline ported to TypeScript
- Perceptual hashing (192-bit color pHash)
- CLAHE preprocessing for lighting normalization
- Local SQLite cache with Convex sync
- Collection management with offline support
Removes all .NET/MAUI code (src/, test/, tools/).
💘 Generated with Crush
Assisted-by: Claude Opus 4.5 via Crush <crush@charm.land>
This commit is contained in:
parent
56499d5af9
commit
83ab4df537
138 changed files with 19136 additions and 7681 deletions
93
convex/schema.ts
Normal file
93
convex/schema.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
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"]),
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue