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:
LogiFlowandClaude Opus 5.5 authored and GitHub committed 2026-10-15 20:31:14 +02:00
1 parent 6760f43e87
commit 600627c632
146 files changed
+31521 -20331

No files matched your search

-98
View File
@@ -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 };
-103
View File
@@ -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 };