Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function __construct(...)

| Fichier | Méthode |
|--------------------------------|--------------------------------------------------------------------|
| Migration | `make:migration` **obligatoire** (diff schema/entités automatique) |
| Migration | `make:migration` **obligatoire** (diff schema/entités automatique) — **relire et ne garder que l'évolution voulue** (DROP parasites possibles venant de la base de dev, cf. #571) ; `SchemaInSyncTest` échoue si une entité diverge des migrations |
| Entité/Controller/Service/Test | Claude génère directement (maker interactif incompatible) |

## Pipeline média
Expand Down
14 changes: 12 additions & 2 deletions .github/avancement.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@

---

## 🚧 Dérive schéma/migrations (2026-10-02, #571, branche `chore/571-schema-drift`)

- Audit : base vierge construite depuis les 25 migrations = **un seul écart** avec les entités (défauts `0` de `takeout_imports.processed_count` / `extracted_zip_count`, cosmétique). Les `DROP broadcast_messages` / `DROP users.last_broadcast_seen_at` venaient uniquement de la base de dev locale (jamais créés par aucune migration ni entité, absents de la prod ronan).
- `options: ['default' => 0]` sur les deux propriétés : l'entité s'aligne sur la base, **aucune migration** (rien à exécuter sur les instances).
- `SchemaInSyncTest` : échoue si le schéma issu des migrations diverge des entités (vérifié par mutation : rouge avec un champ non migré). La CI construit sa base de test par `migrations:migrate`, donc le test y est fiable.
- Base de dev nettoyée (table et colonne orphelines, vides) ; commentaires obsolètes « non repris, à traiter séparément » mis à jour dans deux migrations ; consigne `make:migration` ajoutée à `.claude/architecture.md`.
- Reste : revue, `gh pr create` (label + `Closes #571` + assignee + board), CI verte, merge ; relancer l'audit `schema:update --dump-sql` sur les 6 autres instances une fois réparées (#569).

---

## ✅ Déploiement : composer en CLI, vérification de vendor/, rollback (2026-10-02, #570, PR #602 mergée)

- Cause de l'incident #569 (6 instances en 500) : composer lancé en PHP CGI par le cron affichait son aide et sortait en 0 ; code déjà mis à jour par `git checkout`, `vendor/` ancien, aucun rollback.
Expand All @@ -18,15 +28,15 @@

---

## 🚧 Admin : charge serveur et imports Takeout différés (2026-10-02, #528, branche `feat/528-admin-charge-takeout`)
## ✅ Admin : charge serveur et imports Takeout différés (2026-10-02, #528, PR #603 mergée)

