mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-23 15:18:16 +02:00
Merge pull request #114 from R0m1k3/claude/add-database-cleanup-017GyBxnCE3LSomAPinGgDT5
Claude/add database cleanup 017 gy bxn ce3 l som a pin gg dt5
This commit is contained in:
4 files changed
+550
-17
No files matched your search
@@ -0,0 +1,295 @@
|
|||||||
|
# Système de Migrations Automatiques
|
||||||
|
|
||||||
|
Ce document décrit le système de migrations automatiques de NoteFlow.
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
Le système de migrations automatiques s'exécute **à chaque démarrage de l'application** pour s'assurer que le schéma de la base de données est à jour. Cela permet de :
|
||||||
|
|
||||||
|
- ✅ Déployer automatiquement les changements de schéma
|
||||||
|
- ✅ Éviter les erreurs de "colonne inexistante"
|
||||||
|
- ✅ Simplifier le processus de mise à jour
|
||||||
|
- ✅ Garantir la cohérence entre le code et la base de données
|
||||||
|
|
||||||
|
## Comment ça fonctionne
|
||||||
|
|
||||||
|
### 1. Démarrage de l'application
|
||||||
|
|
||||||
|
Lors du démarrage, l'application exécute dans l'ordre :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
1. initDatabase() // Crée les tables de base
|
||||||
|
2. autoMigrate() // Applique les migrations manquantes
|
||||||
|
3. startSchedulers() // Démarre les services
|
||||||
|
4. listen() // Lance le serveur
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Vérification intelligente
|
||||||
|
|
||||||
|
Le script `scripts/auto-migrate.js` vérifie pour chaque migration :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Est-ce que la colonne existe déjà ?
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='ma_table' AND column_name='ma_colonne'
|
||||||
|
```
|
||||||
|
|
||||||
|
Si la colonne n'existe pas, elle est créée automatiquement.
|
||||||
|
|
||||||
|
### 3. Sécurité
|
||||||
|
|
||||||
|
- **Idempotent** : Les migrations peuvent être exécutées plusieurs fois sans problème
|
||||||
|
- **Transactionnel** : Utilise `BEGIN`/`COMMIT`/`ROLLBACK`
|
||||||
|
- **Non-bloquant** : En cas d'erreur, l'application démarre quand même (mais log l'erreur)
|
||||||
|
- **Double protection** : Le code API a aussi une rétrocompatibilité en cas d'échec
|
||||||
|
|
||||||
|
## Migrations actuelles
|
||||||
|
|
||||||
|
### Migration 1 : Champs de tracking pour purge
|
||||||
|
|
||||||
|
**Ajouté** : v1.1.0
|
||||||
|
|
||||||
|
**Objectif** : Permettre la purge automatique des données obsolètes
|
||||||
|
|
||||||
|
**Changements** :
|
||||||
|
- `notes.archived_at` - Date d'archivage d'une note
|
||||||
|
- `global_todos.completed_at` - Date de complétion d'une tâche globale
|
||||||
|
- `note_todos.completed_at` - Date de complétion d'une tâche de note
|
||||||
|
- `note_todos.created_at` - Date de création d'une tâche de note
|
||||||
|
- Triggers PostgreSQL pour mise à jour automatique
|
||||||
|
|
||||||
|
**Script manuel** (si besoin) :
|
||||||
|
```bash
|
||||||
|
npm run db:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migration 2 : Champ priority pour tâches
|
||||||
|
|
||||||
|
**Ajouté** : v1.2.0
|
||||||
|
|
||||||
|
**Objectif** : Permettre de marquer les tâches importantes avec une étoile
|
||||||
|
|
||||||
|
**Changements** :
|
||||||
|
- `global_todos.priority` - Indicateur de priorité (BOOLEAN)
|
||||||
|
- `note_todos.priority` - Indicateur de priorité (BOOLEAN)
|
||||||
|
- Index pour optimiser le tri par priorité
|
||||||
|
|
||||||
|
**Script manuel** (si besoin) :
|
||||||
|
```bash
|
||||||
|
npm run db:migrate:priority
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ajouter une nouvelle migration
|
||||||
|
|
||||||
|
Pour ajouter une nouvelle migration au système automatique :
|
||||||
|
|
||||||
|
### 1. Créer le script manuel (optionnel)
|
||||||
|
|
||||||
|
Créez un fichier dans `scripts/` pour permettre l'exécution manuelle :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// scripts/add-mon-champ.js
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
// ... votre migration
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Ajouter au script auto-migrate.js
|
||||||
|
|
||||||
|
Éditez `scripts/auto-migrate.js` et ajoutez votre migration :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Migration 3: Votre nouvelle fonctionnalité
|
||||||
|
logger.info(' Vérification: mon nouveau champ...');
|
||||||
|
|
||||||
|
const monChampExists = await client.query(`
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='ma_table' AND column_name='mon_champ'
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (monChampExists.rows.length === 0) {
|
||||||
|
logger.info(' → Ajout du champ mon_champ à ma_table');
|
||||||
|
await client.query(`ALTER TABLE ma_table ADD COLUMN mon_champ TYPE DEFAULT valeur`);
|
||||||
|
|
||||||
|
// Mise à jour des données existantes si nécessaire
|
||||||
|
await client.query(`UPDATE ma_table SET mon_champ = ... WHERE ...`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Tester localement
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Démarrer l'application
|
||||||
|
npm run start
|
||||||
|
|
||||||
|
# Vérifier les logs
|
||||||
|
# Vous devriez voir : "✓ Migrations automatiques appliquées"
|
||||||
|
|
||||||
|
# Vérifier que le champ existe
|
||||||
|
psql $DATABASE_URL -c "\d ma_table"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Ajouter un script npm (optionnel)
|
||||||
|
|
||||||
|
Dans `package.json` :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"db:migrate:mon-feature": "node scripts/add-mon-champ.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dépannage
|
||||||
|
|
||||||
|
### La migration ne s'exécute pas
|
||||||
|
|
||||||
|
**Vérifiez les logs au démarrage** :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker logs noteflow-notes-app-1 | grep -i migration
|
||||||
|
```
|
||||||
|
|
||||||
|
Vous devriez voir :
|
||||||
|
```
|
||||||
|
✓ Base de données initialisée avec succès
|
||||||
|
🔄 Vérification des migrations...
|
||||||
|
Vérification: champs de tracking pour purge...
|
||||||
|
Vérification: champ priority pour tâches...
|
||||||
|
✅ Migrations automatiques terminées avec succès
|
||||||
|
✓ Migrations automatiques appliquées
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreur lors de la migration
|
||||||
|
|
||||||
|
Les erreurs de migration sont **loggées mais ne bloquent pas le démarrage** :
|
||||||
|
|
||||||
|
```
|
||||||
|
❌ Erreur lors des migrations automatiques: ...
|
||||||
|
✓ Scheduler RSS démarré
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour corriger :
|
||||||
|
|
||||||
|
1. Identifiez l'erreur dans les logs
|
||||||
|
2. Corrigez le problème (droits, syntaxe SQL, etc.)
|
||||||
|
3. Redémarrez l'application
|
||||||
|
|
||||||
|
### Forcer une migration manuelle
|
||||||
|
|
||||||
|
Si vous préférez exécuter manuellement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dans le conteneur Docker
|
||||||
|
docker exec -it noteflow-notes-app-1 node scripts/auto-migrate.js
|
||||||
|
|
||||||
|
# Ou avec npm
|
||||||
|
docker exec -it noteflow-notes-app-1 npm run db:migrate
|
||||||
|
docker exec -it noteflow-notes-app-1 npm run db:migrate:priority
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vérifier l'état des migrations
|
||||||
|
|
||||||
|
Utilisez les commandes SQL directement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it noteflow-postgres-1 psql -U noteflow noteflow
|
||||||
|
|
||||||
|
-- Vérifier les colonnes d'une table
|
||||||
|
\d notes
|
||||||
|
\d global_todos
|
||||||
|
\d note_todos
|
||||||
|
|
||||||
|
-- Vérifier une colonne spécifique
|
||||||
|
SELECT column_name, data_type, is_nullable
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='global_todos';
|
||||||
|
|
||||||
|
-- Vérifier les triggers
|
||||||
|
SELECT trigger_name, event_manipulation, event_object_table
|
||||||
|
FROM information_schema.triggers;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bonnes pratiques
|
||||||
|
|
||||||
|
### ✅ À faire
|
||||||
|
|
||||||
|
- **Toujours** utiliser `IF NOT EXISTS` / `IF EXISTS`
|
||||||
|
- **Toujours** tester en local avant de déployer
|
||||||
|
- **Toujours** ajouter des logs explicites
|
||||||
|
- **Toujours** gérer la rétrocompatibilité dans le code
|
||||||
|
- **Toujours** utiliser des transactions (`BEGIN`/`COMMIT`)
|
||||||
|
- **Toujours** mettre à jour cette documentation
|
||||||
|
|
||||||
|
### ❌ À éviter
|
||||||
|
|
||||||
|
- Supprimer des colonnes directement (préférer un soft-delete)
|
||||||
|
- Modifier le type d'une colonne avec données
|
||||||
|
- Faire des migrations lourdes au démarrage (>5 secondes)
|
||||||
|
- Oublier les valeurs par défaut pour les colonnes existantes
|
||||||
|
- Crasher l'application en cas d'erreur de migration
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
📁 noteflow/
|
||||||
|
├── 📁 scripts/
|
||||||
|
│ ├── auto-migrate.js ← Script principal (s'exécute au démarrage)
|
||||||
|
│ ├── add-cleanup-tracking-fields.js ← Migration manuelle 1
|
||||||
|
│ ├── add-priority-field.js ← Migration manuelle 2
|
||||||
|
│ └── ... ← Futures migrations
|
||||||
|
├── 📁 config/
|
||||||
|
│ └── database-postgres.js ← Schéma initial (CREATE TABLE)
|
||||||
|
├── server.js ← Appelle autoMigrate() au démarrage
|
||||||
|
└── package.json ← Scripts npm pour migrations manuelles
|
||||||
|
```
|
||||||
|
|
||||||
|
## Flux de démarrage
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ docker-compose up / npm start │
|
||||||
|
└──────────────────┬──────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ server.js: startServer() │
|
||||||
|
└──────────────────┬──────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ initDatabase() │
|
||||||
|
│ → Crée les tables de base (si n'existent pas) │
|
||||||
|
└──────────────────┬──────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ autoMigrate() │
|
||||||
|
│ → Vérifie et ajoute les colonnes manquantes │
|
||||||
|
│ → Crée les triggers │
|
||||||
|
│ → Crée les index │
|
||||||
|
└──────────────────┬──────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Démarrage des services │
|
||||||
|
│ → RSS Scheduler │
|
||||||
|
│ → Cleanup Scheduler │
|
||||||
|
└──────────────────┬──────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ app.listen() - Serveur prêt ! 🚀 │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Pour plus d'informations :
|
||||||
|
- Consultez les logs : `docker logs noteflow-notes-app-1`
|
||||||
|
- Exécutez manuellement : `node scripts/auto-migrate.js`
|
||||||
|
- Vérifiez la base : `psql $DATABASE_URL -c "\d"`
|
||||||
+46
-17
@@ -16,14 +16,31 @@ router.use(authenticateToken);
|
|||||||
*/
|
*/
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const todos = await getAll(`
|
// Essayer d'abord avec le champ priority (nouvelle version)
|
||||||
SELECT id, text, completed, priority, created_at
|
try {
|
||||||
FROM global_todos
|
const todos = await getAll(`
|
||||||
WHERE user_id = $1
|
SELECT id, text, completed, priority, created_at
|
||||||
ORDER BY priority DESC, created_at DESC
|
FROM global_todos
|
||||||
`, [req.user.id]);
|
WHERE user_id = $1
|
||||||
|
ORDER BY priority DESC, created_at DESC
|
||||||
|
`, [req.user.id]);
|
||||||
|
|
||||||
res.json(todos);
|
res.json(todos);
|
||||||
|
return;
|
||||||
|
} catch (priorityError) {
|
||||||
|
// Si le champ priority n'existe pas, utiliser l'ancienne requête
|
||||||
|
logger.info('Champ priority non trouvé, utilisation de la requête sans priority');
|
||||||
|
const todos = await getAll(`
|
||||||
|
SELECT id, text, completed, created_at
|
||||||
|
FROM global_todos
|
||||||
|
WHERE user_id = $1
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`, [req.user.id]);
|
||||||
|
|
||||||
|
// Ajouter priority=false par défaut pour compatibilité frontend
|
||||||
|
const todosWithPriority = todos.map(t => ({ ...t, priority: false }));
|
||||||
|
res.json(todosWithPriority);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Erreur lors de la récupération des todos:', error);
|
logger.error('Erreur lors de la récupération des todos:', error);
|
||||||
res.status(500).json({ error: 'Erreur serveur' });
|
res.status(500).json({ error: 'Erreur serveur' });
|
||||||
@@ -159,17 +176,29 @@ router.patch('/:id/toggle', async (req, res) => {
|
|||||||
router.patch('/:id/priority', async (req, res) => {
|
router.patch('/:id/priority', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Vérifier que le todo appartient à l'utilisateur
|
// Vérifier que le todo appartient à l'utilisateur
|
||||||
const todo = await getOne('SELECT id, priority FROM global_todos WHERE id = $1 AND user_id = $2', [req.params.id, req.user.id]);
|
try {
|
||||||
if (!todo) {
|
const todo = await getOne('SELECT id, priority FROM global_todos WHERE id = $1 AND user_id = $2', [req.params.id, req.user.id]);
|
||||||
return res.status(404).json({ error: 'Todo non trouvé' });
|
if (!todo) {
|
||||||
|
return res.status(404).json({ error: 'Todo non trouvé' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPriority = todo.priority ? 0 : 1;
|
||||||
|
await runQuery('UPDATE global_todos SET priority = $1 WHERE id = $2', [newPriority, req.params.id]);
|
||||||
|
|
||||||
|
logger.info(`Todo global ${newPriority ? 'marqué prioritaire' : 'démarqué prioritaire'} (ID: ${req.params.id}) par ${req.user.username}`);
|
||||||
|
|
||||||
|
res.json({ message: 'Priorité modifiée avec succès', priority: newPriority === 1 });
|
||||||
|
} catch (priorityError) {
|
||||||
|
// Si le champ priority n'existe pas encore, retourner un message d'erreur explicite
|
||||||
|
if (priorityError.message && priorityError.message.includes('priority')) {
|
||||||
|
logger.warn('Tentative d\'utilisation de la fonctionnalité priority sans migration');
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Fonctionnalité priority non disponible',
|
||||||
|
message: 'Veuillez exécuter la migration: npm run db:migrate:priority'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw priorityError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newPriority = todo.priority ? 0 : 1;
|
|
||||||
await runQuery('UPDATE global_todos SET priority = $1 WHERE id = $2', [newPriority, req.params.id]);
|
|
||||||
|
|
||||||
logger.info(`Todo global ${newPriority ? 'marqué prioritaire' : 'démarqué prioritaire'} (ID: ${req.params.id}) par ${req.user.username}`);
|
|
||||||
|
|
||||||
res.json({ message: 'Priorité modifiée avec succès', priority: newPriority === 1 });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Erreur lors du toggle de la priorité:', error);
|
logger.error('Erreur lors du toggle de la priorité:', error);
|
||||||
res.status(500).json({ error: 'Erreur serveur' });
|
res.status(500).json({ error: 'Erreur serveur' });
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script de migrations automatiques
|
||||||
|
* S'exécute au démarrage de l'application pour mettre à jour le schéma
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
const logger = require('../config/logger');
|
||||||
|
|
||||||
|
const DATABASE_URL = process.env.DATABASE_URL ||
|
||||||
|
`postgresql://${process.env.PGUSER || 'noteflow'}:${process.env.PGPASSWORD || 'noteflow_secure_password_change_me'}@${process.env.PGHOST || 'postgres'}:${process.env.PGPORT || '5499'}/${process.env.PGDATABASE || 'noteflow'}`;
|
||||||
|
|
||||||
|
async function autoMigrate() {
|
||||||
|
const pool = new Pool({ connectionString: DATABASE_URL });
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info('🔄 Vérification des migrations...');
|
||||||
|
|
||||||
|
const client = await pool.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
// Migration 1: Champs de tracking pour la purge (archived_at, completed_at)
|
||||||
|
logger.info(' Vérification: champs de tracking pour purge...');
|
||||||
|
|
||||||
|
// Vérifier si archived_at existe
|
||||||
|
const archivedAtExists = await client.query(`
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='notes' AND column_name='archived_at'
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (archivedAtExists.rows.length === 0) {
|
||||||
|
logger.info(' → Ajout du champ archived_at à notes');
|
||||||
|
await client.query(`ALTER TABLE notes ADD COLUMN archived_at TIMESTAMP`);
|
||||||
|
await client.query(`UPDATE notes SET archived_at = updated_at WHERE archived = TRUE AND archived_at IS NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si completed_at existe pour global_todos
|
||||||
|
const globalTodosCompletedAtExists = await client.query(`
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='global_todos' AND column_name='completed_at'
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (globalTodosCompletedAtExists.rows.length === 0) {
|
||||||
|
logger.info(' → Ajout du champ completed_at à global_todos');
|
||||||
|
await client.query(`ALTER TABLE global_todos ADD COLUMN completed_at TIMESTAMP`);
|
||||||
|
await client.query(`UPDATE global_todos SET completed_at = created_at WHERE completed = TRUE AND completed_at IS NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si note_todos a created_at et completed_at
|
||||||
|
const noteTodosCreatedAtExists = await client.query(`
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='note_todos' AND column_name='created_at'
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (noteTodosCreatedAtExists.rows.length === 0) {
|
||||||
|
logger.info(' → Ajout du champ created_at à note_todos');
|
||||||
|
await client.query(`ALTER TABLE note_todos ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteTodosCompletedAtExists = await client.query(`
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='note_todos' AND column_name='completed_at'
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (noteTodosCompletedAtExists.rows.length === 0) {
|
||||||
|
logger.info(' → Ajout du champ completed_at à note_todos');
|
||||||
|
await client.query(`ALTER TABLE note_todos ADD COLUMN completed_at TIMESTAMP`);
|
||||||
|
await client.query(`UPDATE note_todos SET completed_at = CURRENT_TIMESTAMP WHERE completed = TRUE AND completed_at IS NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer les triggers pour mise à jour automatique
|
||||||
|
await client.query(`
|
||||||
|
CREATE OR REPLACE FUNCTION update_notes_archived_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.archived = TRUE AND OLD.archived = FALSE THEN
|
||||||
|
NEW.archived_at = CURRENT_TIMESTAMP;
|
||||||
|
ELSIF NEW.archived = FALSE THEN
|
||||||
|
NEW.archived_at = NULL;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
DROP TRIGGER IF EXISTS trigger_notes_archived_at ON notes;
|
||||||
|
CREATE TRIGGER trigger_notes_archived_at
|
||||||
|
BEFORE UPDATE ON notes
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_notes_archived_at();
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE OR REPLACE FUNCTION update_global_todos_completed_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.completed = TRUE AND OLD.completed = FALSE THEN
|
||||||
|
NEW.completed_at = CURRENT_TIMESTAMP;
|
||||||
|
ELSIF NEW.completed = FALSE THEN
|
||||||
|
NEW.completed_at = NULL;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
DROP TRIGGER IF EXISTS trigger_global_todos_completed_at ON global_todos;
|
||||||
|
CREATE TRIGGER trigger_global_todos_completed_at
|
||||||
|
BEFORE UPDATE ON global_todos
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_global_todos_completed_at();
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE OR REPLACE FUNCTION update_note_todos_completed_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.completed = TRUE AND (OLD.completed IS NULL OR OLD.completed = FALSE) THEN
|
||||||
|
NEW.completed_at = CURRENT_TIMESTAMP;
|
||||||
|
ELSIF NEW.completed = FALSE THEN
|
||||||
|
NEW.completed_at = NULL;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
DROP TRIGGER IF EXISTS trigger_note_todos_completed_at ON note_todos;
|
||||||
|
CREATE TRIGGER trigger_note_todos_completed_at
|
||||||
|
BEFORE UPDATE ON note_todos
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_note_todos_completed_at();
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Migration 2: Champ priority pour les tâches
|
||||||
|
logger.info(' Vérification: champ priority pour tâches...');
|
||||||
|
|
||||||
|
const globalTodosPriorityExists = await client.query(`
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='global_todos' AND column_name='priority'
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (globalTodosPriorityExists.rows.length === 0) {
|
||||||
|
logger.info(' → Ajout du champ priority à global_todos');
|
||||||
|
await client.query(`ALTER TABLE global_todos ADD COLUMN priority BOOLEAN DEFAULT FALSE`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteTodosPriorityExists = await client.query(`
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name='note_todos' AND column_name='priority'
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (noteTodosPriorityExists.rows.length === 0) {
|
||||||
|
logger.info(' → Ajout du champ priority à note_todos');
|
||||||
|
await client.query(`ALTER TABLE note_todos ADD COLUMN priority BOOLEAN DEFAULT FALSE`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer les index pour performance
|
||||||
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_global_todos_priority ON global_todos(priority DESC, created_at DESC)`);
|
||||||
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_note_todos_priority ON note_todos(priority DESC, position)`);
|
||||||
|
|
||||||
|
await client.query('COMMIT');
|
||||||
|
|
||||||
|
logger.info('✅ Migrations automatiques terminées avec succès');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
logger.error('❌ Erreur lors des migrations automatiques:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Erreur fatale lors des migrations:', error);
|
||||||
|
// Ne pas crasher l'application, juste logger l'erreur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si appelé directement
|
||||||
|
if (require.main === module) {
|
||||||
|
autoMigrate().then(() => {
|
||||||
|
process.exit(0);
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { autoMigrate };
|
||||||
@@ -134,6 +134,11 @@ async function startServer() {
|
|||||||
await initDatabase();
|
await initDatabase();
|
||||||
logger.info('✓ Base de données initialisée avec succès');
|
logger.info('✓ Base de données initialisée avec succès');
|
||||||
|
|
||||||
|
// Exécuter les migrations automatiques
|
||||||
|
const { autoMigrate } = require('./scripts/auto-migrate');
|
||||||
|
await autoMigrate();
|
||||||
|
logger.info('✓ Migrations automatiques appliquées');
|
||||||
|
|
||||||
// Démarrer le scheduler RSS
|
// Démarrer le scheduler RSS
|
||||||
const rssScheduler = require('./services/rss-scheduler');
|
const rssScheduler = require('./services/rss-scheduler');
|
||||||
rssScheduler.startScheduler();
|
rssScheduler.startScheduler();
|
||||||
|
|||||||
Reference in new issue
Block a user