Skip to content

YeboShops PRD (Product Requirements Document)

Product Name: YeboShops (formerly Vavu)
Type: African Marketplace/Commerce Platform
Status: Built & Operational
Last Updated: 2026-03-19


1. Vision & Problem Statement

Vision

YeboShops is a mobile-first social commerce platform connecting African buyers and sellers through an intuitive, trust-first marketplace experience. It combines the familiarity of social media with the functionality of e-commerce, designed specifically for African markets.

Problem Statement

African commerce faces unique challenges:

  1. Trust Deficit: Buyers are skeptical of online sellers; fraud is common on existing platforms
  2. Fragmented Markets: Sellers operate across WhatsApp, Facebook, and Instagram with no unified presence
  3. Payment Uncertainty: No escrow or secure payment options for peer-to-peer transactions
  4. Discovery Issues: Hard for buyers to find quality local products and verified sellers
  5. Mobile Constraints: Solutions must work on low-bandwidth networks and affordable devices

Solution

YeboShops addresses these through:

  • Secure Payments: Escrow-style "completion code" system
  • AI-Powered Listings: Automatic product descriptions and categorization from photos
  • Social Commerce: Built-in chat, comments, follows, and engagement features
  • Multi-Currency Support: Native support for African currencies (SZL, ZAR, etc.)
  • Trust Signals: Seller verification, reviews, and ratings

2. Technical Stack

Backend (vavu-api)

ComponentTechnology
RuntimeNode.js + TypeScript
FrameworkExpress.js
DatabasePostgreSQL with pgvector extension
ORMPrisma
AuthJWT (access + refresh tokens)
File StorageCloudflare R2
AI/MLGemini API (embeddings, search, content generation)
SMSTwilio

Frontend (vavu-app)

ComponentTechnology
FrameworkReact 18 + TypeScript
Build ToolVite
StylingTailwind CSS
StateReact Context
IconsLucide React

Infrastructure

  • Cloud Run (API hosting)
  • Cloudflare Pages (Frontend)
  • Neon PostgreSQL (Database)
  • Cloudflare R2 (Media storage)

3. Core Features (Implemented)

3.1 User Management

  • Phone-based registration with OTP verification
  • Country-aware profiles (currency, calling code auto-set)
  • Account states: Active, Suspended, Deactivation Requested, Deletion Requested
  • Soft delete with 90-day grace period before permanent deletion
  • Avatar uploads to R2
  • Online status tracking (isActive, lastOnline)

3.2 Shop System

  • One shop per user (enforced)
  • Auto-generated slugs with uniqueness handling
  • Plan tiers: FREE, BASIC, PRO, ENTERPRISE
  • Plan limits: Product limits, media per product, video posting, listing duration
  • Country-based currency inheritance
  • Shop verification (isVerified flag)
  • Shop stats tracking: Products, views, favorites, followers

