πŸ‘€

User & Creator Profiles

Levende identiteiten: mood-ring, role badge, Fantasy Profile DNA, Gift Vault, AI Teezer-status en RBAC-gestuurde creator-stats.

Fastify 5 PostgreSQL 16 + Drizzle ORM React 19 + TanStack Query v5 RBAC

🧬 Profielstructuur

Live voorbeeld β€” Creator profiel

πŸ”₯
LovelyLena βœ”οΈ CREATOR
@lovelylena Β· Amsterdam πŸ“
😈 spicy
● LIVE

Dutch creator πŸ”₯ | Erotic entertainment | Streaming daily 20:00–23:00 πŸŽ₯

4.2K
Followers
312
Following
18.7K
Likes
142h
Stream

Anatomie

  • ●
    Mood color ring β€” Kleurring rond avatar weerspiegelt huidige mood. Pulseert rood bij LIVE. CSS box-shadow gebaseerd op user.currentMood.
  • ●
    Role badge β€” CREATOR / MEMBER / VIP / STAFF. Kleur en gradient per rol via RBAC enum.
  • ●
    Verified checkmark β€” Blauw βœ”οΈ na identity-verificatie. Opgeslagen in users.isVerified.
  • ●
    Live indicator β€” Rood puls-badge als user.isLive = true. Realtime via Socket.IO.
  • ●
    Stats row β€” Followers, Following, Likes, Stream hours. EfficiΓ«nt via gedenormaliseerde counters in user_stats tabel.

πŸ§ͺ Fantasy Profile β€” Erotic DNA & Kink Preferences

Het Fantasy Profile is het erotische hart van een TeezU-profiel. Alleen zichtbaar voor 18+-verified users met passend consent-level.

Erotic DNA Visualisatie

Dominant78%
Exhibitionist92%
Voyeur45%
Romantic61%
Kinky84%

Scores worden ingevuld via de DNA-quiz (dna_quiz.html) en opgeslagen in fantasy_profiles.dna_scores (JSONB).

Kink Preferences & Intent

πŸ”₯ BDSM-light πŸ“Έ Sexting 🎭 Role-play πŸŽ₯ Livestream πŸ’‹ Teasing
Intent indicators
🎯 DoelEntertainment + Fantasy
🀝 Consent level18+ explicit
πŸ’¬ Beschikbaar voorDM, Livestream, Custom

πŸ€– AI Features β€” Teezer & AI DoppelgΓ€nger

Teezer Status (AI Clone)

🟒
Teezer Active
AI-clone reageert op berichten als LovelyLena. Respons <2s.
LIVE
⚫
Teezer Inactive
Creator is zelf beschikbaar of Teezer uitgeschakeld.
OFF

Status opgeslagen in users.teezerActive (boolean). Toggle via PATCH /api/users/:id/teezer.

AI DoppelgΓ€nger Schedule

⏰
08:00 – 12:00
Teezer ON
πŸ”₯
20:00 – 23:00
LIVE (Creator)
😴
23:00 – 08:00
Offline

Schedule opgeslagen in teezer_schedules tabel als JSONB array van tijdssloten per dag.

🎁 Gift Vault

Publiek ontvangen gifts worden prominent weergegeven op het profiel. Dient als sociaal bewijs en status-signaal.

πŸ’Ž
Diamond
Γ—3
🌹
Rose
Γ—47
πŸ‘‘
Crown
Γ—12
πŸ”₯
Fire
Γ—89
πŸ¦‹
Butterfly
Γ—23

Gifts worden gefetcht via GET /api/users/:id/gifts (top 10 meest ontvangen, gegroepeerd per type). Zichtbaar voor iedereen.

πŸ–ΌοΈ Content Grid β€” Publiek, Premium & Locked

🌸
πŸŒ™
πŸ”’
50 tokens
πŸ”₯
🧊
Premium
πŸ’š

Grid toont maximaal 9 recente items. Locked preview heeft altijd thumbnail (geblurd) zichtbaar. Unlock via Freeze & Reveal flow.

πŸ“Š Creator Stats β€” RBAC

Earnings-stats, subscriber count en streaming schedule zijn alleen zichtbaar voor de creator zelf en admins. Nooit publiek.

Private Creator Dashboard (eigen profiel)

πŸ’° Earnings (deze maand) € 2.840
πŸ’Ž Tip totaal (all-time) 14.200 tokens
πŸ‘₯ Subscribers 342
πŸ“… Stream schema ma–vr 20:00–23:00

Public stats (iedereen zichtbaar)

πŸ‘€ Followers 4.217
πŸ’™ Likes ontvangen 18.742
πŸŽ₯ Stream uren (totaal) 142h
πŸ“Έ Posts gepubliceerd 89

πŸ› οΈ API Endpoints

GET /api/users/:id/profile Volledig profiel incl. stats, fantasy profile (indien 18+ access), gift vault
// Drizzle ORM – profile query
const profile = await db.query.users.findFirst({
  where: eq(users.id, id),
  with: {
    fantasyProfile: true,    // enkel als req.user 18+ verified
    giftVault: { limit: 10, orderBy: desc(gifts.count) },
    contentGrid: { limit: 9, where: not(eq(content.deletedAt, null)) },
    stats: true,
  }
});
PATCH /api/users/:id/profile Update bio, mood, kinks, teezer-status, schedule. Eigen profiel only.
Body: { bio?, currentMood?, teezerActive?, fantasyProfile?, streamingSchedule? }
POST /api/users/:id/follow Toggle follow/unfollow. Triggert notification:new (new_follower) via Socket.IO.
Response: { following: boolean, followerCount: number }

πŸ—„οΈ Database Schema β€” PostgreSQL 16 + Drizzle ORM

// schema/profiles.ts  (Drizzle ORM)
export const users = pgTable('users', {
  id:            uuid('id').defaultRandom().primaryKey(),
  username:      varchar('username', { length: 32 }).notNull().unique(),
  displayName:   varchar('display_name', { length: 64 }),
  bio:           text('bio'),
  avatarKey:     text('avatar_key'),
  coverKey:      text('cover_key'),
  role:          userRoleEnum('role').default('member'),
  currentMood:   moodEnum('current_mood'),
  isVerified:    boolean('is_verified').default(false),
  isLive:        boolean('is_live').default(false),
  teezerActive:  boolean('teezer_active').default(false),
  createdAt:     timestamp('created_at').defaultNow(),
});

export const fantasyProfiles = pgTable('fantasy_profiles', {
  id:          uuid('id').defaultRandom().primaryKey(),
  userId:      uuid('user_id').notNull().unique().references(() => users.id),
  dnaScores:   jsonb('dna_scores').default({}),
  // { dominant:78, exhibitionist:92, voyeur:45, romantic:61, kinky:84 }
  kinks:       text('kinks').array(),
  intent:      text('intent').array(),
  consentLevel: varchar('consent_level', { length: 32 }),
});

export const userStats = pgTable('user_stats', {
  userId:         uuid('user_id').notNull().primaryKey().references(() => users.id),
  followerCount:  integer('follower_count').default(0),
  followingCount: integer('following_count').default(0),
  likeCount:      integer('like_count').default(0),
  streamHours:    numeric('stream_hours', { precision:8, scale:1 }).default('0'),
  tipTotal:       integer('tip_total').default(0),   // only visible to creator/admin
  subscriberCount: integer('subscriber_count').default(0),
});