feat: implement MoloApp plans 01-15 with integration
Full C++23 framework tree: runtime, observability, security, data, app, realtime, supervisor, clients, CLI, example plus enterprise tenancy, audit, operability, governance, supplychain. Postgres via docker compose for tests.
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
#include "Data.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "Caps.hpp"
|
||||
|
||||
namespace molo::example::fw {
|
||||
|
||||
std::string SessionStore::create() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
std::string id = "sess-" + std::to_string(next_id_++);
|
||||
sessions_[id] = {};
|
||||
return id;
|
||||
}
|
||||
|
||||
bool SessionStore::get(const std::string& id,
|
||||
std::map<std::string, std::string>& out) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
auto it = sessions_.find(id);
|
||||
if (it == sessions_.end()) return false;
|
||||
out = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SessionStore::put(const std::string& id, const std::string& key,
|
||||
const std::string& value) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
if (!sessions_.count(id) && sessions_.size() >= caps::kMaxSessions) return;
|
||||
sessions_[id][key] = value;
|
||||
}
|
||||
|
||||
void SessionStore::rotate(const std::string& old_id, std::string& new_id) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
new_id = "sess-" + std::to_string(next_id_++);
|
||||
auto it = sessions_.find(old_id);
|
||||
if (it != sessions_.end()) {
|
||||
sessions_[new_id] = it->second;
|
||||
sessions_.erase(it);
|
||||
} else {
|
||||
sessions_[new_id] = {};
|
||||
}
|
||||
}
|
||||
|
||||
std::string demo_hash_password(const std::string& password) {
|
||||
unsigned long long h = 1469598103934665603ULL;
|
||||
std::string salted = "demo-salt|" + password;
|
||||
for (unsigned char c : salted) {
|
||||
h ^= c;
|
||||
h *= 1099511628211ULL;
|
||||
}
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "demo$%016llx", (unsigned long long)h);
|
||||
return buf;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int b64val(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '+') return 62;
|
||||
if (c == '/') return 63;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string b64decode(const std::string& in) {
|
||||
std::string out;
|
||||
int acc = 0, bits = 0;
|
||||
for (char c : in) {
|
||||
if (c == '=') break;
|
||||
int v = b64val(c);
|
||||
if (v < 0) continue;
|
||||
acc = (acc << 6) | v;
|
||||
bits += 6;
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
out.push_back((char)((acc >> bits) & 0xFF));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool parse_basic_auth(const std::string& header, std::string& user,
|
||||
std::string& password) {
|
||||
const std::string prefix = "Basic ";
|
||||
if (header.compare(0, prefix.size(), prefix) != 0) return false;
|
||||
std::string decoded = b64decode(header.substr(prefix.size()));
|
||||
std::size_t colon = decoded.find(':');
|
||||
if (colon == std::string::npos) return false;
|
||||
user = decoded.substr(0, colon);
|
||||
password = decoded.substr(colon + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Auth::add_user(const std::string& username, const std::string& password) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
users_[username] = demo_hash_password(password);
|
||||
}
|
||||
|
||||
bool Auth::check(const std::string& username,
|
||||
const std::string& password) const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
auto it = users_.find(username);
|
||||
if (it == users_.end()) return false;
|
||||
return it->second == demo_hash_password(password);
|
||||
}
|
||||
|
||||
std::string backend_name(OrmBackend backend) {
|
||||
return backend == OrmBackend::Sqlite ? "sqlite" : "postgres";
|
||||
}
|
||||
|
||||
OrmBackend parse_backend(const std::string& name) {
|
||||
return name == "postgres" ? OrmBackend::Postgres : OrmBackend::Sqlite;
|
||||
}
|
||||
|
||||
OrmDb::OrmDb(OrmBackend backend) : backend_(backend) {}
|
||||
|
||||
OrmBackend OrmDb::backend() const { return backend_; }
|
||||
|
||||
OrmResult OrmDb::insert(const std::string& table,
|
||||
const std::map<std::string, std::string>& values) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
OrmResult res;
|
||||
auto& cols = schema_[table];
|
||||
if (cols.empty()) {
|
||||
if (schema_.size() > caps::kMaxOrmTables) {
|
||||
res.ok = false;
|
||||
return res;
|
||||
}
|
||||
for (const auto& [k, v] : values) cols.push_back(k);
|
||||
res.healed = true;
|
||||
res.healed_note = "auto-created table " + table;
|
||||
} else {
|
||||
for (const auto& [k, v] : values) {
|
||||
bool known = false;
|
||||
for (const auto& c : cols) {
|
||||
if (c == k) {
|
||||
known = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!known) {
|
||||
cols.push_back(k);
|
||||
res.healed = true;
|
||||
res.healed_note = "auto-added column " + k + " to " + table;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto& vec = rows_[table];
|
||||
if (vec.size() >= caps::kMaxOrmRows) {
|
||||
res.ok = false;
|
||||
return res;
|
||||
}
|
||||
OrmRow row;
|
||||
row.columns = values;
|
||||
vec.push_back(row);
|
||||
res.rows = {row};
|
||||
return res;
|
||||
}
|
||||
|
||||
OrmResult OrmDb::select_all(const std::string& table) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
OrmResult res;
|
||||
auto it = rows_.find(table);
|
||||
if (it == rows_.end()) {
|
||||
if (!schema_.count(table)) {
|
||||
schema_[table] = {"id"};
|
||||
res.healed = true;
|
||||
res.healed_note = "auto-created table " + table;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
res.rows = it->second;
|
||||
return res;
|
||||
}
|
||||
|
||||
OrmResult OrmDb::ensure_column(const std::string& table,
|
||||
const std::string& column) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
OrmResult res;
|
||||
auto& cols = schema_[table];
|
||||
for (const auto& c : cols) {
|
||||
if (c == column) return res;
|
||||
}
|
||||
cols.push_back(column);
|
||||
res.healed = true;
|
||||
res.healed_note = "auto-added column " + column + " to " + table;
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::string> OrmDb::tables() const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
std::vector<std::string> out;
|
||||
for (const auto& [k, v] : schema_) out.push_back(k);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::string> OrmDb::columns(const std::string& table) const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
auto it = schema_.find(table);
|
||||
if (it == schema_.end()) return {};
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool UploadStore::store(const std::string& filename,
|
||||
const std::string& content_type,
|
||||
const std::string& data, UploadInfo& out,
|
||||
std::string& error) {
|
||||
if (data.size() > caps::kMaxUploadBytes) {
|
||||
error = "file too large";
|
||||
return false;
|
||||
}
|
||||
bool allowed = content_type == "text/plain" || content_type == "image/png" ||
|
||||
content_type == "application/json";
|
||||
if (!allowed) {
|
||||
error = "content type not allowed";
|
||||
return false;
|
||||
}
|
||||
if (filename.find("..") != std::string::npos ||
|
||||
filename.find('/') != std::string::npos) {
|
||||
error = "unsafe filename";
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
out = UploadInfo{filename, content_type, data.size()};
|
||||
uploads_.push_back(out);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<UploadInfo> UploadStore::list() const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return uploads_;
|
||||
}
|
||||
|
||||
void WebdavStore::put(const std::string& path, const std::string& content) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
if (!files_.count(path) && files_.size() >= caps::kMaxWebdavEntries) return;
|
||||
files_[path] = content;
|
||||
}
|
||||
|
||||
bool WebdavStore::get(const std::string& path, std::string& out) const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
auto it = files_.find(path);
|
||||
if (it == files_.end()) return false;
|
||||
out = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebdavStore::remove(const std::string& path) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return files_.erase(path) > 0;
|
||||
}
|
||||
|
||||
std::string WebdavStore::propfind_xml(const std::string& root) const {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
std::string out =
|
||||
"<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">";
|
||||
for (const auto& [k, v] : files_) {
|
||||
if (k.compare(0, root.size(), root) != 0) continue;
|
||||
out += "<D:response><D:href>" + k +
|
||||
"</D:href><D:propstat><D:prop><D:getcontentlength>" +
|
||||
std::to_string(v.size()) +
|
||||
"</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK"
|
||||
"</D:status></D:propstat></D:response>";
|
||||
}
|
||||
return out + "</D:multistatus>";
|
||||
}
|
||||
|
||||
} // namespace molo::example::fw
|
||||
Reference in New Issue
Block a user