database functions
This commit is contained in:
@@ -12,6 +12,7 @@ add_executable(plural++
|
||||
src/main.cpp
|
||||
src/database.cpp
|
||||
src/hashing.cpp
|
||||
src/timestamp.cpp
|
||||
)
|
||||
|
||||
# Include directories
|
||||
|
||||
@@ -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
|
||||
@@ -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
11
include/timestamp.hpp
Normal 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
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef TYPES_HPP
|
||||
#define TYPES_HPP
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include "hashing.hpp"
|
||||
|
||||
struct AcessToken {
|
||||
@@ -53,3 +56,15 @@ struct User {
|
||||
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
|
||||
218
src/database.cpp
218
src/database.cpp
@@ -1,5 +1,7 @@
|
||||
#include <pqxx/pqxx>
|
||||
#include "database.hpp"
|
||||
#include <format>
|
||||
|
||||
|
||||
User PostgresDB::GetUser(const int uid) {
|
||||
pqxx::connection C(conn_str);
|
||||
@@ -85,3 +87,219 @@ 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -25,3 +28,17 @@ bool hashing::VerifyPassword(const std::string& password, const std::string& sto
|
||||
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;
|
||||
}
|
||||
@@ -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
8
src/timestamp.cpp
Normal 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));
|
||||
}
|
||||
Reference in New Issue
Block a user