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
|
#ifndef _VERSION_H
|
||||||
#define _VERSION_H
|
#define _VERSION_H
|
||||||
|
|
||||||
#define VERSION "260924_084534"
|
#define VERSION "260930_081832"
|
||||||
|
|
||||||
#endif // _VERSION_H
|
#endif // _VERSION_H
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#include "_version.h"
|
#include "_version.h"
|
||||||
|
|
||||||
@@ -166,6 +170,25 @@ static QHttpServerResponse withCors(QHttpServerResponse &&resp)
|
|||||||
return std::move(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
|
// 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
|
// 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()));
|
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();
|
auto tcpServer = new QTcpServer();
|
||||||
if (!tcpServer->listen(QHostAddress::Any, 8080) || !server.bind(tcpServer)) {
|
if (!tcpServer->listen(QHostAddress::Any, 8080) || !server.bind(tcpServer)) {
|
||||||
qCritical() << "Impossible de démarrer le serveur HTTP sur le port 8080.";
|
qCritical() << "Impossible de démarrer le serveur HTTP sur le port 8080.";
|
||||||
|
|||||||
Binary file not shown.
@@ -15,6 +15,8 @@
|
|||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "_version.h"
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// shipheart_api - Phase 4 : première tranche verticale, domaine "circuit"
|
// 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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
|
qInfo() << "shipheart_api version" << VERSION;
|
||||||
|
|
||||||
// --- Connexion MariaDB locale ---
|
// --- Connexion MariaDB locale ---
|
||||||
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
|
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