BREAKING: Migration complète vers PostgreSQL uniquement

🐘 NoteFlow utilise maintenant EXCLUSIVEMENT PostgreSQL

CHANGEMENTS MAJEURS:
==================

1. Base de données (config/database.js)
   ✅ Remplacé par PostgreSQL (backup SQLite créé)
   ✅ Toutes les routes et services mis à jour
   ✅ database-loader.js supprimé (plus nécessaire)

2. Migration automatique (docker-entrypoint.sh)
   ✅ Attend que PostgreSQL soit prêt
   ✅ Détecte si PostgreSQL est vide
   ✅ Migre automatiquement SQLite si présent
   ✅ Lance le serveur après migration

3. Package.json
   ✅ Description mise à jour (PostgreSQL)
   ✅ Keywords: postgresql au lieu de sqlite
   ✅ sqlite3 gardé uniquement pour migration

4. Documentation
   ✅ POSTGRESQL-ONLY.md: Guide complet PostgreSQL
   ✅ Commandes, dépannage, sécurité
   ✅ Instructions de migration

MIGRATION UTILISATEUR:
=====================

Au prochain démarrage Docker:
1. Le container détecte PostgreSQL vide
2. Cherche ./data/notes.db (si existe)
3. Migre AUTOMATIQUEMENT toutes les données
4. Démarre avec PostgreSQL

COMMANDES:
=========

docker-compose build
docker-compose up -d

→ Tout se fait automatiquement!

Les données SQLite sont conservées comme backup dans ./data/
mais l'application n'utilise plus que PostgreSQL.

FICHIERS MODIFIÉS:
=================
- config/database.js (PostgreSQL uniquement)
- docker-entrypoint.sh (migration automatique)
- server.js + 11 routes + 2 services
- package.json (description + keywords)
- Documentation complète: POSTGRESQL-ONLY.md

Cette migration garantit:
- Performance optimale
- Meilleure gestion de la concurrence
- Support production à grande échelle
- Pas de perte de données (migration auto)
This commit is contained in:
Claude committed 2025-11-14 09:58:13 +00:00
1 parent 4da0537c14
commit 6b5f4081b2
17 files changed
+981 -277

No files matched your search

