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

Feedlink System Architecture Diagram

Data Flow: Mobile/Web → Next.js API Routes → Django Backend → PostgreSQL + M-Pesa/LocationIQ → Callbacks → Notifications

Web Dashboard

Built with Next.js + Tailwind CSS. Used by supermarkets to:
  • Create & manage food listings
  • Track inventory and sales
  • View real-time impact analytics
  • Schedule pickups

Mobile App

Built with Kotlin (Android). Used by buyers and recyclers to:
  • 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, Admin
  • Product — Surplus food items with stock, price, category
  • Order — Links user, product, quantity, status
  • Payment — Tracks M-Pesa transaction ID and callback data

Feedlink Entity Relationship Diagram

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

python
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

python
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

python
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

python
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

View Swagger Documentation

Key Endpoints

POST/api/signup/

User registration

POST/api/login/

Token authentication and access

GETPOSTPATCH/api/listings/

CRUD operations for product inventory

GETPOSTPATCH/api/orders/

Order management and tracking

GETPOST/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:
    • camelCase for variables and functions
    • PascalCase for component names
    • SCREAMING_SNAKE_CASE for 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_case for variables and functions
    • PascalCase for 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
    • flake8 and black for 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)

bash
# 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)

bash
# 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)

bash
# 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 .env in 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.properties or 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 8000https://xxxx.ngrok.io/api/payments/callback/

Also register this exact URL in the Safaricom Daraja Portal under your M-Pesa API credentials.

Environment Variables

.env (Example – NEVER commit real keys!)
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 .env files with Daraja keys
  • Use ngrok for 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 ngrok to 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).

bash
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 Tests

Includes: 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.