Aller au contenu principal
Dorloter - Plateforme d'Adoption et de Retrouvailles d'Animaux
En cours
SAAS

Étude de cas

Dorloter - Plateforme d'Adoption et de Retrouvailles d'Animaux

Plateforme web et mobile française dédiée aux animaux domestiques, articulée autour de trois fonctions, l'adoption (vitrine des refuges, candidatures, back-office complet), les perdus et trouvés (signalements géolocalisés avec rapprochement scoré) et les pensions professionnelles agréées. Monorepo TypeScript, avec une API REST NestJS en monolithe modulaire, deux applications web distinctes (vitrine publique et espace pro), une application mobile Expo et des packages partagés. Accès aux données en SQL typé, sans ORM, sur PostgreSQL et PostGIS. Authentification par jetons avec rotation, autorisation refuge par matrice de permissions fines. Auto-hébergeable en Europe.

Durée
Depuis juin 2024, périmètre MVP livré en 4 mois
Équipe
1 personne
Statut
En cours
Partager sur TwitterPartager sur LinkedInPartager sur FacebookCopier le lien

En chiffres

3

Domaines métier

Adoption, perdus et trouvés, pensions

142

Routes REST

Toutes sous /api/v1

36

Tables

PostgreSQL et PostGIS

3

Clients

Web public, pro, mobile

Le contexte

Dorloter est une plateforme française dédiée aux animaux domestiques, qui réunit trois services habituellement dispersés : l’adoption en refuge, le signalement d’animaux perdus ou trouvés, et l’annuaire des pensions professionnelles agréées. Le MVP cible chats et chiens, avec une conception extensible aux NAC sans refonte.

Elle s’adresse à quatre types d’acteurs, adoptants, refuges, pensions et administrateurs, servis par trois clients distincts : une vitrine publique, un back-office professionnel et une application mobile. Je l’ai conçue et développée seul, en quatre mois. Réunir trois métiers dans un seul produit est moins une somme de fonctionnalités qu’un problème de frontières, et trois contraintes en découlent.

Trois métiers, un seul produit. L’adoption, les retrouvailles et les pensions n’ont ni les mêmes acteurs, ni les mêmes règles, ni le même rythme d’évolution, mais elles partagent des utilisateurs, une base de données et une API. Chaque domaine doit pouvoir évoluer sans entraîner les autres.

Un rapprochement qui doit tomber juste. Rapprocher un animal perdu d’un animal trouvé croise proximité, morphologie et date. Un tri fait par l’application, après avoir remonté les candidats, ne tient pas la charge.

Un bénévole n’est pas un rôle global. Les droits dans un refuge dépendent de l’équipe à laquelle on appartient, pas du rôle porté par le jeton d’authentification. Confondre les deux mène à une hiérarchie de rôles qui finit toujours par craquer.

La chaîne des retrouvailles

C’est la fonction qui justifie le choix de la pile de données : tout le reste en découle.

Du signalement à la mise en relation

particulier

Signalement

Déclaration d'un animal perdu ou trouvé, photos à l'appui.

plateforme

Géolocalisation

Le point est enregistré comme donnée géographique, pas comme deux nombres.

base

Recherche de proximité

La base restreint elle-même les candidats du type opposé à un périmètre.

plateforme

Scoring

Plusieurs critères pondérés donnent une note unique à chaque candidat.

plateforme

Mise en relation

Au-delà du seuil, la correspondance est proposée aux deux déclarants.

Un signalement reste actif : chaque nouvelle déclaration est confrontée à celles déjà en base, dans les deux sens. Un animal trouvé aujourd'hui peut correspondre à une perte signalée la semaine dernière.

Trois conséquences ont orienté l’architecture. La position est stockée comme une géométrie et non comme un couple de coordonnées, ce qui permet à la base de faire le travail. Le rapprochement est une recherche continue, pas une requête ponctuelle : la fraîcheur des données compte autant que la justesse du calcul. Et le barème est isolé dans une fonction pure, sans accès à la base, ce qui le rend vérifiable sans aucune infrastructure. Cette dernière frontière est celle qui rapporte le plus, et j’y reviens plus bas.

Les choix techniques

API

NestJS, monolithe modulaire

Des modules métier dans un seul déploiement, où chacun n'accède aux autres que par leurs services exportés. Les frontières des microservices sans leur exploitation, pour une équipe d'une personne.

Accès aux données

Kysely, sans ORM

Le typage des requêtes est vérifié à la compilation, mais rien ne s'interpose entre le code et le SQL. Le schéma est décrit en TypeScript et les migrations restent des fichiers versionnés, appliqués au démarrage.

Recherche géospatiale

Déléguée à la base

Proximité et distance sont calculées là où sont les données, sur une colonne géographique indexée, au lieu de remonter les candidats pour les filtrer en mémoire.

Autorisation refuge

Permissions dérivées de l'équipe

Des permissions fines dérivées de rôles d'équipe. Un bénévole invité garde le rôle global le plus faible : ses droits viennent de son appartenance, pas de son jeton.

Deux décisions de contrat complètent ces choix. Les réponses de l’API suivent des enveloppes figées, avec des codes d’erreur stables et une pagination par curseur : ce contrat n’a pas bougé depuis l’origine, donc les trois clients restent alignés sans régénération. Et la plateforme n’embarque aucun cookie non essentiel, aucune ressource servie par un tiers, aucun traceur, ce qui l’exempte de bandeau de consentement et rend la politique publiée conforme à ce que le code fait réellement.

L’architecture qui en découle

