A RESTful backend for Coderr, a freelancer service marketplace. It provides token-authenticated endpoints for business users to publish service offers, for customers to order and review them, and for platform-wide statistics, built with Django and the Django REST Framework.
👉 Live demo · API documentation
The login page offers guest access for both user types, so the demo can be
explored without registering. The frontend is provided by the Developer
Akademie; everything behind /api/ is this project.
- Token-based authentication (registration & login)
- Two user types with dedicated profiles: business and customer
- Offers, each with exactly three tiers (basic, standard, premium)
- Orders created from an offer tier as an immutable snapshot
- Reviews (at most one per business per customer)
- Order statistics (in-progress / completed counts) and platform base info
- Filtering, searching, ordering and pagination on the offers list
- Object-level permissions (owner / creator / business / customer rules)
- Auto-generated OpenAPI 3 documentation (Swagger UI & ReDoc) via drf-spectacular
Backend
Infrastructure
| Component | Version |
|---|---|
| Language | Python 3.12+ (required by Django 6) |
| Framework | Django 6.0.6 |
| API | Django REST Framework 3.17.1 |
| Database | PostgreSQL 16 with pgvector (Docker locally) |
| Auth | DRF Token Authentication |
| API Docs | drf-spectacular 0.30.0 (OpenAPI 3) |
| Serving | Gunicorn behind Nginx (Ubuntu 24.04) |
coderr_backend/
├── core/ # Project settings, root URL config, WSGI/ASGI
├── auth_app/ # Registration, login, profiles
│ └── api/ # serializers.py, views.py, urls.py, permissions.py
├── offers_app/ # Offers and offer details
│ └── api/ # serializers.py, views.py, urls.py, permissions.py, pagination.py
├── orders_app/ # Orders and order statistics
│ └── api/ # serializers.py, views.py, urls.py, permissions.py
├── reviews_app/ # Reviews
│ └── api/ # serializers.py, views.py, urls.py, permissions.py
├── base_app/ # Aggregated platform statistics (base-info)
│ └── api/ # views.py, urls.py
├── contact_app/ # Contact form of the portfolio site
├── assistant_app/ # Input filter and retrieval for the portfolio assistant
│ ├── api/ # serializers.py, views.py, urls.py
│ ├── knowledge/ # Knowledge base, one Markdown file per topic
│ └── management/ # build_index, evaluate_retrieval, evaluate_guard
├── embedding_service/ # Standalone FastAPI service that embeds texts
├── laya_service/ # Setup and smoke test for the Laya input filter
├── deploy/ # Deployment script and Nginx configuration
├── compose.yml # Local PostgreSQL with pgvector
├── manage.py
└── requirements.txt
- Python 3.12 or newer
pipandvenv- Docker with Docker Compose, for the local PostgreSQL database
-
Clone the repository
git clone https://github.com/B-Blarr/Coderr-Backend.git cd Coderr-Backend -
Create and activate a virtual environment
python -m venv .venv
Activate it. Windows (PowerShell):
.\.venv\Scripts\Activate.ps1
macOS / Linux:
source .venv/bin/activate -
Install the dependencies
pip install -r requirements.txt
-
Set up your environment file, copy the provided template, then set your own secret key. The
.envfile itself is git-ignored.Windows (PowerShell):
Copy-Item .env.example .envmacOS / Linux:
cp .env.example .env
Then open
.envand replace the value with your ownSECRET_KEY. You can generate one with:python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())" -
Start the database, PostgreSQL 16 with pgvector in Docker:
docker compose up -d
It listens on
127.0.0.1:5433and matches the values in.env.example. Wait untildocker compose psshows the container ashealthy. -
Apply the database migrations
python manage.py migrate
-
(Optional) Create an admin user to use the Django admin at
/admin/:python manage.py createsuperuser
-
(Optional) Seed demo data, six offers, five reviews and the guest accounts the frontend expects:
python manage.py seed_demo
The command is idempotent and runs in a transaction, so it can be repeated safely.
-
Run the development server
python manage.py runserver
The API is now available at
http://127.0.0.1:8000/.
The project needs PostgreSQL: settings.py stops with an error when
DB_NAME is missing, because the portfolio assistant stores its vectors with
pgvector. Everything else defaults to development mode (DEBUG=True), and
production settings are switched on purely through the .env file.
Run the full test suite:
python manage.py testMeasure test coverage (target: ≥ 95 %):
coverage run --source=auth_app,offers_app,orders_app,reviews_app,base_app,assistant_app --omit='*/migrations/*,*/tests/*' manage.py test
coverage reportThe API uses token authentication. Register or log in to receive a token,
then send it with every authenticated request in the Authorization header:
Authorization: Token <your-token>
Registration and login responses both return:
{
"token": "...",
"username": "max",
"email": "max@example.com",
"user_id": 1
}Note: logging in is done with the
username, not the email address.
Interactive, auto-generated API documentation is available while the server is running:
| View | URL |
|---|---|
| Swagger UI | /api/schema/swagger-ui/ |
| ReDoc | /api/schema/redoc/ |
| Raw schema | /api/schema/ |
Base path: /api/
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /registration/ |
Create a new user | No |
| POST | /login/ |
Log in and obtain a token | No |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /profile/{pk}/ |
Profile detail | Authenticated |
| PATCH | /profile/{pk}/ |
Update own profile | Owner only |
| GET | /profiles/business/ |
List all business profiles | Authenticated |
| GET | /profiles/customer/ |
List all customer profiles | Authenticated |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /offers/ |
List offers (filter/search/order, paged) | Public |
| POST | /offers/ |
Create an offer (exactly 3 details) | Business only |
| GET | /offers/{id}/ |
Offer detail | Authenticated |
| PATCH | /offers/{id}/ |
Update an offer | Creator only |
| DELETE | /offers/{id}/ |
Delete an offer | Creator only |
| GET | /offerdetails/{id}/ |
Single offer detail | Authenticated |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /orders/ |
List the user's orders | Authenticated |
| POST | /orders/ |
Create an order from a detail | Customer only |
| PATCH | /orders/{id}/ |
Update the order status | Business only |
| DELETE | /orders/{id}/ |
Delete an order | Admin / staff |
| GET | /order-count/{business_user_id}/ |
Count of in-progress orders | Authenticated |
| GET | /completed-order-count/{business_user_id}/ |
Count of completed orders | Authenticated |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /reviews/ |
List reviews (filter / order) | Authenticated |
| POST | /reviews/ |
Create a review (one per business) | Customer only |
| PATCH | /reviews/{id}/ |
Update a review | Creator only |
| DELETE | /reviews/{id}/ |
Delete a review | Creator only |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /base-info/ |
Platform-wide statistics | Public |
- User model: The project uses a custom user (
auth_app.User, extendingAbstractUser). Profile fields (first_name,last_name,location,tel,description,working_hours) live directly on the user. - User
type: one ofcustomerorbusiness. - Profile fields are never
null: missing values are returned as empty strings"". - Offer
offer_type: one ofbasic,standard,premium. Every offer has exactly three details, one per type. - Order
status: one ofin_progress,completed,cancelled. - Orders are snapshots: creating an order copies the chosen offer detail's fields (title, price, features, …); there is no foreign key back to the detail, so later changes to the offer do not affect existing orders.
- Reviews: a customer may leave at most one review per business user.
average_ratingin the base-info response is rounded to one decimal place.- Offers list query params:
creator_id,min_price,max_delivery_time,search(title/description),ordering(updated_at|min_price) andpage_size. The response is paginated (count,next,previous,results).
Besides the Coderr API, this backend serves the assistant on my portfolio site: visitors ask questions about me and my projects, and the answer comes from a knowledge base I maintain instead of being made up. The feature is under construction and not live yet.
The current stage covers the input filter and retrieval, without a language model yet:
assistant_app/knowledge/*.mdholds the knowledge base, cut into sections at every##heading.python manage.py build_indexembeds every section through the embedding service and stores the vectors in PostgreSQL with pgvector.POST /api/assistant/with{"question": "..."}first sends the question to Laya, a small classifier for jailbreak attempts, and answers403if its score reachesLAYA_THRESHOLD(0.8). Otherwise it returns the five closest sections with their cosine similarity.python manage.py evaluate_retrievalchecks a fixed list of questions, in German and English, against the sections they should find.python manage.py evaluate_guardsends the same questions and a list of attacks through Laya and reports false alarms and missed attacks.
Laya is a cheap pre-filter against obvious attacks, not a security boundary.
Measured with evaluate_guard, it blocks none of the 49 questions on topic
and catches 8 of 15 attacks; quiet attacks without typical jailbreak wording
get through. The threshold is a trade-off: a lower one also blocked genuine
questions about AI, which is worse for a portfolio than a missed attack,
because the knowledge base holds only public content.
The endpoint fails closed. It answers 503 when Laya or the embedding service
does not respond, and unless ASSISTANT_ENABLED=True is set. Only the Laya
scores of a rejected question are logged, never its text. Locally Laya and the
embedding service each run in their own terminal, see their READMEs.
The live instance runs on a VPS I set up and maintain myself: Ubuntu 24.04, Nginx as the reverse proxy, Gunicorn serving Django over a Unix socket, PostgreSQL as the database and TLS certificates from Let's Encrypt. Database and media backups run nightly through a systemd timer.
The complete runbook is in DEPLOYMENT.md: every step from an empty server to the running site, the configuration files, and a section on the mistakes that actually happened along the way.
