Build EHSAN Closed Donation Loop POC — full bilingual Arabic/English app
- Backend (api-server): Complete in-memory mock DB with 11 seed cases, 5 eligible beneficiaries, 3 donors, and WhatsApp log. All 14 API routes implemented across requests, donors, stats, and whatsapp-log. OpenClaw integration with OPENCLAW_SIMULATE toggle. UUID-based IDs. Full status machine (new → closed, 10 steps). - Frontend (ehsan-poc): 8 pages fully implemented using all generated API hooks: Home (stats counters, 10-step workflow diagram), Request (form with eligibility result), Opportunities (card grid with progress bars), Donate (case summary + donor form), Admin (full data table with contextual action buttons), Track (10-step visual timeline in green), ThankYou (message form), WhatsApp Log (WhatsApp bubble preview + OpenClaw send button). - Bilingual LanguageContext (AR/EN) with RTL/LTR toggle, localStorage persistence. EHSAN green palette (HSL 143), Tajawal font, fully responsive. TypeScript clean — zero errors.
This commit is contained in:
+535
-2
@@ -3,20 +3,28 @@ info:
|
||||
# Do not change the title, if the title changes, the import paths will be broken
|
||||
title: Api
|
||||
version: 0.1.0
|
||||
description: API specification
|
||||
description: EHSAN Closed Donation Loop API
|
||||
servers:
|
||||
- url: /api
|
||||
description: Base API path
|
||||
tags:
|
||||
- name: health
|
||||
description: Health operations
|
||||
- name: requests
|
||||
description: Donation requests (beneficiary cases)
|
||||
- name: donors
|
||||
description: Donor records
|
||||
- name: stats
|
||||
description: Dashboard statistics
|
||||
- name: whatsapp
|
||||
description: WhatsApp message log
|
||||
|
||||
paths:
|
||||
/healthz:
|
||||
get:
|
||||
operationId: healthCheck
|
||||
tags: [health]
|
||||
summary: Health check
|
||||
description: Returns server health status
|
||||
responses:
|
||||
"200":
|
||||
description: Healthy
|
||||
@@ -24,6 +32,331 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HealthStatus"
|
||||
|
||||
/requests:
|
||||
get:
|
||||
operationId: listRequests
|
||||
tags: [requests]
|
||||
summary: List all donation requests
|
||||
parameters:
|
||||
- in: query
|
||||
name: status
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: needType
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: List of requests
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
post:
|
||||
operationId: createRequest
|
||||
tags: [requests]
|
||||
summary: Submit a new beneficiary request
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequestInput"
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/new:
|
||||
get:
|
||||
operationId: listNewRequests
|
||||
tags: [requests]
|
||||
summary: List new (unreviewed) requests
|
||||
responses:
|
||||
"200":
|
||||
description: List of new requests
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/published:
|
||||
get:
|
||||
operationId: listPublishedRequests
|
||||
tags: [requests]
|
||||
summary: List published donation opportunities (public)
|
||||
responses:
|
||||
"200":
|
||||
description: Published opportunities
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/{id}:
|
||||
get:
|
||||
operationId: getRequest
|
||||
tags: [requests]
|
||||
summary: Get a single request by ID
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Request detail
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
"404":
|
||||
description: Not found
|
||||
|
||||
/requests/{id}/verify:
|
||||
post:
|
||||
operationId: verifyRequest
|
||||
tags: [requests]
|
||||
summary: Verify a request (admin)
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Updated request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/{id}/publish:
|
||||
post:
|
||||
operationId: publishRequest
|
||||
tags: [requests]
|
||||
summary: Publish a verified request as a donation opportunity
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Updated request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/{id}/donate:
|
||||
post:
|
||||
operationId: donateToRequest
|
||||
tags: [requests]
|
||||
summary: Simulate a donation to a published request
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationInput"
|
||||
responses:
|
||||
"200":
|
||||
description: Updated request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/{id}/deliver:
|
||||
post:
|
||||
operationId: deliverSupport
|
||||
tags: [requests]
|
||||
summary: Mark support as delivered
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Updated request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/{id}/confirm-receipt:
|
||||
post:
|
||||
operationId: confirmReceipt
|
||||
tags: [requests]
|
||||
summary: Confirm beneficiary received support
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Updated request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/{id}/thank-you:
|
||||
post:
|
||||
operationId: submitThankYou
|
||||
tags: [requests]
|
||||
summary: Submit beneficiary thank-you message
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ThankYouInput"
|
||||
responses:
|
||||
"200":
|
||||
description: Updated request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/{id}/send-whatsapp:
|
||||
post:
|
||||
operationId: sendWhatsapp
|
||||
tags: [requests]
|
||||
summary: Send thank-you WhatsApp to donor via OpenClaw
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: WhatsApp send result
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WhatsappResult"
|
||||
|
||||
/requests/{id}/close:
|
||||
post:
|
||||
operationId: closeRequest
|
||||
tags: [requests]
|
||||
summary: Close the case
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Updated request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/requests/{id}/reject:
|
||||
post:
|
||||
operationId: rejectRequest
|
||||
tags: [requests]
|
||||
summary: Reject a request
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RejectInput"
|
||||
responses:
|
||||
"200":
|
||||
description: Updated request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DonationRequest"
|
||||
|
||||
/donors:
|
||||
get:
|
||||
operationId: listDonors
|
||||
tags: [donors]
|
||||
summary: List all donors
|
||||
responses:
|
||||
"200":
|
||||
description: List of donors
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Donor"
|
||||
|
||||
/stats:
|
||||
get:
|
||||
operationId: getStats
|
||||
tags: [stats]
|
||||
summary: Dashboard statistics summary
|
||||
responses:
|
||||
"200":
|
||||
description: Statistics
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Stats"
|
||||
|
||||
/whatsapp-log:
|
||||
get:
|
||||
operationId: listWhatsappLog
|
||||
tags: [whatsapp]
|
||||
summary: List all WhatsApp message log entries
|
||||
responses:
|
||||
"200":
|
||||
description: WhatsApp log entries
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/WhatsappLogEntry"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
HealthStatus:
|
||||
@@ -34,3 +367,203 @@ components:
|
||||
required:
|
||||
- status
|
||||
|
||||
DonationRequest:
|
||||
type: object
|
||||
required: [id, caseId, beneficiaryName, nationalId, phone, source, sourceName, needType, requestedAmount, collectedAmount, description, status, currentStep, createdAt, updatedAt]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
caseId:
|
||||
type: string
|
||||
beneficiaryName:
|
||||
type: string
|
||||
nationalId:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
enum: [beneficiary, charity, official]
|
||||
sourceName:
|
||||
type: string
|
||||
needType:
|
||||
type: string
|
||||
enum: [electricity, water, food, health, housing, refrigerator, air_conditioner, court_order]
|
||||
requestedAmount:
|
||||
type: number
|
||||
collectedAmount:
|
||||
type: number
|
||||
description:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [new, pending_review, verified, published, donated, delivered, receipt_confirmed, thank_you_submitted, whatsapp_sent, closed, rejected]
|
||||
currentStep:
|
||||
type: integer
|
||||
donorId:
|
||||
type: ["string", "null"]
|
||||
donorName:
|
||||
type: ["string", "null"]
|
||||
thankYouMessage:
|
||||
type: ["string", "null"]
|
||||
whatsappStatus:
|
||||
type: ["string", "null"]
|
||||
enum: ["pending", "sent", "failed", null]
|
||||
whatsappSentAt:
|
||||
type: ["string", "null"]
|
||||
rejectionReason:
|
||||
type: ["string", "null"]
|
||||
createdAt:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
|
||||
DonationRequestInput:
|
||||
type: object
|
||||
required: [beneficiaryName, nationalId, phone, source, sourceName, needType, requestedAmount, description]
|
||||
properties:
|
||||
beneficiaryName:
|
||||
type: string
|
||||
nationalId:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
enum: [beneficiary, charity, official]
|
||||
sourceName:
|
||||
type: string
|
||||
needType:
|
||||
type: string
|
||||
enum: [electricity, water, food, health, housing, refrigerator, air_conditioner, court_order]
|
||||
requestedAmount:
|
||||
type: number
|
||||
description:
|
||||
type: string
|
||||
|
||||
DonationInput:
|
||||
type: object
|
||||
required: [donorName, donorPhone, amount]
|
||||
properties:
|
||||
donorName:
|
||||
type: string
|
||||
donorPhone:
|
||||
type: string
|
||||
donorEmail:
|
||||
type: ["string", "null"]
|
||||
amount:
|
||||
type: number
|
||||
|
||||
ThankYouInput:
|
||||
type: object
|
||||
required: [beneficiaryName, message]
|
||||
properties:
|
||||
beneficiaryName:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
RejectInput:
|
||||
type: object
|
||||
properties:
|
||||
reason:
|
||||
type: string
|
||||
|
||||
WhatsappResult:
|
||||
type: object
|
||||
required: [success, message, simulated]
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
simulated:
|
||||
type: boolean
|
||||
sentAt:
|
||||
type: ["string", "null"]
|
||||
|
||||
Donor:
|
||||
type: object
|
||||
required: [id, name, phone, totalDonated, donationCount]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
email:
|
||||
type: ["string", "null"]
|
||||
totalDonated:
|
||||
type: number
|
||||
donationCount:
|
||||
type: integer
|
||||
|
||||
Stats:
|
||||
type: object
|
||||
required: [totalRequests, totalDonated, totalCollected, totalClosed, byStatus, byNeedType]
|
||||
properties:
|
||||
totalRequests:
|
||||
type: integer
|
||||
totalDonated:
|
||||
type: integer
|
||||
totalCollected:
|
||||
type: number
|
||||
totalClosed:
|
||||
type: integer
|
||||
pendingVerification:
|
||||
type: integer
|
||||
activeOpportunities:
|
||||
type: integer
|
||||
byStatus:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/StatusCount"
|
||||
byNeedType:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/NeedTypeCount"
|
||||
|
||||
StatusCount:
|
||||
type: object
|
||||
required: [status, count]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
count:
|
||||
type: integer
|
||||
|
||||
NeedTypeCount:
|
||||
type: object
|
||||
required: [needType, count, totalAmount]
|
||||
properties:
|
||||
needType:
|
||||
type: string
|
||||
count:
|
||||
type: integer
|
||||
totalAmount:
|
||||
type: number
|
||||
|
||||
WhatsappLogEntry:
|
||||
type: object
|
||||
required: [id, caseId, donorName, donorPhone, beneficiaryMessage, whatsappMessage, status, createdAt]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
caseId:
|
||||
type: string
|
||||
donorName:
|
||||
type: string
|
||||
donorPhone:
|
||||
type: string
|
||||
beneficiaryMessage:
|
||||
type: string
|
||||
whatsappMessage:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [pending, sent, failed]
|
||||
sentAt:
|
||||
type: ["string", "null"]
|
||||
createdAt:
|
||||
type: string
|
||||
|
||||
Reference in New Issue
Block a user