Itqan CMS helps Quranic data publishers distribute high-quality, licensed content while enabling developers to integrate it reliably across apps and platforms.
- Empower publishers to upload, manage, and govern Quranic resources with versioning and clear licensing.
- Provide developers with standardized, well-documented access via download, APIs, and packages.
- Maintain authenticity and consistency across formats (audio, text, tafsir, translation).
- Foster a collaborative, open-source ecosystem that advances Quranic accessibility.
- Content publishing: Upload and manage Quranic resources with metadata, categories, and ownership.
- Licensing & versioning: Attach clear licenses (e.g., Creative Commons) and maintain version history.
- Access control: Govern who can view, download, or integrate specific assets.
- Developer APIs: Clean REST APIs with documentation for easy integration.
- File/resource distribution: Offer standardized downloads and resource endpoints.
- Usage & analytics: Track access and usage patterns to inform curation and scaling.
- Admin & workflows: Robust admin tools and flows to review, publish, and maintain content quality.
- Internationalization: Support multilingual descriptions and metadata.
- Extensible architecture: Modular apps-based design for new content types and integrations.
- Backend: Django + Django Ninja (Python 3.13+)
- Dependency Management: uv
- Database: PostgreSQL
- Containerization: Docker & Docker Compose
- CI/CD: GitHub Actions
- Linting: pre-commit hooks (enforced on all PRs)
- Hosting/Infra: DigitalOcean (e.g., Droplets, Managed PostgreSQL)
Itqan CMS exposes four distinct APIs, each serving a different audience and purpose. All APIs are built with Django Ninja.
- Mount:
cms-api/ - Purpose: The internal API powering our frontend web application. Through this API, users ("the developers") can create accounts, log in, explore the platform, and create OAuth applications for themselves.
- Authentication: django-allauth (JWT) with social login support (Google/GitHub)
- Audience: Registered platform users interacting through the CMS frontend
- Mount:
/(root) - Purpose: The public-facing API consumed by external developers. After a developer creates an OAuth application through the CMS API, they use the Public API to access Quranic content in their own web apps, mobile apps, and other clients. This is where we expect the majority of traffic as adoption grows.
- Authentication: django-oauth-toolkit (OAuth2 client credentials flow)
- Audience: External developer applications
- Mount:
tenant/ - Purpose: A multi-tenant SaaS API for one of our core stakeholders -- the publishers. Each publisher can have their own domain and serve content filtered to show only the data they have uploaded, hiding everyone else's. Traffic is differentiated by the
Domainheader. All tenants share a single database; this architecture will not change in the near term. - Authentication: JWT/Session authentication
- Audience: Publisher-branded frontend applications
- Mount:
portal/ - Purpose: The internal admin portal for uploading, writing, updating, and managing content. Supports full CRUD operations. Relies on a permission system; all users of this API are expected to be company staff. The focus is on heavy lifting and functionality rather than aesthetics.
- Authentication: JWT/Session authentication with role-based permissions
- Audience: Internal company staff and admins
| API | Mount | Audience | Auth | Traffic Profile |
|---|---|---|---|---|
| CMS API | cms-api/ |
Platform users | allauth (JWT) | Moderate |
| Public API | / |
External developers | OAuth2 | High (primary) |
| Tenant API | tenant/ |
Publisher domains | JWT/Session | Varies per tenant |
| Portal API | portal/ |
Internal staff | JWT/Session + permissions | Low |
For detailed authentication flows, security practices, and developer guides, see docs/AUTHENTICATION.md.
All APIs return errors using the same structure, ensuring a consistent developer experience regardless of which API is consumed:
class ItqanError(Exception):
def __init__(self, error_name: str, message: str, status_code: int = 400, extra=None):
"""
error_name: a unique name for the error, must not contain spaces
message: a human-readable message (should be localized)
"""
class NinjaErrorResponse[ERROR_NAME, EXTRA_TYPE](Schema):
error_name: ERROR_NAME
message: str
extra: EXTRA_TYPE | None = NoneExample error response:
{
"error_name": "asset_not_found",
"message": "The requested asset does not exist.",
"extra": null
}- Docker and Docker Compose
- Python 3.13+ (for native setup)
- uv (Python package manager)
git clone https://github.com/Itqan-community/cms-backend.git
cd cms-backend
# Native dev env
cp env.example .env
# Docker env (used by compose)
cp deployment/docker/env.template deployment/docker/.env
# edit deployment/docker/.env with your DB_*, SECRET_KEY, and SITE_DOMAIN# Start services (web + caddy)
docker compose -f docker-compose.local.yml up -d
# Check status
docker compose -f docker-compose.local.yml ps
# Tail logs
docker compose -f docker-compose.local.yml logs -f webuv sync --group dev
source .venv/bin/activate
pre-commit install
python manage.py migrate
python manage.py createsuperuser
python manage.py runserverNote:
pre-commit installsets up Git hooks that run linting checks before each commit. All PRs must pass linting to be merged.
- API Docs: http://localhost:8000/docs/
- Health check: http://localhost:8000/health/
- Django Admin: http://localhost:8000/django-admin/
cms-backend/
├── apps/
│ ├── core/ # Shared utilities, error handling, pagination
│ │ ├── ninja_utils/ # ItqanError, NinjaErrorResponse, etc.
│ │ └── ...
│ ├── content/
│ │ ├── api/
│ │ │ ├── internal/ # CMS API endpoints
│ │ │ ├── public/ # Public (Developers') API endpoints
│ │ │ ├── tenant/ # Tenant API endpoints
│ │ │ └── portal/ # Portal API endpoints
│ │ ├── models.py
│ │ ├── services/
│ │ └── tests/
│ ├── users/
│ │ ├── api/
│ │ │ ├── internal/
│ │ │ ├── public/
│ │ │ ├── tenant/
│ │ │ └── portal/
│ │ ├── models.py
│ │ └── tests/
│ └── publishers/
│ ├── api/
│ │ ├── internal/
│ │ ├── public/
│ │ ├── tenant/
│ │ └── portal/
│ ├── models.py
│ └── tests/
├── config/
│ ├── settings/ # base.py, development.py, staging.py, production.py
│ ├── cms_api.py # CMS API (internal) Ninja config
│ ├── developers_api.py # Public API Ninja config
│ ├── tenant_api.py # Tenant API Ninja config
│ ├── portal_api.py # Portal API Ninja config
│ └── urls.py # Root URL mounting for all APIs
├── deployment/
│ └── docker/ # Dockerfile, compose, Caddyfile, env.template
├── pyproject.toml
├── uv.lock
├── manage.py
└── ...
Each app contains an api/ directory with subdirectories per API surface (internal/, public/, tenant/, portal/). Each API surface has its own tests.
- Documentation Index — Overview of all available documentation
- Architecture Guide — System architecture, domain models, and component interactions
- Authentication Guide — Complete OAuth flows, security practices, and developer guides
- Contributing Guide — How to contribute to the project
See CONTRIBUTING.md for detailed guidelines.
Quick Summary:
mainandstaging: PR-only (no direct commits)- Flow:
feature branch→staging(PR) →main(PR) - Start all changes from
stagingor feature branches
Containerized deployment uses Caddy (TLS, static) and the Django app container. See deployment/docker/README.md for end-to-end steps (environment variables, logs, and health checks). Database is typically a managed PostgreSQL instance; configure DB_* env vars accordingly.
- Use strong unique
SECRET_KEYper environment; keep it out of VCS - CORS/CSRF configured per environment settings
- OAuth
client_secretmust be kept secure (see docs/AUTHENTICATION.md) - JWT tokens via SimpleJWT for
cms-api,tenant/, andportal/; OAuth2 via django-oauth-toolkit for the Public API
MIT License. See LICENSE for details.
Built with care by Itqan Community