5.9 KiB
api-python-001
Réécriture en Python (FastAPI + SQLite) de afuh/rick-and-morty-api (Node.js/Express + MongoDB) — https://api-python-001.tips-of-mine.com
Correspondance avec l'original
| Original (Node.js) | Ici (Python) |
|---|---|
server.js (Express) |
app/main.py (FastAPI) |
routes/ + handlers/ |
app/routers/ (+ common.py : pagination, filtres, ids multiples) |
models/ (Mongoose / MongoDB) |
app/models.py (SQLAlchemy / SQLite) |
utils/helpers.js (messages) |
app/errors.py |
| lecture seule | + POST / PUT, DELETE protégé par X-API-Key |
images/ servies sur /api/character/avatar/N.jpeg |
idem, fichiers envoyés par upload dans data/avatars |
| — | site web de consultation (/, Jinja2, sans JavaScript) |
graphql/ |
chantier suivant |
Endpoints
| Méthode | Chemin |
|---|---|
| GET | /api — index des ressources |
| GET | /api/{character,location,episode} — 20 par page, ?page=N + filtres |
| GET | /api/{ressource}/1, /1,2,3, /[1,2,3] |
| POST | /api/{ressource} — id et created facultatifs (conservés si fournis) |
| PUT | /api/{ressource}/{id} — remplacement complet (id/created du corps ignorés) |
| DELETE | /api/{ressource}/{id} — en-tête X-API-Key |
| PUT | /api/episode/{id}/characters/{character_id} — corps {"role": "major"|"minor"|"mentioned"|null, "died": false} : rattache le personnage si besoin et fixe son rôle |
| PUT | /api/episode/{id}/locations/{location_id} — relie l'épisode à un lieu où il se déroule (sans effet si déjà lié) |
| DELETE | /api/episode/{id}/locations/{location_id} — retire ce lien (X-API-Key), le lieu est conservé |
| PUT | /api/location/{id}/parents/{parent_id} — place le lieu dans un autre (plusieurs parents possibles, sans effet si déjà lié) |
| DELETE | /api/location/{id}/parents/{parent_id} — retire ce lien (X-API-Key), les lieux sont conservés |
| PUT | /api/character/{id}/avatar — corps brut JPEG/PNG (2 Mo max), X-API-Key ; met à jour image |
| GET | /api/character/avatar/{id}.jpeg (ou .png) |
| GET | /health, /hello, /docs |
Filtres (partiels, insensibles à la casse) : character name status species type gender,
location name type dimension, episode name episode.
Format et relations
Réponses identiques à l'original, plus un champ modified (date de dernière modification, égale à created tant que l'objet n'a pas été modifié) ; une ressource lue en GET peut être renvoyée telle quelle en POST.
Les URL (origin, location, episode, residents) sont décodées sur /api/<ressource>/<id>, quel que soit l'hôte.
-
character.episode↔episode.characters: une seule relation. Le champcharactersd'un épisode en entrée est ignoré. -
location.residents: liste saisie telle quelle (ordre conservé), indépendante decharacter.location. -
Rôle par épisode : chaque lien personnage ↔ épisode porte un rôle (
major,minor,mentioned, vide = non classé) et un indicateurdied. Champs ajoutés en fin d'objet :episode_roles(character) etcharacters_by_role(episode :major,minor,mentioned,unclassified,dead). Un PUT de character conserve les rôles des épisodes qu'il garde. -
Lieux d'un épisode : relation épisode ↔ location saisie via l'API ; champs ajoutés en fin d'objet :
locations(episode) etepisodes(location). -
Hiérarchie des lieux : un lieu peut être contenu dans plusieurs lieux (ex. une seule « Smith Residence » sur plusieurs planètes de plusieurs dimensions). Champs ajoutés en fin d'objet :
parentsetchildren(location). En entrée,parents(URL) remplace les parents ; absent, ils sont conservés ;childrenest ignoré. Un parent doit être d'un niveau strictement inférieur (niveaux sautables), sinon 422 ; cycles refusés :Niveau Types 0 Multiverse 1 Dimension, Reality, Non-Diegetic Alternative Reality 2 Planet, Dwarf planet (Celestial Dwarf), Asteroid, Star, Cluster, Quadrant, Quasar, Space, Space station, Spacecraft, Death Star, Artificially generated world 3 Country, State, City, Fantasy town, Woods, Lake, Mount, Liquid 4 Residence, Building, School, Restaurant, Arcade, Spa, Resort, Daycare, Customs, Police Department, Convention, Lair, Company, Theme park, Acid Plant, Menagerie, Base libre tout autre type (Microverse, Dream, TV, unknown…) : aucune contrainte de niveau dimensionest calculée : noms des lieux de niveau 1 parmi le lieu et ses ancêtres (séparés par « , »), sinon la valeur saisie. Le filtre?dimension=porte sur cette valeur calculée. Table dansapp/hierarchy.py. -
Migration : les colonnes ajoutées après la mise en production sont créées au démarrage (
app/migrations.py).
Ordre d'import (n8n) : locations → episodes → characters → avatars.
Site web
/ accueil, /characters, /locations, /episodes (listes paginées + filtres) et leurs fiches /<ressource>/<id>.
Fiche épisode : lieux de l'épisode en arbre indenté (lieux englobants grisés), puis personnages rangés en Major / Minor / Mentioned / Dead / Non classés ; fiche personnage : rôle par épisode ; fiche location : lieux parents et contenus, épisodes où elle apparaît.
Bascule ?vue=images / ?vue=liste sur la liste des characters, les residents d'une location et la fiche épisode.
Rendu serveur sans JavaScript, contenu échappé et en-tête CSP strict (les données sont saisissables via l'API).
CI/CD
.gitea/workflows/build.yml : tests (étape test du Dockerfile) puis push Harbor
${HARBOR_REGISTRY}/api-python-001/api-python-001 — main → latest + sha-xxx, tag v1.2.3 → 1.2.3, 1.2.
Déploiement SLDOKP03
mkdir -p /opt/api-python-001/data && cd /opt/api-python-001
# copier docker-compose.yml et .env.example -> .env
chown 10001:10001 data
docker compose pull && docker compose up -d