From 767f1d8382365dc15b175b67360f5f327fbe993a Mon Sep 17 00:00:00 2001 From: Alfie King Date: Fri, 13 Mar 2026 02:51:44 +0000 Subject: [PATCH] api basic routes --- CMakeLists.txt | 4 +- include/auth_middleware.hpp | 18 +++++++ include/database.hpp | 9 ++-- include/timestamp.hpp | 11 ----- include/types.hpp | 7 ++- include/{hashing.hpp => utils.hpp} | 15 +++++- src/auth_middleware.cpp | 32 +++++++++++++ src/database.cpp | 18 +++++-- src/main.cpp | 75 +++++++++++++++++++++++++++--- src/timestamp.cpp | 8 ---- src/{hashing.cpp => utils.cpp} | 22 +++++++-- 11 files changed, 173 insertions(+), 46 deletions(-) create mode 100644 include/auth_middleware.hpp delete mode 100644 include/timestamp.hpp rename include/{hashing.hpp => utils.hpp} (52%) create mode 100644 src/auth_middleware.cpp delete mode 100644 src/timestamp.cpp rename src/{hashing.cpp => utils.cpp} (68%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d926416..48afad9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,8 @@ add_subdirectory(libs/crow) add_executable(plural++ src/main.cpp src/database.cpp - src/hashing.cpp - src/timestamp.cpp + src/auth_middleware.cpp + src/utils.cpp ) # Include directories diff --git a/include/auth_middleware.hpp b/include/auth_middleware.hpp new file mode 100644 index 0000000..27a289f --- /dev/null +++ b/include/auth_middleware.hpp @@ -0,0 +1,18 @@ +#ifndef AUTH_MIDDLEWARE_HPP +#define AUTH_MIDDLEWARE_HPP +#include "crow.h" +#include "types.hpp" +#include +#include + +struct UserAuthMiddleware { + struct context { + User user; + }; + + void before_handle(crow::request& req, crow::response& res, context& ctx); + + void after_handle(crow::request& req, crow::response& res, context& ctx) {}; +}; + +#endif \ No newline at end of file diff --git a/include/database.hpp b/include/database.hpp index 6e85113..0fb2f2c 100644 --- a/include/database.hpp +++ b/include/database.hpp @@ -1,7 +1,7 @@ #ifndef DATABASE_HPP #define DATABASE_HPP #include "types.hpp" -#include "timestamp.hpp" +#include "utils.hpp" #include #include @@ -28,10 +28,11 @@ public: void CreateAccessToken(User& user); void DeleteAccessToken(const User& user); + User ValidateAccessToken(const std::string& token); - void StartFront(const User& user, const Member& member, const std::string& note = "", Timestamp start_time = std::chrono::system_clock::now()); - void EndFront(const User& user, const Member& member, Timestamp end_time = std::chrono::system_clock::now()); - std::vector GetFronts(const User& user, Timestamp start_time, std::optional end_time = std::nullopt); + void StartFront(const User& user, const Member& member, const std::string& note = "", timestamp::Timestamp start_time = timestamp::Now()); + void EndFront(const User& user, const Member& member, timestamp::Timestamp end_time = timestamp::Now()); + std::vector GetFronts(const User& user, timestamp::Timestamp start_time, std::optional end_time = std::nullopt); }; #endif \ No newline at end of file diff --git a/include/timestamp.hpp b/include/timestamp.hpp deleted file mode 100644 index c365cd4..0000000 --- a/include/timestamp.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef TIMESTAMP_HPP -#define TIMESTAMP_HPP -#include -#include -#include - -typedef std::chrono::system_clock::time_point Timestamp; - -Timestamp ParseTimestamp(const std::string& ts); - -#endif \ No newline at end of file diff --git a/include/types.hpp b/include/types.hpp index cb0bf37..53cf585 100644 --- a/include/types.hpp +++ b/include/types.hpp @@ -2,8 +2,7 @@ #define TYPES_HPP #include #include -#include -#include "hashing.hpp" +#include "utils.hpp" struct AcessToken { int uid; @@ -62,9 +61,9 @@ struct Front { int uid; Member *member; std::string note; - std::chrono::system_clock::time_point start_time; + timestamp::Timestamp start_time; bool is_active; - std::chrono::system_clock::time_point end_time; + timestamp::Timestamp end_time; }; #endif \ No newline at end of file diff --git a/include/hashing.hpp b/include/utils.hpp similarity index 52% rename from include/hashing.hpp rename to include/utils.hpp index 0bd0f51..6e4142f 100644 --- a/include/hashing.hpp +++ b/include/utils.hpp @@ -1,6 +1,9 @@ -#ifndef HASHING_HPP -#define HASHING_HPP +#ifndef UTILS_HPP +#define UTILS_HPP #include +#include +#include +#include namespace hashing { std::string GenerateSetting(unsigned long cost = 0); @@ -9,4 +12,12 @@ namespace hashing { std::string generate_token(size_t bytes = 32); } +namespace timestamp { + typedef std::chrono::system_clock::time_point Timestamp; + + Timestamp ParseTimestamp(const std::string& ts); + Timestamp Now(); + std::string FormatTimestamp(const Timestamp& ts); +} + #endif \ No newline at end of file diff --git a/src/auth_middleware.cpp b/src/auth_middleware.cpp new file mode 100644 index 0000000..5c6da05 --- /dev/null +++ b/src/auth_middleware.cpp @@ -0,0 +1,32 @@ +#include "auth_middleware.hpp" +#include "database.hpp" +#include "creds.hpp" + +std::vector public_routes = { + "/auth", + "/" +}; + +inline bool isPublicRoute(const std::string& url) { + return std::find(public_routes.begin(), public_routes.end(), url) != public_routes.end(); +} + +void UserAuthMiddleware::before_handle(crow::request& req, crow::response& res, context& ctx) { + if (isPublicRoute(req.url)) return; + + auto token = req.get_header_value("Authorization"); + + if (token.empty()) { + res.code = 401; + res.end("Missing token"); + return; + } + + try { + auto db = PostgresDB(DB_CREDS); + ctx.user = db.ValidateAccessToken(token); + } catch (const std::exception& e) { + res.code = 403; + res.end("Invalid token"); + } +} \ No newline at end of file diff --git a/src/database.cpp b/src/database.cpp index 9eedb14..f57ec45 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -217,7 +217,15 @@ void PostgresDB::DeleteAccessToken(const User& user) { W.commit(); } -void PostgresDB::StartFront(const User& user, const Member& member, const std::string& note, Timestamp start_time) { +User PostgresDB::ValidateAccessToken(const std::string& token) { + pqxx::connection C(conn_str); + pqxx::work W(C); + + int uid = W.query_value("SELECT uid FROM access_tokens WHERE token = $1", pqxx::params{token}); + return GetUser(uid); +} + +void PostgresDB::StartFront(const User& user, const Member& member, const std::string& note, timestamp::Timestamp start_time) { std::string time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", start_time); pqxx::connection C(conn_str); @@ -235,7 +243,7 @@ void PostgresDB::StartFront(const User& user, const Member& member, const std::s W.commit(); } -void PostgresDB::EndFront(const User& user, const Member& member, Timestamp end_time) { +void PostgresDB::EndFront(const User& user, const Member& member, timestamp::Timestamp end_time) { std::string time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", end_time); pqxx::connection C(conn_str); @@ -255,7 +263,7 @@ void PostgresDB::EndFront(const User& user, const Member& member, Timestamp end_ W.commit(); } -std::vector PostgresDB::GetFronts(const User& user, Timestamp start_time, std::optional end_time) { +std::vector PostgresDB::GetFronts(const User& user, timestamp::Timestamp start_time, std::optional end_time) { std::string start_time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", start_time); std::vector front_history; @@ -291,10 +299,10 @@ std::vector PostgresDB::GetFronts(const User& user, Timestamp start_time, front.member->description = R2[0]["description"].as(); front.is_active = true; - front.start_time = ParseTimestamp(row["start_time"].as()); + front.start_time = timestamp::ParseTimestamp(row["start_time"].as()); if (!row["end_time"].is_null()) { front.is_active = false; - front.end_time = ParseTimestamp(row["end_time"].as()); + front.end_time = timestamp::ParseTimestamp(row["end_time"].as()); } front.note = row["note"].as(); diff --git a/src/main.cpp b/src/main.cpp index dedd29d..793375e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,78 @@ +#include "auth_middleware.hpp" #include "database.hpp" #include "creds.hpp" -#include +#include "crow.h" + +typedef crow::json::wvalue json_t; + +inline UserAuthMiddleware::context& GetCtx(crow::App& app, const crow::request& req) { + return app.get_context(req); +} int main() { + crow::App app; PostgresDB db(DB_CREDS); - User user = db.GetUser("toaster", "password"); + CROW_ROUTE(app, "/")([](){ + return "Welcome to the api :3"; + }); - std::cout << "User: " << user.username << std::endl; - std::cout << "System id: " << user.system->sid << std::endl; - std::cout << "Member 0 Name: " << user.system->members[0].name << std::endl; + CROW_ROUTE(app, "/user/info").methods(crow::HTTPMethod::GET)([&](const crow::request& req){ + auto& ctx = GetCtx(app, req); + json_t json; - auto fronts = db.GetFronts(user, ParseTimestamp("2026-03-11 10:30:00")); + json["username"] = ctx.user.username; + json["is_system"] = ctx.user.is_system; - return 0; + return crow::response(json); + }); + + CROW_ROUTE(app, "/user/members").methods(crow::HTTPMethod::GET)([&](const crow::request& req){ + auto& ctx = GetCtx(app, req); + if (!ctx.user.is_system) return crow::response(422, "User does not have a system"); + json_t json; + + for (int i = 0; i < ctx.user.system->members.size(); i++) { + json[i]["name"] = ctx.user.system->members[i].name; + json[i]["pronouns"] = ctx.user.system->members[i].pronouns; + json[i]["description"] = ctx.user.system->members[i].description; + json[i]["id"] = ctx.user.system->members[i].mid; + } + + return crow::response(json); + }); + + CROW_ROUTE(app, "/front/history").methods(crow::HTTPMethod::GET)([&](const crow::request& req){ + auto& ctx = GetCtx(app, req); + + std::vector front_history; + std::string start_time = req.get_header_value("start_timestamp"); + std::string end_time = req.get_header_value("end_timestamp"); + if (start_time.empty()) return crow::response(422, "Missing start_timestamp"); + timestamp::Timestamp start_timestamp = timestamp::ParseTimestamp(start_time); + + if (end_time.empty()) { + front_history = db.GetFronts(ctx.user, start_timestamp); + } else { + timestamp::Timestamp end_timestamp = timestamp::ParseTimestamp(end_time); + front_history = db.GetFronts(ctx.user, start_timestamp, end_timestamp); + } + + json_t json; + for (int i = 0; i < front_history.size(); i++) { + json["fronts"][i]["member"]["name"] = front_history[i].member->name; + json["fronts"][i]["member"]["id"] = front_history[i].member->mid; + json["fronts"][i]["note"] = front_history[i].note; + json["fronts"][i]["start_time"] = timestamp::FormatTimestamp(front_history[i].start_time); + if (front_history[i].is_active) { + json["fronts"][i]["end_time"] = "Active"; + } else { + json["fronts"][i]["end_time"] = timestamp::FormatTimestamp(front_history[i].end_time); + } + } + + return crow::response(json); + }); + + app.port(18080).multithreaded().run(); } \ No newline at end of file diff --git a/src/timestamp.cpp b/src/timestamp.cpp deleted file mode 100644 index 5d6a493..0000000 --- a/src/timestamp.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "timestamp.hpp" - -Timestamp ParseTimestamp(const std::string& ts) { - std::tm tm = {}; - std::istringstream ss(ts); - ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); - return std::chrono::system_clock::from_time_t(std::mktime(&tm)); -} \ No newline at end of file diff --git a/src/hashing.cpp b/src/utils.cpp similarity index 68% rename from src/hashing.cpp rename to src/utils.cpp index 193940e..115cfd6 100644 --- a/src/hashing.cpp +++ b/src/utils.cpp @@ -1,15 +1,16 @@ #include #include -#include "hashing.hpp" +#include "utils.hpp" #include #include #include +#include std::string hashing::GenerateSetting(unsigned long cost) { // "$y$" is the yescrypt prefix const char* setting = crypt_gensalt("$y$", cost, nullptr, 0); if (!setting) - throw std::runtime_error("crypt_gensalt() failed – yescrypt may not be supported"); + throw std::runtime_error("crypt_gensalt() failed - yescrypt may not be supported"); return setting; } @@ -41,4 +42,19 @@ std::string hashing::generate_token(size_t bytes) { token += ss.str(); } return token; -} \ No newline at end of file +} + +timestamp::Timestamp timestamp::ParseTimestamp(const std::string& ts) { + std::tm tm = {}; + std::istringstream ss(ts); + ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); + return std::chrono::system_clock::from_time_t(std::mktime(&tm)); +} + +timestamp::Timestamp timestamp::Now() { + return std::chrono::system_clock::now(); +} + +std::string timestamp::FormatTimestamp(const timestamp::Timestamp& ts) { + return std::format("{:%Y-%m-%d %H:%M:%S}", ts); +}