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:
- Trust Deficit: Buyers are skeptical of online sellers; fraud is common on existing platforms
- Fragmented Markets: Sellers operate across WhatsApp, Facebook, and Instagram with no unified presence
- Payment Uncertainty: No escrow or secure payment options for peer-to-peer transactions
- Discovery Issues: Hard for buyers to find quality local products and verified sellers
- 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)
| Component | Technology |
|---|---|
| Runtime | Node.js + TypeScript |
| Framework | Express.js |
| Database | PostgreSQL with pgvector extension |
| ORM | Prisma |
| Auth | JWT (access + refresh tokens) |
| File Storage | Cloudflare R2 |
| AI/ML | Gemini API (embeddings, search, content generation) |
| SMS | Twilio |
Frontend (vavu-app)
| Component | Technology |
|---|---|
| Framework | React 18 + TypeScript |
| Build Tool | Vite |
| Styling | Tailwind CSS |
| State | React Context |
| Icons | Lucide 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:
- Buyer initiates payment → funds debited immediately
- Seller accepts → funds move to unconfirmed balance
- Transaction occurs in person
- 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 occur4.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, verification4.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 badges5. Data Models
5.1 User Model
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
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
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
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
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
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
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
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
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
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
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
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
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)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /register | Initiate registration (sends OTP) | - |
| POST | /verify-otp | Verify OTP and create user | - |
| POST | /login | Login with phone/password | - |
| POST | /refresh-token | Refresh access token | - |
| POST | /logout | Logout (invalidate refresh token) | ✓ |
| POST | /resend-otp | Resend registration OTP | - |
| POST | /forgot-password | Initiate password reset | - |
| POST | /reset-password | Reset password with OTP | - |
Register Request:
{
"phoneNumber": "+26876123456",
"password": "securePassword123",
"country": "country_cuid"
}Login Response:
{
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc...",
"user": {
"id": "user_cuid",
"username": "+26876123456",
"phoneNumber": "+26876123456",
"role": "USER",
"isPhoneVerified": true
}
}6.2 Users (/api/v1/users)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /me | Get current user profile | ✓ |
| PUT | /me | Update current user profile | ✓ |
| GET | /:id | Get user public profile | - |
| PUT | /password | Change password | ✓ |
| PUT | /avatar | Upload avatar | ✓ |
| GET | /me/shops | Get user's shops | ✓ |
| GET | /me/stats | Get user statistics | ✓ |
| POST | /deactivate | Deactivate account | ✓ |
| DELETE | /delete | Request account deletion | ✓ |
6.3 Shops (/api/v1/shops)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / | List all shops | Optional |
| POST | / | Create shop | ✓ |
| GET | /:id | Get shop details | - |
| GET | /slug/:slug | Get shop by slug | - |
| PUT | /:id | Update shop | ✓ (owner) |
| DELETE | /:id | Delete shop | ✓ (owner/admin) |
| GET | /:id/stats | Get shop statistics | - |
| GET | /:id/products | Get shop products | - |
| GET | /:id/reviews | Get shop reviews | - |
| POST | /:id/reviews | Add shop review | ✓ |
| POST | /:id/follow | Follow shop | ✓ |
| DELETE | /:id/follow | Unfollow shop | ✓ |
| GET | /check-slug/:slug | Check slug availability | ✓ |
Create Shop Response:
{
"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)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / | List products (with filters) | Optional |
| POST | / | Create product | ✓ |
| GET | /:id | Get product by ID | - |
| GET | /slug/:slug | Get product by slug | - |
| PUT | /:id | Update product | ✓ (owner) |
| DELETE | /:id | Delete product (soft) | ✓ (owner) |
| POST | /:id/restore | Restore deleted product | ✓ (owner) |
| GET | /:id/comments | Get product comments | - |
| POST | /:id/comments | Add comment | ✓ |
| POST | /:id/favorite | Favorite product | ✓ |
| DELETE | /:id/favorite | Unfavorite product | ✓ |
| POST | /:id/bookmark | Bookmark product | ✓ |
| DELETE | /:id/bookmark | Remove bookmark | ✓ |
| POST | /upload | Upload product media | ✓ |
Create Product Request:
{
"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:
{
"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)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / | Get personalized feed | Optional |
| GET | /trending | Get trending products | - |
| GET | /categories | Get category breakdown | - |
6.6 Search (/api/v1/search)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /products | Search products | Optional |
| GET | /shops | Search shops | - |
| GET | /suggestions | Get search suggestions | - |
Search Request:
GET /api/v1/search/products?q=iphone&category=Electronics&minPrice=5000&maxPrice=20000&condition=like_new&page=1&limit=20Search Response:
{
"results": [...],
"totalCount": 42,
"query": "iphone",
"searchTime": 127,
"page": 1,
"limit": 20,
"hasMore": true
}6.7 Chat (/api/v1/chats)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / | Get user's chats | ✓ |
| POST | /messages | Send message | ✓ |
| GET | /:chatId | Get chat details | ✓ |
| GET | /:chatId/messages | Get chat messages | ✓ |
| POST | /:chatId/read | Mark messages as read | ✓ |
| GET | /unread-counts | Get all unread counts | ✓ |
| GET | /check | Check if chat exists | ✓ |
Send Message Request:
{
"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:
{
"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)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | / | Create secure payment | ✓ |
| GET | / | Get user's payments | ✓ |
| GET | /:paymentId | Get payment details | ✓ |
| POST | /:paymentId/accept | Accept payment (seller) | ✓ |
| POST | /:paymentId/refuse | Refuse payment (seller) | ✓ |
| POST | /complete | Complete with code | ✓ |
| POST | /:paymentId/dispute | Open dispute | ✓ |
| POST | /:paymentId/cancel | Cancel payment | ✓ |
| GET | /lookup/:code | Lookup by completion code | - |
Create Payment Request:
{
"shopId": "shop_cuid",
"amount": 15000,
"description": "iPhone 14 Pro purchase",
"productId": "product_cuid",
"chatId": "chat_cuid"
}Payment Response:
{
"id": "secpay_cuid",
"paymentId": "SP1710849600123",
"amount": "15000.00",
"status": "PENDING",
"completionCode": "847291",
"shopName": "John's Shop",
"buyer": {...},
"shop": {...}
}6.9 Wallet (/api/v1/wallet)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / | Get wallet balance | ✓ |
| GET | /transactions | Get transaction history | ✓ |
Wallet Response:
{
"balance": 25000.00,
"unconfirmedBalance": 5000.00,
"totalBalance": 30000.00,
"currency": "SZL"
}6.10 Notifications (/api/v1/notifications)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / | Get notifications | ✓ |
| PUT | /:id/read | Mark as read | ✓ |
| PUT | /read-all | Mark all as read | ✓ |
| GET | /settings | Get notification settings | ✓ |
| PUT | /settings | Update settings | ✓ |
| GET | /unread-count | Get unread count | ✓ |
6.11 Admin (/api/v1/admin)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /users | List all users | Admin |
| GET | /users/:id | Get user details | Admin |
| PUT | /users/:id/status | Update user status | Admin |
| GET | /shops | List all shops | Admin |
| PUT | /shops/:id/verify | Verify shop | Admin |
| PUT | /shops/:id/status | Toggle shop status | Admin |
| GET | /products | List all products | Admin |
| PUT | /products/:id/status | Update product status | Admin |
| GET | /reports | List reports | Admin |
| PUT | /reports/:id | Update report | Admin |
| GET | /payments | Payment analytics | Admin |
| GET | /pending-account-requests | Pending deactivation/deletion | Admin |
6.12 Webhooks (/api/v1/webhooks)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /product-media | AI media processing callback | Webhook Secret |
Webhook Payload:
{
"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 search8. 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 issued8.3 Middleware Stack
// 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 signature8.4 Role-Based Access
| Role | Capabilities |
|---|---|
| USER | Browse, buy, message, favorite, comment |
| VENDOR | All USER + create shop, list products |
| ADMIN | All VENDOR + moderate, verify, manage users |
9. Payment/Billing Integration
9.1 Secure Payment System (Built)
The platform uses an escrow-style secure payment system:
- Initiation: Buyer creates payment → Funds debited immediately
- Acceptance: Seller accepts → Funds in unconfirmed balance
- Completion: Seller enters 6-digit code → Funds confirmed
- 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 results10.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
| Type | Trigger |
|---|---|
| CHAT | New message received |
| FOLLOW | Someone followed you/your shop |
| COMMENT | New comment on your product/shop |
| REVIEW | New shop review |
| PAYMENT | Payment status changes |
| PRODUCT | Product status changes |
| SHOP | Shop verification, status |
| ORDER | Order status changes |
| SYSTEM | Platform 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
# 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=xxx14.2 Platform Fees
fees: {
platformFee: 0.10, // 10% on transactions
withdrawalFee: 0.02 // 2% on withdrawals
}14.3 Upload Limits
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:
// 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 ratesAppendix A: Enum Reference
// 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.