database functions

This commit is contained in:
2026-03-11 17:48:24 +00:00
parent fe8a46a488
commit 1333de2b37
9 changed files with 308 additions and 4 deletions

View File

@@ -12,6 +12,7 @@ add_executable(plural++
src/main.cpp
src/database.cpp
src/hashing.cpp
src/timestamp.cpp
)
# Include directories

View File

@@ -1,4 +1,9 @@
#ifndef DATABASE_HPP
#define DATABASE_HPP
#include "types.hpp"
#include "timestamp.hpp"
#include <chrono>
#include <optional>
class PostgresDB {
private:
@@ -7,6 +12,26 @@ private:
public:
PostgresDB(const std::string& connection) : conn_str(connection) {}
User GetUser(const int uid);
User GetUser(int uid);
User GetUser(const std::string& username, const std::string& password);
};
void GetUser(User& user) { user = GetUser(user.uid);};
User CreateUser(const std::string& username, const std::string& password, bool is_system);
void UpdateUserPassowrd(User& user, const std::string& password);
void DeleteUser(const User& user);
void CreateMember(User& user, const std::string& name, const std::string& pronouns, const std::string& description);
void UpdateMemberDescription(Member& member, const std::string& description);
void UpdateMemberPronouns(Member& member, const std::string& pronouns);
void UpdateMemberName(Member& member, const std::string& name);
void DeleteMember(const Member& member);
void CreateAccessToken(User& user);
void DeleteAccessToken(const User& user);
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<Front> GetFronts(const User& user, Timestamp start_time, std::optional<Timestamp> end_time = std::nullopt);
};
#endif

View File

@@ -1,7 +1,12 @@
#ifndef HASHING_HPP
#define HASHING_HPP
#include <string>
namespace hashing {
std::string GenerateSetting(unsigned long cost = 0);
std::string HashPassword(const std::string& password, const std::string& setting);
bool VerifyPassword(const std::string& password, const std::string& stored_password);
}
std::string generate_token(size_t bytes = 32);
}
#endif

11
include/timestamp.hpp Normal file
View File

@@ -0,0 +1,11 @@
#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

View File

@@ -1,5 +1,8 @@
#ifndef TYPES_HPP
#define TYPES_HPP
#include <string>
#include <vector>
#include <chrono>
#include "hashing.hpp"
struct AcessToken {
@@ -52,4 +55,16 @@ struct User {
const bool CheckPassword(const std::string& password) const {
return hashing::VerifyPassword(password, password_hash);
}
};
};
struct Front {
int fid;
int uid;
Member *member;
std::string note;
std::chrono::system_clock::time_point start_time;
bool is_active;
std::chrono::system_clock::time_point end_time;
};
#endif

View File

