mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-20 05:28:22 +02:00
feat(rust) : serveur réécrit en Rust, Node retiré ; Calendrier en tête du menu (#571)
- Serveur Rust (server-rs/, axum + sqlx) : les 148 adresses de l'API, l'interface, les migrations de démarrage et les tâches de fond ; même comportement que Node (banc : 2027 cas, 0 écart hors écarts déclarés USERS-02, RAPPRO-21, R4, R5). - Node retiré : server/, dépendances serveur, image Docker sans Node (51 Mo), docker-compose à un seul service applicatif. - R4 : écritures d'un autre site refusées (Sec-Fetch-Site / Origin). - R5 : « aujourd'hui » et sauvegarde automatique à l'heure de Paris. - R6 : mainteneur dans .github/CODEOWNERS. - Menu latéral : Accueil puis Calendrier en tête. - Plan et état : docs/PLAN-MIGRATION-RUST.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ke2qyS8rgacP3ap5iezDbM
This commit is contained in:
1 parent
6760f43e87
commit
600627c632
146 files changed
+31521
-20331
No files matched your search
@@ -1,98 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Database Migration Script
|
||||
* Automatically runs database migrations for production deployment
|
||||
*/
|
||||
|
||||
const { Pool } = require('pg');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Database connection
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
});
|
||||
|
||||
async function runMigrations() {
|
||||
console.log('🔄 Starting database migrations...');
|
||||
|
||||
try {
|
||||
// Create migrations table if it doesn't exist
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
filename VARCHAR(255) UNIQUE NOT NULL,
|
||||
executed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
console.log('✅ Migrations table ready');
|
||||
|
||||
// Get list of executed migrations
|
||||
const { rows: executedMigrations } = await pool.query(
|
||||
'SELECT filename FROM migrations ORDER BY executed_at'
|
||||
);
|
||||
const executedSet = new Set(executedMigrations.map(row => row.filename));
|
||||
|
||||
// Read migration files
|
||||
const migrationsDir = path.join(__dirname);
|
||||
const files = fs.readdirSync(migrationsDir)
|
||||
.filter(file => file.endsWith('.sql'))
|
||||
.sort();
|
||||
|
||||
console.log(`📁 Found ${files.length} migration files`);
|
||||
|
||||
let appliedCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
if (executedSet.has(file)) {
|
||||
console.log(`⏭️ Skipping ${file} (already executed)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`🔄 Executing migration: ${file}`);
|
||||
|
||||
const filePath = path.join(migrationsDir, file);
|
||||
const sql = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Execute migration in a transaction
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
await client.query(sql);
|
||||
await client.query(
|
||||
'INSERT INTO migrations (filename) VALUES ($1)',
|
||||
[file]
|
||||
);
|
||||
await client.query('COMMIT');
|
||||
console.log(`✅ Successfully executed: ${file}`);
|
||||
appliedCount++;
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
throw new Error(`Failed to execute ${file}: ${error.message}`);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
if (appliedCount === 0) {
|
||||
console.log('✅ All migrations are up to date');
|
||||
} else {
|
||||
console.log(`✅ Successfully applied ${appliedCount} migration(s)`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Migration failed:', error.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Run migrations if this file is executed directly
|
||||
if (require.main === module) {
|
||||
runMigrations();
|
||||
}
|
||||
|
||||
module.exports = { runMigrations };
|
||||
@@ -1,103 +0,0 @@
|
||||
// Script de migration production pour créer la table announcements
|
||||
// Usage: node migrations/production_fix_announcements.js
|
||||
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import pg from 'pg';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
const { Pool } = pg;
|
||||
|
||||
async function createAnnouncementsTable() {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
});
|
||||
|
||||
const db = drizzle(pool);
|
||||
|
||||
try {
|
||||
console.log('🔄 Création de la table announcements...');
|
||||
|
||||
// SQL pour créer la table announcements
|
||||
const sql = `
|
||||
-- Création de la table announcements
|
||||
CREATE TABLE IF NOT EXISTS announcements (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
priority VARCHAR(20) NOT NULL DEFAULT 'normal' CHECK (priority IN ('normal', 'important', 'urgent')),
|
||||
author_id VARCHAR(255) NOT NULL,
|
||||
group_id INTEGER,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
CONSTRAINT announcements_author_id_fkey FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT announcements_group_id_fkey FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Création des index
|
||||
CREATE INDEX IF NOT EXISTS idx_announcements_priority ON announcements(priority);
|
||||
CREATE INDEX IF NOT EXISTS idx_announcements_created_at ON announcements(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_announcements_author_id ON announcements(author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_announcements_group_id ON announcements(group_id);
|
||||
|
||||
-- Commentaires
|
||||
COMMENT ON TABLE announcements IS 'Admin-managed announcements/information system';
|
||||
COMMENT ON COLUMN announcements.priority IS 'Priority level: normal, important, urgent';
|
||||
COMMENT ON COLUMN announcements.group_id IS 'NULL for global announcements, specific group_id for store-specific announcements';
|
||||
`;
|
||||
|
||||
await pool.query(sql);
|
||||
|
||||
console.log('✅ Table announcements créée avec succès!');
|
||||
|
||||
// Vérification que la table existe
|
||||
const checkResult = await pool.query(`
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'announcements'
|
||||
);
|
||||
`);
|
||||
|
||||
if (checkResult.rows[0].exists) {
|
||||
console.log('✅ Vérification: La table announcements existe bien');
|
||||
|
||||
// Afficher la structure de la table
|
||||
const structure = await pool.query(`
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'announcements'
|
||||
ORDER BY ordinal_position;
|
||||
`);
|
||||
|
||||
console.log('📋 Structure de la table:');
|
||||
structure.rows.forEach(row => {
|
||||
console.log(` ${row.column_name}: ${row.data_type}${row.is_nullable === 'NO' ? ' NOT NULL' : ''}`);
|
||||
});
|
||||
} else {
|
||||
console.log('❌ Erreur: La table announcements n\'a pas été créée');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la création de la table:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Exécution
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
createAnnouncementsTable()
|
||||
.then(() => {
|
||||
console.log('🎉 Migration terminée avec succès');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 Échec de la migration:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { createAnnouncementsTable };
|
||||
Reference in new issue
Block a user