database functions
This commit is contained in:
@@ -12,6 +12,7 @@ add_executable(plural++
|
|||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/database.cpp
|
src/database.cpp
|
||||||
src/hashing.cpp
|
src/hashing.cpp
|
||||||
|
src/timestamp.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
#ifndef DATABASE_HPP
|
||||||
|
#define DATABASE_HPP
|
||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
|
#include "timestamp.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
class PostgresDB {
|
class PostgresDB {
|
||||||
private:
|
private:
|
||||||
@@ -7,6 +12,26 @@ private:
|
|||||||
public:
|
public:
|
||||||
PostgresDB(const std::string& connection) : conn_str(connection) {}
|
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);
|
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>
|
#include <string>
|
||||||
|
|
||||||
namespace hashing {
|
namespace hashing {
|
||||||
std::string GenerateSetting(unsigned long cost = 0);
|
std::string GenerateSetting(unsigned long cost = 0);
|
||||||
std::string HashPassword(const std::string& password, const std::string& setting);
|
std::string HashPassword(const std::string& password, const std::string& setting);
|
||||||
bool VerifyPassword(const std::string& password, const std::string& stored_password);
|
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 <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
#include "hashing.hpp"
|
#include "hashing.hpp"
|
||||||
|
|
||||||
struct AcessToken {
|
struct AcessToken {
|
||||||
@@ -52,4 +55,16 @@ struct User {
|
|||||||
const bool CheckPassword(const std::string& password) const {
|
const bool CheckPassword(const std::string& password) const {
|
||||||
return hashing::VerifyPassword(password, password_hash);
|
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 <pqxx/pqxx>
|
||||||
#include "database.hpp"
|
#include "database.hpp"
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
|
||||||
User PostgresDB::GetUser(const int uid) {
|
User PostgresDB::GetUser(const int uid) {
|
||||||
pqxx::connection C(conn_str);
|
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>());
|
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 <crypt.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include "hashing.hpp"
|
#include "hashing.hpp"
|
||||||
|
#include <random>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
std::string hashing::GenerateSetting(unsigned long cost) {
|
std::string hashing::GenerateSetting(unsigned long cost) {
|
||||||
// "$y$" is the yescrypt prefix
|
// "$y$" is the yescrypt prefix
|
||||||
@@ -24,4 +27,18 @@ bool hashing::VerifyPassword(const std::string& password, const std::string& sto
|
|||||||
if (!result || result[0] == '*')
|
if (!result || result[0] == '*')
|
||||||
return false;
|
return false;
|
||||||
return stored_hash == result;
|
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 << "User: " << user.username << std::endl;
|
||||||
std::cout << "System id: " << user.system->sid << std::endl;
|
std::cout << "System id: " << user.system->sid << std::endl;
|
||||||
std::cout << "Member 0 Name: " << user.system->members[0].name << 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