build-dev / nix-build (push) Successful in 32s
The portal's v2 API replaced nested categories (proclamation_categories.parent_id)
with flat categories plus tag-like groups: a proclamation now belongs to one
category and optionally one group. This lets the Proclamation Options page drop
its special-cased two-level tree.
Database:
- Add the proclamation_groups table (id, name, sort_order) and a
proclamations.group_id column.
- Drop parent_id from the proclamation_categories DDL so fresh databases get a
flat table.
- runMigrations() creates the new table/column, then runs a one-time
transactional v1->v2 migration keyed on PRAGMA user_version: every legacy
sub-category becomes a group (its id is preserved), proclamations that
pointed at a sub-category are remapped to (parent category, new group), and
the now-stale sub-category rows are deleted. selected flags are never
touched, so user choices survive. No network is needed, so offline kiosks
migrate at startup; re-running is a no-op.
- Add replaceGroups() and getAllGroups(); stop reading/writing parent_id.
API sync:
- Add PATH_GROUPS ("/groups") and parseGroupsJson().
- parseProclamationsJson() now also reads groupId.
- syncProclamationsFromApi() fetches /proclamations, /categories and /groups,
leaves the database untouched if any request or parse fails, then replaces
categories, replaces groups and upserts proclamations.
Proclamation Options page:
- FlatItem is now Category / Group / Proclamation.
- Each category lists only the groups that have at least one member in that
category, so impossible pairings (e.g. Physical/Prayers) never render.
- Proclamations with no group, or with a group the portal has removed, sit
directly under the category. Groups are collapsible and use a distinct color.
Tests:
- test_db: groups round-trip plus a legacy->v2 migration test asserting the
group is created, proclamations are remapped, selection is preserved and a
second initialize() is a no-op.
- test_proclamation_sync / test_api_client: parse groupId, parse groups, and
the live smoke test now also fetches /groups.
Docs: README database table and AGENTS notes updated for groups.
123 lines
4.6 KiB
C++
123 lines
4.6 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace radiantwave::api {
|
|
|
|
// Base URL of the RadiantWave content API.
|
|
constexpr const char* API_BASE_URL = "https://portal.radiantwavetech.com/api/v2";
|
|
|
|
// Paths relative to API_BASE_URL.
|
|
constexpr const char* PATH_PROCLAMATIONS = "/proclamations";
|
|
constexpr const char* PATH_CATEGORIES = "/categories";
|
|
constexpr const char* PATH_GROUPS = "/groups";
|
|
constexpr const char* PATH_FREQUENCIES = "/frequencies";
|
|
constexpr const char* PATH_LICENSE = "/license";
|
|
constexpr const char* PATH_CREATE_ACCOUNT = "/automations/create/account";
|
|
constexpr const char* PATH_COLOR_PALETTES = "/color-palettes";
|
|
|
|
// Result of a single HTTP request.
|
|
struct HttpResponse {
|
|
long status = 0; // HTTP status code; 0 if the request never completed
|
|
std::string body; // raw response body (any status)
|
|
bool transportOk = false; // true when the request completed without a transport error
|
|
};
|
|
|
|
// Performs a GET/POST against `path` (relative to API_BASE_URL).
|
|
HttpResponse httpGet(const std::string& path);
|
|
HttpResponse httpPost(const std::string& path, const std::string& jsonBody);
|
|
|
|
// ─── GET /frequencies ──────────────────────────────────────────────────
|
|
|
|
struct Frequency {
|
|
int id = 0;
|
|
std::string name;
|
|
int value = 0; // frequency in Hz
|
|
};
|
|
|
|
// Parse the JSON payload returned by the frequencies endpoint.
|
|
bool parseFrequenciesJson(const std::string& body, std::vector<Frequency>& out);
|
|
|
|
// Fetch the solfeggio frequencies. Returns true only on HTTP 2xx + successful parse.
|
|
bool fetchFrequencies(std::vector<Frequency>& out);
|
|
|
|
// ─── GET /color-palettes ───────────────────────────────────────────────
|
|
|
|
// A single color swatch (RGBA). Mirrors db::PaletteColor without coupling the
|
|
// API layer to the database.
|
|
struct PaletteColor {
|
|
uint8_t r = 0;
|
|
uint8_t g = 0;
|
|
uint8_t b = 0;
|
|
uint8_t a = 255;
|
|
};
|
|
|
|
// The ordered 7-color palette for one solfeggio frequency.
|
|
struct ColorPalette {
|
|
int solfeggio_frequency_id = 0;
|
|
std::vector<PaletteColor> colors;
|
|
};
|
|
|
|
// Parse the JSON payload returned by the color-palettes endpoint. Each entry
|
|
// is expected to look like {"solfeggioFrequencyId": 1, "colors": ["#RRGGBB", ...]}.
|
|
// Returns false on malformed input.
|
|
bool parseColorPalettesJson(const std::string& body, std::vector<ColorPalette>& out);
|
|
|
|
// Fetch the color palettes for every solfeggio frequency. Returns true only on
|
|
// HTTP 2xx + successful parse.
|
|
//
|
|
// NOTE: the portal does not expose this route yet. This is readiness code — it
|
|
// is deliberately not called anywhere in the app until the route ships, so
|
|
// palettes can be updated from the portal rather than code releases.
|
|
bool fetchColorPalettes(std::vector<ColorPalette>& out);
|
|
|
|
// ─── POST /license ─────────────────────────────────────────────────────
|
|
|
|
struct LicenseInfo {
|
|
std::string key;
|
|
std::string email_address;
|
|
};
|
|
|
|
// Parse the 200 JSON payload returned by the license endpoint.
|
|
bool parseLicenseJson(const std::string& body, LicenseInfo& out);
|
|
|
|
struct LicenseResult {
|
|
int status = 0;
|
|
LicenseInfo info;
|
|
std::string error_message; // extracted from the error body on failure
|
|
|
|
bool ok() const { return status >= 200 && status < 300; }
|
|
};
|
|
|
|
// Validate a license key / email pair against the portal. Never throws; failures
|
|
// (network, HTTP error status) are reported through the returned LicenseResult.
|
|
// A 2xx response means the license is valid and the subscription is active.
|
|
LicenseResult validateLicense(const std::string& key, const std::string& email);
|
|
|
|
// ─── POST /automations/create/account ──────────────────────────────────
|
|
|
|
struct AccountBody {
|
|
bool success = false;
|
|
std::string message;
|
|
std::string user_id;
|
|
};
|
|
|
|
// Parse the 200 JSON payload returned by the create-account endpoint.
|
|
bool parseCreateAccountJson(const std::string& body, AccountBody& out);
|
|
|
|
struct AccountResult {
|
|
int status = 0;
|
|
AccountBody body;
|
|
std::string error_message;
|
|
|
|
bool ok() const { return status >= 200 && status < 300; }
|
|
};
|
|
|
|
// Creates (or reuses) a user account and issues a license key. The key is emailed
|
|
// to the user and is NOT part of the response. `name` may be empty.
|
|
AccountResult createAccount(const std::string& email, const std::string& name = "");
|
|
|
|
} // namespace radiantwave::api
|