Vue d'ensemble
Hébergement européen, auto-hébergeableproximité calculée en SQLURL présignéesVitrine publiqueReact 19 · ViteConsole professionnellerefuges · pensions · adminApplication mobileExpo · React NativeAPI REST versionnéeNestJS · monolithe modulaire · un seul déploiementPostgreSQL et PostGISmigrations versionnées · index géospatiauxStockage objetdépôts présignés · région européenne
Faites défiler le schéma horizontalement.
Les trois clients consomment le même contrat : aucun n'a de chemin privilégié vers la base.

Deux packages partagés tiennent les clients alignés sans les fusionner : un design system commun aux deux applications web, et une couche d’accès à l’API qui porte les jetons et leur rotation. La vitrine et l’espace pro ont des publics et des besoins d’ergonomie opposés, ils restent donc deux applications sur deux domaines, et l’API demeure la seule frontière de sécurité.

Ce que la chaîne de tests couvre

La suite est volontairement étroite. Elle ne cherche pas un taux de couverture, elle vise les endroits où une erreur ne se voit pas à la relecture et ne casse aucun parcours : un barème qui dérive d’un point, une table oubliée dans un effacement, un hachage qui accepte ce qu’il devrait refuser.

Statique

Complétude de l'effacement RGPD

Le vrai risque n'est pas que la suppression de compte cesse de marcher, c'est qu'une migration ajoute une table rattachée à un utilisateur et que le chemin d'effacement l'oublie. Le test relève les clés étrangères obligatoires vers la table des comptes, les confronte à ce que le service purge réellement, et échoue sur tout écart. Une table nouvellement rattachée doit être purgée ou déclarée conservée avec sa justification légale. La vérification est statique : elle tourne sans base de données.

Unitaire

Barème de rapprochement

Le barème étant une fonction pure, chaque critère est vérifié isolément sur ses paliers, puis leur somme, puis le cas où tout diverge et où le total doit passer sous le seuil de mise en relation. Aucune base, aucune fixture, donc aucune raison de ne pas rejouer ces cas à chaque exécution.

Unitaire

Hachage des mots de passe

Vecteurs de référence de l'implémentation d'origine, normalisation Unicode du mot de passe avant hachage, aller-retour encodage puis vérification, et rejet d'un hash malformé sans lever d'exception. Ce dernier point compte : une erreur non rattrapée à la vérification transforme une donnée corrompue en panne.

Unitaire

Routage des notifications mobiles

La traduction d'une notification en destination dans l'application, sur des charges utiles hostiles : absentes, sans identifiant exploitable, portant deux identifiants concurrents, ou avec des types inattendus. Une notification qui ouvre le mauvais écran est un bug qu'aucun test de rendu ne rattrape.

Vingt et un cas, cinquante-deux assertions, quatre fichiers, moins d’une seconde d’exécution. C’est peu, et c’est assumé : sur un MVP mené seul, une suite courte qui tourne à chaque commit vaut mieux qu’une suite large qu’on finit par désactiver. Le test d’effacement est celui que je referais en premier sur n’importe quel produit manipulant des comptes.

Le back-office qui va avec

Un refuge ne se gère pas avec une liste d’annonces. La console pro couvre le registre légal d’entrée et de sortie, les contrats d’adoption, les familles d’accueil, le suivi médical, les bénévoles, les événements, le stock et les campagnes de communication. C’est la partie la moins visible du produit et la plus volumineuse.

L’accès aux pensions, lui, passe par une vérification humaine : numéro d’entreprise et agrément préfecture sont contrôlés avant publication. C’est le seul point de la plateforme où un humain bloque volontairement le flux, parce qu’un annuaire de professionnels agréés qui ne vérifie pas les agréments ne vaut rien.

Où en est le produit

Le périmètre MVP est livré : une API de cent quarante routes, trois clients qui consomment le même contrat, un rapprochement géospatial délégué à la base, et la conformité RGPD écrite dans le code plutôt que dans une page. Restent des chantiers identifiés, dont les notifications navigateur et une description d’API exhaustive. La conception vise les chats et les chiens sans fermer la porte aux NAC : l’extension est une affaire de données, pas de refonte.

L'interface

Page d'accueil publique de Dorloter
Catalogue des animaux à adopter avec ses filtres par espèce et par âge
Carte des signalements perdus et trouvés, avec la liste latérale des alertes
Tableau de bord du back-office refuge, avec indicateurs et candidatures récentes

La vitrine publique, porte d'entrée des trois fonctions du produit

Le catalogue d'adoption, filtrable par espèce, âge et compatibilité

Signalements géolocalisés, perdus en rouge et trouvés en vert sur la même carte

Le back-office refuge, du registre d'entrée au suivi post-adoption

1 / 4

Leçons apprisesLeçons apprises

Technical

Des frontières valent mieux qu'une discipline

Technique

Découper une API en modules qui ne se parlent que par leurs services publics coûte quelques indirections, et garde le produit modifiable quand un domaine grossit plus vite que les autres.

Technical

Séparer ce que la base fait de ce qu'on peut tester

Technique

La recherche de proximité appartient au moteur géospatial. Le calcul du score, lui, est une fonction pure, donc vérifiable sans base ni fixture. La frontière entre les deux est ce qui rend le rapprochement testable.

Technical

Un ORM en moins, du SQL en plus

Technique

Avec une part importante de requêtes géospatiales, une couche qui traduit les objets en SQL coûte plus qu'elle ne rapporte. Kysely donne le typage à la compilation sans s'interposer entre le code et la requête.

Design

Le rôle du jeton ne dit pas ce qu'on a le droit de faire

Design

Un bénévole invité dans un refuge porte le rôle global le plus faible. Résoudre l'autorisation par l'appartenance à l'équipe, et non par le rôle du token, évite une hiérarchie de rôles globaux qui aurait fini par craquer.

Navigation

Articles

Projets

Actions