惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
腾讯CDC
Recent Announcements
Recent Announcements
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Last Week in AI
Last Week in AI
博客园_首页
D
DataBreaches.Net
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
V
Visual Studio Blog
月光博客
月光博客
Jina AI
Jina AI
Stack Overflow Blog
Stack Overflow Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
Vercel News
Vercel News
WordPress大学
WordPress大学
J
Java Code Geeks
博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Architecting Digital Trust: A Relational Deep Dive into t...
Tiani pekins · 2026-05-03 · via DEV Community

Subtitle: How to model a secure, escrow-based marketplace for emerging economies using Prisma and PostgreSQL.
Schema.

In my previous article on Medium, I discussed the sociotechnical challenge of Information Poverty in the African gig economy. But as engineers, we know that solving social problems requires more than vision it requires a robust, type-safe, and scalable data architecture.
For LocalHands, I chose Prisma ORM with PostgreSQL. The goal was to build a "Technical Source of Truth" that could handle the complexity of service listings, competitive bidding (proposals), and secure escrow payments.
Below, I break down the core relational logic of the LocalHands schema

1. The Core Actor Model: User vs. Profile
In a marketplace, users often play multiple roles. However, security is paramount. I separated the User (authentication and roles) from the Profile (sensitive KYC data)

model User {
  id               Int              @id @default(autoincrement())
  role             UserRole         @default(CLIENT)
  phoneNumber      String           @unique
  email            String           @unique
  passwordHash     String
  profile          Profile?
  // ... relations to orders, contracts, and services
}

model Profile {
  id                 Int              @id @default(autoincrement())
  userId             Int              @unique
  user               User             @relation(fields: [userId], references: [id])
  verificationStatus VerificationStatus? @default(PENDING)
  nationalIdUrl      String?          // URL to encrypted storage
  mobileMoneyNumber  String?
}

Enter fullscreen mode Exit fullscreen mode

Engineering Decision: By using a 1:1 relation for the Profile, we keep the User model lean for frequent authentication checks while isolating heavier metadata and verification documents.

2. Modeling the Bidding Lifecycle (Service -> Order -> Proposal)
Unlike standard e-commerce, a service marketplace is dynamic. A client doesn't just "buy"; they post a ServiceOrder, and providers reply with Proposals.

model ServiceOrder {
  id            Int              @id @default(autoincrement())
  serviceId     Int
  clientId      Int
  budget        Float?
  status        ServiceOrderStatus @default(PENDING)
  contract      Contract?          // Only exists once a proposal is accepted
}

model Proposal {
  id           Int          @id @default(autoincrement())
  providerId   Int
  serviceId    Int
  bidAmount    Float
  status       ProposalStatus @default(PENDING)
  contractId   Int?
}

Enter fullscreen mode Exit fullscreen mode

Relational Integrity: Notice the optional contractId in the Proposal. This allows multiple providers to bid on one job, but ensures that only the accepted proposal transitions into a formal, binding Contract.

3. The Trust Engine: Contract and Escrow
This is where the code solves the Trust Gap. The Contract model acts as the central node for the entire transaction lifecycle.

model Contract {
  id            Int              @id @default(autoincrement())
  serviceOrderId Int              @unique
  escrowAmount  Float
  status        ContractStatus   @default(ACTIVE)
  payments      Payment[]
  reviews       Review[]
}

Enter fullscreen mode Exit fullscreen mode

By enforcing a @unique constraint on the serviceOrderId, we prevent the "Double-Payment" bug. The contract is the only entity authorized to trigger a Payment release.

4. Localized FinTech Integration
To meet the reality of the Cameroonian market, the schema explicitly supports MTN Mobile Money and localized currency settings.

model Payment {
  id            Int             @id @default(autoincrement())
  contractId    Int
  amount        Float
  paymentMethod PaymentMethod   @default(MTN_MOBILE_MONEY)
  status        PaymentStatus   @default(PENDING)
}

model SystemSettings {
  currency           String   @default("XAF")
  currency_symbol    String   @default("FCFA")
  payment_gateway     String   @default("fapshi")
}

Enter fullscreen mode Exit fullscreen mode

Why this matters: Hardcoding these enums and settings at the database level ensures that the business logic remains consistent and compliant with regional financial regulations.

Conclusion
This schema is designed to do more than just store data; it is designed to enforce trust. By leveraging Prisma's powerful relational features, I have built a foundation where Information Poverty is replaced by a transparent, verifiable history of service.
What’s Next?
Currently, I am stabilizing the Escrow Algorithm and the Fapshi payment integration logic. In my next post, I will dive deep into the system UI then later "Fund-Lock-Release" cycle and real-time payment webhooks.