Initial commit : API Node.js + PostgreSQL + sync service Docker
- API Express/pg connectée à PostgreSQL (converti depuis mssql) - 8 routes : articles, fournisseurs, commandes, stock, mouvements, performance, schema, sync - Service sync nightly (mssql → pg, cron 3h00) - Docker Compose : postgres + api + sync + frpc (tunnel sortant) - Schéma PostgreSQL 17 tables dans postgres/init.sql Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
d565ed9f10
28 files changed
+2423
No files matched your search
@@ -0,0 +1,136 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { getPool } = require('../config/database');
|
||||
|
||||
// GET /api/schema - Structure complète de la base de données
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
table_schema AS TABLE_SCHEMA,
|
||||
table_name AS TABLE_NAME,
|
||||
table_type AS TABLE_TYPE,
|
||||
column_name AS COLUMN_NAME,
|
||||
ordinal_position AS ORDINAL_POSITION,
|
||||
data_type AS DATA_TYPE,
|
||||
character_maximum_length AS CHARACTER_MAXIMUM_LENGTH,
|
||||
numeric_precision AS NUMERIC_PRECISION,
|
||||
numeric_scale AS NUMERIC_SCALE,
|
||||
is_nullable AS IS_NULLABLE,
|
||||
column_default AS COLUMN_DEFAULT
|
||||
FROM information_schema.tables
|
||||
JOIN information_schema.columns USING (table_schema, table_name)
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name, ordinal_position
|
||||
`);
|
||||
|
||||
const schema = {};
|
||||
for (const row of result.rows) {
|
||||
const key = `${row.table_schema}.${row.table_name}`;
|
||||
if (!schema[key]) {
|
||||
schema[key] = {
|
||||
schema: row.table_schema,
|
||||
table: row.table_name,
|
||||
type: row.table_type,
|
||||
columns: [],
|
||||
};
|
||||
}
|
||||
schema[key].columns.push({
|
||||
name: row.column_name,
|
||||
position: row.ordinal_position,
|
||||
type: row.data_type,
|
||||
maxLength: row.character_maximum_length,
|
||||
precision: row.numeric_precision,
|
||||
scale: row.numeric_scale,
|
||||
nullable: row.is_nullable === 'YES',
|
||||
default: row.column_default,
|
||||
});
|
||||
}
|
||||
|
||||
const tables = Object.values(schema);
|
||||
res.json({
|
||||
database: process.env.PG_DB,
|
||||
server: process.env.PG_HOST,
|
||||
tableCount: tables.filter(t => t.type === 'BASE TABLE').length,
|
||||
viewCount: tables.filter(t => t.type === 'VIEW').length,
|
||||
tables,
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur connexion base de données', detail: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/schema/tables?search= - Liste des tables avec nombre de lignes (estimé)
|
||||
router.get('/tables', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
const search = req.query.search || '';
|
||||
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
schemaname AS TABLE_SCHEMA,
|
||||
tablename AS TABLE_NAME,
|
||||
'BASE TABLE' AS TABLE_TYPE,
|
||||
n_live_tup AS ROW_COUNT
|
||||
FROM pg_stat_user_tables
|
||||
WHERE tablename LIKE $1
|
||||
ORDER BY n_live_tup DESC, tablename
|
||||
`, [`%${search}%`]);
|
||||
|
||||
res.json({ count: result.rows.length, tables: result.rows });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur connexion base de données', detail: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/schema/top?limit= - Top tables par nombre de lignes
|
||||
router.get('/top', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
const limitNum = Math.max(1, Math.min(parseInt(req.query.limit) || 50, 200));
|
||||
|
||||
const result = await pool.query(`
|
||||
SELECT tablename AS TABLE_NAME, 'BASE TABLE' AS TABLE_TYPE, n_live_tup AS ROW_COUNT
|
||||
FROM pg_stat_user_tables
|
||||
ORDER BY n_live_tup DESC
|
||||
LIMIT $1
|
||||
`, [limitNum]);
|
||||
|
||||
res.json({ tables: result.rows });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur connexion base de données', detail: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/schema/tables/:tableName - Colonnes d'une table
|
||||
router.get('/tables/:tableName', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
column_name AS COLUMN_NAME,
|
||||
ordinal_position AS ORDINAL_POSITION,
|
||||
data_type AS DATA_TYPE,
|
||||
character_maximum_length AS CHARACTER_MAXIMUM_LENGTH,
|
||||
numeric_precision AS NUMERIC_PRECISION,
|
||||
numeric_scale AS NUMERIC_SCALE,
|
||||
is_nullable AS IS_NULLABLE,
|
||||
column_default AS COLUMN_DEFAULT
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = $1 AND table_schema = 'public'
|
||||
ORDER BY ordinal_position
|
||||
`, [req.params.tableName]);
|
||||
|
||||
if (!result.rows.length)
|
||||
return res.status(404).json({ error: `Table '${req.params.tableName}' introuvable` });
|
||||
|
||||
res.json({ table: req.params.tableName, columns: result.rows });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur connexion base de données', detail: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in new issue
Block a user