fix: corrections post-audit (traçabilité, identité État, accents, graphique mobile)
Backend: - table audits réellement alimentée via trait Auditable (observers Eloquent) sur Utilisateur, SaisieTracker, Feed, Emotion ; mot de passe jamais loggé - 4 nouveaux tests (19/19 au total) Frontend web: - en-tête/pied République Française ajoutés aux pages connexion/inscription - bouton variant danger réellement rouge (tokens erreur DSFR) Mobile: - ~30 libellés ré-accentués (Intensité, Succès, Déconnexion, Répartition...) - Colors.secondary = rouge Marianne #E1000F (différenciation cartes/icônes) - vrai camembert SVG des rapports (react-native-svg) Docs: plans oraux mis à jour (audits actif, 29 tests, stockage JWT en Q/R) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Concerns;
|
||||
|
||||
use App\Models\Audit;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Trait de traçabilité Eloquent.
|
||||
*
|
||||
* Ajoute automatiquement une ligne dans la table `audits` lors des
|
||||
* événements created / updated / deleted du modèle qui l'utilise.
|
||||
*/
|
||||
trait Auditable
|
||||
{
|
||||
/**
|
||||
* Champs sensibles à ne jamais stocker dans l'audit.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected static array $champsAuditExclus = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Enregistre les hooks d'audit sur les événements du modèle.
|
||||
*/
|
||||
public static function bootAuditable(): void
|
||||
{
|
||||
static::created(function (Model $model) {
|
||||
$model->ecrireAudit('creation', null, $model->valeursAuditables($model->getAttributes()));
|
||||
});
|
||||
|
||||
static::updated(function (Model $model) {
|
||||
$modifies = array_keys($model->getDirty());
|
||||
|
||||
if ($modifies === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$anciennes = $model->valeursAuditables($model->getOriginal());
|
||||
$nouvelles = $model->valeursAuditables($model->getDirty());
|
||||
|
||||
$model->ecrireAudit(
|
||||
'modification',
|
||||
array_intersect_key($anciennes, $nouvelles),
|
||||
$nouvelles
|
||||
);
|
||||
});
|
||||
|
||||
static::deleted(function (Model $model) {
|
||||
$model->ecrireAudit('suppression', $model->valeursAuditables($model->getAttributes()), null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtre les champs sensibles d'un jeu d'attributs.
|
||||
*
|
||||
* @param array<string, mixed> $valeurs
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function valeursAuditables(array $valeurs): array
|
||||
{
|
||||
return array_diff_key($valeurs, array_flip(static::$champsAuditExclus));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insère une ligne d'audit.
|
||||
*
|
||||
* @param array<string, mixed>|null $anciennes
|
||||
* @param array<string, mixed>|null $nouvelles
|
||||
*/
|
||||
protected function ecrireAudit(string $action, ?array $anciennes, ?array $nouvelles): void
|
||||
{
|
||||
Audit::create([
|
||||
'utilisateur_id' => auth()->id(),
|
||||
'action' => $action,
|
||||
'table_cible' => $this->getTable(),
|
||||
'enregistrement_id' => $this->getKey(),
|
||||
'anciennes_valeurs' => $anciennes,
|
||||
'nouvelles_valeurs' => $nouvelles,
|
||||
'ip_address' => request()->ip(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Concerns\Auditable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -10,7 +11,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Emotion extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use Auditable, HasFactory;
|
||||
|
||||
protected $table = 'emotions';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Concerns\Auditable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Feed extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use Auditable, HasFactory;
|
||||
|
||||
protected $table = 'feeds';
|
||||
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Concerns\Auditable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class SaisieTracker extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use Auditable, HasFactory;
|
||||
|
||||
protected $table = 'saisie_trackers';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Concerns\Auditable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@@ -13,7 +14,7 @@ use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||
|
||||
class Utilisateur extends Authenticatable implements JWTSubject
|
||||
{
|
||||
use HasFactory, Notifiable, SoftDeletes;
|
||||
use Auditable, HasFactory, Notifiable, SoftDeletes;
|
||||
|
||||
protected $table = 'utilisateurs';
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Audit;
|
||||
use App\Models\Feed;
|
||||
use App\Models\Role;
|
||||
use App\Models\Utilisateur;
|
||||
use Database\Seeders\RoleSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AuditTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected Utilisateur $auteur;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->seed(RoleSeeder::class);
|
||||
|
||||
$role = Role::where('nom', 'membre')->first();
|
||||
$this->auteur = Utilisateur::create([
|
||||
'nom' => 'Audit',
|
||||
'prenom' => 'Test',
|
||||
'email' => 'audit@test.fr',
|
||||
'password' => 'Password123!',
|
||||
'role_id' => $role->id,
|
||||
'consentement_rgpd' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
private function creerFeed(array $attributs = []): Feed
|
||||
{
|
||||
return Feed::create(array_merge([
|
||||
'titre' => 'Article test',
|
||||
'slug' => 'article-test-'.uniqid(),
|
||||
'contenu' => 'Contenu',
|
||||
'est_publie' => true,
|
||||
'ordre' => 1,
|
||||
'auteur_id' => $this->auteur->id,
|
||||
], $attributs));
|
||||
}
|
||||
|
||||
public function test_la_creation_d_un_feed_genere_un_audit(): void
|
||||
{
|
||||
$feed = $this->creerFeed();
|
||||
|
||||
$this->assertDatabaseHas('audits', [
|
||||
'action' => 'creation',
|
||||
'table_cible' => 'feeds',
|
||||
'enregistrement_id' => $feed->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_la_modification_genere_un_audit_avec_anciennes_et_nouvelles_valeurs(): void
|
||||
{
|
||||
$feed = $this->creerFeed(['titre' => 'Avant']);
|
||||
|
||||
$feed->update(['titre' => 'Apres']);
|
||||
|
||||
$audit = Audit::where('action', 'modification')
|
||||
->where('enregistrement_id', $feed->id)
|
||||
->first();
|
||||
|
||||
$this->assertNotNull($audit);
|
||||
$this->assertSame('Avant', $audit->anciennes_valeurs['titre']);
|
||||
$this->assertSame('Apres', $audit->nouvelles_valeurs['titre']);
|
||||
}
|
||||
|
||||
public function test_la_suppression_genere_un_audit(): void
|
||||
{
|
||||
$feed = $this->creerFeed();
|
||||
$id = $feed->id;
|
||||
$feed->delete();
|
||||
|
||||
$this->assertDatabaseHas('audits', [
|
||||
'action' => 'suppression',
|
||||
'table_cible' => 'feeds',
|
||||
'enregistrement_id' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_le_mot_de_passe_n_est_pas_stocke_dans_l_audit(): void
|
||||
{
|
||||
$audit = Audit::where('table_cible', 'utilisateurs')
|
||||
->where('enregistrement_id', $this->auteur->id)
|
||||
->first();
|
||||
|
||||
$this->assertNotNull($audit);
|
||||
$this->assertArrayNotHasKey('password', $audit->nouvelles_valeurs);
|
||||
$this->assertArrayNotHasKey('remember_token', $audit->nouvelles_valeurs);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user