setup electron app with boilerplate
This commit is contained in:
@@ -0,0 +1,822 @@
|
||||
#include "store.h"
|
||||
#include "networkmanager.h"
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Operation
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
Operation::Operation(QNetworkReply *reply, QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(this, &Operation::finished, this, &QObject::deleteLater);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
QByteArray body = reply->readAll();
|
||||
QJsonDocument doc = QJsonDocument::fromJson(body);
|
||||
QString message = doc.isObject() ? doc.object().value("message").toString() : QString();
|
||||
if (message.isEmpty())
|
||||
{
|
||||
message = reply->errorString();
|
||||
}
|
||||
qWarning() << message;
|
||||
emit failed(message);
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray body = reply->readAll();
|
||||
QJsonDocument doc = body.isEmpty() ? QJsonDocument() : QJsonDocument::fromJson(body);
|
||||
emit success(doc);
|
||||
emit finished();
|
||||
});
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Store — construction
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
Store::Store(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// JSON parsing
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
Human Store::parseHuman(const QJsonObject &obj)
|
||||
{
|
||||
Human h;
|
||||
h.id = obj["id"].toString();
|
||||
h.email = obj["email"].toString();
|
||||
h.emailPrefix = obj["email_prefix"].toString();
|
||||
return h;
|
||||
}
|
||||
|
||||
Network Store::parseNetwork(const QJsonObject &obj)
|
||||
{
|
||||
Network n;
|
||||
n.id = obj["id"].toString();
|
||||
n.name = obj["name"].toString();
|
||||
n.admin = parseHuman(obj["admin_human"].toObject());
|
||||
n.openStreamCount = obj["open_stream_count"].toInt();
|
||||
n.openStreamCapacity = obj["open_stream_capacity"].toInt();
|
||||
|
||||
const QJsonArray humansArr = obj["humans"].toArray();
|
||||
for (const QJsonValue &v : humansArr)
|
||||
{
|
||||
n.members.push_back(parseHuman(v.toObject()));
|
||||
}
|
||||
|
||||
// Streams are parsed at a higher level (populateFromStartupData) so we can
|
||||
// also extract particles per stream. Individual fetch calls parse streams inline.
|
||||
return n;
|
||||
}
|
||||
|
||||
Stream Store::parseStream(const QJsonObject &obj)
|
||||
{
|
||||
Stream s;
|
||||
s.id = obj["id"].toString();
|
||||
s.name = obj["name"].toString();
|
||||
s.description = obj["description"].toString();
|
||||
s.isOpen = (obj["status"].toString() == "open");
|
||||
s.unseenCount = obj["unseen_count"].toInt();
|
||||
|
||||
const QJsonArray membersArr = obj["members"].toArray();
|
||||
for (const QJsonValue &v : membersArr)
|
||||
{
|
||||
s.memberEmails.push_back(v.toString());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Particle Store::parseParticle(const QJsonObject &obj)
|
||||
{
|
||||
Particle p;
|
||||
p.id = obj["id"].toString();
|
||||
p.type = obj["type"].toString();
|
||||
p.createdByEmail = obj["created_by_email"].toString();
|
||||
p.seen = obj["seen"].toBool();
|
||||
|
||||
const QJsonObject dataObj = obj["data"].toObject();
|
||||
for (auto it = dataObj.begin(); it != dataObj.end(); ++it)
|
||||
{
|
||||
p.data[it.key()] = it.value().toVariant();
|
||||
}
|
||||
|
||||
const QJsonArray acksArr = obj["acks"].toArray();
|
||||
for (const QJsonValue &v : acksArr)
|
||||
{
|
||||
// acks can be objects with { email, acked_at } or plain strings
|
||||
if (v.isObject())
|
||||
{
|
||||
p.ackedByEmails.push_back(v.toObject()["email"].toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
p.ackedByEmails.push_back(v.toString());
|
||||
}
|
||||
}
|
||||
|
||||
p.updatedAt = QDateTime::fromString(obj["updated_at"].toString(), Qt::ISODate);
|
||||
p.createdAt = QDateTime::fromString(obj["created_at"].toString(), Qt::ISODate);
|
||||
return p;
|
||||
}
|
||||
|
||||
void Store::populateFromStartupData(const QJsonObject &root)
|
||||
{
|
||||
m_networks.clear();
|
||||
m_particles.clear();
|
||||
m_networkIndex.clear();
|
||||
m_streamToNetwork.clear();
|
||||
|
||||
const QJsonArray networksArr = root["networks"].toArray();
|
||||
for (const QJsonValue &netVal : networksArr)
|
||||
{
|
||||
QJsonObject netObj = netVal.toObject();
|
||||
Network network = parseNetwork(netObj);
|
||||
|
||||
const QJsonArray streamsArr = netObj["streams"].toArray();
|
||||
for (const QJsonValue &streamVal : streamsArr)
|
||||
{
|
||||
QJsonObject streamObj = streamVal.toObject();
|
||||
Stream stream = parseStream(streamObj);
|
||||
network.streams.push_back(stream);
|
||||
m_streamToNetwork[stream.id] = network.id;
|
||||
|
||||
// Parse particles embedded in stream
|
||||
QList<Particle> particleList;
|
||||
const QJsonArray particlesArr = streamObj["particles"].toArray();
|
||||
for (const QJsonValue &partVal : particlesArr)
|
||||
{
|
||||
particleList.append(parseParticle(partVal.toObject()));
|
||||
}
|
||||
m_particles[stream.id] = particleList;
|
||||
}
|
||||
|
||||
m_networkIndex[network.id] = m_networks.size();
|
||||
m_networks.append(network);
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// State mutation helpers
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
void Store::upsertNetwork(const Network &network)
|
||||
{
|
||||
if (m_networkIndex.contains(network.id))
|
||||
{
|
||||
int idx = m_networkIndex[network.id];
|
||||
std::vector<Stream> existingStreams = m_networks[idx].streams;
|
||||
m_networks[idx] = network;
|
||||
if (network.streams.empty())
|
||||
{
|
||||
m_networks[idx].streams = existingStreams;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_networkIndex[network.id] = m_networks.size();
|
||||
m_networks.append(network);
|
||||
}
|
||||
}
|
||||
|
||||
void Store::upsertStream(const QString &networkId, const Stream &stream)
|
||||
{
|
||||
if (!m_networkIndex.contains(networkId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto &streams = m_networks[m_networkIndex[networkId]].streams;
|
||||
for (size_t i = 0; i < streams.size(); ++i)
|
||||
{
|
||||
if (streams[i].id == stream.id)
|
||||
{
|
||||
streams[i] = stream;
|
||||
m_streamToNetwork[stream.id] = networkId;
|
||||
return;
|
||||
}
|
||||
}
|
||||
streams.push_back(stream);
|
||||
m_streamToNetwork[stream.id] = networkId;
|
||||
}
|
||||
|
||||
void Store::upsertParticle(const QString &streamId, const Particle &particle)
|
||||
{
|
||||
QList<Particle> &list = m_particles[streamId];
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
{
|
||||
if (list[i].id == particle.id)
|
||||
{
|
||||
list[i] = particle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
list.append(particle);
|
||||
}
|
||||
|
||||
void Store::removeParticleFromState(const QString &particleId)
|
||||
{
|
||||
for (auto it = m_particles.begin(); it != m_particles.end(); ++it)
|
||||
{
|
||||
QList<Particle> &list = it.value();
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
{
|
||||
if (list[i].id == particleId)
|
||||
{
|
||||
list.removeAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Store::findStreamForParticle(const QString &particleId) const
|
||||
{
|
||||
for (auto it = m_particles.constBegin(); it != m_particles.constEnd(); ++it)
|
||||
{
|
||||
for (const auto &p : it.value())
|
||||
{
|
||||
if (p.id == particleId)
|
||||
{
|
||||
return it.key();
|
||||
}
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Accessors
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
const QList<Network> &Store::networks() const { return m_networks; }
|
||||
|
||||
int Store::networkCount() const { return m_networks.size(); }
|
||||
|
||||
const Network *Store::networkById(const QString &id) const
|
||||
{
|
||||
auto it = m_networkIndex.find(id);
|
||||
if (it == m_networkIndex.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return &m_networks[it.value()];
|
||||
}
|
||||
|
||||
QList<Stream> Store::streamsForNetwork(const QString &networkId) const
|
||||
{
|
||||
auto it = m_networkIndex.find(networkId);
|
||||
if (it == m_networkIndex.end())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const auto &streams = m_networks[it.value()].streams;
|
||||
QList<Stream> result;
|
||||
result.reserve(static_cast<int>(streams.size()));
|
||||
for (const auto &s : streams)
|
||||
{
|
||||
result.append(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int Store::streamCount(const QString &networkId) const
|
||||
{
|
||||
auto it = m_networkIndex.find(networkId);
|
||||
if (it == m_networkIndex.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(m_networks[it.value()].streams.size());
|
||||
}
|
||||
|
||||
const Stream *Store::streamById(const QString &id) const
|
||||
{
|
||||
auto netIt = m_streamToNetwork.find(id);
|
||||
if (netIt == m_streamToNetwork.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto idxIt = m_networkIndex.find(netIt.value());
|
||||
if (idxIt == m_networkIndex.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const auto &streams = m_networks[idxIt.value()].streams;
|
||||
for (const auto &s : streams)
|
||||
{
|
||||
if (s.id == id)
|
||||
{
|
||||
return &s;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QList<Particle> &Store::particlesForStream(const QString &streamId) const
|
||||
{
|
||||
static const QList<Particle> empty;
|
||||
auto it = m_particles.find(streamId);
|
||||
if (it == m_particles.end())
|
||||
{
|
||||
return empty;
|
||||
}
|
||||
return it.value();
|
||||
}
|
||||
|
||||
int Store::particleCount(const QString &streamId) const
|
||||
{
|
||||
return m_particles.value(streamId).size();
|
||||
}
|
||||
|
||||
const Particle *Store::particleById(const QString &id) const
|
||||
{
|
||||
for (auto it = m_particles.constBegin(); it != m_particles.constEnd(); ++it)
|
||||
{
|
||||
for (const auto &p : it.value())
|
||||
{
|
||||
if (p.id == id)
|
||||
{
|
||||
return &p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Bootstrap
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
Operation *Store::loadStartupData()
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().get("/startup");
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this](const QJsonDocument &doc) {
|
||||
if (!doc.isObject())
|
||||
{
|
||||
qWarning() << "Invalid startup data";
|
||||
return;
|
||||
}
|
||||
populateFromStartupData(doc.object());
|
||||
emit startupDataLoaded();
|
||||
emit networksChanged();
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Network operations
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
Operation *Store::createNetwork(const QString &name)
|
||||
{
|
||||
QJsonObject body;
|
||||
body["name"] = name;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post("/networks", QJsonDocument(body));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this](const QJsonDocument &doc) {
|
||||
if (doc.isObject())
|
||||
{
|
||||
upsertNetwork(parseNetwork(doc.object()));
|
||||
emit networksChanged();
|
||||
}
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::fetchNetworks()
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().get("/networks");
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this](const QJsonDocument &doc) {
|
||||
const QJsonArray arr = doc.array();
|
||||
for (const QJsonValue &v : arr)
|
||||
{
|
||||
upsertNetwork(parseNetwork(v.toObject()));
|
||||
}
|
||||
emit networksChanged();
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::fetchNetwork(const QString &networkId)
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().get(QString("/networks/%1").arg(networkId));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, networkId](const QJsonDocument &doc) {
|
||||
QJsonObject obj = doc.object();
|
||||
Network network = parseNetwork(obj);
|
||||
|
||||
const QJsonArray streamsArr = obj["streams"].toArray();
|
||||
for (const QJsonValue &sv : streamsArr)
|
||||
{
|
||||
Stream stream = parseStream(sv.toObject());
|
||||
network.streams.push_back(stream);
|
||||
m_streamToNetwork[stream.id] = network.id;
|
||||
}
|
||||
upsertNetwork(network);
|
||||
emit networksChanged();
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::addNetworkMembers(const QString &networkId, const QStringList &emails)
|
||||
{
|
||||
QJsonObject body;
|
||||
QJsonArray arr;
|
||||
for (const QString &e : emails)
|
||||
{
|
||||
arr.append(e);
|
||||
}
|
||||
body["email_addresses"] = arr;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post(
|
||||
QString("/networks/%1/members").arg(networkId), QJsonDocument(body));
|
||||
return new Operation(reply, this);
|
||||
}
|
||||
|
||||
Operation *Store::removeNetworkMember(const QString &networkId, const QString &email)
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().del(
|
||||
QString("/networks/%1/members/%2").arg(networkId, email));
|
||||
return new Operation(reply, this);
|
||||
}
|
||||
|
||||
Operation *Store::setStreamCapacity(const QString &networkId, int capacity)
|
||||
{
|
||||
QJsonObject body;
|
||||
body["capacity"] = capacity;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().put(
|
||||
QString("/networks/%1/capacity").arg(networkId), QJsonDocument(body));
|
||||
return new Operation(reply, this);
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Stream operations
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
Operation *Store::createStream(const QString &networkId, const QString &name,
|
||||
const QString &description, const QString &visibility,
|
||||
const QStringList &members)
|
||||
{
|
||||
QJsonObject body;
|
||||
body["name"] = name;
|
||||
body["description"] = description;
|
||||
body["visibility"] = visibility;
|
||||
QJsonArray membersArr;
|
||||
for (const QString &m : members)
|
||||
{
|
||||
membersArr.append(m);
|
||||
}
|
||||
body["members"] = membersArr;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post(
|
||||
QString("/networks/%1/streams").arg(networkId), QJsonDocument(body));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, networkId](const QJsonDocument &doc) {
|
||||
if (doc.isObject())
|
||||
{
|
||||
Stream stream = parseStream(doc.object());
|
||||
upsertStream(networkId, stream);
|
||||
emit streamsChanged(networkId);
|
||||
}
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::fetchStream(const QString &streamId)
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().get(QString("/streams/%1").arg(streamId));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &doc) {
|
||||
QJsonObject obj = doc.object();
|
||||
Stream stream = parseStream(obj);
|
||||
|
||||
QString networkId = m_streamToNetwork.value(streamId);
|
||||
if (!networkId.isEmpty())
|
||||
{
|
||||
upsertStream(networkId, stream);
|
||||
emit streamsChanged(networkId);
|
||||
}
|
||||
|
||||
// Parse particles if present
|
||||
const QJsonArray particlesArr = obj["particles"].toArray();
|
||||
QList<Particle> particleList;
|
||||
for (const QJsonValue &pv : particlesArr)
|
||||
{
|
||||
particleList.append(parseParticle(pv.toObject()));
|
||||
}
|
||||
m_particles[streamId] = particleList;
|
||||
emit particlesChanged(streamId);
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::updateStream(const QString &streamId, const QString &name, const QString &description)
|
||||
{
|
||||
QJsonObject body;
|
||||
if (!name.isEmpty())
|
||||
{
|
||||
body["name"] = name;
|
||||
}
|
||||
if (!description.isEmpty())
|
||||
{
|
||||
body["description"] = description;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().patch(
|
||||
QString("/streams/%1").arg(streamId), QJsonDocument(body));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &doc) {
|
||||
if (doc.isObject())
|
||||
{
|
||||
Stream stream = parseStream(doc.object());
|
||||
QString networkId = m_streamToNetwork.value(streamId);
|
||||
if (!networkId.isEmpty())
|
||||
{
|
||||
upsertStream(networkId, stream);
|
||||
emit streamsChanged(networkId);
|
||||
}
|
||||
}
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::openStream(const QString &streamId)
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().post(
|
||||
QString("/streams/%1/open").arg(streamId), QJsonDocument());
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &) {
|
||||
QString networkId = m_streamToNetwork.value(streamId);
|
||||
if (m_networkIndex.contains(networkId))
|
||||
{
|
||||
auto &streams = m_networks[m_networkIndex[networkId]].streams;
|
||||
for (auto &s : streams)
|
||||
{
|
||||
if (s.id == streamId)
|
||||
{
|
||||
s.isOpen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
emit streamsChanged(networkId);
|
||||
}
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::closeStream(const QString &streamId)
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().post(
|
||||
QString("/streams/%1/close").arg(streamId), QJsonDocument());
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &) {
|
||||
QString networkId = m_streamToNetwork.value(streamId);
|
||||
if (m_networkIndex.contains(networkId))
|
||||
{
|
||||
auto &streams = m_networks[m_networkIndex[networkId]].streams;
|
||||
for (auto &s : streams)
|
||||
{
|
||||
if (s.id == streamId)
|
||||
{
|
||||
s.isOpen = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
emit streamsChanged(networkId);
|
||||
}
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::addStreamMembers(const QString &streamId, const QStringList &emails)
|
||||
{
|
||||
QJsonObject body;
|
||||
QJsonArray arr;
|
||||
for (const QString &e : emails)
|
||||
{
|
||||
arr.append(e);
|
||||
}
|
||||
body["emails"] = arr;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post(
|
||||
QString("/streams/%1/members").arg(streamId), QJsonDocument(body));
|
||||
return new Operation(reply, this);
|
||||
}
|
||||
|
||||
Operation *Store::removeStreamMembers(const QString &streamId, const QStringList &emails)
|
||||
{
|
||||
QJsonObject body;
|
||||
QJsonArray arr;
|
||||
for (const QString &e : emails)
|
||||
arr.append(e);
|
||||
body["emails"] = arr;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().del(
|
||||
QString("/streams/%1/members").arg(streamId), QJsonDocument(body));
|
||||
return new Operation(reply, this);
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Particle operations
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
Operation *Store::createParticle(const QString &streamId, const QString &type,
|
||||
const QMap<QString, QVariant> &data)
|
||||
{
|
||||
QJsonObject body;
|
||||
body["type"] = type;
|
||||
QJsonObject dataObj;
|
||||
for (auto it = data.constBegin(); it != data.constEnd(); ++it)
|
||||
dataObj[it.key()] = QJsonValue::fromVariant(it.value());
|
||||
body["data"] = dataObj;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post(
|
||||
QString("/streams/%1/particles").arg(streamId), QJsonDocument(body));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &doc) {
|
||||
if (doc.isObject())
|
||||
{
|
||||
Particle particle = parseParticle(doc.object());
|
||||
upsertParticle(streamId, particle);
|
||||
emit particlesChanged(streamId);
|
||||
}
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::fetchParticle(const QString &particleId)
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().get(QString("/particles/%1").arg(particleId));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, particleId](const QJsonDocument &doc) {
|
||||
if (doc.isObject())
|
||||
{
|
||||
Particle particle = parseParticle(doc.object());
|
||||
QString streamId = findStreamForParticle(particleId);
|
||||
if (!streamId.isEmpty())
|
||||
{
|
||||
upsertParticle(streamId, particle);
|
||||
emit particlesChanged(streamId);
|
||||
}
|
||||
}
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::updateParticle(const QString &particleId, const QMap<QString, QVariant> &data)
|
||||
{
|
||||
QJsonObject body;
|
||||
QJsonObject dataObj;
|
||||
for (auto it = data.constBegin(); it != data.constEnd(); ++it)
|
||||
dataObj[it.key()] = QJsonValue::fromVariant(it.value());
|
||||
body["data"] = dataObj;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().patch(
|
||||
QString("/particles/%1").arg(particleId), QJsonDocument(body));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, particleId](const QJsonDocument &doc) {
|
||||
if (doc.isObject())
|
||||
{
|
||||
Particle particle = parseParticle(doc.object());
|
||||
QString streamId = findStreamForParticle(particleId);
|
||||
if (!streamId.isEmpty())
|
||||
{
|
||||
upsertParticle(streamId, particle);
|
||||
emit particlesChanged(streamId);
|
||||
}
|
||||
}
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::deleteParticle(const QString &particleId)
|
||||
{
|
||||
QNetworkReply *reply = NetworkManager::instance().del(QString("/particles/%1").arg(particleId));
|
||||
auto *op = new Operation(reply, this);
|
||||
connect(op, &Operation::success, this, [this, particleId](const QJsonDocument &) {
|
||||
QString streamId = findStreamForParticle(particleId);
|
||||
removeParticleFromState(particleId);
|
||||
if (!streamId.isEmpty())
|
||||
emit particlesChanged(streamId);
|
||||
});
|
||||
return op;
|
||||
}
|
||||
|
||||
Operation *Store::markParticleSeen(const QString &particleId)
|
||||
{
|
||||
// Optimistic local update
|
||||
for (auto it = m_particles.begin(); it != m_particles.end(); ++it)
|
||||
{
|
||||
for (int i = 0; i < it.value().size(); ++i)
|
||||
{
|
||||
if (it.value()[i].id == particleId && !it.value()[i].seen)
|
||||
{
|
||||
it.value()[i].seen = true;
|
||||
QString streamId = it.key();
|
||||
|
||||
// Decrement the parent stream's unseen count
|
||||
QString networkId = m_streamToNetwork.value(streamId);
|
||||
if (m_networkIndex.contains(networkId))
|
||||
{
|
||||
auto &streams = m_networks[m_networkIndex[networkId]].streams;
|
||||
for (auto &s : streams)
|
||||
{
|
||||
if (s.id == streamId && s.unseenCount > 0)
|
||||
{
|
||||
s.unseenCount--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit particleUpdated(streamId, particleId);
|
||||
emit streamUpdated(streamId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post(
|
||||
QString("/particles/%1/seen").arg(particleId), QJsonDocument());
|
||||
return new Operation(reply, this);
|
||||
}
|
||||
|
||||
Operation *Store::markParticlesSeen(const QStringList &particleIds)
|
||||
{
|
||||
// Optimistic local update
|
||||
QSet<QString> affectedStreams;
|
||||
for (const QString &pid : particleIds)
|
||||
{
|
||||
for (auto it = m_particles.begin(); it != m_particles.end(); ++it)
|
||||
{
|
||||
for (int i = 0; i < it.value().size(); ++i)
|
||||
{
|
||||
if (it.value()[i].id == pid)
|
||||
{
|
||||
it.value()[i].seen = true;
|
||||
affectedStreams.insert(it.key());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const QString &streamId : affectedStreams)
|
||||
emit particlesChanged(streamId);
|
||||
|
||||
QJsonObject body;
|
||||
QJsonArray ids;
|
||||
for (const QString &id : particleIds)
|
||||
ids.append(id);
|
||||
body["particle_ids"] = ids;
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post("/particles/seen", QJsonDocument(body));
|
||||
return new Operation(reply, this);
|
||||
}
|
||||
|
||||
void Store::setCurrentUserEmail(const QString &email) { m_currentUserEmail = email; }
|
||||
|
||||
QString Store::currentUserEmail() const { return m_currentUserEmail; }
|
||||
|
||||
Operation *Store::ackParticle(const QString &particleId)
|
||||
{
|
||||
// Optimistic local update
|
||||
for (auto it = m_particles.begin(); it != m_particles.end(); ++it)
|
||||
{
|
||||
for (int i = 0; i < it.value().size(); ++i)
|
||||
{
|
||||
if (it.value()[i].id == particleId)
|
||||
{
|
||||
auto &emails = it.value()[i].ackedByEmails;
|
||||
if (!m_currentUserEmail.isEmpty() &&
|
||||
std::find(emails.begin(), emails.end(), m_currentUserEmail) == emails.end())
|
||||
{
|
||||
emails.push_back(m_currentUserEmail);
|
||||
emit particleUpdated(it.key(), particleId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post(
|
||||
QString("/particles/%1/ack").arg(particleId), QJsonDocument());
|
||||
return new Operation(reply, this);
|
||||
}
|
||||
Reference in New Issue
Block a user