Architecture

Suddenly est une application Django standard qui tourne partout où Python et PostgreSQL sont disponibles. Docker est optionnel.

Stack technique

ComposantTechnologieRôle
BackendPython 3.11+ + Django 5.0Application principale (SSR)
API RESTDjango REST FrameworkAPI + OpenAPI (drf-spectacular)
Authdjango-allauthAuthentification
FrontendHTMX + Alpine.js + UnoCSSUI sans SPA (~32KB total)
FédérationActivityPub customProtocole de fédération
SignaturesHTTP Signatures (RSA-SHA256)Authentification des requêtes AP
Base de donnéesPostgreSQL 16Datastore principal
Cache / QueueRedis 7 (optionnel)Broker Celery + cache
WorkersCelery + django-celery-beatTâches asynchrones

Flux de services

Browser ──HTMX/JSON──► Django App ──ORM──► PostgreSQL
                           │
Remote Instance ──AP──►    │ ──enqueue──► Celery Worker
                           │                  │
                           └──────────────► Redis (optionnel)
                                        ou DB cache (fallback)

Matrice de dépendances

ComposantRequisOptionnelFallback
Python 3.11+
PostgreSQL
RedisDB cache
CeleryTâches synchrones

Profils de déploiement

  • Petite instance (< 50 utilisateurs) — Django + PostgreSQL uniquement
  • Instance moyenne (50–500) — + Redis recommandé
  • Grande instance (500+) — + Celery pour la livraison fédérée asynchrone

Fédération ActivityPub

Endpoints exposés

EndpointProtocoleDescription
/.well-known/webfingerWebFingerDécouverte des acteurs
/.well-known/nodeinfoNodeInfoMétadonnées de l'instance
/users/{id}/inboxActivityPubRéception des activités
/users/{id}/outboxActivityPubPublication des activités

Sécurité des requêtes

Toutes les requêtes ActivityPub sortantes sont signées avec RSA-SHA256. Les en-têtes signés sont : (request-target) host date digest.

Les clés ne se génèrent pas à la main : Suddenly les crée lui-même.

  • Clés d'instanceensure_instance_keys() écrit keys/private.pem (chmod 0600) et keys/public.pem s'ils sont absents. C'est ce que fait ./scripts/init-dev.sh en local.
  • Clés d'acteur — chaque utilisateur, personnage et partie reçoit sa propre paire RSA à la création, via signal Django. Rien à faire.

Rate limiting

SurfaceLimite
Inbox AP — instance connue100 req/min par domainedjango-ratelimit
Inbox AP — instance inconnue10 req/min par domainedjango-ratelimit
Connexion (POST /accounts/login/)10 tentatives/min par IPmiddleware Django
Inscription (POST /accounts/signup/)5 tentatives/min par IPmiddleware Django

L'API REST n'est pas limitée à ce jour : aucune classe de throttling DRF n'est configurée. Si vous exposez l'API publiquement, prévoyez une limitation en amont (nginx, WAF).

Structure du projet

suddenly/
├── config/settings/
│   ├── base.py          # Paramètres partagés
│   ├── development.py   # Overrides dev
│   └── production.py    # Production (sécurité renforcée, env requis)
├── config/asgi.py
├── suddenly/wsgi.py     # Point d'entrée WSGI (pas config/wsgi.py)
├── manage.py
├── pyproject.toml       # Dépendances (pip install -e ".[federation]")
├── docker-compose.yml
├── docker-compose.dev.yml
├── scripts/             # install.sh, entrypoint.sh, backup.sh, restore.sh…
├── staticfiles/         # Static collectés (whitenoise)
└── media/               # Uploads utilisateurs

Le module WSGI est suddenly/wsgi.py — le paquet config/ ne contient que les settings et l'ASGI. Une commande gunicorn config.wsgi:application échouera.

Qualité et CI

  • Lint : Ruff (règles E, F, I, N, W, UP)
  • Type check : mypy strict + django-stubs
  • Tests : pytest + pytest-cov, seuil de couverture 50%
  • CI : GitHub Actions — ruff + mypy + pytest --cov, bloque les merges en cas d'échec
  • Pre-commit : ruff --fix + mypy

Lancer tous les checks en local :

make check