Taste Graph is a production-grade MVP for cross-media identity mapping. Users log films, books, music, food, places, and ideas that shaped them, generate a taste fingerprint, and discover people through overlap stories and rare shared tastes.
- No algorithmic feed
- Discovery via constellation map + search + overlap matches
- TypeScript end-to-end
- Local-first setup with Docker + pnpm
- Frontend: Next.js (App Router), Tailwind CSS, shadcn-style UI primitives, React Hook Form, Zod
- Data viz: D3 (
CategoryBarChart),react-force-graph-2d(ConstellationMap) - Backend: tRPC + Next.js route handlers
- DB: PostgreSQL (Docker) + Prisma ORM
- Auth: NextAuth (Credentials + optional GitHub OAuth)
- Tooling: pnpm, ESLint, Prettier, Playwright smoke test
- Install dependencies:
pnpm install- Start PostgreSQL:
docker compose up -d- Run migrations:
pnpm prisma migrate dev- Seed realistic sample data:
pnpm db:seed- Start app:
pnpm devApp URL: http://localhost:3000
- Username:
devaanand(primary demo profile) - Password:
tastegraph123
All seeded users use the same password for local development.
Copy .env.example to .env and update values.
Required:
DATABASE_URLNEXTAUTH_SECRETNEXTAUTH_URL
Optional integrations (feature-flagged; never required for local run):
FEATURE_TMDB,TMDB_API_KEYFEATURE_GOOGLE_BOOKS,GOOGLE_BOOKS_API_KEYFEATURE_SPOTIFY,SPOTIFY_CLIENT_ID,SPOTIFY_CLIENT_SECRET
pnpm dev
pnpm build
pnpm start
pnpm lint
pnpm test
pnpm test:e2e
pnpm db:push
pnpm db:migrate
pnpm db:seedPublic:
/landing/login,/signup/u/:usernameprofile (shareable)/u/:username/world/:slug/explore/match/:usernameA/:usernameB
Auth required:
/meredirect to own profile/me/add/me/settings/me/worlds
auth.signUpuser.searchPeopleuser.getByUsernameuser.updateProfileitem.searchitem.createManualuserItem.addExistinguserItem.updatefingerprint.computeForUsersimilarity.computeAll(admin/dev only)similarity.getForPairsimilarity.getTopMatchesForUserworld.createworld.addItemworld.removeItemworld.getBySlug
Prisma models:
User,Account,Session,VerificationTokenItem,Tag,ItemTagUserItem,UserItemTagWorld,WorldEntryTasteFingerprintSimilarityCache
Indexes include:
Item(canonicalSlug),Item(title)Tag(name)User(username)SimilarityCache(userAId,userBId)
Implemented in [src/lib/services/matching.ts](/Users/devaanand/Desktop/Coding Stuff/TasteGraph/src/lib/services/matching.ts):
itemWeight(item) = log((N_users + 1) / (users_who_saved_item + 1))tagWeight(tag) = log((N_users + 1) / (users_who_used_tag + 1))shapedMeboost =x2- user fingerprint stores:
- category weights (shapedMe-first)
- top tags
- signature items (top 10 rarity-weighted)
- similarity:
itemSim = cosine(weightedItemVectorA, weightedItemVectorB)tagSim = cosine(weightedTagVectorA, weightedTagVectorB)categorySim = 1 - JS_divergence(categoryDistA, categoryDistB)overall = 0.45*tagSim + 0.45*itemSim + 0.10*categorySim
- unexpected overlaps sorted by rarity descending
prisma/seed.ts creates:
- 20 users
- 300 canonical items (50 per category)
- 30+ tags including:
slow cinema,existentialism,magical realism,shoegaze,ethiopian,ramen,brutalism
- realistic overlap clusters:
- arthouse
- pop culture
- philosophy
- travel/food
- rare cross-cluster overlaps for “unexpected overlap” results
- worlds/collections per user
- recomputed fingerprint + similarity caches
Playwright smoke test:
- login with seeded user
- add an item
- open explore
- open a match page
Test file:
- [
tests/e2e/smoke.spec.ts](/Users/devaanand/Desktop/Coding Stuff/TasteGraph/tests/e2e/smoke.spec.ts)
Run:
pnpm test:e2e.
├── docker-compose.yml
├── prisma
│ ├── migrations
│ ├── schema.prisma
│ └── seed.ts
├── src
│ ├── app
│ │ ├── api
│ │ ├── explore
│ │ ├── login
│ │ ├── match/[usernameA]/[usernameB]
│ │ ├── me/{add,settings,worlds}
│ │ ├── signup
│ │ └── u/[username]/world/[slug]
│ ├── components
│ │ ├── charts
│ │ ├── forms
│ │ ├── map
│ │ ├── shared
│ │ └── ui
│ ├── lib
│ │ ├── services
│ │ └── ...
│ ├── server
│ │ ├── api
│ │ └── auth
│ └── trpc
├── tests/e2e/smoke.spec.ts
├── .env.example
└── README.md
- External API search integrations (TMDB, Google Books, Spotify)
- Embedding-based matching (optional OpenAI)
- Real-time updates for worlds and overlap recalculation
- Invite-only growth loops and referral graph mechanics