- `ServerLoadChecker` expose `getLoadAverage()`, `getThreshold()` et `isCalm(?array)` (décision sur une mesure déjà prise : la commande mesure une fois, l'issue loguée reste cohérente avec la charge loguée).
- Nouvelle table `takeout_dispatch_log` (entité `TakeoutDispatchLog`) : `app:takeout:nightly-dispatch` y écrit une ligne par cycle de 15 min (`dispatched` / `deferred` / `idle`, load 1/5/15, seuil, nb en attente / dispatchés), puis purge au-delà de 30 jours (un seul `DELETE` indexé, pas de nouveau cron). Les cycles `idle` sont tracés exprès : ils servent d'échantillonnage de charge pour #547. Écriture et purge n'interrompent jamais le dispatch (erreur journalisée seulement).
- `TakeoutImport::scheduledAt` (migration) posée par `markScheduled()` pour afficher « en attente depuis » ; repli sur `createdAt` signalé dans l'UI pour les imports déjà scheduled avant la colonne.
- Page `/admin/takeout-load` (whitelist AdminVoter, lien « Charge Takeout » dans le layout admin) : load courant vs seuil, imports `scheduled` (propriétaire, depuis quand), historique des 50 derniers cycles et part de cycles calmes sur 24 h. Dates affichées en `Europe/Paris`.
- Migrations : deux, additives (CREATE TABLE + ADD COLUMN nullable). `make:migration` avait généré de la dérive de la base de dev locale (DROP `broadcast_messages`, DROP `users.last_broadcast_seen_at`, CHANGE sur `takeout_imports`) : retirée à la main, à garder en tête pour les prochaines migrations.
- Suite complète : 1410/1410 verts.
- Reste : revue, `gh pr create` (label + `Closes #528` + assignee + board), CI verte, merge ; l'échantillonnage ne démarre qu'au déploiement.
- PR #603 mergée, CI verte. L'échantillonnage de charge ne démarre qu'au déploiement (déploiement exceptionnel prévu, cf. #569).

---

Expand Down
8 changes: 4 additions & 4 deletions migrations/Version20260926172610.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public function getDescription(): string

public function up(Schema $schema): void
{
// Note : make:migration détecte aussi un drift préexistant sans
// rapport avec ce ticket (broadcast_messages / last_broadcast_seen_at,
// entité déjà retirée du code mais jamais migrée en base) — non repris
// ici volontairement, à traiter séparément.
// Note : make:migration détectait aussi des DROP sans rapport avec ce
// ticket (broadcast_messages / last_broadcast_seen_at). Audit #571 : ils
// viennent uniquement de la base de dev locale — aucune migration ni
// entité ne les a jamais créés, et ils n'existent pas en prod. Non repris.
$this->addSql('CREATE TABLE content_fingerprints (id BINARY(16) NOT NULL, content_hash VARCHAR(64) NOT NULL, first_seen_at DATETIME NOT NULL, owner_id BINARY(16) NOT NULL, INDEX IDX_8973604F7E3C61F9 (owner_id), INDEX idx_content_fingerprints_hash (content_hash), UNIQUE INDEX uniq_owner_content_hash (owner_id, content_hash), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
$this->addSql('ALTER TABLE content_fingerprints ADD CONSTRAINT FK_8973604F7E3C61F9 FOREIGN KEY (owner_id) REFERENCES users (id)');
}
Expand Down
8 changes: 4 additions & 4 deletions migrations/Version20260926183234.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public function getDescription(): string

public function up(Schema $schema): void
{
// Note : make:migration détecte ici aussi content_fingerprints (déjà
// migré sur main via #449) et le même drift preexistant sans rapport
// (broadcast_messages / last_broadcast_seen_at) — non repris, cf.
// Version20260926172610.
// Note : make:migration détectait ici aussi content_fingerprints (déjà
// migré sur main via #449) et des DROP sans rapport (broadcast_messages /
// last_broadcast_seen_at) venant de la base de dev locale, jamais créés
// par aucune migration ni en prod — non repris, cf. #571.
$this->addSql('CREATE TABLE takeout_imports (id BINARY(16) NOT NULL, status VARCHAR(16) NOT NULL, media_imported_count INT DEFAULT NULL, duplicates_skipped_count INT DEFAULT NULL, unrecognized_files_count INT DEFAULT NULL, created_at DATETIME NOT NULL, completed_at DATETIME DEFAULT NULL, error_message LONGTEXT DEFAULT NULL, owner_id BINARY(16) NOT NULL, INDEX IDX_416C1617E3C61F9 (owner_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
$this->addSql('ALTER TABLE takeout_imports ADD CONSTRAINT FK_416C1617E3C61F9 FOREIGN KEY (owner_id) REFERENCES users (id)');
}
Expand Down
4 changes: 2 additions & 2 deletions src/Entity/TakeoutImport.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class TakeoutImport
* (contrairement aux compteurs finaux qui restent null tant que
* l'import n'est pas terminé).
*/
#[ORM\Column]
#[ORM\Column(options: ['default' => 0])]
private int $processedCount = 0;

/**
Expand All @@ -89,7 +89,7 @@ class TakeoutImport
* Progress bar pendant l'extraction (#515) : nombre de ZIP déjà extraits
* — incrémenté au fil du Handler, même pattern que processedCount.
*/
#[ORM\Column]
#[ORM\Column(options: ['default' => 0])]
private int $extractedZipCount = 0;

#[ORM\Column]
Expand Down
48 changes: 48 additions & 0 deletions tests/Integration/SchemaInSyncTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace App\Tests\Integration;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
* #571 : les migrations doivent produire exactement le schéma décrit par les
* entités. La base de test est construite par `doctrine:migrations:migrate`
* (CI comme local) : tout écart ici signifie qu'une entité a changé sans
* migration, ou qu'une migration a laissé le schéma dériver.
*
* Pourquoi c'est un test : un écart permanent fait que chaque `make:migration`
* embarque des instructions sans rapport (DROP parasites), qui partiraient
* sur les 7 instances au déploiement si on oublie de les retirer à la main.
*
* En local, si ce test échoue alors que la CI est verte, la base de test a
* dérivé : la recréer depuis les migrations
* php bin/console doctrine:schema:drop --full-database --force --env=test
* php bin/console doctrine:migrations:migrate --no-interaction --env=test
*/
final class SchemaInSyncTest extends KernelTestCase
{
public function testMigrationsProduceTheSchemaDescribedByTheEntities(): void
{
self::bootKernel();
/** @var EntityManagerInterface $em */
$em = static::getContainer()->get(EntityManagerInterface::class);

$statements = (new SchemaTool($em))->getUpdateSchemaSql($em->getMetadataFactory()->getAllMetadata());
// La table de suivi des migrations n'appartient à aucune entité : la
// commande doctrine:schema:update l'ignore, SchemaTool non.
$statements = array_values(array_filter(
$statements,
static fn (string $sql): bool => !str_contains($sql, 'doctrine_migration_versions'),
));

$this->assertSame(
[],
$statements,
"Le schéma issu des migrations diverge des entités. Ajouter la migration manquante (ou corriger le mapping) :\n" . implode("\n", $statements),
);
}
}
Loading