Feedlink Technical Documentation
A digital platform that reduces food waste in Kenya by connecting supermarkets, buyers, and recyclers through an integrated web dashboard and mobile application.
System Overview
Feedlink enables a circular food economy:
- Supermarkets list surplus edible food at discounted prices or non-edible waste for recycling.
- Buyers (e.g., households, food banks) purchase affordable meals via the mobile app.
- Recyclers claim inedible surplus for compost, biogas, or animal feed.
All participants receive real-time impact metrics: meals saved, CO₂ reduced, and waste diverted from landfills.
System Architecture
Feedlink follows a modular, API-driven architecture to ensure scalability, security, and ease of maintenance. All components communicate via REST APIs.
Architecture Diagram

Data Flow: Mobile/Web → Next.js API Routes → Django Backend → PostgreSQL + M-Pesa/LocationIQ → Callbacks → Notifications
Web Dashboard
- Create & manage food listings
- Track inventory and sales
- View real-time impact analytics
- Schedule pickups
Mobile App
- Browse listings by proximity
- Place orders & pay via M-Pesa
- Receive pickup notifications
- Track order status
Integrated Services
M-Pesa Daraja API handles secure payments.
LocationIQ enables geolocation-based discovery.
All callbacks are handled at /api/payments/callback/.
For New Contributors
Frontend
Web: src/app/ (Next.js App Router)
Mobile: feedlink-mobile/ (Kotlin)
Backend
REST APIs in src/app/api/
Auth: JWT + role-based access
DB: PostgreSQL + Prisma ORM
Workflow
1. Clone repos
2. Set up .env
3. Run migrations
4. Start dev servers
Full setup instructions, API contracts, and testing guides are available in the project README and /docs.
Platform Features
Location-based Discovery
Buyers and recyclers see listings within 5km by default. Uses Location reverse geocoding to convert coordinates into readable addresses for pickup instructions.
For developers: Frontend: Geolocation API + LocationIQ. Backend: Proximity filtering in /api/listings/ with lat/lng query params.
Waste Management
Recyclers claim inedible surplus (e.g., spoiled produce) for compost or biogas. Each pickup is tracked with status: 'scheduled', 'collected', or 'completed'. Supermarkets receive recycling certificates.
For developers: Uses the same Order model as edible items, but with is_edible=false. Pickup scheduling handled via /api/orders/ PATCH requests.
Inventory Management
Supermarkets upload surplus items with name, category, price, stock, and image. Stock auto-decreases on order fulfillment.
For developers: CRUD via /api/listings/. Product model includes stock_quantity, is_edible, and foreign key to User (supermarket).
Real-time Notifications
All users receive in-app and (future) SMS alerts for: order confirmations, payment success, pickup windows, and low inventory. Notifications are persisted in the UI.
For developers: Currently simulated via React state. In production: WebSocket or Firebase Cloud Messaging (FCM) integration planned.
ERD
Feedlink follows a modular, API-driven architecture to ensure scalability, security, and ease of maintenance. All components communicate via REST APIs.
Entity Relationship Diagram (ERD)
The entity relationship diagram outlines the database relationships. Core models:
User— Supermarkets, Buyers, Recyclers, AdminProduct— Surplus food items with stock, price, categoryOrder— Links user, product, quantity, statusPayment— Tracks M-Pesa transaction ID and callback data