@@ -1,5 +1,7 @@
#include <pqxx/pqxx>
#include "database.hpp"
#include <format>
User PostgresDB::GetUser(const int uid) {
pqxx::connection C(conn_str);
@@ -84,4 +86,220 @@ User PostgresDB::GetUser(const std::string& username, const std::string& passwor
}
return PostgresDB::GetUser(row["uid"].as<int>());
}
User PostgresDB::CreateUser(const std::string& username, const std::string& password, bool is_system) {
pqxx::connection C(conn_str);
pqxx::work W(C);
pqxx::result R = W.exec("SELECT * FROM users WHERE username = $1", pqxx::params{username});
if (R.size() != 0) {
W.commit();
throw std::runtime_error("User already exists");
}
W.exec(
"INSERT INTO users (username, password_hash, is_system) VALUES ($1, $2, $3)",
pqxx::params{username, hashing::HashPassword(password, hashing::GenerateSetting()), is_system}
);
int uid = W.query_value<int>("SELECT uid FROM users WHERE username = $1", pqxx::params{username});
if (is_system) {
W.exec("INSERT INTO systems (uid) VALUES ($1)", pqxx::params{uid});
}
W.commit();
return GetUser(uid);
}
void PostgresDB::DeleteUser(const User& user) {
pqxx::connection C(conn_str);
pqxx::work W(C);
W.exec("DELETE FROM users WHERE uid = $1", pqxx::params{user.uid});
W.commit();
}
void PostgresDB::UpdateUserPassowrd(User& user, const std::string& password) {
pqxx::connection C(conn_str);
pqxx::work W(C);
W.exec(
"UPDATE users SET password_hash = $1 WHERE uid = $2",
pqxx::params{hashing::HashPassword(password, hashing::GenerateSetting()), user.uid}
);
W.commit();
GetUser(user);
}
void PostgresDB::CreateMember(User& user, const std::string& name, const std::string& pronouns, const std::string& description) {
if (!user.is_system) throw std::runtime_error("User does not have a system");
for (const auto& member : user.system->members) {
if (member.name == name) throw std::runtime_error("Member already exists");
}
pqxx::connection C(conn_str);
pqxx::work W(C);
W.exec(
"INSERT INTO members (sid, name, pronouns, description) VALUES ($1, $2, $3, $4)",
pqxx::params{user.system->sid, name, pronouns, description}
);
W.commit();
GetUser(user);
}
void PostgresDB::UpdateMemberDescription(Member& member, const std::string& description) {
pqxx::connection C(conn_str);
pqxx::work W(C);
W.exec("UPDATE members SET description = $1 WHERE mid = $2", pqxx::params{description, member.mid});
W.commit();
}
void PostgresDB::UpdateMemberPronouns(Member& member, const std::string& pronouns) {
pqxx::connection C(conn_str);
pqxx::work W(C);
W.exec("UPDATE members SET pronouns = $1 WHERE mid = $2", pqxx::params{pronouns, member.mid});
W.commit();
}
void PostgresDB::UpdateMemberName(Member& member, const std::string& name) {
pqxx::connection C(conn_str);
pqxx::work W(C);
pqxx::result R = W.exec("SELECT * FROM members WHERE sid = $1", pqxx::params{member.sid});
for (auto row : R) {
if (row["name"].as<std::string>() == name) throw std::runtime_error("Member already exists");
}
W.exec("UPDATE members SET name = $1 WHERE mid = $2", pqxx::params{name, member.mid});
W.commit();
}
void PostgresDB::DeleteMember(const Member& member) {
pqxx::connection C(conn_str);
pqxx::work W(C);
W.exec("DELETE FROM members WHERE mid = $1", pqxx::params{member.mid});
W.commit();
}
void PostgresDB::CreateAccessToken(User& user) {
pqxx::connection C(conn_str);
pqxx::work W(C);
pqxx::result R = W.exec("SELECT * FROM access_tokens WHERE uid = $1", pqxx::params{user.uid});
if (R.size() != 0) throw std::runtime_error("User already has an access token");
W.exec(
"INSERT INTO access_tokens (uid, token) VALUES ($1, $2)",
pqxx::params{user.uid, hashing::generate_token()}
);
W.commit();
GetUser(user);
}
void PostgresDB::DeleteAccessToken(const User& user) {
if (user.accesstoken == nullptr) throw std::runtime_error("User has no access token");
pqxx::connection C(conn_str);
pqxx::work W(C);
W.exec("DELETE FROM access_tokens WHERE uid = $1", pqxx::params{user.uid});
W.commit();
}
void PostgresDB::StartFront(const User& user, const Member& member, const std::string& note, Timestamp start_time) {
std::string time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", start_time);
pqxx::connection C(conn_str);
pqxx::work W(C);
pqxx::result R = W.exec("SELECT * FROM front_history WHERE mid = $1 AND end_time IS NULL", pqxx::params{member.mid});
if (R.size() != 0) throw std::runtime_error("Front already started");
W.exec(
"INSERT INTO front_history (uid, mid, start_time, note) VALUES ($1, $2, $3, $4)",
pqxx::params{user.uid, member.mid, time_string, note}
);
W.commit();
}
void PostgresDB::EndFront(const User& user, const Member& member, Timestamp end_time) {
std::string time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", end_time);
pqxx::connection C(conn_str);
pqxx::work W(C);
pqxx::result R = W.exec("SELECT * FROM front_history WHERE mid = $1 AND end_time IS NULL", pqxx::params{member.mid});
if (R.size() == 0) throw std::runtime_error("Front not started");
int64_t fid = R[0]["fid"].as<int64_t>();
W.exec(
"UPDATE front_history SET end_time = $1 WHERE fid = $2",
pqxx::params{time_string, fid}
);
W.commit();
}
std::vector<Front> PostgresDB::GetFronts(const User& user, Timestamp start_time, std::optional<Timestamp> end_time) {
std::string start_time_string = std::format("{:%Y-%m-%d %H:%M:%S+00:00}", start_time);
std::vector<Front> front_history;
pqxx::connection C(conn_str);
pqxx::work W(C);
pqxx::result R;
if (end_time.has_value()) {
std::string end_time_string = std::format("{:%Y-%m-%d %H:%M:%S+00}", end_time.value());
R = W.exec(
"SELECT * FROM front_history WHERE uid = $1 AND start_time >= $2 AND end_time <= $3 ORDER BY start_time DESC",
pqxx::params{user.uid, start_time_string, end_time_string}
);
} else {
R = W.exec(
"SELECT * FROM front_history WHERE uid = $1 AND start_time >= $2 ORDER BY start_time DESC",
pqxx::params{user.uid, start_time_string}
);
}
for (auto row : R) {
Front front;
front.fid = row["fid"].as<int64_t>();
front.uid = row["uid"].as<int>();
pqxx::result R2 = W.exec("SELECT * FROM members WHERE mid = $1", pqxx::params{row["mid"].as<int>()});
front.member = new Member;
front.member->mid = R2[0]["mid"].as<int>();
front.member->sid = R2[0]["sid"].as<int>();
front.member->name = R2[0]["name"].as<std::string>();
front.member->pronouns = R2[0]["pronouns"].as<std::string>();
front.member->description = R2[0]["description"].as<std::string>();
front.is_active = true;
front.start_time = ParseTimestamp(row["start_time"].as<std::string>());
if (!row["end_time"].is_null()) {
front.is_active = false;
front.end_time = ParseTimestamp(row["end_time"].as<std::string>());
}
front.note = row["note"].as<std::string>();
front_history.push_back(front);
}
return front_history;
}

View File

@@ -1,6 +1,9 @@
#include <crypt.h>
#include <stdexcept>
#include "hashing.hpp"
#include <random>
#include <sstream>
#include <iomanip>
std::string hashing::GenerateSetting(unsigned long cost) {
// "$y$" is the yescrypt prefix
@@ -24,4 +27,18 @@ bool hashing::VerifyPassword(const std::string& password, const std::string& sto
if (!result || result[0] == '*')
return false;
return stored_hash == result;
}
std::string hashing::generate_token(size_t bytes) {
std::random_device rd;
std::string token;
token.reserve(bytes * 2);
std::uniform_int_distribution<uint8_t> dist(0, 255);
for (size_t i = 0; i < bytes; ++i) {
std::ostringstream ss;
ss << std::hex << std::setw(2) << std::setfill('0') << (int)dist(rd);
token += ss.str();
}
return token;
}

View File

@@ -10,4 +10,8 @@ int main() {
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;
auto fronts = db.GetFronts(user, ParseTimestamp("2026-03-11 10:30:00"));
return 0;
}

8
src/timestamp.cpp Normal file
View File

@@ -0,0 +1,8 @@
#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));
}