api basic routes
This commit is contained in:
@@ -11,8 +11,8 @@ add_subdirectory(libs/crow)
|
|||||||
add_executable(plural++
|
add_executable(plural++
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/database.cpp
|
src/database.cpp
|
||||||
src/hashing.cpp
|
src/auth_middleware.cpp
|
||||||
src/timestamp.cpp
|
src/utils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
|
|||||||
18
include/auth_middleware.hpp
Normal file
18
include/auth_middleware.hpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef AUTH_MIDDLEWARE_HPP
|
||||||
|
#define AUTH_MIDDLEWARE_HPP
|
||||||
|
#include "crow.h"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <database.hpp>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef DATABASE_HPP
|
#ifndef DATABASE_HPP
|
||||||
#define DATABASE_HPP
|
#define DATABASE_HPP
|
||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
#include "timestamp.hpp"
|
#include "utils.hpp"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
@@ -28,10 +28,11 @@ public:
|
|||||||
|
|
||||||
void CreateAccessToken(User& user);
|
void CreateAccessToken(User& user);
|
||||||
void DeleteAccessToken(const 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 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 end_time = std::chrono::system_clock::now());
|
void EndFront(const User& user, const Member& member, timestamp::Timestamp end_time = timestamp::Now());
|
||||||
std::vector<Front> GetFronts(const User& user, Timestamp start_time, std::optional<Timestamp> end_time = std::nullopt);
|
std::vector<Front> GetFronts(const User& user, timestamp::Timestamp start_time, std::optional<timestamp::Timestamp> end_time = std::nullopt);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#ifndef TIMESTAMP_HPP
|
|
||||||
#define TIMESTAMP_HPP
|
|
||||||
#include <chrono>
|
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
typedef std::chrono::system_clock::time_point Timestamp;
|
|
||||||
|
|
||||||
Timestamp ParseTimestamp(const std::string& ts);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
#define TYPES_HPP
|
#define TYPES_HPP
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <chrono>
|
#include "utils.hpp"
|
||||||
#include "hashing.hpp"
|
|
||||||
|
|
||||||
struct AcessToken {
|
struct AcessToken {
|
||||||
int uid;
|
int uid;
|
||||||
@@ -62,9 +61,9 @@ struct Front {
|
|||||||
int uid;
|
int uid;
|
||||||
Member *member;
|
Member *member;
|
||||||
std::string note;
|
std::string note;
|
||||||
std::chrono::system_clock::time_point start_time;
|
timestamp::Timestamp start_time;
|
||||||
bool is_active;
|
bool is_active;
|
||||||
std::chrono::system_clock::time_point end_time;
|
timestamp::Timestamp end_time;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#ifndef HASHING_HPP
|
#ifndef UTILS_HPP
|
||||||
#define HASHING_HPP
|
#define UTILS_HPP
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
namespace hashing {
|
namespace hashing {
|
||||||
std::string GenerateSetting(unsigned long cost = 0);
|
std::string GenerateSetting(unsigned long cost = 0);
|
||||||
@@ -9,4 +12,12 @@ namespace hashing {
|
|||||||
std::string generate_token(size_t bytes = 32);
|
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
|
#endif
|
||||||
32
src/auth_middleware.cpp
Normal file
32
src/auth_middleware.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include "auth_middleware.hpp"
|
||||||
|
#include "database.hpp"
|
||||||
|
#include "creds.hpp"
|
||||||
|
|
||||||
|
std::vector<std::string> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -217,7 +217,15 @@ void PostgresDB::DeleteAccessToken(const User& user) {
|
|||||||
W.commit();
|
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<int>("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);
|
std::string time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", start_time);
|
||||||
|
|
||||||
pqxx::connection C(conn_str);
|
pqxx::connection C(conn_str);
|
||||||
@@ -235,7 +243,7 @@ void PostgresDB::StartFront(const User& user, const Member& member, const std::s
|
|||||||
W.commit();
|
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);
|
std::string time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", end_time);
|
||||||
|
|
||||||
pqxx::connection C(conn_str);
|
pqxx::connection C(conn_str);
|
||||||
@@ -255,7 +263,7 @@ void PostgresDB::EndFront(const User& user, const Member& member, Timestamp end_
|
|||||||
W.commit();
|
W.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Front> PostgresDB::GetFronts(const User& user, Timestamp start_time, std::optional<Timestamp> end_time) {
|
std::vector<Front> PostgresDB::GetFronts(const User& user, timestamp::Timestamp start_time, std::optional<timestamp::Timestamp> end_time) {
|
||||||
std::string start_time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", start_time);
|
std::string start_time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", start_time);
|
||||||
std::vector<Front> front_history;
|
std::vector<Front> front_history;
|
||||||
|
|
||||||
@@ -291,10 +299,10 @@ std::vector<Front> PostgresDB::GetFronts(const User& user, Timestamp start_time,
|
|||||||
front.member->description = R2[0]["description"].as<std::string>();
|
front.member->description = R2[0]["description"].as<std::string>();
|
||||||
|
|
||||||
front.is_active = true;
|
front.is_active = true;
|
||||||
front.start_time = ParseTimestamp(row["start_time"].as<std::string>());
|
front.start_time = timestamp::ParseTimestamp(row["start_time"].as<std::string>());
|
||||||
if (!row["end_time"].is_null()) {
|
if (!row["end_time"].is_null()) {
|
||||||
front.is_active = false;
|
front.is_active = false;
|
||||||
front.end_time = ParseTimestamp(row["end_time"].as<std::string>());
|
front.end_time = timestamp::ParseTimestamp(row["end_time"].as<std::string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
front.note = row["note"].as<std::string>();
|
front.note = row["note"].as<std::string>();
|
||||||
|
|||||||
75
src/main.cpp
75
src/main.cpp
@@ -1,17 +1,78 @@
|
|||||||
|
#include "auth_middleware.hpp"
|
||||||
#include "database.hpp"
|
#include "database.hpp"
|
||||||
#include "creds.hpp"
|
#include "creds.hpp"
|
||||||
#include <iostream>
|
#include "crow.h"
|
||||||
|
|
||||||
|
typedef crow::json::wvalue json_t;
|
||||||
|
|
||||||
|
inline UserAuthMiddleware::context& GetCtx(crow::App<UserAuthMiddleware>& app, const crow::request& req) {
|
||||||
|
return app.get_context<UserAuthMiddleware>(req);
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
crow::App<UserAuthMiddleware> app;
|
||||||
PostgresDB db(DB_CREDS);
|
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;
|
CROW_ROUTE(app, "/user/info").methods(crow::HTTPMethod::GET)([&](const crow::request& req){
|
||||||
std::cout << "System id: " << user.system->sid << std::endl;
|
auto& ctx = GetCtx(app, req);
|
||||||
std::cout << "Member 0 Name: " << user.system->members[0].name << std::endl;
|
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> 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();
|
||||||
}
|
}
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
#include <crypt.h>
|
#include <crypt.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include "hashing.hpp"
|
#include "utils.hpp"
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
std::string hashing::GenerateSetting(unsigned long cost) {
|
std::string hashing::GenerateSetting(unsigned long cost) {
|
||||||
// "$y$" is the yescrypt prefix
|
// "$y$" is the yescrypt prefix
|
||||||
const char* setting = crypt_gensalt("$y$", cost, nullptr, 0);
|
const char* setting = crypt_gensalt("$y$", cost, nullptr, 0);
|
||||||
if (!setting)
|
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;
|
return setting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,4 +42,19 @@ std::string hashing::generate_token(size_t bytes) {
|
|||||||
token += ss.str();
|
token += ss.str();
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user