3.3 Product Listings

  • AI-powered processing: Title, description, category, tags auto-generated from images
  • Multi-media support: Up to 5 images/videos per product (plan-dependent)
  • Product statuses: DRAFT → PUBLISHED → ARCHIVED/REJECTED
  • Soft delete with restore capability
  • Price with currency (inherited from shop's country)
  • Conditions: New, Like New, Good, Fair, Poor
  • Stock management: Quantity, SKU, availability
  • Vector embeddings for semantic search

3.4 Social Features

  • Comments on products and shops (with replies, likes)
  • Favorites (like/heart products)
  • Bookmarks (save for later)
  • Follows: User-to-user and user-to-shop
  • Shop reviews with 1-5 star ratings
  • Sharing with tracking (WhatsApp, Facebook, Instagram, Telegram, SMS, Link)

3.5 Messaging (Chat)

  • User-to-shop conversations (buyers initiate)
  • Product context preservation in chats
  • Message attachments (images, videos)
  • Reply threading
  • Read receipts (read, readAt)
  • Unread counts per chat
  • Suggested actions (AI-generated contextual responses)

3.6 Secure Payments (Escrow)

  • Flow:
    1. Buyer initiates payment → funds debited immediately
    2. Seller accepts → funds move to unconfirmed balance
    3. Transaction occurs in person
    4. Seller enters 6-digit completion code → funds confirmed
  • States: PENDING → ACCEPTED → COMPLETED (or REFUSED/DISPUTED/CANCELLED)
  • Dispute handling (both parties can dispute)
  • Full audit trail (SecurePaymentLog)

3.7 Wallet System

  • Per-user wallets with confirmed + unconfirmed balances
  • Currency tied to user's country
  • Transaction types: CREDIT, DEBIT
  • Reference types: SECURE_PAYMENT, DEPOSIT, WITHDRAWAL, TRANSFER, REFUND
  • Full transaction history

3.8 Search & Discovery

  • Vector search using pgvector (1536-dim embeddings)
  • Query expansion via Gemini AI
  • Keyword fallback when vector search fails
  • Filters: Category, price range, condition, shop
  • Feed algorithms: Personalized (authenticated), guest, anonymous
  • Trending products by view/favorite counts

3.9 Notifications

  • Types: ORDER, COMMENT, REVIEW, SYSTEM, PAYMENT, SHOP, PRODUCT, CHAT, FOLLOW
  • De-duplication (5-minute window per source)
  • Settings per channel: Push, Email, SMS
  • Specific toggles: newMessages, follows, comments, likes

3.10 Admin Features

  • User management: Approve deactivation/deletion requests
  • Shop management: Toggle activation, verify shops
  • Product moderation: Flag products, manual approval
  • Reports system: Handle spam, inappropriate content, scams
  • Analytics: Payment stats, platform metrics

4. User Journeys

4.1 Buyer Journey

1. Discovery
   └─ Browse feed → See products from various shops
   └─ Search → Vector + keyword search with filters
   └─ Category browse → Filter by category

2. Evaluation
   └─ View product → See details, photos, price, seller info
   └─ View shop → See all products, reviews, verification status
   └─ Read comments → See buyer feedback

3. Engagement
   └─ Favorite/Bookmark → Save for later
   └─ Follow shop → Get updates
   └─ Comment/Ask → Public questions

4. Inquiry
   └─ Start chat → "Is this available?"
   └─ Negotiate → Discuss price, meetup
   └─ Attachments → Send/receive photos

5. Purchase
   └─ Initiate Secure Payment → Funds held
   └─ Seller accepts → Funds in escrow
   └─ Meet in person → Exchange goods
   └─ Share completion code → Release funds

6. Post-Purchase
   └─ Review shop → 1-5 stars + comment
   └─ Report issues → If problems occur

4.2 Seller Journey

1. Onboarding
   └─ Register with phone → OTP verification
   └─ Create shop → Auto-generated from profile
   └─ Customize shop → Logo, cover, description

2. Listing
   └─ Upload photos → AI processes automatically
   └─ Review AI suggestions → Title, description, category
   └─ Set price → In local currency
   └─ Publish → Goes live

3. Management
   └─ View dashboard → Products, stats, messages
   └─ Update listings → Edit, archive, delete
   └─ Manage stock → Quantity, availability

4. Engagement
   └─ Respond to comments → Answer questions
   └─ Chat with buyers → Negotiate, arrange meetup
   └─ View notifications → New inquiries, follows

5. Sales
   └─ Receive payment request → Accept/Refuse
   └─ Meet buyer → Exchange goods
   └─ Enter completion code → Receive funds
   └─ Handle disputes → If issues arise

6. Growth
   └─ Boost products → Promoted placement
   └─ Upgrade plan → More products, features
   └─ Build reputation → Reviews, verification

4.3 Admin Journey

1. Monitoring
   └─ View dashboard → Platform metrics
   └─ Check reports → Flagged content
   └─ Review verifications → User ID verification

2. Moderation
   └─ Review products → Approve/reject flagged items
   └─ Handle reports → Spam, scams, inappropriate
   └─ Manage users → Suspend, reactivate

3. Operations
   └─ Resolve disputes → Payment issues
   └─ Process deletions → Account deletion requests
   └─ Verify shops → Grant verification badges

5. Data Models

5.1 User Model

prisma
model User {
  id                    String   @id @default(cuid())
  username              String   @unique
  phoneNumber           String   @unique
  password              String   // bcrypt hashed
  name                  String?
  role                  UserRole @default(USER)  // USER | ADMIN | VENDOR
  
  // Country/Locale
  countryId             String?
  countryCode           String?
  callingCode           String?
  
  // Verification
  isPhoneVerified       Boolean  @default(false)
  isIdVerified          Boolean  @default(false)
  idVerificationStatus  VerificationStatus @default(NONE)
  idVerificationDetails Json?
  
  // Status
  isActive              Boolean  @default(true)
  accountStatus         AccountStatus @default(ACTIVE)
  lastOnline            DateTime?
  
  // Auth
  refreshToken          String?
  otp                   String?
  otpExpiry             DateTime?
  otpAttempts           Int      @default(0)
  otpLockedUntil        DateTime?
  resetPasswordOTP      String?
  resetPasswordOTPExpiry DateTime?
  
  // Profile
  avatar                String?
  bio                   String?
  followingCount        Int      @default(0)
  
  // Soft Delete
  deletedAt             DateTime?
  scheduledDeletionDate DateTime?
  
  createdAt             DateTime @default(now())
  updatedAt             DateTime @updatedAt
  
  // Relations
  shops                 Shop[]
  wallet                Wallet?
  comments              Comment[]
  favorites             Favorite[]
  bookmarks             Bookmark[]
  notifications         Notification[]
  securePaymentsBuyer   SecurePayment[] @relation("BuyerPayments")
  securePaymentsShopOwner SecurePayment[] @relation("ShopOwnerPayments")
  // ... more relations
}

5.2 Shop Model

prisma
model Shop {
  id              String    @id @default(cuid())
  name            String
  slug            String    @unique
  description     String?
  ownerId         String
  countryId       String?
  
  // Branding
  logo            String?
  coverImage      String?
  
  // Contact
  address         Json?     // {country, street, city, state, zipCode}
  contactPhone    String?
  
  // Status
  isActive        Boolean   @default(true)
  isVerified      Boolean   @default(false)
  
  // Config
  categories      String[]
  plan            ShopPlan  @default(FREE)  // FREE | BASIC | PRO | ENTERPRISE
  planLimits      Json?     // {productLimit, mediaPerProduct, canPostVideo, listingDurationDays}
  openingHours    Json?
  
  // Stats
  ratingAverage   Float     @default(0)
  ratingCount     Int       @default(0)
  followersCount  Int       @default(0)
  
  // Search
  score           Float     @default(0)
  embedding       Float[]   // 1536-dim vector
  
  // Currency (from country)
  defaultCurrency Json?     // {code, name, symbol}
  countryDetails  Json?     // {code, region, subregion, currency, languages}
  
  createdAt       DateTime  @default(now())
  updatedAt       DateTime  @updatedAt
  
  // Relations
  owner           User      @relation(fields: [ownerId], references: [id])
  country         Country?  @relation(fields: [countryId], references: [id])
  products        Product[]
  shopStats       ShopStats?
  shopFollows     ShopFollow[]
  shopReviews     ShopReview[]
  chats           Chat[]
  // ... more relations
}

5.3 Product Model

prisma
model Product {
  id                    String        @id @default(cuid())
  title                 String
  slug                  String        @unique
  description           String?
  shopId                String
  
  // Pricing
  priceAmount           Decimal       @db.Decimal(12, 2)
  priceCurrency         String        @default("SZL")
  priceDiscount         Decimal?      @db.Decimal(5, 2)
  
  // Classification
  category              String?
  tags                  String[]
  seoDescription        String?
  
  // Attributes
  color                 String[]
  material              String?
  condition             String?       // new, like_new, good, fair, poor
  
  // AI Processing
  aiProcessingCompleted Boolean       @default(false)
  detectedText          String[]
  hasNudity             Boolean       @default(false)
  hasPrice              Boolean       @default(false)
  mediaQuality          String?
  mediaType             String?
  needsManualApproval   Boolean       @default(false)
  mediaProcessing       Json?         // {status, lastAttempt, retryCount, errorMessage}
  
  // Stock
  stockQuantity         Int           @default(0)
  stockSku              String?
  stockIsInStock        Boolean       @default(true)
  specifications        Json?
  
  // Status
  status                ProductStatus @default(DRAFT)  // DRAFT | PUBLISHED | ARCHIVED | REJECTED
  isActive              Boolean       @default(true)
  isAvailable           Boolean       @default(true)
  
  // Soft Delete
  isDeleted             Boolean       @default(false)
  deletedAt             DateTime?
  deletedById           String?
  
  // Search/Ranking
  score                 Float         @default(0)
  embedding             Float[]       // 1536-dim vector
  
  // Stats (denormalized)
  favoriteCount         Int           @default(0)
  viewCount             Int           @default(0)
  commentCount          Int           @default(0)
  bookmarkCount         Int           @default(0)
  inquiryCount          Int           @default(0)
  
  createdAt             DateTime      @default(now())
  updatedAt             DateTime      @updatedAt
  
  // Relations
  shop                  Shop          @relation(fields: [shopId], references: [id])
  deletedBy             User?         @relation("DeletedByUser", fields: [deletedById], references: [id])
  media                 ProductMedia[]
  productStats          ProductStats?
  favorites             Favorite[]
  bookmarks             Bookmark[]
  comments              Comment[]     @relation("ProductComments")
  // ... more relations
}

5.4 ProductMedia Model

prisma
model ProductMedia {
  id        String   @id @default(cuid())
  productId String
  key       String   // R2 object key
  url       String   // Public URL
  size      Int?     // bytes
  type      String   // image, video
  isPrimary Boolean  @default(false)
  order     Int      @default(0)
  createdAt DateTime @default(now())
  
  product   Product  @relation(fields: [productId], references: [id], onDelete: Cascade)
}

5.5 Wallet Model

prisma
model Wallet {
  id                 String    @id @default(cuid())
  userId             String    @unique
  balance            Decimal   @default(0) @db.Decimal(12, 2)
  unconfirmedBalance Decimal   @default(0) @db.Decimal(12, 2)
  currency           String    @default("SZL")
  isActive           Boolean   @default(true)
  lastTransactionAt  DateTime?
  createdAt          DateTime  @default(now())
  updatedAt          DateTime  @updatedAt
  
  user               User      @relation(fields: [userId], references: [id])
  transactions       WalletTransaction[]
}

5.6 WalletTransaction Model

prisma
model WalletTransaction {
  id            String          @id @default(cuid())
  walletId      String
  userId        String
  type          TransactionType // CREDIT | DEBIT
  amount        Decimal         @db.Decimal(12, 2)
  balanceBefore Decimal         @db.Decimal(12, 2)
  balanceAfter  Decimal         @db.Decimal(12, 2)
  description   String?
  reference     String?
  referenceType WalletRefType?  // SECURE_PAYMENT | DEPOSIT | WITHDRAWAL | TRANSFER | REFUND
  metadata      Json?
  createdAt     DateTime        @default(now())
  
  wallet        Wallet          @relation(fields: [walletId], references: [id])
  user          User            @relation(fields: [userId], references: [id])
}

5.7 SecurePayment Model

prisma
model SecurePayment {
  id                 String              @id @default(cuid())
  paymentId          String              @unique  // SP{timestamp}{random}
  buyerId            String
  shopId             String
  shopOwnerId        String
  shopName           String
  shopPhone          String?
  amount             Decimal             @db.Decimal(12, 2)
  description        String?
  status             SecurePaymentStatus @default(PENDING)
  completionCode     String              @unique  // 6-digit code
  
  // Timestamps
  completedAt        DateTime?
  disputedAt         DateTime?
  cancelledAt        DateTime?
  acceptedAt         DateTime?
  refusedAt          DateTime?
  
  // Reasons
  disputeReason      String?
  cancellationReason String?
  refusalReason      String?
  
  // Context
  productId          String?
  chatId             String?
  
  createdAt          DateTime            @default(now())
  updatedAt          DateTime            @updatedAt
  
  // Relations
  buyer              User                @relation("BuyerPayments", fields: [buyerId], references: [id])
  shop               Shop                @relation(fields: [shopId], references: [id])
  shopOwner          User                @relation("ShopOwnerPayments", fields: [shopOwnerId], references: [id])
  product            Product?            @relation(fields: [productId], references: [id])
  chat               Chat?               @relation(fields: [chatId], references: [id])
  logs               SecurePaymentLog[]
}

5.8 Chat Model

prisma
model Chat {
  id            String   @id @default(cuid())
  shopId        String?
  lastProductId String?  // Last product discussed
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
  
  shop           Shop?             @relation(fields: [shopId], references: [id])
  participants   ChatParticipant[]
  messages       Message[]
  securePayments SecurePayment[]
}

model ChatParticipant {
  id        String   @id @default(cuid())
  chatId    String
  userId    String
  createdAt DateTime @default(now())
  
  chat      Chat     @relation(fields: [chatId], references: [id], onDelete: Cascade)
  user      User     @relation(fields: [userId], references: [id])
  
  @@unique([chatId, userId])
}

model Message {
  id             String     @id @default(cuid())
  chatId         String
  senderId       String
  senderType     SenderType @default(USER)  // USER | SHOP
  content        String?
  productContext Json?      // {productId, title, image, mediaType, price, url}
  replyToId      String?
  read           Boolean    @default(false)
  readAt         DateTime?
  createdAt      DateTime   @default(now())
  
  chat           Chat       @relation(fields: [chatId], references: [id])
  sender         User       @relation(fields: [senderId], references: [id])
  replyTo        Message?   @relation("MessageReplies", fields: [replyToId], references: [id])
  replies        Message[]  @relation("MessageReplies")
  attachments    MessageAttachment[]
}

model MessageAttachment {
  id        String   @id @default(cuid())
  messageId String
  key       String
  url       String
  type      String   // image, video
  size      Int?
  createdAt DateTime @default(now())
  
  message   Message  @relation(fields: [messageId], references: [id], onDelete: Cascade)
}

5.9 Social Models

prisma
model Comment {
  id         String        @id @default(cuid())
  productId  String?
  shopId     String?
  targetType CommentTarget // PRODUCT | SHOP
  userId     String
  comment    String
  parentId   String?       // For replies
  likeCount  Int           @default(0)
  rating     Int?          // 1-5 for reviews
  isDeleted  Boolean       @default(false)
  deletedAt  DateTime?
  editedAt   DateTime?
  createdAt  DateTime      @default(now())
  updatedAt  DateTime      @updatedAt
  
  product    Product?      @relation("ProductComments", fields: [productId], references: [id])
  shop       Shop?         @relation("ShopComments", fields: [shopId], references: [id])
  user       User          @relation(fields: [userId], references: [id])
  parent     Comment?      @relation("CommentReplies", fields: [parentId], references: [id])
  replies    Comment[]     @relation("CommentReplies")
  media      CommentMedia[]
  likes      CommentLike[]
}

model Favorite {
  id        String   @id @default(cuid())
  userId    String
  productId String
  createdAt DateTime @default(now())
  
  user      User     @relation(fields: [userId], references: [id])
  product   Product  @relation(fields: [productId], references: [id])
  
  @@unique([userId, productId])
}

model Bookmark {
  id        String   @id @default(cuid())
  userId    String
  productId String
  createdAt DateTime @default(now())
  
  user      User     @relation(fields: [userId], references: [id])
  product   Product  @relation(fields: [productId], references: [id])
  
  @@unique([userId, productId])
}

model Follow {
  id          String   @id @default(cuid())
  followerId  String
  followingId String
  createdAt   DateTime @default(now())
  
  follower    User     @relation("Follower", fields: [followerId], references: [id])
  following   User     @relation("Following", fields: [followingId], references: [id])
  
  @@unique([followerId, followingId])
}

model ShopFollow {
  id        String   @id @default(cuid())
  userId    String
  shopId    String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  
  user      User     @relation(fields: [userId], references: [id])
  shop      Shop     @relation(fields: [shopId], references: [id])
  
  @@unique([userId, shopId])
}

model ShopReview {
  id        String   @id @default(cuid())
  shopId    String
  userId    String
  rating    Int      // 1-5
  comment   String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  
  shop      Shop     @relation(fields: [shopId], references: [id])
  user      User     @relation(fields: [userId], references: [id])
  
  @@unique([shopId, userId])
}

5.10 Notification Model

prisma
model Notification {
  id        String           @id @default(cuid())
  userId    String
  type      NotificationType // ORDER | COMMENT | REVIEW | SYSTEM | PAYMENT | SHOP | PRODUCT | CHAT | FOLLOW
  title     String
  message   String
  isRead    Boolean          @default(false)
  data      Json?
  createdAt DateTime         @default(now())
  updatedAt DateTime         @updatedAt
  
  user      User             @relation(fields: [userId], references: [id])
}

model NotificationSettings {
  id                   String   @id @default(cuid())
  userId               String   @unique
  pushSettings         Json     @default("{}")
  emailSettings        Json     @default("{}")
  smsSettings          Json     @default("{}")
  marketingPreferences Json     @default("{}")
  createdAt            DateTime @default(now())
  updatedAt            DateTime @updatedAt
  
  user                 User     @relation(fields: [userId], references: [id])
}

5.11 Country Model

prisma
model Country {
  id          String   @id @default(cuid())
  name        String
  code        String   @unique   // ISO 2-letter
  code3       String   @unique   // ISO 3-letter
  callingCode String
  region      String?
  subregion   String?
  currency    Json     // {code, name, symbol}
  languages   String[]
  flag        String?
  isActive    Boolean  @default(true)
  isSupported Boolean  @default(false)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  
  users       User[]
  shops       Shop[]
}

5.12 Analytics Models

prisma
model ProductStats {
  id            String    @id @default(cuid())
  productId     String    @unique
  views         Int       @default(0)
  likes         String[]  // User IDs
  favorites     String[]  // User IDs
  bookmarks     String[]  // User IDs
  inquiries     Int       @default(0)
  isFlagged     Boolean   @default(false)
  flaggedReason String?
  lastViewedAt  DateTime?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  
  product       Product   @relation(fields: [productId], references: [id])
}

model ShopStats {
  id             String    @id @default(cuid())
  shopId         String    @unique
  totalProducts  Int       @default(0)
  totalViews     Int       @default(0)
  totalFavorites Int       @default(0)
  totalSold      Int       @default(0)
  followers      String[]  // User IDs
  lastActive     DateTime?
  createdAt      DateTime  @default(now())
  updatedAt      DateTime  @updatedAt
  
  shop           Shop      @relation(fields: [shopId], references: [id])
}

model View {
  id         String     @id @default(cuid())
  userId     String?
  productId  String?
  shopId     String?
  entityType EntityType // PRODUCT | SHOP
  viewedAt   DateTime   @default(now())
  
  user       User?      @relation(fields: [userId], references: [id])
  product    Product?   @relation("ProductViews", fields: [productId], references: [id])
  shop       Shop?      @relation("ShopViews", fields: [shopId], references: [id])
}

model Share {
  id           String        @id @default(cuid())
  userId       String
  productId    String?
  shopId       String?
  entityType   EntityType
  platform     SharePlatform // WHATSAPP | FACEBOOK | INSTAGRAM | TELEGRAM | SMS | LINK
  sharedTo     String?
  caption      String?
  trackingCode String        @unique
  clicks       Int           @default(0)
  conversions  Int           @default(0)
  createdAt    DateTime      @default(now())
  updatedAt    DateTime      @updatedAt
  
  user         User          @relation(fields: [userId], references: [id])
  product      Product?      @relation("ProductShares", fields: [productId], references: [id])
  shop         Shop?         @relation("ShopShares", fields: [shopId], references: [id])
}

model UserBehavior {
  id          String         @id @default(cuid())
  userId      String
  actionType  BehaviorAction // VIEW | SEARCH | LIKE | SHARE | ADD_TO_CART | PURCHASE
  entityType  EntityType
  entityId    String
  entityData  Json?
  searchQuery String?
  sessionId   String?
  metadata    Json?
  createdAt   DateTime       @default(now())
  
  user        User           @relation(fields: [userId], references: [id])
}

5.13 Report Model

prisma
model Report {
  id           String         @id @default(cuid())
  reporterId   String
  entityId     String
  entityType   ReportEntity   // PRODUCT | SHOP | USER | COMMENT
  reportType   ReportType     // SPAM | INAPPROPRIATE | COUNTERFEIT | SCAM | HARASSMENT | COPYRIGHT | OTHER
  reason       String?
  description  String?
  evidence     String[]       // URLs to evidence
  status       ReportStatus   @default(PENDING)  // PENDING | UNDER_REVIEW | RESOLVED | DISMISSED
  priority     ReportPriority @default(MEDIUM)   // LOW | MEDIUM | HIGH | URGENT
  reviewerId   String?
  reviewNotes  String?
  actionTaken  String?
  isActive     Boolean        @default(true)
  resolvedAt   DateTime?
  createdAt    DateTime       @default(now())
  updatedAt    DateTime       @updatedAt
  
  reporter     User           @relation("Reporter", fields: [reporterId], references: [id])
  reviewer     User?          @relation("Reviewer", fields: [reviewerId], references: [id])
  
  @@unique([reporterId, entityId, entityType])
}

6. API Reference

6.1 Authentication (/api/v1/auth)

MethodEndpointDescriptionAuth
POST/registerInitiate registration (sends OTP)-
POST/verify-otpVerify OTP and create user-
POST/loginLogin with phone/password-
POST/refresh-tokenRefresh access token-
POST/logoutLogout (invalidate refresh token)
POST/resend-otpResend registration OTP-
POST/forgot-passwordInitiate password reset-
POST/reset-passwordReset password with OTP-

Register Request:

json
{
  "phoneNumber": "+26876123456",
  "password": "securePassword123",
  "country": "country_cuid"
}

Login Response:

json
{
  "accessToken": "eyJhbGc...",
  "refreshToken": "eyJhbGc...",
  "user": {
    "id": "user_cuid",
    "username": "+26876123456",
    "phoneNumber": "+26876123456",
    "role": "USER",
    "isPhoneVerified": true
  }
}

6.2 Users (/api/v1/users)

MethodEndpointDescriptionAuth
GET/meGet current user profile
PUT/meUpdate current user profile
GET/:idGet user public profile-
PUT/passwordChange password
PUT/avatarUpload avatar
GET/me/shopsGet user's shops
GET/me/statsGet user statistics
POST/deactivateDeactivate account
DELETE/deleteRequest account deletion

6.3 Shops (/api/v1/shops)

MethodEndpointDescriptionAuth
GET/List all shopsOptional
POST/Create shop
GET/:idGet shop details-
GET/slug/:slugGet shop by slug-
PUT/:idUpdate shop✓ (owner)
DELETE/:idDelete shop✓ (owner/admin)
GET/:id/statsGet shop statistics-
GET/:id/productsGet shop products-
GET/:id/reviewsGet shop reviews-
POST/:id/reviewsAdd shop review
POST/:id/followFollow shop
DELETE/:id/followUnfollow shop
GET/check-slug/:slugCheck slug availability

Create Shop Response:

json
{
  "shop": {
    "id": "shop_cuid",
    "name": "John's Shop",
    "slug": "johns-shop-awesome-123",
    "description": "Welcome to my Vavu shop in Eswatini...",
    "plan": "FREE",
    "planLimits": {
      "productLimit": 10,
      "mediaPerProduct": 5,
      "canPostVideo": false,
      "listingDurationDays": 30
    },
    "defaultCurrency": {
      "code": "SZL",
      "name": "Swazi Lilangeni",
      "symbol": "E"
    }
  },
  "message": "Your shop has been created..."
}

6.4 Products (/api/v1/products)

MethodEndpointDescriptionAuth
GET/List products (with filters)Optional
POST/Create product
GET/:idGet product by ID-
GET/slug/:slugGet product by slug-
PUT/:idUpdate product✓ (owner)
DELETE/:idDelete product (soft)✓ (owner)
POST/:id/restoreRestore deleted product✓ (owner)
GET/:id/commentsGet product comments-
POST/:id/commentsAdd comment
POST/:id/favoriteFavorite product
DELETE/:id/favoriteUnfavorite product
POST/:id/bookmarkBookmark product
DELETE/:id/bookmarkRemove bookmark
POST/uploadUpload product media

Create Product Request:

json
{
  "shopId": "shop_cuid",
  "title": "iPhone 14 Pro",
  "description": "Excellent condition...",
  "price": 15000,
  "category": "Electronics",
  "condition": "like_new",
  "tags": ["iphone", "apple", "phone"],
  "stock": 1
}

Product Response:

json
{
  "product": {
    "id": "product_cuid",
    "title": "iPhone 14 Pro",
    "slug": "iphone-14-pro-123456-abc1-2345",
    "description": "Excellent condition...",
    "priceAmount": "15000.00",
    "priceCurrency": "SZL",
    "status": "DRAFT",
    "isActive": false,
    "aiProcessingCompleted": false,
    "media": [],
    "shop": {
      "id": "shop_cuid",
      "name": "John's Shop",
      "owner": {...}
    },
    "stats": {
      "views": 0,
      "favoriteCount": 0,
      "commentCount": 0
    }
  }
}

6.5 Feed (/api/v1/feed)

MethodEndpointDescriptionAuth
GET/Get personalized feedOptional
GET/trendingGet trending products-
GET/categoriesGet category breakdown-
MethodEndpointDescriptionAuth
GET/productsSearch productsOptional
GET/shopsSearch shops-
GET/suggestionsGet search suggestions-

Search Request:

GET /api/v1/search/products?q=iphone&category=Electronics&minPrice=5000&maxPrice=20000&condition=like_new&page=1&limit=20

Search Response:

json
{
  "results": [...],
  "totalCount": 42,
  "query": "iphone",
  "searchTime": 127,
  "page": 1,
  "limit": 20,
  "hasMore": true
}

6.7 Chat (/api/v1/chats)

MethodEndpointDescriptionAuth
GET/Get user's chats
POST/messagesSend message
GET/:chatIdGet chat details
GET/:chatId/messagesGet chat messages
POST/:chatId/readMark messages as read
GET/unread-countsGet all unread counts
GET/checkCheck if chat exists

Send Message Request:

json
{
  "shopId": "shop_cuid",
  "content": "Hi, is this item still available?",
  "productId": "product_cuid",
  "attachments": [
    {
      "key": "uploads/abc123.jpg",
      "url": "https://cdn.../uploads/abc123.jpg",
      "type": "image"
    }
  ]
}

Message Response:

json
{
  "chatId": "chat_cuid",
  "shopId": "shop_cuid",
  "message": {
    "id": "msg_cuid",
    "senderId": "user_cuid",
    "senderType": "User",
    "content": "Hi, is this item still available?",
    "productContext": {
      "productId": "product_cuid",
      "title": "iPhone 14 Pro",
      "price": "SZL 15000",
      "image": "https://cdn.../image.jpg"
    },
    "sender": {
      "id": "user_cuid",
      "name": "John",
      "type": "user"
    },
    "timestamp": {
      "raw": "2026-03-19T12:00:00Z",
      "formatted": "3/19/2026, 12:00:00 PM",
      "relative": "Just now"
    }
  },
  "isNewChat": true,
  "suggestedActions": [
    "Is this still available?",
    "What's your best price?",
    "Can I see more photos?"
  ]
}

6.8 Secure Payments (/api/v1/secure-payments)

MethodEndpointDescriptionAuth
POST/Create secure payment
GET/Get user's payments
GET/:paymentIdGet payment details
POST/:paymentId/acceptAccept payment (seller)
POST/:paymentId/refuseRefuse payment (seller)
POST/completeComplete with code
POST/:paymentId/disputeOpen dispute
POST/:paymentId/cancelCancel payment
GET/lookup/:codeLookup by completion code-

Create Payment Request:

json
{
  "shopId": "shop_cuid",
  "amount": 15000,
  "description": "iPhone 14 Pro purchase",
  "productId": "product_cuid",
  "chatId": "chat_cuid"
}

Payment Response:

json
{
  "id": "secpay_cuid",
  "paymentId": "SP1710849600123",
  "amount": "15000.00",
  "status": "PENDING",
  "completionCode": "847291",
  "shopName": "John's Shop",
  "buyer": {...},
  "shop": {...}
}

6.9 Wallet (/api/v1/wallet)

MethodEndpointDescriptionAuth
GET/Get wallet balance
GET/transactionsGet transaction history

Wallet Response:

json
{
  "balance": 25000.00,
  "unconfirmedBalance": 5000.00,
  "totalBalance": 30000.00,
  "currency": "SZL"
}

6.10 Notifications (/api/v1/notifications)

MethodEndpointDescriptionAuth
GET/Get notifications
PUT/:id/readMark as read
PUT/read-allMark all as read
GET/settingsGet notification settings
PUT/settingsUpdate settings
GET/unread-countGet unread count

6.11 Admin (/api/v1/admin)

MethodEndpointDescriptionAuth
GET/usersList all usersAdmin
GET/users/:idGet user detailsAdmin
PUT/users/:id/statusUpdate user statusAdmin
GET/shopsList all shopsAdmin
PUT/shops/:id/verifyVerify shopAdmin
PUT/shops/:id/statusToggle shop statusAdmin
GET/productsList all productsAdmin
PUT/products/:id/statusUpdate product statusAdmin
GET/reportsList reportsAdmin
PUT/reports/:idUpdate reportAdmin
GET/paymentsPayment analyticsAdmin
GET/pending-account-requestsPending deactivation/deletionAdmin

6.12 Webhooks (/api/v1/webhooks)

MethodEndpointDescriptionAuth
POST/product-mediaAI media processing callbackWebhook Secret

Webhook Payload:

json
{
  "productId": "product_cuid",
  "processed_data": {
    "title": "AI-generated title",
    "description": "AI-generated description",
    "seoDescription": "SEO description",
    "category": "Electronics",
    "tags": ["phone", "apple"],
    "color": ["black"],
    "condition": "like_new",
    "price": 15000,
    "hasNudity": false,
    "mediaQuality": "high"
  }
}

7. Service Architecture

7.1 Service Layer Overview

┌─────────────────────────────────────────────────────────────────┐
│                         API Routes                               │
│  /auth  /users  /shops  /products  /chats  /payments  /admin    │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                       Controllers                                │
│  AuthController  ShopController  ProductController  etc.        │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                        Services                                  │
│                                                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │ AuthService  │  │ ShopService  │  │ProductService│          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
│                                                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │ UserService  │  │ ChatService  │  │SearchService │          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
│                                                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │WalletService │  │SecurePayment │  │Notification  │          │
│  │              │  │   Service    │  │   Service    │          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
│                                                                 │
│  ┌──────────────┐  ┌──────────────┐                            │
│  │EmbeddingServ │  │  R2Service   │                            │
│  └──────────────┘  └──────────────┘                            │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                    Data Layer (Prisma)                          │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    PostgreSQL                            │   │
│  │  - User, Shop, Product tables                           │   │
│  │  - pgvector for embeddings                              │   │
│  │  - Full-text search                                     │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                  Cloudflare R2                           │   │
│  │  - Product media                                        │   │
│  │  - User avatars                                         │   │
│  │  - Message attachments                                  │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

7.2 Service Interactions

Auth Flow

User → AuthController → AuthService → Prisma (User)
                                    → WalletService (create wallet)
                                    → SMS Service (send OTP)

Product Creation Flow

User → ProductController → ProductService → Prisma (Product, ProductMedia)
                                          → R2Service (upload media)
                                          → Webhook (trigger AI processing)
                                          
AI Processor → Webhook Endpoint → ProductService → Prisma (update product)
                                                 → EmbeddingService (generate embedding)

Secure Payment Flow

Buyer → SecurePaymentController → SecurePaymentService

                                       ├─→ WalletService (debit buyer)
                                       ├─→ Prisma (create SecurePayment)
                                       └─→ NotificationService (notify seller)

Seller Accept:
SecurePaymentService → WalletService (add to unconfirmed)
                    → NotificationService (notify buyer)

Complete:
SecurePaymentService → WalletService (confirm balance)
                    → Prisma (update status)

Search Flow

User → SearchController → SearchService

                              ├─→ EmbeddingService (embed query)
                              ├─→ pgvector (similarity search)
                              ├─→ Prisma (fetch product details)
                              └─→ Fallback: keyword search

8. Authentication & Authorization

8.1 Authentication

  • Method: JWT Bearer tokens
  • Access Token: 15 minutes expiry
  • Refresh Token: 7 days expiry
  • Storage: RefreshToken stored in User model

8.2 Token Flow

1. Login → Access Token + Refresh Token
2. API Request → Authorization: Bearer <access_token>
3. Token Expired → POST /refresh-token with refresh_token
4. New tokens issued

8.3 Middleware Stack

typescript
// auth.middleware.ts
authenticate     // Require valid JWT
optionalAuthenticate  // JWT optional, attach user if valid
authorize(...roles)   // Require specific roles

// admin.middleware.ts
requireAdmin     // Require admin role

// shop.middleware.ts
requireShopOwner // Require shop ownership

// webhook.middleware.ts
validateWebhookSecret // Validate webhook signature

8.4 Role-Based Access

RoleCapabilities
USERBrowse, buy, message, favorite, comment
VENDORAll USER + create shop, list products
ADMINAll VENDOR + moderate, verify, manage users

9. Payment/Billing Integration

9.1 Secure Payment System (Built)

The platform uses an escrow-style secure payment system:

  1. Initiation: Buyer creates payment → Funds debited immediately
  2. Acceptance: Seller accepts → Funds in unconfirmed balance
  3. Completion: Seller enters 6-digit code → Funds confirmed
  4. Dispute: Either party can dispute before completion

9.2 Wallet System (Built)

  • Each user has one wallet in their country's currency
  • Balances: confirmed (available) + unconfirmed (pending)
  • Full transaction history with audit trail

9.3 External Payment Integration (Not Built)

Missing: Integration with payment gateways for:

  • Mobile Money (MTN, Airtel, Vodacom M-Pesa)
  • Card payments
  • Bank transfers
  • Wallet top-up/withdrawal

10. Search & Discovery

10.1 Vector Search (Built)

  • Embedding Model: Gemini text-embedding-004 (1536 dimensions)
  • Storage: pgvector extension in PostgreSQL
  • Index: IVF with 100 lists

10.2 Search Pipeline

Query → Query Embedding → pgvector Similarity Search


                          Top 20 results by cosine similarity


                          Keyword fallback (if < 3 results)


                          Merge & return results

10.3 Query Expansion (Built)

Using Gemini to expand queries:

  • Extract keywords
  • Identify categories
  • Detect price ranges
  • Understand intent

10.4 Feed Algorithms (Built)

  • Authenticated: Personalized (TODO: implement ML personalization)
  • Guest: Recent + popular
  • Anonymous: Trending by views/favorites

11. Notifications

11.1 Notification Types

TypeTrigger
CHATNew message received
FOLLOWSomeone followed you/your shop
COMMENTNew comment on your product/shop
REVIEWNew shop review
PAYMENTPayment status changes
PRODUCTProduct status changes
SHOPShop verification, status
ORDEROrder status changes
SYSTEMPlatform announcements

11.2 De-duplication

  • 5-minute window per source entity
  • Prevents notification spam from rapid actions

11.3 Settings

Per-channel settings (push, email, SMS):

  • newMessages
  • follows
  • comments
  • likes
  • orderUpdates
  • promotions

11.4 Push Notifications (Partially Built)

  • Notification records created in DB
  • Missing: Actual push delivery (FCM/APNs integration)

12. Admin/Moderation

12.1 User Management

  • View all users with filters
  • Approve/reject deactivation requests
  • Approve/reject deletion requests
  • Suspend/reactivate accounts

12.2 Shop Management

  • Toggle shop activation
  • Verify/unverify shops
  • View shop statistics

12.3 Product Moderation

  • Flag products for review
  • Approve/reject flagged products
  • Force publish/archive

12.4 Reports System

  • View reports by status/priority
  • Assign reviewers
  • Take action and document

12.5 Analytics (Partial)

  • Payment analytics
  • Missing: Comprehensive dashboard

13. What's Missing / Roadmap

13.1 Critical Missing Features

Payment Gateway Integration

  • [ ] Mobile Money integration (MTN, M-Pesa, etc.)
  • [ ] Card payment processing
  • [ ] Wallet top-up from external sources
  • [ ] Withdrawal to bank/mobile money

Push Notifications

  • [ ] Firebase Cloud Messaging (FCM) integration
  • [ ] Apple Push Notification Service (APNs)
  • [ ] Real-time message delivery

Real-time Features

  • [ ] WebSocket support for chat
  • [ ] Live typing indicators
  • [ ] Online presence indicators

13.2 Feature Enhancements

Discovery & Recommendations

  • [ ] ML-based personalization
  • [ ] "Similar products" recommendations
  • [ ] "Buyers also viewed" suggestions
  • [ ] Location-based search/sorting

Seller Tools

  • [ ] Sales analytics dashboard
  • [ ] Inventory management
  • [ ] Bulk product upload
  • [ ] Product templates

Buyer Experience

  • [ ] Shopping cart
  • [ ] Order tracking
  • [ ] Delivery integration
  • [ ] Price alerts

Trust & Safety

  • [ ] Automated fraud detection
  • [ ] Seller verification badges
  • [ ] Transaction protection insurance
  • [ ] Buyer protection policy

13.3 Technical Debt

  • [ ] API rate limiting implementation
  • [ ] Caching layer (Redis)
  • [ ] Background job processing
  • [ ] Comprehensive logging/monitoring
  • [ ] API versioning strategy
  • [ ] Automated testing suite

13.4 Platform Expansion

  • [ ] Multi-language support (Swati, Zulu, etc.)
  • [ ] More country/currency support
  • [ ] Seller onboarding flow
  • [ ] In-app tutorials

14. Configuration

14.1 Environment Variables

bash
# Server
PORT=3000
NODE_ENV=production

# Database
DATABASE_URL=postgresql://...

# JWT
JWT_SECRET=xxx
JWT_REFRESH_SECRET=xxx

# Twilio (SMS)
TWILIO_ACCOUNT_SID=xxx
TWILIO_AUTH_TOKEN=xxx
TWILIO_FROM_NUMBER=+1234567890

# Cloudflare R2
R2_ACCOUNT_ID=xxx
R2_ACCESS_KEY_ID=xxx
R2_SECRET_ACCESS_KEY=xxx
R2_BUCKET_NAME=vavu-media

# AI
GEMINI_API_KEY=xxx

# Webhooks
WEBHOOK_PRODUCT_MEDIA_ENABLED=true
WEBHOOK_PRODUCT_MEDIA_SECRET=xxx

# Notifications
NOTIFICATION_SERVICE_URL=xxx
NOTIFICATION_SERVICE_API_KEY=xxx

14.2 Platform Fees

javascript
fees: {
  platformFee: 0.10,    // 10% on transactions
  withdrawalFee: 0.02   // 2% on withdrawals
}

14.3 Upload Limits

javascript
upload: {
  maxFileSize: 5 * 1024 * 1024,  // 5MB
  allowedMimeTypes: [
    'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/avif',
    'video/mp4', 'video/quicktime', 'video/webm'
  ],
  maxVideoDurationSeconds: 30
}

15. CEO Dashboard Integration

The CEO Dashboard (ceo-dashboard) queries YeboShops metrics for platform-wide analytics:

javascript
// From ceo-dashboard/backend/server.js
const YEBOSHOPS_API = process.env.YEBOSHOPS_API_URL;

// Metrics fetched:
- Total users
- Total shops
- Total products
- Active listings
- Total transactions
- Transaction volume
- Daily/monthly growth rates

Appendix A: Enum Reference

typescript
// User roles
enum UserRole { USER, ADMIN, VENDOR }

// Account states
enum AccountStatus { ACTIVE, DEACTIVATION_REQUESTED, DELETION_REQUESTED, SUSPENDED }

// Verification
enum VerificationStatus { NONE, PENDING, APPROVED, REJECTED }

// Shop plans
enum ShopPlan { FREE, BASIC, PRO, ENTERPRISE }

// Product lifecycle
enum ProductStatus { DRAFT, PUBLISHED, ARCHIVED, REJECTED }

// Secure payment states
enum SecurePaymentStatus { PENDING, ACCEPTED, REFUSED, COMPLETED, DISPUTED, CANCELLED, EXPIRED }

// Wallet transactions
enum TransactionType { CREDIT, DEBIT }
enum WalletRefType { SECURE_PAYMENT, DEPOSIT, WITHDRAWAL, TRANSFER, REFUND }

// Notifications
enum NotificationType { ORDER, COMMENT, REVIEW, SYSTEM, PAYMENT, SHOP, PRODUCT, CHAT, FOLLOW }

// Reports
enum ReportEntity { PRODUCT, SHOP, USER, COMMENT }
enum ReportType { SPAM, INAPPROPRIATE, COUNTERFEIT, SCAM, HARASSMENT, COPYRIGHT, OTHER }
enum ReportStatus { PENDING, UNDER_REVIEW, RESOLVED, DISMISSED }
enum ReportPriority { LOW, MEDIUM, HIGH, URGENT }

Document generated from codebase analysis of vavu-api and vavu-app repositories.

One chat. Everything done.