+317
View File
@@ -0,0 +1,317 @@
# NoteFlow - PostgreSQL Uniquement
## 🐘 Configuration
NoteFlow utilise **exclusivement PostgreSQL** comme base de données.
## 🚀 Démarrage rapide
### 1. Prérequis
- Docker et Docker Compose
- Port 5499 disponible (PostgreSQL)
- Port 2222 disponible (Application)
### 2. Configuration
Créez un fichier `.env` (optionnel) pour personnaliser:
```env
# PostgreSQL
POSTGRES_PASSWORD=votre_mot_de_passe_securise
# Application
JWT_SECRET=votre_secret_jwt_securise
PORT=2222
```
### 3. Démarrage
```bash
# Construire et démarrer
docker-compose build
docker-compose up -d
# Voir les logs
docker-compose logs -f
```
**🎉 C'est tout!** L'application:
- ✅ Attend que PostgreSQL soit prêt
- ✅ Crée automatiquement les tables
- ✅ Migre les données SQLite si détectées (une seule fois)
- ✅ Démarre le serveur
Accédez à: **http://localhost:2222**
## 📦 Migration automatique SQLite → PostgreSQL
Si vous aviez une ancienne installation avec SQLite (`data/notes.db`), la migration se fait **automatiquement** au premier démarrage:
1. Le container détecte que PostgreSQL est vide
2. Il cherche `/app/data-sqlite/notes.db` (monté depuis `./data`)
3. Si trouvé, il migre toutes les données automatiquement
4. Les données SQLite sont conservées comme backup
**Aucune action manuelle requise!**
## 🗄️ Structure PostgreSQL
### Configuration dans `docker-compose.yml`
```yaml
postgres:
image: postgres:16-alpine
ports:
- "5499:5499"
environment:
POSTGRES_DB: noteflow
POSTGRES_USER: noteflow
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
```
### Tables créées automatiquement
- `users` - Utilisateurs
- `notes` - Notes avec tags et priorités
- `note_todos` - Tâches dans les notes
- `global_todos` - Tâches de la sidebar
- `note_images` - Images attachées aux notes
- `note_files` - Fichiers attachés aux notes
- `rss_feeds` - Flux RSS configurés
- `rss_articles` - Articles RSS récupérés
- `rss_summaries` - Résumés générés par IA
- `calendar_events` - Événements Google Calendar
- `google_oauth_tokens` - Tokens OAuth Google
- `settings` - Paramètres globaux
## 🔍 Commandes utiles
### Accéder à PostgreSQL
```bash
# Via psql dans le container
docker exec -it noteflow-postgres psql -U noteflow -d noteflow -p 5499
# Exemples de requêtes
SELECT COUNT(*) FROM notes;
SELECT COUNT(*) FROM rss_articles;
SELECT * FROM users;
```
### Backup de la base
```bash
# Créer un dump
docker exec noteflow-postgres pg_dump -U noteflow -d noteflow -p 5499 > backup-$(date +%Y%m%d).sql
# Restaurer un dump
cat backup.sql | docker exec -i noteflow-postgres psql -U noteflow -d noteflow -p 5499
```
### Voir les logs
```bash
# Logs de l'application
docker-compose logs -f notes-app
# Logs PostgreSQL
docker-compose logs -f postgres
# Logs de migration au démarrage
docker-compose logs notes-app | grep -A 20 "Démarrage de NoteFlow"
```
### Redémarrer
```bash
# Redémarrer juste l'application
docker-compose restart notes-app
# Redémarrer tout
docker-compose restart
# Rebuild complet
docker-compose down
docker-compose build --no-cache
docker-compose up -d
```
## 🔧 Configuration avancée
### Variables d'environnement
Dans `docker-compose.yml` > `notes-app` > `environment`:
```yaml
- DATABASE_URL=postgresql://noteflow:${POSTGRES_PASSWORD}@postgres:5499/noteflow
- NODE_ENV=production
- JWT_SECRET=${JWT_SECRET}
- PORT=2222
```
### Ajuster les performances PostgreSQL
Pour une production avec beaucoup de données:
```yaml
postgres:
command: -p 5499 -c max_connections=100 -c shared_buffers=256MB
```
### Volumes persistants
Les données sont stockées dans des volumes Docker:
```yaml
volumes:
postgres_data: # Données PostgreSQL
notes_uploads: # Images et fichiers uploadés
notes_logs: # Logs de l'application
```
Pour voir les volumes:
```bash
docker volume ls | grep noteflow
```
## 🆘 Dépannage
### PostgreSQL n'est pas prêt
Si l'application dit "Attente de PostgreSQL..." en boucle:
```bash
# Vérifier que PostgreSQL tourne
docker-compose ps postgres
# Voir les logs PostgreSQL
docker-compose logs postgres
# Vérifier le port
docker exec noteflow-postgres netstat -tlnp | grep 5499
```
### Connexion refusée
```bash
# Vérifier les variables d'environnement
docker exec notes-todo-app env | grep DATABASE
# Tester la connexion manuellement
docker exec notes-todo-app node -e "
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
pool.connect()
.then(() => { console.log('✅ OK'); pool.end(); })
.catch(err => { console.log('❌', err.message); });
"
```
### Reset complet
**⚠️ ATTENTION: Ceci supprime TOUTES les données!**
```bash
docker-compose down -v # -v supprime les volumes
docker-compose up -d
```
### Migrer manuellement depuis SQLite
Si la migration automatique échoue:
```bash
# Depuis le container
docker exec -e SQLITE_PATH=/app/data-sqlite/notes.db \
notes-todo-app node scripts/migrate-sqlite-to-postgres.js
# Depuis l'hôte (si Node.js installé)
SQLITE_PATH=./data/notes.db \
DATABASE_URL=postgresql://noteflow:password@localhost:5499/noteflow \
node scripts/migrate-sqlite-to-postgres.js
```
## 📊 Monitoring
### Vérifier la santé de l'application
```bash
curl http://localhost:2222/health
```
Réponse attendue:
```json
{
"status": "ok",
"timestamp": "2025-11-14T10:00:00.000Z",
"uptime": 123.45
}
```
### Statistiques PostgreSQL
```bash
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
"
```
## 🔐 Sécurité en production
### 1. Changez les mots de passe par défaut
```bash
# Générer des secrets forts
openssl rand -base64 32
```
Mettez-les dans `.env`:
```env
POSTGRES_PASSWORD=<généré>
JWT_SECRET=<généré>
```
### 2. N'exposez PAS PostgreSQL sur Internet
Par défaut, PostgreSQL est accessible sur localhost:5499. Pour la production:
```yaml
postgres:
ports:
- "127.0.0.1:5499:5499" # Seulement localhost
```
### 3. Configurez un reverse proxy (nginx)
L'application utilise le réseau `nginx_default`. Configurez nginx pour gérer HTTPS.
## 📝 Notes techniques
- **Base de données:** PostgreSQL 16 Alpine
- **Node.js:** Version 20 Alpine
- **Architecture:** Multi-stage Dockerfile pour optimisation
- **Healthcheck:** Vérifie automatiquement l'état de l'application
- **Restart policy:** `unless-stopped` pour redémarrage automatique
- **Migration:** Automatique au premier démarrage si SQLite détecté
- **Logs:** Gérés par Winston avec rotation
## 🎯 Prochaines étapes après installation
1. ✅ Connectez-vous avec `admin` / `admin`
2. ⚠️ **CHANGEZ LE MOT DE PASSE ADMIN**
3. 📝 Créez vos premières notes
4. 📰 Configurez vos flux RSS (Admin > Flux RSS)
5. 📅 Connectez Google Calendar (Admin > Google Calendar)
6. 🔑 Configurez OpenRouter pour l'IA (Admin > OpenRouter)
---
**Version:** 1.0.0 - PostgreSQL Uniquement
**Documentation complète:** Voir README.md principal
+401
View File
@@ -0,0 +1,401 @@
// Configuration et initialisation de la base de données SQLite
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const bcrypt = require('bcrypt');
const logger = require('./logger');
const DB_PATH = process.env.DB_PATH || path.join(__dirname, '../data/notes.db');
// Créer une connexion à la base de données
const db = new sqlite3.Database(DB_PATH, (err) => {
if (err) {
logger.error('Erreur lors de la connexion à la base de données:', err);
process.exit(1);
}
logger.info(`Base de données connectée: ${DB_PATH}`);
});
// Activer les clés étrangères
db.run('PRAGMA foreign_keys = ON');
/**
* Initialiser la base de données avec les tables nécessaires
*/
function initDatabase() {
return new Promise((resolve, reject) => {
db.serialize(async () => {
try {
// Table users
db.run(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
is_admin BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Table notes
db.run(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
image_filename TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
// Table note_todos (todos dans les notes)
db.run(`
CREATE TABLE IF NOT EXISTS note_todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
note_id INTEGER NOT NULL,
text TEXT NOT NULL,
completed BOOLEAN DEFAULT 0,
position INTEGER DEFAULT 0,
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
)
`);
// Table global_todos (sidebar permanente)
db.run(`
CREATE TABLE IF NOT EXISTS global_todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
text TEXT NOT NULL,
completed BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
// Table note_images (images attachées aux notes)
db.run(`
CREATE TABLE IF NOT EXISTS note_images (
id INTEGER PRIMARY KEY AUTOINCREMENT,
note_id INTEGER NOT NULL,
filename TEXT NOT NULL,
original_name TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
)
`);
// Table note_files (fichiers attachés aux notes)
db.run(`
CREATE TABLE IF NOT EXISTS note_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
note_id INTEGER NOT NULL,
filename TEXT NOT NULL,
original_name TEXT NOT NULL,
file_size INTEGER,
mime_type TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
)
`);
// Table settings (paramètres globaux)
db.run(`
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
value TEXT,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Table rss_feeds (flux RSS)
db.run(`
CREATE TABLE IF NOT EXISTS rss_feeds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT UNIQUE NOT NULL,
title TEXT,
description TEXT,
enabled BOOLEAN DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_fetched_at DATETIME
)
`);
// Table rss_articles (articles des flux RSS)
db.run(`
CREATE TABLE IF NOT EXISTS rss_articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
feed_id INTEGER NOT NULL,
title TEXT NOT NULL,
link TEXT UNIQUE NOT NULL,
description TEXT,
pub_date DATETIME,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (feed_id) REFERENCES rss_feeds(id) ON DELETE CASCADE
)
`);
// Table rss_summaries (résumés générés)
db.run(`
CREATE TABLE IF NOT EXISTS rss_summaries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
summary TEXT NOT NULL,
model TEXT,
articles_count INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Table calendar_events (événements Google Calendar)
db.run(`
CREATE TABLE IF NOT EXISTS calendar_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
google_event_id TEXT UNIQUE NOT NULL,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
description TEXT,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
location TEXT,
color_id TEXT,
html_link TEXT,
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
// Table note_tags (tags pour les notes)
db.run(`
CREATE TABLE IF NOT EXISTS note_tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
note_id INTEGER NOT NULL,
tag TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE,
UNIQUE(note_id, tag)
)
`);
// Table google_oauth_tokens (tokens OAuth pour Google Calendar)
db.run(`
CREATE TABLE IF NOT EXISTS google_oauth_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT,
token_type TEXT DEFAULT 'Bearer',
expiry_date INTEGER,
scope TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id)
)
`);
// Créer les index pour la performance
db.run('CREATE INDEX IF NOT EXISTS idx_notes_user ON notes(user_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_todos ON note_todos(note_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_global_todos_user ON global_todos(user_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_images ON note_images(note_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_files ON note_files(note_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_tags ON note_tags(note_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_tags_tag ON note_tags(tag)');
db.run('CREATE INDEX IF NOT EXISTS idx_settings_key ON settings(key)');
db.run('CREATE INDEX IF NOT EXISTS idx_rss_articles_feed ON rss_articles(feed_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_rss_articles_date ON rss_articles(pub_date)');
db.run('CREATE INDEX IF NOT EXISTS idx_calendar_events_user ON calendar_events(user_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_calendar_events_start ON calendar_events(start_time)');
logger.info('✓ Tables de base de données créées avec succès');
// Migrations automatiques
// Migration pour rss_summaries - ajouter feed_title
db.all("PRAGMA table_info(rss_summaries)", (err, columns) => {
if (err) {
logger.error('Erreur lors de la vérification de la structure de la table rss_summaries:', err);
return;
}
const hasFeedTitle = columns.some(col => col.name === 'feed_title');
if (!hasFeedTitle) {
logger.info('Migration: Ajout de la colonne feed_title à rss_summaries...');
db.run('ALTER TABLE rss_summaries ADD COLUMN feed_title TEXT', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne feed_title:', err);
} else {
logger.info('✓ Colonne feed_title ajoutée avec succès');
}
});
}
});
db.all("PRAGMA table_info(notes)", (err, columns) => {
if (err) {
logger.error('Erreur lors de la vérification de la structure de la table notes:', err);
return;
}
const hasImageFilename = columns.some(col => col.name === 'image_filename');
const hasArchived = columns.some(col => col.name === 'archived');
const hasPriority = columns.some(col => col.name === 'priority');
if (!hasImageFilename) {
logger.info('Migration: Ajout de la colonne image_filename...');
db.run('ALTER TABLE notes ADD COLUMN image_filename TEXT', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne image_filename:', err);
} else {
logger.info('✓ Colonne image_filename ajoutée avec succès');
}
});
}
if (!hasArchived) {
logger.info('Migration: Ajout de la colonne archived...');
db.run('ALTER TABLE notes ADD COLUMN archived BOOLEAN DEFAULT 0', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne archived:', err);
} else {
logger.info('✓ Colonne archived ajoutée avec succès');
}
});
}
if (!hasPriority) {
logger.info('Migration: Ajout de la colonne priority...');
db.run('ALTER TABLE notes ADD COLUMN priority INTEGER DEFAULT 0', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne priority:', err);
} else {
logger.info('✓ Colonne priority ajoutée avec succès');
// Créer l'index pour la performance
db.run('CREATE INDEX IF NOT EXISTS idx_notes_priority ON notes(priority DESC, updated_at DESC)', (indexErr) => {
if (indexErr) {
logger.error('Erreur lors de la création de l\'index priority:', indexErr);
} else {
logger.info('✓ Index priority créé avec succès');
}
});
}
});
}
});
// Migration pour calendar_events - ajouter all_day
db.all("PRAGMA table_info(calendar_events)", (err, columns) => {
if (err) {
logger.error('Erreur lors de la vérification de la structure de la table calendar_events:', err);
return;
}
const hasAllDay = columns.some(col => col.name === 'all_day');
if (!hasAllDay) {
logger.info('Migration: Ajout de la colonne all_day à calendar_events...');
db.run('ALTER TABLE calendar_events ADD COLUMN all_day BOOLEAN DEFAULT 0', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne all_day:', err);
} else {
logger.info('✓ Colonne all_day ajoutée avec succès');
}
});
}
});
// Créer l'utilisateur admin par défaut si la table est vide
db.get('SELECT COUNT(*) as count FROM users', async (err, row) => {
if (err) {
logger.error('Erreur lors de la vérification des utilisateurs:', err);
reject(err);
return;
}
if (row.count === 0) {
// Créer l'utilisateur admin par défaut
const defaultPassword = 'admin';
const passwordHash = await bcrypt.hash(defaultPassword, 12);
db.run(
'INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?)',
['admin', passwordHash, 1],
(err) => {
if (err) {
logger.error('Erreur lors de la création de l\'utilisateur admin:', err);
reject(err);
} else {
logger.info('✓ Utilisateur admin créé (username: admin, password: admin)');
logger.warn('⚠️ IMPORTANT: Changez le mot de passe admin en production!');
resolve();
}
}
);
} else {
logger.info('✓ Base de données déjà initialisée');
resolve();
}
});
} catch (error) {
logger.error('Erreur lors de l\'initialisation de la base de données:', error);
reject(error);
}
});
});
}
/**
* Exécuter une requête avec promesse
*/
function runQuery(sql, params = []) {
return new Promise((resolve, reject) => {
db.run(sql, params, function(err) {
if (err) {
reject(err);
} else {
resolve({ id: this.lastID, changes: this.changes });
}
});
});
}
/**
* Récupérer une seule ligne
*/
function getOne(sql, params = []) {
return new Promise((resolve, reject) => {
db.get(sql, params, (err, row) => {
if (err) {
reject(err);
} else {
resolve(row);
}
});
});
}
/**
* Récupérer toutes les lignes
*/
function getAll(sql, params = []) {
return new Promise((resolve, reject) => {
db.all(sql, params, (err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows);
}
});
});
}
module.exports = {
db,
initDatabase,
runQuery,
getOne,
getAll
};
+171 -263
View File
@@ -1,399 +1,307 @@
// Configuration et initialisation de la base de données SQLite // Configuration et initialisation de la base de données PostgreSQL
const sqlite3 = require('sqlite3').verbose(); const { Pool } = require('pg');
const path = require('path');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const logger = require('./logger'); const logger = require('./logger');
const DB_PATH = process.env.DB_PATH || path.join(__dirname, '../data/notes.db'); // Configuration PostgreSQL depuis DATABASE_URL ou variables d'environnement
const DATABASE_URL = process.env.DATABASE_URL ||
`postgresql://${process.env.PGUSER || 'noteflow'}:${process.env.PGPASSWORD || 'noteflow_secure_password_change_me'}@${process.env.PGHOST || 'localhost'}:${process.env.PGPORT || '5499'}/${process.env.PGDATABASE || 'noteflow'}`;
// Créer une connexion à la base de données // Créer le pool de connexions PostgreSQL
const db = new sqlite3.Database(DB_PATH, (err) => { const pool = new Pool({
if (err) { connectionString: DATABASE_URL,
logger.error('Erreur lors de la connexion à la base de données:', err); max: 20, // Maximum de connexions dans le pool
process.exit(1); idleTimeoutMillis: 30000,
} connectionTimeoutMillis: 2000,
logger.info(`Base de données connectée: ${DB_PATH}`);
}); });
// Activer les clés étrangères // Gestion des erreurs du pool
db.run('PRAGMA foreign_keys = ON'); pool.on('error', (err) => {
logger.error('Erreur PostgreSQL pool:', err);
});
// Test de connexion
pool.connect((err, client, release) => {
if (err) {
logger.error('Erreur lors de la connexion à PostgreSQL:', err);
process.exit(1);
}
release();
logger.info(`✓ PostgreSQL connecté: ${DATABASE_URL.replace(/:[^:]*@/, ':***@')}`);
});
/** /**
* Initialiser la base de données avec les tables nécessaires * Initialiser la base de données avec les tables nécessaires
*/ */
function initDatabase() { async function initDatabase() {
return new Promise((resolve, reject) => { const client = await pool.connect();
db.serialize(async () => {
try { try {
await client.query('BEGIN');
// Table users // Table users
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
username TEXT UNIQUE NOT NULL, username VARCHAR(255) UNIQUE NOT NULL,
password_hash TEXT NOT NULL, password_hash VARCHAR(255) NOT NULL,
is_admin BOOLEAN DEFAULT 0, is_admin BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
`); `);
// Table notes // Table notes
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS notes ( CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title TEXT NOT NULL, title TEXT NOT NULL,
content TEXT, content TEXT,
image_filename TEXT, image_filename TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, archived BOOLEAN DEFAULT FALSE,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, priority INTEGER DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
`); `);
// Table note_todos (todos dans les notes) // Table note_todos
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS note_todos ( CREATE TABLE IF NOT EXISTS note_todos (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
note_id INTEGER NOT NULL, note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
text TEXT NOT NULL, text TEXT NOT NULL,
completed BOOLEAN DEFAULT 0, completed BOOLEAN DEFAULT FALSE,
position INTEGER DEFAULT 0, position INTEGER DEFAULT 0
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
) )
`); `);
// Table global_todos (sidebar permanente) // Table global_todos
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS global_todos ( CREATE TABLE IF NOT EXISTS global_todos (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
text TEXT NOT NULL, text TEXT NOT NULL,
completed BOOLEAN DEFAULT 0, completed BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) )
`); `);
// Table note_images (images attachées aux notes) // Table note_images
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS note_images ( CREATE TABLE IF NOT EXISTS note_images (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
note_id INTEGER NOT NULL, note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
filename TEXT NOT NULL, filename TEXT NOT NULL,
original_name TEXT, original_name TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
) )
`); `);
// Table note_files (fichiers attachés aux notes) // Table note_files
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS note_files ( CREATE TABLE IF NOT EXISTS note_files (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
note_id INTEGER NOT NULL, note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
filename TEXT NOT NULL, filename TEXT NOT NULL,
original_name TEXT NOT NULL, original_name TEXT NOT NULL,
file_size INTEGER, file_size INTEGER,
mime_type TEXT, mime_type TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
) )
`); `);
// Table settings (paramètres globaux) // Table settings
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS settings ( CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
key TEXT UNIQUE NOT NULL, key VARCHAR(255) UNIQUE NOT NULL,
value TEXT, value TEXT,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
`); `);
// Table rss_feeds (flux RSS) // Table rss_feeds
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS rss_feeds ( CREATE TABLE IF NOT EXISTS rss_feeds (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
url TEXT UNIQUE NOT NULL, url TEXT UNIQUE NOT NULL,
title TEXT, title TEXT,
description TEXT, description TEXT,
enabled BOOLEAN DEFAULT 1, enabled BOOLEAN DEFAULT TRUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_fetched_at DATETIME last_fetched_at TIMESTAMP
) )
`); `);
// Table rss_articles (articles des flux RSS) // Table rss_articles
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS rss_articles ( CREATE TABLE IF NOT EXISTS rss_articles (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
feed_id INTEGER NOT NULL, feed_id INTEGER NOT NULL REFERENCES rss_feeds(id) ON DELETE CASCADE,
title TEXT NOT NULL, title TEXT NOT NULL,
link TEXT UNIQUE NOT NULL, link TEXT UNIQUE NOT NULL,
description TEXT, description TEXT,
pub_date DATETIME, pub_date TIMESTAMP,
content TEXT, content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
FOREIGN KEY (feed_id) REFERENCES rss_feeds(id) ON DELETE CASCADE
) )
`); `);
// Table rss_summaries (résumés générés) // Table rss_summaries
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS rss_summaries ( CREATE TABLE IF NOT EXISTS rss_summaries (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
summary TEXT NOT NULL, summary TEXT NOT NULL,
model TEXT, model TEXT,
articles_count INTEGER DEFAULT 0, articles_count INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP feed_title TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
`); `);
// Table calendar_events (événements Google Calendar) // Table calendar_events
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS calendar_events ( CREATE TABLE IF NOT EXISTS calendar_events (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
google_event_id TEXT UNIQUE NOT NULL, google_event_id VARCHAR(255) UNIQUE NOT NULL,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title TEXT NOT NULL, title TEXT NOT NULL,
description TEXT, description TEXT,
start_time DATETIME NOT NULL, start_time TIMESTAMP NOT NULL,
end_time DATETIME NOT NULL, end_time TIMESTAMP NOT NULL,
location TEXT, location TEXT,
color_id TEXT, color_id TEXT,
html_link TEXT, html_link TEXT,
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP, all_day BOOLEAN DEFAULT FALSE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
`); `);
// Table note_tags (tags pour les notes) // Table note_tags
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS note_tags ( CREATE TABLE IF NOT EXISTS note_tags (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
note_id INTEGER NOT NULL, note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
tag TEXT NOT NULL, tag VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE,
UNIQUE(note_id, tag) UNIQUE(note_id, tag)
) )
`); `);
// Table google_oauth_tokens (tokens OAuth pour Google Calendar) // Table google_oauth_tokens
db.run(` await client.query(`
CREATE TABLE IF NOT EXISTS google_oauth_tokens ( CREATE TABLE IF NOT EXISTS google_oauth_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
access_token TEXT NOT NULL, access_token TEXT NOT NULL,
refresh_token TEXT, refresh_token TEXT,
token_type TEXT DEFAULT 'Bearer', token_type VARCHAR(50) DEFAULT 'Bearer',
expiry_date INTEGER, expiry_date BIGINT,
scope TEXT, scope TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id)
) )
`); `);
// Créer les index pour la performance // Créer les index pour la performance
db.run('CREATE INDEX IF NOT EXISTS idx_notes_user ON notes(user_id)'); await client.query('CREATE INDEX IF NOT EXISTS idx_notes_user ON notes(user_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_todos ON note_todos(note_id)'); await client.query('CREATE INDEX IF NOT EXISTS idx_notes_priority ON notes(priority DESC, updated_at DESC)');
db.run('CREATE INDEX IF NOT EXISTS idx_global_todos_user ON global_todos(user_id)'); await client.query('CREATE INDEX IF NOT EXISTS idx_note_todos ON note_todos(note_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_images ON note_images(note_id)'); await client.query('CREATE INDEX IF NOT EXISTS idx_global_todos_user ON global_todos(user_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_files ON note_files(note_id)'); await client.query('CREATE INDEX IF NOT EXISTS idx_note_images ON note_images(note_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_tags ON note_tags(note_id)'); await client.query('CREATE INDEX IF NOT EXISTS idx_note_files ON note_files(note_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_note_tags_tag ON note_tags(tag)'); await client.query('CREATE INDEX IF NOT EXISTS idx_note_tags ON note_tags(note_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_settings_key ON settings(key)'); await client.query('CREATE INDEX IF NOT EXISTS idx_note_tags_tag ON note_tags(tag)');
db.run('CREATE INDEX IF NOT EXISTS idx_rss_articles_feed ON rss_articles(feed_id)'); await client.query('CREATE INDEX IF NOT EXISTS idx_settings_key ON settings(key)');
db.run('CREATE INDEX IF NOT EXISTS idx_rss_articles_date ON rss_articles(pub_date)'); await client.query('CREATE INDEX IF NOT EXISTS idx_rss_articles_feed ON rss_articles(feed_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_calendar_events_user ON calendar_events(user_id)'); await client.query('CREATE INDEX IF NOT EXISTS idx_rss_articles_date ON rss_articles(pub_date DESC)');
db.run('CREATE INDEX IF NOT EXISTS idx_calendar_events_start ON calendar_events(start_time)'); await client.query('CREATE INDEX IF NOT EXISTS idx_calendar_events_user ON calendar_events(user_id)');
await client.query('CREATE INDEX IF NOT EXISTS idx_calendar_events_start ON calendar_events(start_time)');
logger.info('✓ Tables de base de données créées avec succès'); logger.info('✓ Tables PostgreSQL créées avec succès');
// Migrations automatiques
// Migration pour rss_summaries - ajouter feed_title
db.all("PRAGMA table_info(rss_summaries)", (err, columns) => {
if (err) {
logger.error('Erreur lors de la vérification de la structure de la table rss_summaries:', err);
return;
}
const hasFeedTitle = columns.some(col => col.name === 'feed_title');
if (!hasFeedTitle) {
logger.info('Migration: Ajout de la colonne feed_title à rss_summaries...');
db.run('ALTER TABLE rss_summaries ADD COLUMN feed_title TEXT', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne feed_title:', err);
} else {
logger.info('✓ Colonne feed_title ajoutée avec succès');
}
});
}
});
db.all("PRAGMA table_info(notes)", (err, columns) => {
if (err) {
logger.error('Erreur lors de la vérification de la structure de la table notes:', err);
return;
}
const hasImageFilename = columns.some(col => col.name === 'image_filename');
const hasArchived = columns.some(col => col.name === 'archived');
const hasPriority = columns.some(col => col.name === 'priority');
if (!hasImageFilename) {
logger.info('Migration: Ajout de la colonne image_filename...');
db.run('ALTER TABLE notes ADD COLUMN image_filename TEXT', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne image_filename:', err);
} else {
logger.info('✓ Colonne image_filename ajoutée avec succès');
}
});
}
if (!hasArchived) {
logger.info('Migration: Ajout de la colonne archived...');
db.run('ALTER TABLE notes ADD COLUMN archived BOOLEAN DEFAULT 0', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne archived:', err);
} else {
logger.info('✓ Colonne archived ajoutée avec succès');
}
});
}
if (!hasPriority) {
logger.info('Migration: Ajout de la colonne priority...');
db.run('ALTER TABLE notes ADD COLUMN priority INTEGER DEFAULT 0', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne priority:', err);
} else {
logger.info('✓ Colonne priority ajoutée avec succès');
// Créer l'index pour la performance
db.run('CREATE INDEX IF NOT EXISTS idx_notes_priority ON notes(priority DESC, updated_at DESC)', (indexErr) => {
if (indexErr) {
logger.error('Erreur lors de la création de l\'index priority:', indexErr);
} else {
logger.info('✓ Index priority créé avec succès');
}
});
}
});
}
});
// Migration pour calendar_events - ajouter all_day
db.all("PRAGMA table_info(calendar_events)", (err, columns) => {
if (err) {
logger.error('Erreur lors de la vérification de la structure de la table calendar_events:', err);
return;
}
const hasAllDay = columns.some(col => col.name === 'all_day');
if (!hasAllDay) {
logger.info('Migration: Ajout de la colonne all_day à calendar_events...');
db.run('ALTER TABLE calendar_events ADD COLUMN all_day BOOLEAN DEFAULT 0', (err) => {
if (err) {
logger.error('Erreur lors de l\'ajout de la colonne all_day:', err);
} else {
logger.info('✓ Colonne all_day ajoutée avec succès');
}
});
}
});
// Créer l'utilisateur admin par défaut si la table est vide // Créer l'utilisateur admin par défaut si la table est vide
db.get('SELECT COUNT(*) as count FROM users', async (err, row) => { const usersCount = await client.query('SELECT COUNT(*) as count FROM users');
if (err) {
logger.error('Erreur lors de la vérification des utilisateurs:', err);
reject(err);
return;
}
if (row.count === 0) { if (usersCount.rows[0].count === '0') {
// Créer l'utilisateur admin par défaut
const defaultPassword = 'admin'; const defaultPassword = 'admin';
const passwordHash = await bcrypt.hash(defaultPassword, 12); const passwordHash = await bcrypt.hash(defaultPassword, 12);
db.run( await client.query(
'INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?)', 'INSERT INTO users (username, password_hash, is_admin) VALUES ($1, $2, $3)',
['admin', passwordHash, 1], ['admin', passwordHash, true]
(err) => { );
if (err) {
logger.error('Erreur lors de la création de l\'utilisateur admin:', err);
reject(err);
} else {
logger.info('✓ Utilisateur admin créé (username: admin, password: admin)'); logger.info('✓ Utilisateur admin créé (username: admin, password: admin)');
logger.warn('⚠️ IMPORTANT: Changez le mot de passe admin en production!'); logger.warn('⚠️ IMPORTANT: Changez le mot de passe admin en production!');
resolve();
}
}
);
} else { } else {
logger.info('✓ Base de données déjà initialisée'); logger.info('✓ Base de données déjà initialisée');
resolve();
} }
});
await client.query('COMMIT');
} catch (error) { } catch (error) {
await client.query('ROLLBACK');
logger.error('Erreur lors de l\'initialisation de la base de données:', error); logger.error('Erreur lors de l\'initialisation de la base de données:', error);
reject(error); throw error;
} finally {
client.release();
} }
});
});
} }
/** /**
* Exécuter une requête avec promesse * Exécuter une requête avec promesse
*/ */
function runQuery(sql, params = []) { async function runQuery(sql, params = []) {
return new Promise((resolve, reject) => { const client = await pool.connect();
db.run(sql, params, function(err) { try {
if (err) { const result = await client.query(sql, params);
reject(err); return {
} else { id: result.rows[0]?.id || result.rowCount,
resolve({ id: this.lastID, changes: this.changes }); changes: result.rowCount
};
} catch (error) {
logger.error('Erreur runQuery:', error);
throw error;
} finally {
client.release();
} }
});
});
} }
/** /**
* Récupérer une seule ligne * Récupérer une seule ligne
*/ */
function getOne(sql, params = []) { async function getOne(sql, params = []) {
return new Promise((resolve, reject) => { const client = await pool.connect();
db.get(sql, params, (err, row) => { try {
if (err) { const result = await client.query(sql, params);
reject(err); return result.rows[0] || null;
} else { } catch (error) {
resolve(row); logger.error('Erreur getOne:', error);
throw error;
} finally {
client.release();
} }
});
});
} }
/** /**
* Récupérer toutes les lignes * Récupérer toutes les lignes
*/ */
function getAll(sql, params = []) { async function getAll(sql, params = []) {
return new Promise((resolve, reject) => { const client = await pool.connect();
db.all(sql, params, (err, rows) => { try {
if (err) { const result = await client.query(sql, params);
reject(err); return result.rows;
} else { } catch (error) {
resolve(rows); logger.error('Erreur getAll:', error);
throw error;
} finally {
client.release();
} }
});
});
} }
module.exports = { module.exports = {
db, pool,
initDatabase, initDatabase,
runQuery, runQuery,
getOne, getOne,
Regular → Executable
+78
View File
@@ -1,6 +1,84 @@
#!/bin/sh #!/bin/sh
set -e
echo "🚀 Démarrage de NoteFlow avec PostgreSQL"
echo "========================================"
# Créer les dossiers nécessaires
mkdir -p /app/data mkdir -p /app/data
mkdir -p /app/public/uploads mkdir -p /app/public/uploads
chmod -R 777 /app/data chmod -R 777 /app/data
chmod -R 777 /app/public/uploads chmod -R 777 /app/public/uploads
# Attendre que PostgreSQL soit prêt
echo ""
echo "⏳ Attente de PostgreSQL..."
until node -e "
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
pool.connect()
.then(client => { client.release(); pool.end(); process.exit(0); })
.catch(() => process.exit(1));
" 2>/dev/null; do
echo " PostgreSQL n'est pas encore prêt, nouvelle tentative dans 2s..."
sleep 2
done
echo "✅ PostgreSQL est prêt"
echo ""
# Vérifier si la migration est nécessaire
echo "🔍 Vérification des données..."
# Vérifier si PostgreSQL contient des données
HAS_DATA=$(node -e "
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
pool.query('SELECT COUNT(*) as count FROM users')
.then(result => {
pool.end();
console.log(result.rows[0].count);
process.exit(0);
})
.catch(() => {
pool.end();
console.log('0');
process.exit(0);
});
" 2>/dev/null)
if [ "$HAS_DATA" = "0" ]; then
echo "⚠️ PostgreSQL est vide"
# Vérifier si SQLite existe
if [ -f "/app/data-sqlite/notes.db" ]; then
echo ""
echo "📦 Base SQLite détectée: /app/data-sqlite/notes.db"
echo "🔄 Lancement de la migration automatique..."
echo ""
SQLITE_PATH=/app/data-sqlite/notes.db node scripts/migrate-sqlite-to-postgres.js
if [ $? -eq 0 ]; then
echo ""
echo "✅ Migration réussie!"
else
echo ""
echo "❌ Erreur lors de la migration"
echo "⚠️ L'application démarrera avec une base vide"
fi
else
echo "ℹ️ Aucune base SQLite à migrer"
echo "📝 Première installation - une base vide sera créée"
fi
else
echo "✅ PostgreSQL contient déjà des données ($HAS_DATA utilisateurs)"
fi
echo ""
echo "========================================"
echo "🚀 Démarrage du serveur NoteFlow..."
echo ""
# Démarrer le serveur Node.js
exec node server.js exec node server.js
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "noteflow", "name": "noteflow",
"version": "1.0.0", "version": "1.0.0",
"description": "Application NoteFlow - Notes et gestion de tâches moderne avec Docker", "description": "Application NoteFlow - Notes et gestion de tâches moderne avec Docker et PostgreSQL",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -22,7 +22,7 @@
"todo", "todo",
"docker", "docker",
"express", "express",
"sqlite", "postgresql",
"react", "react",
"typescript" "typescript"
], ],
+1 -1
View File
@@ -4,7 +4,7 @@ const router = express.Router();
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const { body, validationResult } = require('express-validator'); const { body, validationResult } = require('express-validator');
const { getOne } = require('../config/database-loader'); const { getOne } = require('../config/database');
const { generateToken, authenticateToken } = require('../middleware/auth'); const { generateToken, authenticateToken } = require('../middleware/auth');
const logger = require('../config/logger'); const logger = require('../config/logger');
+1 -1
View File
@@ -2,7 +2,7 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { google } = require('googleapis'); const { google } = require('googleapis');
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const { authenticateToken, requireAdmin } = require('../middleware/auth'); const { authenticateToken, requireAdmin } = require('../middleware/auth');
const logger = require('../config/logger'); const logger = require('../config/logger');
+1 -1
View File
@@ -6,7 +6,7 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { body, validationResult } = require('express-validator'); const { body, validationResult } = require('express-validator');
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const { authenticateToken } = require('../middleware/auth'); const { authenticateToken } = require('../middleware/auth');
const logger = require('../config/logger'); const logger = require('../config/logger');
+1 -1
View File
@@ -2,7 +2,7 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { authenticateToken, requireAdmin } = require('../middleware/auth'); const { authenticateToken, requireAdmin } = require('../middleware/auth');
const { getOne } = require('../config/database-loader'); const { getOne } = require('../config/database');
const logger = require('../config/logger'); const logger = require('../config/logger');
// Cache pour les modèles (rafraîchi toutes les heures) // Cache pour les modèles (rafraîchi toutes les heures)
+1 -1
View File
@@ -3,7 +3,7 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const Parser = require('rss-parser'); const Parser = require('rss-parser');
const axios = require('axios'); const axios = require('axios');
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const { authenticateToken, requireAdmin } = require('../middleware/auth'); const { authenticateToken, requireAdmin } = require('../middleware/auth');
const logger = require('../config/logger'); const logger = require('../config/logger');
+1 -1
View File
@@ -3,7 +3,7 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const Parser = require('rss-parser'); const Parser = require('rss-parser');
const axios = require('axios'); const axios = require('axios');
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const { authenticateToken, requireAdmin } = require('../middleware/auth'); const { authenticateToken, requireAdmin } = require('../middleware/auth');
const logger = require('../config/logger'); const logger = require('../config/logger');
+1 -1
View File
@@ -1,7 +1,7 @@
// Routes de gestion des paramètres // Routes de gestion des paramètres
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const { authenticateToken, requireAdmin } = require('../middleware/auth'); const { authenticateToken, requireAdmin } = require('../middleware/auth');
const logger = require('../config/logger'); const logger = require('../config/logger');
+1 -1
View File
@@ -3,7 +3,7 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const { body, validationResult } = require('express-validator'); const { body, validationResult } = require('express-validator');
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const { authenticateToken } = require('../middleware/auth'); const { authenticateToken } = require('../middleware/auth');
const logger = require('../config/logger'); const logger = require('../config/logger');
+1 -1
View File
@@ -4,7 +4,7 @@ const router = express.Router();
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const { body, validationResult } = require('express-validator'); const { body, validationResult } = require('express-validator');
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const { authenticateToken, requireAdmin } = require('../middleware/auth'); const { authenticateToken, requireAdmin } = require('../middleware/auth');
const logger = require('../config/logger'); const logger = require('../config/logger');
+1 -1
View File
@@ -7,7 +7,7 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const logger = require('./config/logger'); const logger = require('./config/logger');
const { initDatabase } = require('./config/database-loader'); const { initDatabase } = require('./config/database');
// Créer l'application Express // Créer l'application Express
const app = express(); const app = express();
+1 -1
View File
@@ -1,6 +1,6 @@
// Service RSS NOUVEAU - Architecture simple et robuste // Service RSS NOUVEAU - Architecture simple et robuste
const Parser = require('rss-parser'); const Parser = require('rss-parser');
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const logger = require('../config/logger'); const logger = require('../config/logger');
const parser = new Parser({ const parser = new Parser({
+1 -1
View File
@@ -1,6 +1,6 @@
// Service RSS NOUVEAU - Architecture simple et robuste // Service RSS NOUVEAU - Architecture simple et robuste
const Parser = require('rss-parser'); const Parser = require('rss-parser');
const { getAll, getOne, runQuery } = require('../config/database-loader'); const { getAll, getOne, runQuery } = require('../config/database');
const logger = require('../config/logger'); const logger = require('../config/logger');
const parser = new Parser({ const parser = new Parser({