fix: resolver DNS nginx pour éviter cache IP stale après restart backend

This commit is contained in:
deploy
2026-04-30 21:14:27 +00:00
parent a1e7aa46d5
commit 018fb1d70f
57 changed files with 7 additions and 7579 deletions
-172
View File
@@ -1,172 +0,0 @@
import { db } from '../db';
import { decrypt } from '../crypto';
// ─── Types ───────────────────────────────────────────────────
interface GraphConfig {
tenantId: string;
clientId: string;
clientSecret: string;
sharepointSiteId: string;
sharepointItemId: string;
sharepointSheet: string;
}
export interface SharepointRowData {
category: string;
companyName: string;
comment: string;
guests: Array<{ name: string; company?: string | null }>;
date: string; // format JJ/MM/AAAA
amount: number;
userName: string; // 'Greg' ou 'Gaël'
}
// ─── Helpers ─────────────────────────────────────────────────
async function getGraphConfig(): Promise<GraphConfig> {
const result = await db.query(
`SELECT key, value FROM app_settings
WHERE key IN (
'graph_tenant_id', 'graph_client_id', 'graph_client_secret_enc',
'sharepoint_site_id', 'sharepoint_item_id', 'sharepoint_sheet_name'
)`
);
const cfg: Record<string, string> = {};
for (const row of result.rows) cfg[row.key] = row.value;
if (!cfg.graph_tenant_id || !cfg.graph_client_id || !cfg.graph_client_secret_enc) {
throw new Error('Microsoft Graph non configuré (tenant_id, client_id ou client_secret manquant)');
}
if (!cfg.sharepoint_site_id || !cfg.sharepoint_item_id) {
throw new Error('Fichier Excel SharePoint non configuré (site_id ou item_id manquant dans Paramètres → Microsoft 365)');
}
return {
tenantId: cfg.graph_tenant_id,
clientId: cfg.graph_client_id,
clientSecret: decrypt(cfg.graph_client_secret_enc),
sharepointSiteId: cfg.sharepoint_site_id,
sharepointItemId: cfg.sharepoint_item_id,
sharepointSheet: cfg.sharepoint_sheet_name ?? 'Feuil1',
};
}
async function getAccessToken(cfg: GraphConfig): Promise<string> {
const response = await fetch(
`https://login.microsoftonline.com/${cfg.tenantId}/oauth2/v2.0/token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: cfg.clientId,
client_secret: cfg.clientSecret,
scope: 'https://graph.microsoft.com/.default',
}),
}
);
if (!response.ok) {
const body = await response.text();
throw new Error(`Obtention token Graph échouée (${response.status}) : ${body}`);
}
const data = (await response.json()) as { access_token: string };
return data.access_token;
}
// ─── Test de connexion (sans écriture) ───────────────────────
/**
* Vérifie que le token Graph s'obtient et que la feuille Excel est accessible.
* Utilisé par POST /api/settings/sharepoint/test.
*/
export async function testSharepointConnection(): Promise<void> {
const cfg = await getGraphConfig();
const token = await getAccessToken(cfg);
const baseUrl = `https://graph.microsoft.com/v1.0/sites/${cfg.sharepointSiteId}/drive/items/${cfg.sharepointItemId}/workbook/worksheets/${encodeURIComponent(cfg.sharepointSheet)}`;
const resp = await fetch(`${baseUrl}/usedRange?$select=rowCount`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!resp.ok) {
const body = await resp.text();
throw new Error(`Impossible d'accéder à la feuille "${cfg.sharepointSheet}" (${resp.status}) : ${body.slice(0, 200)}`);
}
}
// ─── Fonction principale ──────────────────────────────────────
/**
* Ajoute une ligne dans le fichier Excel SharePoint commun selon le mapping :
* A : Catégorie
* B : Société facturée
* C : Commentaire + invités
* D : Date (JJ/MM/AAAA)
* E : Montant si Greg
* F : Montant si Gaël
*/
export async function addRowToExcel(row: SharepointRowData): Promise<void> {
const cfg = await getGraphConfig();
const token = await getAccessToken(cfg);
// Construction de la cellule C (commentaire + invités)
let cellComment = row.comment || '';
if (row.guests.length > 0) {
const guestStr = row.guests
.map((g) => (g.company ? `${g.name}${g.company}` : g.name))
.join(' ; ');
cellComment = cellComment
? `${cellComment}. Invités : ${guestStr}`
: `Invités : ${guestStr}`;
}
// Colonnes E/F selon l'utilisateur
const nameLower = row.userName.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
const isGreg = nameLower === 'greg';
const isGael = nameLower === 'gael';
const values = [[
row.category, // A
row.companyName, // B
cellComment, // C
row.date, // D
isGreg ? row.amount : null, // E — Greg
isGael ? row.amount : null, // F — Gaël
]];
// ── Trouver la première ligne vide ───────────────────────────
const baseUrl = `https://graph.microsoft.com/v1.0/sites/${cfg.sharepointSiteId}/drive/items/${cfg.sharepointItemId}/workbook/worksheets/${encodeURIComponent(cfg.sharepointSheet)}`;
const rangeResp = await fetch(`${baseUrl}/usedRange`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!rangeResp.ok) {
const body = await rangeResp.text();
throw new Error(`Graph usedRange échoué (${rangeResp.status}) : ${body}`);
}
const rangeData = (await rangeResp.json()) as { rowCount: number };
const nextRow = rangeData.rowCount + 1;
// ── Écriture de la nouvelle ligne ────────────────────────────
const writeResp = await fetch(
`${baseUrl}/range(address='A${nextRow}:F${nextRow}')`,
{
method: 'PATCH',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ values }),
}
);
if (!writeResp.ok) {
const body = await writeResp.text();
throw new Error(`Graph écriture ligne échouée (${writeResp.status}) : ${body}`);
}
}