241-327_Deev_ASD/lab-3/api_client.cpp

197 lines
9.1 KiB
C++

/*
* api_client.cpp — Реализация ApiClient (Singleton + HTTP + Adapter)
*
* Жизненный цикл одного запроса (пример fetchQuizzes):
*
* 1. fetchQuizzes() создаёт QNetworkRequest и вызывает m_manager->get()
* 2. QNetworkAccessManager отправляет HTTP GET асинхронно
* (не блокирует event loop)
* 3. Когда ответ получен, Qt вызывает лямбду, подключённую к
* сигналу QNetworkReply::finished
* 4. Лямбда читает байты через reply->readAll()
* 5. QJsonDocument::fromJson() парсит байты в дерево JSON
* 6. m_adapter->toQuizList() десериализует дерево в Quiz-объекты
* 7. emit quizzesReceived(...) уведомляет подписчиков
*/
#include "api_client.h"
#include "quiz_json_adapter.h"
#include <QNetworkReply> // результат асинхронного HTTP-запроса
#include <QNetworkRequest> // описание запроса: URL, заголовки
#include <QJsonDocument> // точка входа в парсер JSON
#include <QJsonArray> // JSON-массив [ ... ]
#include <QJsonObject> // JSON-объект { ... }
#include <QDebug>
// ─────────────────────────────────────────────────────────────────────────────
// Реализация паттерна Синглтон
// ─────────────────────────────────────────────────────────────────────────────
ApiClient &ApiClient::instance()
{
/*
* Meyers' Singleton: статическая локальная переменная.
* C++11 гарантирует потокобезопасность инициализации.
* Хранится как статическое свойство функции — одна на всё приложение.
*/
static ApiClient s_instance;
return s_instance;
}
ApiClient::ApiClient(QObject *parent)
: QObject(parent)
, m_manager(new QNetworkAccessManager(this))
, m_adapter(new QuizJsonAdapter())
, m_baseUrl("http://localhost:80")
{
}
ApiClient::~ApiClient()
{
delete m_adapter;
}
// ─────────────────────────────────────────────────────────────────────────────
// fetchQuizzes — GET /api/quiz/
// ─────────────────────────────────────────────────────────────────────────────
void ApiClient::fetchQuizzes()
{
QNetworkRequest request(QUrl(m_baseUrl + "/api/quiz/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply *reply = m_manager->get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
emit errorOccurred(reply->errorString());
return;
}
// reply->readAll() — читаем весь буфер HTTP-ответа
QByteArray rawData = reply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(rawData);
if (!doc.isArray()) {
emit errorOccurred("Unexpected JSON format: array expected");
return;
}
QList<Quiz> quizzes = m_adapter->toQuizList(doc.array());
emit quizzesReceived(quizzes);
});
}
// ─────────────────────────────────────────────────────────────────────────────
// fetchQuiz — GET /api/quiz/:id/
// ─────────────────────────────────────────────────────────────────────────────
void ApiClient::fetchQuiz(int id)
{
QNetworkRequest request(QUrl(m_baseUrl + "/api/quiz/" + QString::number(id) + "/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply *reply = m_manager->get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
emit errorOccurred(reply->errorString());
return;
}
QByteArray rawData = reply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(rawData);
if (!doc.isObject()) {
emit errorOccurred("Unexpected JSON format: object expected");
return;
}
emit quizReceived(m_adapter->toQuiz(doc.object()));
});
}
// ─────────────────────────────────────────────────────────────────────────────
// createQuiz — POST /api/quiz/
// ─────────────────────────────────────────────────────────────────────────────
void ApiClient::createQuiz(const QJsonObject &data)
{
QNetworkRequest request(QUrl(m_baseUrl + "/api/quiz/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QByteArray body = QJsonDocument(data).toJson(QJsonDocument::Compact);
QNetworkReply *reply = m_manager->post(request, body);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
emit errorOccurred(reply->errorString());
return;
}
QByteArray rawData = reply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(rawData);
if (!doc.isObject()) {
emit errorOccurred("POST: unexpected JSON format");
return;
}
emit quizCreated(m_adapter->toQuiz(doc.object()));
});
}
// ─────────────────────────────────────────────────────────────────────────────
// updateQuiz — PUT /api/quiz/:id/
// ─────────────────────────────────────────────────────────────────────────────
void ApiClient::updateQuiz(int id, const QJsonObject &data)
{
QNetworkRequest request(QUrl(m_baseUrl + "/api/quiz/" + QString::number(id) + "/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QByteArray body = QJsonDocument(data).toJson(QJsonDocument::Compact);
QNetworkReply *reply = m_manager->put(request, body);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
emit errorOccurred(reply->errorString());
return;
}
QByteArray rawData = reply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(rawData);
if (!doc.isObject()) {
emit errorOccurred("PUT: unexpected JSON format");
return;
}
emit quizUpdated(m_adapter->toQuiz(doc.object()));
});
}
// ─────────────────────────────────────────────────────────────────────────────
// deleteQuiz — DELETE /api/quiz/:id/
// ─────────────────────────────────────────────────────────────────────────────
void ApiClient::deleteQuiz(int id)
{
QNetworkRequest request(QUrl(m_baseUrl + "/api/quiz/" + QString::number(id) + "/"));
QNetworkReply *reply = m_manager->deleteResource(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
emit errorOccurred(reply->errorString());
return;
}
QByteArray rawData = reply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(rawData);
QString message = "Тест удалён";
if (doc.isObject() && doc.object().contains("detail"))
message = doc.object()["detail"].toString();
emit quizDeleted(message);
});
}