Ajout des routes /archives (GET liste, GET contenu, DELETE confirmation) pour le nouveau mécanisme d'archivage flotte - remplace le transport sshpass/scp existant (cf synthèse §32)

This commit is contained in:
2026-09-30 09:44:11 +02:00
parent b74f14452e
commit 5bbf387aca
8 changed files with 115 additions and 2 deletions
+1 -1
View File
@@ -1 +1 @@
260924_084534
260930_081832
+1 -1
View File
@@ -1,6 +1,6 @@
#ifndef _VERSION_H
#define _VERSION_H
#define VERSION "260924_084534"
#define VERSION "260930_081832"
#endif // _VERSION_H
+110
View File
@@ -14,6 +14,10 @@
#include <QVector>
#include <QPair>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include "_version.h"
@@ -166,6 +170,25 @@ static QHttpServerResponse withCors(QHttpServerResponse &&resp)
return std::move(resp);
}
// ----------------------------------------------------------------------------
// Archives (cf synthèse §32) - répertoire d'upload périodique du bateau vers
// ship2net. Chemin à confirmer/ajuster si différent en pratique sur le CM5.
// ----------------------------------------------------------------------------
static const QString REPERTOIRE_ARCHIVES = "/home/koriolan/shipheart/server/upload";
// Un nom d'archive valide correspond STRICTEMENT au format généré côté CM5 :
// <Adresse_mac (12 hex)>_<Date_fichier (yyMMdd_HHmmss)>_<empreinte (16 hex)>.sql.gz.enc
// Validation impérative avant de construire un chemin sur le disque à partir
// d'un nom reçu en paramètre d'URL - sans elle, un nom arbitraire (par
// exemple "../../etc/passwd") pourrait faire lire ou supprimer n'importe quel
// fichier du système, pas seulement une archive légitime.
static bool nom_archive_est_valide(const QString &nom)
{
static const QRegularExpression motif(
"^[0-9A-Fa-f]{12}_\\d{6}_\\d{6}_[0-9A-Fa-f]{16}\\.sql\\.gz\\.enc$");
return motif.match(nom).hasMatch();
}
// ----------------------------------------------------------------------------
// Vérifie que la connexion MariaDB (ouverte une seule fois au démarrage) est
// toujours active, et la rétablit sinon. isOpen() peut rester vrai même après
@@ -1277,6 +1300,93 @@ int main(int argc, char *argv[])
return withCors(QHttpServerResponse(QJsonDocument(reponse).toJson()));
});
// ------------------------------------------------------------------
// GET /archives - liste les archives en attente de traitement par le VPS
// (cf synthèse §32.5). Remplace le transport sshpass/scp existant.
// ------------------------------------------------------------------
server.route("/archives", [](const QHttpServerRequest &request) {
Q_UNUSED(request)
QDir dossier(REPERTOIRE_ARCHIVES);
QJsonArray archives;
const QFileInfoList fichiers = dossier.entryInfoList(QDir::Files, QDir::Name);
for (const QFileInfo &info : fichiers) {
// Tout fichier qui ne correspond pas au format attendu est
// ignoré silencieusement (ex: un .gitkeep, un fichier temporaire
// laissé par une écriture interrompue) :
if (!nom_archive_est_valide(info.fileName())) continue;
QJsonObject o;
o["nom"] = info.fileName();
o["taille"] = info.size();
archives.append(o);
}
QJsonObject reponse;
reponse["status"] = "ok";
reponse["archives"] = archives;
return withCors(QHttpServerResponse(QJsonDocument(reponse).toJson()));
});
// ------------------------------------------------------------------
// GET /archives/<nom> - contenu brut d'une archive précise.
// ------------------------------------------------------------------
server.route("/archives/<arg>", QHttpServerRequest::Method::Get,
[](const QString &nom, const QHttpServerRequest &request) {
Q_UNUSED(request)
if (!nom_archive_est_valide(nom)) {
QJsonObject err;
err["status"] = "erreur";
err["message"] = "nom d'archive invalide";
return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::BadRequest));
}
QFile fichier(REPERTOIRE_ARCHIVES + "/" + nom);
if (!fichier.open(QIODevice::ReadOnly)) {
QJsonObject err;
err["status"] = "erreur";
err["message"] = "archive introuvable";
return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::NotFound));
}
const QByteArray contenu = fichier.readAll();
fichier.close();
return withCors(QHttpServerResponse("application/octet-stream", contenu));
});
// ------------------------------------------------------------------
// DELETE /archives/<nom> - confirmation de traitement par le serveur,
// entraîne la suppression locale. Idempotent : supprimer une archive déjà
// absente renvoie "ok" (pas une erreur), pour ne pas gêner une
// confirmation qui arriverait en double (par exemple après une coupure
// réseau juste après un premier appel déjà réussi côté disque, mais dont
// la réponse ne serait jamais arrivée jusqu'au VPS).
// ------------------------------------------------------------------
server.route("/archives/<arg>", QHttpServerRequest::Method::Delete,
[](const QString &nom, const QHttpServerRequest &request) {
Q_UNUSED(request)
if (!nom_archive_est_valide(nom)) {
QJsonObject err;
err["status"] = "erreur";
err["message"] = "nom d'archive invalide";
return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::BadRequest));
}
QFile fichier(REPERTOIRE_ARCHIVES + "/" + nom);
if (fichier.exists() && !fichier.remove()) {
QJsonObject err;
err["status"] = "erreur";
err["message"] = "suppression impossible";
return withCors(QHttpServerResponse(QJsonDocument(err).toJson(), QHttpServerResponder::StatusCode::InternalServerError));
}
QJsonObject reponse;
reponse["status"] = "ok";
return withCors(QHttpServerResponse(QJsonDocument(reponse).toJson()));
});
auto tcpServer = new QTcpServer();
if (!tcpServer->listen(QHostAddress::Any, 8080) || !server.bind(tcpServer)) {
qCritical() << "Impossible de démarrer le serveur HTTP sur le port 8080.";
Binary file not shown.
+3
View File
@@ -15,6 +15,8 @@
#include <QPair>
#include <QDebug>
#include "_version.h"
// ============================================================================
// shipheart_api - Phase 4 : première tranche verticale, domaine "circuit"
// ----------------------------------------------------------------------------
@@ -193,6 +195,7 @@ static void assurer_connexion_bdd()
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
qInfo() << "shipheart_api version" << VERSION;
// --- Connexion MariaDB locale ---
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
Binary file not shown.
Binary file not shown.
Binary file not shown.