Important: Always use prisma db push or migrate dev after schema changes.
Database Schema
PostgreSQL relational database using Django ORM. All IDs are UUIDs. Timestamps use DateTime.
User Model
id = UUIDField(primary_key=True, default=uuid.uuid4)
email = EmailField(unique=True)
first_name = CharField(max_length=100)
last_name = CharField(max_length=100)
password = CharField() # hashed via bcrypt
latitude = FloatField(null=True, blank=True)
longitude = FloatField(null=True, blank=True)
role = CharField(choices=[
('admin', 'Admin'),
('supermarket', 'Supermarket'),
('buyer', 'Buyer'),
('recycler', 'Recycler')
])
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)Primary key: id (UUID). Supermarkets, buyers, and recyclers are all stored here with role-based access.
Product Model
id = UUIDField(primary_key=True, default=uuid.uuid4)
name = CharField(max_length=200)
category = CharField(max_length=50) # e.g., 'vegetables', 'dairy'
unit = CharField(max_length=20) # e.g., 'kg', 'piece'
price = DecimalField(max_digits=10, decimal_places=2)
stock_quantity = PositiveIntegerField()
is_edible = BooleanField(default=True) # False → recyclers only
image_url = URLField(blank=True)
created_at = DateTimeField(auto_now_add=True)
# Relations
supermarket = ForeignKey('User', on_delete=CASCADE, limit_choices_to={'role': 'supermarket'})Edible products appear in buyer app; non-edible (is_edible=False) appear only for recyclers.
Order Model
id = UUIDField(primary_key=True, default=uuid.uuid4)
quantity = PositiveIntegerField()
total_amount = DecimalField(max_digits=10, decimal_places=2)
status = CharField(choices=[
('pending', 'Pending'),
('paid', 'Paid'),
('fulfilled', 'Fulfilled'),
('cancelled', 'Cancelled')
], default='pending')
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
# Relations
customer = ForeignKey('User', on_delete=PROTECT, limit_choices_to={'role__in': ['buyer', 'recycler']})
product = ForeignKey('Product', on_delete=PROTECT)Orders link a customer (buyer/recycler) to a product. Stock is reduced only when status = 'paid'.
Payment Model
id = UUIDField(primary_key=True, default=uuid.uuid4)
amount = DecimalField(max_digits=10, decimal_places=2)
status = CharField(choices=[
('initiated', 'Initiated'),
('success', 'Success'),
('failed', 'Failed')
], default='initiated')
mpesa_transaction_id = CharField(max_length=50, unique=True, null=True, blank=True)
callback_data = JSONField(null=True, blank=True) # raw M-Pesa response
created_at = DateTimeField(auto_now_add=True)
# Relations
order = OneToOneField('Order', on_delete=CASCADE)Payment is 1:1 with Order. M-Pesa callbacks update status and store transaction_id. Never store raw card data.
API Documentation
RESTful APIs with comprehensive Swagger documentation.
Interactive API Docs
Key Endpoints
/api/signup/User registration
/api/login/Token authentication and access
/api/listings/CRUD operations for product inventory
/api/orders/Order management and tracking
/api/payments/Payment processing and history
Code Standards
Guidelines to ensure consistency, readability, and maintainability across Feedlink’s codebase.
Frontend (Next.js, TypeScript, Tailwind)
- Components: Always use functional components for consistency and performance.
- Naming:
camelCasefor variables and functionsPascalCasefor component namesSCREAMING_SNAKE_CASEfor constants
- Files: One component per file. Group files by feature/module for maintainability.
- Testing: Use Jest and React Testing Library for unit tests; add tests for each new component or logic.
- Styling: Tailwind CSS for all styling. Avoid inline styles unless absolutely necessary.
Backend (Django, DRF)
- Naming:
snake_casefor variables and functionsPascalCasefor classes and models
- Serializers/Views: Place each in its own file unless they are closely related.
- Testing: Use Django's built-in test runner (
python manage.py test) and maintain high coverage.
General Standards
- Linting:
- Prettier + ESLint for frontend code
flake8andblackfor backend code
- Pull Requests:
- Write clear summaries for every PR
- Link related issues (use
Fixes #issue_num) - Always request at least one review before merging
- Documentation:
- Document components, functions, and modules with JSDoc (frontend) or docstrings (backend)
- Update README and feature docs for significant changes
Best Practices
- Keep functions and components small and focused.
- Prefer composition over inheritance.
- Avoid commented-out code in committed files.
- Refactor and remove unused code regularly.
- Always run lint and tests before pushing or merging.
Getting Started
Follow these steps to set up Feedlink locally. Ensure you have Node.js ≥18, Python 3.10+, and PostgreSQL installed.
Project Setup
1. Backend Setup (Django)
# Clone the repository git clone https://github.com/akirachix/feedlink-back-end cd feedlink-back-end # Create virtual environment python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # Install dependencies (use 'pip' if you don't have 'uv') pip install -r requirements.txt # Set up environment variables cp .env.example .env # → Edit .env: add DATABASE_URL, JWT_SECRET, M-Pesa keys # Run database migrations python manage.py migrate # Start server (runs on http://localhost:8000) python manage.py runserver
Need PostgreSQL? Install via Homebrew (Mac), apt (Linux), or Docker: docker run --name feedlink-db -e POSTGRES_DB=feedlink -e POSTGRES_USER=user -e POSTGRES_PASSWORD=pass -p 5432:5432 -d postgres
2. Frontend Dashboard Setup (Next.js)
# Clone the repository git clone https://github.com/akirachix/feedlink-frontend cd feedlink-frontend # Install dependencies npm install # Create local config cp .env.example .env.local # → Set NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 # Start dev server (http://localhost:3000) npm run dev
The frontend expects the backend to be running on port 8000. Adjust if needed.
3. Mobile App Setup (Android/Kotlin)
# Clone the repository git clone https://github.com/akirachix/feedlink-mobile cd feedlink-mobile # Open in Android Studio: # 1. Launch Android Studio # 2. Select "Open an existing Android Studio project" # 3. Navigate to feedlink-mobile folder # Configure API URL: # Edit app/src/main/res/values/secrets.xml: # <string name="api_base_url">http://10.0.2.2:8000</string> # Run on emulator or physical device
For physical devices: replace 10.0.2.2 with your machine’s local IP (e.g., 192.168.x.x). Ensure both devices are on the same network.
Critical: M-Pesa Callback URL
Never use yourdomain.com in DARAJA_CALLBACK_URL. This leads to callbacks being sent to unrelated sites (e.g., adult services).
During local development, use ngrok:ngrok http 8000 → then set callback to https://xxxx.ngrok.io/api/payments/callback/
Code Structure
A visual map of Feedlink’s codebase to help new developers navigate and contribute.
Backend (Django)
FEEDLINK-BACKEND/ ├── .github/ │ └── pull_request_template.md ├── api/ │ ├── __pycache__/ │ ├── migrations/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── daraja.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── views.py ├── env/ ├── feedlink/ │ ├── __pycache__/ │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── invent/ │ ├── __pycache__/ │ ├── migrations/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── models.py │ ├── tests.py │ └── views.py ├── listing_images/ ├── location/ │ ├── __pycache__/ │ ├── migrations/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── models.py │ └── tests.py └── manage.py
Key folders: api/ for endpoints, models.py for DB schema.
Frontend (Next.js)
FEEDLINK-FRONTEND/ ├── .github/ │ └── workflows/ │ └── ci-cd.yml ├── pull_request_template.md ├── feedlink/ │ ├── .next/ │ ├── .swc/ │ ├── node_modules/ │ ├── public/ │ └── src/ │ └── app/ │ ├── api/ │ ├── choice/ │ ├── component/ │ │ ├── Calendar/ │ │ └── Pagination/ │ ├── dashboard/ │ │ ├── components/ │ │ ├── page.test.tsx │ │ └── page.tsx │ ├── forgotPassword/ │ ├── hooks/ │ ├── inventory/ │ │ ├── components/ │ │ │ └── index.tsx │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── Csv/ │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── CustomSelect/ │ ├── InventoryDetailForm/ │ ├── InventoryFilters/ │ ├── InventoryModals/ │ ├── InventoryPagination/ │ ├── InventorySummary/ │ ├── InventoryTable/ │ ├── page.test.tsx │ ├── page.tsx │ ├── logout/ │ ├── orders/ │ │ ├── page.test.tsx │ │ └── page.tsx │ └── navigation/ └── package.json
Key folders: src/app/api/ for backend calls, components/ for reusable UI.
Mobile (Kotlin/Android)
app/ ├── manifests/ │ └── AndroidManifest.xml ├── kotlin+java/ │ └── com.feedlink.feedlink/ │ ├── di/ │ │ └── AppModule.kt │ ├── api/ │ │ └── ApiInterface.kt │ ├── location/ │ ├── model/ │ │ ├── ApiRequest.kt │ │ ├── ApiResponse.kt │ │ ├── Listing.kt │ │ ├── ListingItem.kt │ │ └── UIState.kt │ ├── repository/ │ │ ├── AuthRepository.kt │ │ ├── CartRepository.kt │ │ └── ListingsRepository.kt │ ├── ui.theme/ │ │ └── Theme.kt │ ├── utils/ │ │ └── Extensions.kt │ ├── viewmodel/ │ │ ├── CartViewModel.kt │ │ ├── ForgotPasswordViewModel.kt │ │ ├── ListingsViewModel.kt │ │ ├── ListingViewModel.kt │ │ ├── NotificationViewModel.kt │ │ ├── OrderViewModel.kt │ │ ├── PaymentViewModel.kt │ │ ├── ProductDetailViewModel.kt │ │ ├── ProfileViewModel.kt │ │ ├── SignInViewModel.kt │ │ ├── SignUpViewModel.kt │ │ ├── TimerViewModel.kt │ │ └── WasteClaimViewModel.kt │ └── screens/ │ ├── authChoice.kt │ ├── CartScreen.kt │ ├── forgotPasssword.kt │ ├── ListingScreen.kt │ ├── LocationPermissionScreen.kt │ ├── login.kt │ ├── navigation.kt │ ├── ProductDetailScreen.kt │ ├── reset.kt │ ├── role.kt │ ├── signup.kt │ ├── splashScreen.kt │ ├── teasera.kt │ ├── teaserb.kt │ ├── teaserc.kt │ ├── TimerScreen.kt │ ├── verification.kt │ ├── ViewProfileScreen.kt │ ├── WasteCollection.kt │ ├── WasteHistory.kt │ ├── WasteHomepage.kt │ └── WasteNotifications.kt └── Logcat
Key folders: api/ for Retrofit services, screens/ for Jetpack Compose screens, viewmodel/ for state management.
Important: Avoid Misconfigured Callbacks
As shown in our testing, using placeholder URLs like yourdomain.com in DARAJA_CALLBACK_URL leads to callbacks being sent to unrelated sites.
Always use a real HTTPS URL or ngrok during development.
Deployment
Frontend Deployment
- Platform: Vercel
- Branch: Auto-deployment from
main - Environment Variables: Managed securely via
.envin Vercel dashboard - Build & Preview: Each push triggers preview builds for PRs and deploys on merge to
main
Backend Deployment
- Platform: Django REST API deployed on Heroku
- Environment Variables: Configured in Heroku dashboard for security
- Scaling: Automatic scaling via Heroku dynos for increased demand
Mobile Deployment
- Platform: Android (Kotlin + Jetpack Compose)
- Build Tool: Gradle (via Android Studio or CLI)
- Distribution: Internal testing via Firebase App Distribution; Production via Google Play Console
- App Setup: Follow Google’s official guide to create and configure your app on Google Play
- Environment Configuration: Use build variants (
debug/release) with separate API endpoints - Secrets Management: Store API URLs/keys in
local.propertiesor Gradle properties — never in source code - CI Integration: GitHub Actions builds and signs AAB on tag pushes
CI/CD Pipeline
- Tool: GitHub Actions
- Pre-Deployment: All codebases run tests, build, and lint checks before deploy
- Automation: Automatic deployment on merge to
main - Status: Build and test status visible in PRs and repository dashboard
Critical: M-Pesa Callback URL Misconfiguration
Never use placeholder URLs like yourdomain.com.
As confirmed by live testing, https://yourdomain.com/api/daraja/callback/ resolves to an adult/escort service page (see: evidence). This breaks M-Pesa payments and may leak sensitive transaction data.
Use your real deployed URL:
- Heroku:
https://your-feedlink-backend.herokuapp.com/api/payments/callback/ - Local dev: Use
ngrok http 8000→https://xxxx.ngrok.io/api/payments/callback/
Also register this exact URL in the Safaricom Daraja Portal under your M-Pesa API credentials.
Environment Variables
DATABASE_URL=postgresql://user:pass@localhost:5432/feedlink SECRET_KEY=your_strong_secret_key_here # Email Configuration EMAIL_HOST_USER=feedlinkteam@gmail.com EMAIL_HOST_PASSWORD=your_app_password # Daraja API (M-Pesa) – SANDBOX DARAJA_CONSUMER_KEY=your_sandbox_key DARAJA_CONSUMER_SECRET=your_sandbox_secret DARAJA_BUSINESS_SHORTCODE=174379 DARAJA_PASSKEY=your_sandbox_passkey DARAJA_CALLBACK_URL=https://your-real-domain.com/api/payments/callback/ DARAJA_BASE_URL=https://sandbox.safaricom.co.ke/
Never commit real secrets to Git. Add .env and local.properties to .gitignore.
Security
Feedlink implements industry-standard security practices — but configuration errors can undermine them. Below are our protections and critical developer responsibilities.
Real Incident: Callback URL Misconfiguration
During testing, we discovered that using placeholder URLs like https://yourdomain.com/api/daraja/callback/ causes M-Pesa to send real payment callbacks to an unrelated site.
This URL currently resolves to an escort/adult service page — meaning:
- Payment confirmations are not received by your backend
- Orders remain stuck in “pending” state
- Transaction data may be exposed to third parties
Prevention: Always use a real, HTTPS-enabled, publicly accessible callback URL.
TLS Encryption
All API communications (including M-Pesa callbacks) require HTTPS. Local development must use ngrok or similar for callback testing.
M-Pesa rejects non-HTTPS callback URLs in production.
Token Authentication
JWT tokens (stored in memory, not localStorage in production) with 24-hour expiry. Role-based access ensures supermarkets can’t impersonate recyclers.
Never log or expose JWT tokens in client-side code.
Input Validation
All API inputs validated via Django REST Framework serializers. SQL injection and XSS mitigated via ORM and escaping.
Always validate geographic coordinates (lat/lng) to prevent abuse.
CORS Protection
Django CORS headers restrict frontend origins to only Vercel-deployed dashboard and mobile app domains.
Never set CORS_ALLOW_ALL_ORIGINS=True in production.
Developer Security Checklist
- Never commit
.envfiles with Daraja keys - Use
ngrokfor local M-Pesa testing — never placeholder URLs - Rotate Daraja credentials if accidentally exposed
- Monitor Heroku logs for unexpected callback traffic
Testing & Monitoring
Critical Test Case: M-Pesa Callback URL
During testing, we confirmed that using placeholder URLs like yourdomain.com causes M-Pesa to send real payment results to an unrelated site (escort service).
Always test your callback endpoint:
- Use
ngrokto expose local server - Send a simulated M-Pesa callback payload
- Verify order status updates to “paid”
Unit Tests
Django TestCase for models, views, and business logic (e.g., stock deduction, role permissions).
python manage.py test # Runs tests in feedlink_back_end/tests/
Include tests for: is_edible=False routing, JWT role validation, and payment status updates.
Integration Testing
Comprehensive Postman collection covering auth, listings, orders, and M-Pesa simulation.
View Postman TestsIncludes: Sample M-Pesa callback payload for testing /api/payments/callback/
Monitoring (Production)
In production, monitor:
- Orders stuck in “pending” 10 minutes
- HTTP 5xx errors on
/api/payments/callback/ - Failed M-Pesa STK push attempts
- Unusual spikes in “cancelled” orders
Use Heroku logs + custom alerts to catch callback failures early.