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:
+1
-1
@@ -1 +1 @@
|
||||
260924_084534
|
||||
260930_081832
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#ifndef _VERSION_H
|
||||
#define _VERSION_H
|
||||
|
||||
#define VERSION "260924_084534"
|
||||
#define VERSION "260930_081832"
|
||||
|
||||
#endif // _VERSION_H
|
||||
|
||||
@@ -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.
@@ -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");
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user