183 lines
6.8 KiB
C++
183 lines
6.8 KiB
C++
#include "supabase_client.h"
|
|
#include <curl/curl.h>
|
|
#include <nlohmann/json.hpp>
|
|
#include <thread>
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
using json = nlohmann::json;
|
|
|
|
namespace {
|
|
// Supabase constants (publishable anon key)
|
|
const std::string SUPABASE_URL = "https://xzxpmvyamjvtxpwnjpad.supabase.co";
|
|
const std::string SUPABASE_ANON_KEY = "sb_publishable_GqQx844xYDizO9-ytlBXfA_MVT6N7yA";
|
|
|
|
std::string buildUrl(const std::string &path) {
|
|
std::string url = SUPABASE_URL;
|
|
if (!url.empty() && url.back() == '/') url.pop_back();
|
|
url += "/rest/v1/" + path;
|
|
return url;
|
|
}
|
|
|
|
size_t curlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
|
size_t realSize = size * nmemb;
|
|
std::string *s = reinterpret_cast<std::string*>(userp);
|
|
s->append(reinterpret_cast<char*>(contents), realSize);
|
|
return realSize;
|
|
}
|
|
|
|
struct CurlInit {
|
|
CurlInit() { curl_global_init(CURL_GLOBAL_DEFAULT); }
|
|
~CurlInit() { curl_global_cleanup(); }
|
|
};
|
|
static CurlInit g_curl_init;
|
|
}
|
|
|
|
namespace supabase {
|
|
|
|
static bool g_verbose = false;
|
|
|
|
void SetVerbose(bool enabled) {
|
|
g_verbose = enabled;
|
|
}
|
|
|
|
|
|
void SubmitHighscoreAsync(const ScoreEntry &entry) {
|
|
std::thread([entry]() {
|
|
try {
|
|
CURL* curl = curl_easy_init();
|
|
if (!curl) return;
|
|
|
|
std::string url = buildUrl("highscores");
|
|
|
|
json j;
|
|
j["score"] = entry.score;
|
|
j["lines"] = entry.lines;
|
|
j["level"] = entry.level;
|
|
j["time_sec"] = static_cast<int>(std::lround(entry.timeSec));
|
|
j["name"] = entry.name;
|
|
j["game_type"] = entry.gameType;
|
|
j["timestamp"] = static_cast<int>(std::time(nullptr));
|
|
|
|
std::string body = j.dump();
|
|
struct curl_slist *headers = nullptr;
|
|
std::string h1 = std::string("apikey: ") + SUPABASE_ANON_KEY;
|
|
std::string h2 = std::string("Authorization: Bearer ") + SUPABASE_ANON_KEY;
|
|
headers = curl_slist_append(headers, h1.c_str());
|
|
headers = curl_slist_append(headers, h2.c_str());
|
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
|
|
|
std::string resp;
|
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
|
|
|
if (g_verbose) {
|
|
std::cerr << "[Supabase] POST " << url << "\n";
|
|
std::cerr << "[Supabase] Body: " << body << "\n";
|
|
}
|
|
|
|
CURLcode res = curl_easy_perform(curl);
|
|
if (res != CURLE_OK) {
|
|
if (g_verbose) std::cerr << "[Supabase] POST error: " << curl_easy_strerror(res) << "\n";
|
|
} else {
|
|
long http_code = 0;
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
if (g_verbose) {
|
|
std::cerr << "[Supabase] POST response code: " << http_code << " body_len=" << resp.size() << "\n";
|
|
if (!resp.empty()) std::cerr << "[Supabase] POST response: " << resp << "\n";
|
|
}
|
|
}
|
|
|
|
curl_slist_free_all(headers);
|
|
curl_easy_cleanup(curl);
|
|
} catch (...) {
|
|
// swallow errors
|
|
}
|
|
}).detach();
|
|
}
|
|
|
|
std::vector<ScoreEntry> FetchHighscores(const std::string &gameType, int limit) {
|
|
std::vector<ScoreEntry> out;
|
|
try {
|
|
CURL* curl = curl_easy_init();
|
|
if (!curl) return out;
|
|
|
|
std::string path = "highscores";
|
|
// Clamp limit to max 10 to keep payloads small
|
|
int l = std::clamp(limit, 1, 10);
|
|
std::string query;
|
|
if (!gameType.empty()) {
|
|
if (gameType == "challenge") {
|
|
query = "?game_type=eq." + gameType + "&order=level.desc,time_sec.asc&limit=" + std::to_string(l);
|
|
} else {
|
|
query = "?game_type=eq." + gameType + "&order=score.desc&limit=" + std::to_string(l);
|
|
}
|
|
} else {
|
|
query = "?order=score.desc&limit=" + std::to_string(l);
|
|
}
|
|
|
|
std::string url = buildUrl(path) + query;
|
|
|
|
struct curl_slist *headers = nullptr;
|
|
headers = curl_slist_append(headers, ("apikey: " + SUPABASE_ANON_KEY).c_str());
|
|
headers = curl_slist_append(headers, ("Authorization: Bearer " + SUPABASE_ANON_KEY).c_str());
|
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
|
|
|
|
|
std::string resp;
|
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
|
|
|
if (g_verbose) std::cerr << "[Supabase] GET " << url << "\n";
|
|
|
|
CURLcode res = curl_easy_perform(curl);
|
|
if (res == CURLE_OK) {
|
|
long http_code = 0;
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
if (g_verbose) {
|
|
std::cerr << "[Supabase] GET response code: " << http_code << " body_len=" << resp.size() << "\n";
|
|
if (!resp.empty()) std::cerr << "[Supabase] GET response: " << resp << "\n";
|
|
}
|
|
try {
|
|
auto j = json::parse(resp);
|
|
if (j.is_array()) {
|
|
for (auto &v : j) {
|
|
ScoreEntry e{};
|
|
if (v.contains("score")) e.score = v["score"].get<int>();
|
|
if (v.contains("lines")) e.lines = v["lines"].get<int>();
|
|
if (v.contains("level")) e.level = v["level"].get<int>();
|
|
if (v.contains("time_sec")) {
|
|
try { e.timeSec = v["time_sec"].get<double>(); } catch(...) { e.timeSec = v["time_sec"].get<int>(); }
|
|
} else if (v.contains("timestamp")) {
|
|
e.timeSec = v["timestamp"].get<int>();
|
|
}
|
|
if (v.contains("name")) e.name = v["name"].get<std::string>();
|
|
if (v.contains("game_type")) e.gameType = v["game_type"].get<std::string>();
|
|
out.push_back(e);
|
|
}
|
|
}
|
|
} catch (...) {
|
|
if (g_verbose) std::cerr << "[Supabase] GET parse error" << std::endl;
|
|
}
|
|
} else {
|
|
if (g_verbose) std::cerr << "[Supabase] GET error: " << curl_easy_strerror(res) << "\n";
|
|
}
|
|
|
|
curl_slist_free_all(headers);
|
|
curl_easy_cleanup(curl);
|
|
} catch (...) {
|
|
// swallow
|
|
}
|
|
return out;
|
|
}
|
|
|
|
} // namespace supabase
|