feat: icone verte si solution utilisee par au moins 1 etablissement
This commit is contained in:
@@ -195,8 +195,8 @@ export default function SolutionsLogicielles() {
|
|||||||
className="flex-1 flex items-center justify-between px-5 py-4 text-left hover:bg-muted/30 transition-colors"
|
className="flex-1 flex items-center justify-between px-5 py-4 text-left hover:bg-muted/30 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4 min-w-0">
|
<div className="flex items-center gap-4 min-w-0">
|
||||||
<div className="w-9 h-9 rounded-lg bg-blue-500/10 flex items-center justify-center flex-shrink-0">
|
<div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${sol.nbEtablissements > 0 ? 'bg-green-500/10' : 'bg-blue-500/10'}`}>
|
||||||
<Package size={16} className="text-blue-600" />
|
<Package size={16} className={sol.nbEtablissements > 0 ? 'text-green-600' : 'text-blue-600'} />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="font-semibold text-foreground truncate">{sol.solutionNom}</div>
|
<div className="font-semibold text-foreground truncate">{sol.solutionNom}</div>
|
||||||
@@ -312,7 +312,7 @@ export default function SolutionsLogicielles() {
|
|||||||
<>
|
<>
|
||||||
<td className={`px-4 py-3 font-semibold text-foreground align-top ${rowBgSpan}`} rowSpan={sol.etablissements.length}>
|
<td className={`px-4 py-3 font-semibold text-foreground align-top ${rowBgSpan}`} rowSpan={sol.etablissements.length}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Package size={13} className="text-blue-500 flex-shrink-0" />
|
<Package size={13} className={sol.nbEtablissements > 0 ? 'text-green-600 flex-shrink-0' : 'text-blue-500 flex-shrink-0'} />
|
||||||
{sol.solutionNom}
|
{sol.solutionNom}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
98
export_sandbox_data.mjs
Normal file
98
export_sandbox_data.mjs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* Export des données de la base Sandbox SONUM vers un script SQL d'injection.
|
||||||
|
* Tables exportées : blocs_fonctionnels, editeurs, etablissements, solutions,
|
||||||
|
* logiciels_etablissements, consultations, demandes_contact, user_etablissements
|
||||||
|
* Tables exclues : users, local_credentials, __drizzle_migrations
|
||||||
|
*/
|
||||||
|
import mysql from 'mysql2/promise';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
const TABLES_TO_EXPORT = [
|
||||||
|
'blocs_fonctionnels',
|
||||||
|
'editeurs',
|
||||||
|
'etablissements',
|
||||||
|
'solutions',
|
||||||
|
'logiciels_etablissements',
|
||||||
|
'consultations',
|
||||||
|
'demandes_contact',
|
||||||
|
'user_etablissements',
|
||||||
|
];
|
||||||
|
|
||||||
|
const conn = await mysql.createConnection(process.env.DATABASE_URL);
|
||||||
|
|
||||||
|
let sql = `-- Export données SONUM Sandbox → VPS
|
||||||
|
-- Généré le ${new Date().toISOString()}
|
||||||
|
-- Tables : ${TABLES_TO_EXPORT.join(', ')}
|
||||||
|
-- ATTENTION : exclut users, local_credentials, __drizzle_migrations
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (const table of TABLES_TO_EXPORT) {
|
||||||
|
console.log(`Exporting table: ${table}...`);
|
||||||
|
|
||||||
|
// Récupérer la structure de la table
|
||||||
|
const [[createResult]] = await conn.execute(`SHOW CREATE TABLE \`${table}\``);
|
||||||
|
const createSQL = createResult['Create Table'];
|
||||||
|
|
||||||
|
// Récupérer les données
|
||||||
|
const [rows] = await conn.execute(`SELECT * FROM \`${table}\``);
|
||||||
|
|
||||||
|
sql += `-- ─────────────────────────────────────────────────────────────────\n`;
|
||||||
|
sql += `-- Table: ${table} (${rows.length} lignes)\n`;
|
||||||
|
sql += `-- ─────────────────────────────────────────────────────────────────\n`;
|
||||||
|
sql += `TRUNCATE TABLE \`${table}\`;\n\n`;
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
sql += `-- (aucune donnée)\n\n`;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer les INSERT par lots de 50
|
||||||
|
const columns = Object.keys(rows[0]);
|
||||||
|
const colList = columns.map(c => `\`${c}\``).join(', ');
|
||||||
|
|
||||||
|
const BATCH_SIZE = 50;
|
||||||
|
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
|
||||||
|
const batch = rows.slice(i, i + BATCH_SIZE);
|
||||||
|
const values = batch.map(row => {
|
||||||
|
const vals = columns.map(col => {
|
||||||
|
const v = row[col];
|
||||||
|
if (v === null || v === undefined) return 'NULL';
|
||||||
|
if (typeof v === 'number' || typeof v === 'bigint') return String(v);
|
||||||
|
if (typeof v === 'boolean') return v ? '1' : '0';
|
||||||
|
if (v instanceof Date) return `'${v.toISOString().slice(0, 19).replace('T', ' ')}'`;
|
||||||
|
// Escape string
|
||||||
|
const escaped = String(v)
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/'/g, "\\'")
|
||||||
|
.replace(/\n/g, '\\n')
|
||||||
|
.replace(/\r/g, '\\r')
|
||||||
|
.replace(/\0/g, '\\0');
|
||||||
|
return `'${escaped}'`;
|
||||||
|
});
|
||||||
|
return `(${vals.join(', ')})`;
|
||||||
|
});
|
||||||
|
sql += `INSERT INTO \`${table}\` (${colList}) VALUES\n${values.join(',\n')};\n`;
|
||||||
|
}
|
||||||
|
sql += `\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += `SET FOREIGN_KEY_CHECKS = 1;\n`;
|
||||||
|
sql += `-- Fin de l'export\n`;
|
||||||
|
|
||||||
|
await conn.end();
|
||||||
|
|
||||||
|
const outputPath = '/home/ubuntu/sonum_data_export.sql';
|
||||||
|
fs.writeFileSync(outputPath, sql, 'utf8');
|
||||||
|
|
||||||
|
const stats = fs.statSync(outputPath);
|
||||||
|
console.log(`\nExport terminé : ${outputPath}`);
|
||||||
|
console.log(`Taille : ${(stats.size / 1024).toFixed(1)} KB`);
|
||||||
|
console.log(`\nRésumé par table :`);
|
||||||
|
for (const table of TABLES_TO_EXPORT) {
|
||||||
|
const matches = sql.match(new RegExp(`-- Table: ${table} \\((\\d+) lignes\\)`));
|
||||||
|
if (matches) console.log(` ${table}: ${matches[1]} lignes`);
|
||||||
|
}
|
||||||
@@ -4,22 +4,33 @@
|
|||||||
*/
|
*/
|
||||||
import mysql from 'mysql2/promise';
|
import mysql from 'mysql2/promise';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
const conn = await mysql.createConnection(process.env.DATABASE_URL);
|
const conn = await mysql.createConnection(process.env.DATABASE_URL);
|
||||||
|
|
||||||
// Vérifier si adminItinova existe déjà
|
// Vérifier si adminItinova existe déjà
|
||||||
const [rows] = await conn.execute("SELECT id FROM users WHERE login = 'adminItinova' LIMIT 1");
|
const [rows] = await conn.execute("SELECT id, openId FROM users WHERE login = 'adminItinova' LIMIT 1");
|
||||||
if (rows.length > 0) {
|
if (rows.length > 0) {
|
||||||
console.log('Compte adminItinova déjà existant, seed ignoré.');
|
const existing = rows[0];
|
||||||
|
// Si openId est null, le corriger pour permettre la connexion JWT
|
||||||
|
if (!existing.openId) {
|
||||||
|
const openId = `local:adminItinova:${randomUUID()}`;
|
||||||
|
await conn.execute("UPDATE users SET openId = ? WHERE id = ?", [openId, existing.id]);
|
||||||
|
console.log(`Compte adminItinova : openId corrigé (id=${existing.id})`);
|
||||||
|
} else {
|
||||||
|
console.log('Compte adminItinova déjà existant, seed ignoré.');
|
||||||
|
}
|
||||||
await conn.end();
|
await conn.end();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Créer l'utilisateur adminItinova
|
// Créer l'utilisateur adminItinova avec un openId local unique
|
||||||
|
const openId = `local:adminItinova:${randomUUID()}`;
|
||||||
|
|
||||||
const [result] = await conn.execute(
|
const [result] = await conn.execute(
|
||||||
`INSERT INTO users (login, email, firstName, lastName, name, role, sonumRole, isActive, loginMethod, cguAccepted, lastSignedIn, createdAt, updatedAt)
|
`INSERT INTO users (login, email, firstName, lastName, name, openId, role, sonumRole, isActive, loginMethod, cguAccepted, lastSignedIn, createdAt, updatedAt)
|
||||||
VALUES (?, ?, ?, ?, ?, 'admin', 'gestionnaire', 1, 'local', 1, NOW(), NOW(), NOW())`,
|
VALUES (?, ?, ?, ?, ?, ?, 'admin', 'gestionnaire', 1, 'local', 1, NOW(), NOW(), NOW())`,
|
||||||
['adminItinova', 'adminItinova@santinova-soft.org', 'Admin', 'SONUM', 'Admin SONUM']
|
['adminItinova', 'adminItinova@santinova-soft.org', 'Admin', 'SONUM', 'Admin SONUM', openId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const userId = result.insertId;
|
const userId = result.insertId;
|
||||||
@@ -31,5 +42,5 @@ await conn.execute(
|
|||||||
[userId, hash]
|
[userId, hash]
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Compte adminItinova créé avec succès (id=${userId})`);
|
console.log(`Compte adminItinova créé avec succès (id=${userId}, openId=${openId})`);
|
||||||
await conn.end();
|
await conn.end();
|
||||||
|
|||||||
Reference in New Issue
Block a user