1
0
forked from blue/pica

some ideas about database structure, began assets fetching request

This commit is contained in:
Blue 2024-01-11 18:33:46 -03:00
parent a1ab1339e3
commit d33ec5def8
Signed by untrusted user: blue
GPG Key ID: 9B203B252A63EE38
13 changed files with 251 additions and 45 deletions

View File

@ -6,7 +6,8 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <memory> #include <memory>
#include <stdint.h> #include <vector>
#include <cstdint>
namespace DB { namespace DB {
struct Session { struct Session {
@ -16,6 +17,18 @@ struct Session {
std::string renewToken; std::string renewToken;
}; };
struct Asset {
unsigned int id;
unsigned int owner;
unsigned int currency;
std::string title;
std::string icon;
// `color` INTEGER UNSIGNED DEFAULT 0,
// `balance` DECIMAL (20, 5) DEFAULT 0,
// `type` INTEGER UNSIGNED NOT NULL,
bool archived;
};
class Interface { class Interface {
public: public:
enum class Type { enum class Type {
@ -46,8 +59,9 @@ public:
virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 0; virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 0;
virtual std::string getAccountHash(const std::string& login) = 0; virtual std::string getAccountHash(const std::string& login) = 0;
virtual unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) = 0; virtual Session createSession(const std::string& login, const std::string& access, const std::string& renew) = 0;
virtual Session findSession(const std::string& accessToken) = 0; virtual Session findSession(const std::string& accessToken) = 0;
virtual std::vector<Asset> listAssets(unsigned int owner) = 0;
protected: protected:
Interface(Type type); Interface(Type type);

View File

@ -33,7 +33,7 @@ CREATE TABLE IF NOT EXISTS roleBindings (
--creating sessings table --creating sessings table
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
`id` INTEGER AUTO_INCREMENT PRIMARY KEY, `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`owner` INTEGER UNSIGNED NOT NULL, `owner` INTEGER UNSIGNED NOT NULL,
`started` TIMESTAMP DEFAULT UTC_TIMESTAMP(), `started` TIMESTAMP DEFAULT UTC_TIMESTAMP(),
`latest` TIMESTAMP DEFAULT UTC_TIMESTAMP(), `latest` TIMESTAMP DEFAULT UTC_TIMESTAMP(),
@ -45,13 +45,90 @@ CREATE TABLE IF NOT EXISTS sessions (
FOREIGN KEY (owner) REFERENCES accounts(id) FOREIGN KEY (owner) REFERENCES accounts(id)
); );
--creating currencies table
CREATE TABLE IF NOT EXISTS currencies (
`id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`code` VARCHAR(16) NOT NULL UNIQUE,
`title` VARCHAR(256),
`manual` BOOLEAN NOT NULL,
`added` TIMESTAMP DEFAULT UTC_TIMESTAMP(),
`type` INTEGER UNSIGNED NOT NULL,
`value` DECIMAL (20, 5) NOT NULL,
`source` TEXT,
`description` TEXT,
INDEX manual_idx (manual)
);
--creating assests table
CREATE TABLE IF NOT EXISTS assets (
`id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`owner` INTEGER UNSIGNED NOT NULL,
`currency` INTEGER UNSIGNED NOT NULL,
`title` VARCHAR(256),
`icon` VARCHAR(256),
`color` INTEGER UNSIGNED DEFAULT 0,
`balance` DECIMAL (20, 5) DEFAULT 0,
`type` INTEGER UNSIGNED NOT NULL,
`archived` BOOLEAN DEFAULT FALSE,
INDEX owner_idx (owner),
INDEX archived_idx (archived),
FOREIGN KEY (owner) REFERENCES accounts(id),
FOREIGN KEY (currency) REFERENCES currencies(id)
);
--creating parties table
CREATE TABLE IF NOT EXISTS parties (
`id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(256) NOT NULL UNIQUE
);
--creating transactions table
CREATE TABLE IF NOT EXISTS transactions (
`id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`initiator` INTEGER UNSIGNED NOT NULL,
`type` INTEGER UNSIGNED NOT NULL,
`asset` INTEGER UNSIGNED NOT NULL,
`parent` INTEGER UNSIGNED,
`value` DECIMAL (20, 5) NOT NULL,
`state` INTEGER UNSIGNED DEFAULT 0,
`modified` TIMESTAMP DEFAULT UTC_TIMESTAMP(),
`performed` TIMESTAMP DEFAULT UTC_TIMESTAMP(),
`party` INTEGER UNSIGNED,
`notes` TEXT,
INDEX initiator_idx (initiator),
INDEX parent_idx (parent),
INDEX asset_idx (asset),
INDEX performed_idx (performed),
INDEX modified_idx (modified),
INDEX party_idx (party),
FOREIGN KEY (initiator) REFERENCES accounts(id),
FOREIGN KEY (asset) REFERENCES assets(id),
FOREIGN KEY (parent) REFERENCES transactions(id),
FOREIGN KEY (party) REFERENCES parties(id)
);
--creating defailt roles --creating defailt roles
INSERT IGNORE INTO roles (`name`) INSERT IGNORE INTO
roles (`name`)
VALUES ('root'), VALUES ('root'),
('default'); ('default');
--inserting initial version --inserting initial version
INSERT INTO system (`key`, `value`) VALUES ('version', '0'); INSERT IGNORE INTO
system (`key`, `value`)
VALUES ('version', '0');
--recording initial time --recording initial time
INSERT INTO system (`key`, `value`) VALUES ('created', UTC_TIMESTAMP()); INSERT IGNORE INTO
system (`key`, `value`)
VALUES ('created', UTC_TIMESTAMP());
--creating default currencies
INSERT IGNORE INTO
currencies (`code`, `title`, `manual`, `description`, `type`, `value`)
VALUES ('USD', 'United States Dollar', TRUE, 'Base currency', 0, 1);

View File

@ -19,8 +19,10 @@ constexpr const char* lastIdQuery = "SELECT LAST_INSERT_ID() AS id";
constexpr const char* assignRoleQuery = "INSERT INTO roleBindings (`account`, `role`) SELECT ?, roles.id FROM roles WHERE roles.name = ?"; constexpr const char* assignRoleQuery = "INSERT INTO roleBindings (`account`, `role`) SELECT ?, roles.id FROM roles WHERE roles.name = ?";
constexpr const char* selectHash = "SELECT password FROM accounts where login = ?"; constexpr const char* selectHash = "SELECT password FROM accounts where login = ?";
constexpr const char* createSessionQuery = "INSERT INTO sessions (`owner`, `access`, `renew`, `persist`, `device`)" constexpr const char* createSessionQuery = "INSERT INTO sessions (`owner`, `access`, `renew`, `persist`, `device`)"
" SELECT accounts.id, ?, ?, true, ? FROM accounts WHERE accounts.login = ?"; " SELECT accounts.id, ?, ?, true, ? FROM accounts WHERE accounts.login = ?"
" RETURNING id, owner";
constexpr const char* selectSession = "SELECT id, owner, renew FROM sessions where access = ?"; constexpr const char* selectSession = "SELECT id, owner, renew FROM sessions where access = ?";
constexpr const char* selectAssets = "SELECT id, owner, currency, title, icon, archived FROM assets where owner = ?";
static const std::filesystem::path buildSQLPath = "database"; static const std::filesystem::path buildSQLPath = "database";
@ -266,20 +268,30 @@ std::string DB::MySQL::getAccountHash(const std::string& login) {
return std::any_cast<const std::string&>(result[0][0]); return std::any_cast<const std::string&>(result[0][0]);
} }
unsigned int DB::MySQL::createSession(const std::string& login, const std::string& access, const std::string& renew) { DB::Session DB::MySQL::createSession(const std::string& login, const std::string& access, const std::string& renew) {
std::string l = login, a = access, r = renew; std::string l = login;
DB::Session res;
res.accessToken = access;
res.renewToken = renew;
static std::string testingDevice("Testing..."); static std::string testingDevice("Testing...");
MYSQL* con = &connection; MYSQL* con = &connection;
Statement session(con, createSessionQuery); Statement session(con, createSessionQuery);
session.bind(a.data(), MYSQL_TYPE_STRING); session.bind(res.accessToken.data(), MYSQL_TYPE_STRING);
session.bind(r.data(), MYSQL_TYPE_STRING); session.bind(res.renewToken.data(), MYSQL_TYPE_STRING);
session.bind(testingDevice.data(), MYSQL_TYPE_STRING); session.bind(testingDevice.data(), MYSQL_TYPE_STRING);
session.bind(l.data(), MYSQL_TYPE_STRING); session.bind(l.data(), MYSQL_TYPE_STRING);
session.execute(); session.execute();
return lastInsertedId(); std::vector<std::vector<std::any>> result = session.fetchResult();
if (result.empty())
throw std::runtime_error("Error returning ids after insertion in sessions table");
res.id = std::any_cast<unsigned int>(result[0][0]);
res.owner = std::any_cast<unsigned int>(result[0][1]);
return res;
} }
unsigned int DB::MySQL::lastInsertedId() { unsigned int DB::MySQL::lastInsertedId() {
@ -320,4 +332,27 @@ DB::Session DB::MySQL::findSession(const std::string& accessToken) {
return res; return res;
} }
std::vector<DB::Asset> DB::MySQL::listAssets(unsigned int owner) {
MYSQL* con = &connection;
Statement st(con, selectSession);
st.bind(&owner, MYSQL_TYPE_LONG, true);
st.execute();
std::vector<std::vector<std::any>> res = st.fetchResult();
std::size_t size = res.size();
std::vector<DB::Asset> result(size);
for (std::size_t i = 0; i < size; ++i) {
const std::vector<std::any>& proto = res[i];
DB::Asset& asset = result[i];
asset.id = std::any_cast<unsigned int>(proto[0]);
asset.owner = std::any_cast<unsigned int>(proto[1]);
asset.currency = std::any_cast<unsigned int>(proto[2]);
asset.title = std::any_cast<const std::string&>(proto[3]);
asset.icon = std::any_cast<const std::string&>(proto[4]);
asset.archived = std::any_cast<bool>(proto[5]); //TODO
}
return result;
}

View File

@ -19,27 +19,28 @@ class MySQL : public Interface {
public: public:
MySQL(); MySQL ();
~MySQL() override; ~MySQL () override;
void connect(const std::string& path) override; void connect (const std::string& path) override;
void disconnect() override; void disconnect () override;
void setCredentials(const std::string& login, const std::string& password) override; void setCredentials (const std::string& login, const std::string& password) override;
void setDatabase(const std::string& database) override; void setDatabase (const std::string& database) override;
void migrate(uint8_t targetVersion) override; void migrate (uint8_t targetVersion) override;
uint8_t getVersion() override; uint8_t getVersion () override;
void setVersion(uint8_t version) override; void setVersion (uint8_t version) override;
unsigned int registerAccount(const std::string& login, const std::string& hash) override; unsigned int registerAccount (const std::string& login, const std::string& hash) override;
std::string getAccountHash(const std::string& login) override; std::string getAccountHash (const std::string& login) override;
unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) override; Session createSession (const std::string& login, const std::string& access, const std::string& renew) override;
Session findSession(const std::string& accessToken) override; Session findSession (const std::string& accessToken) override;
std::vector<Asset> listAssets (unsigned int owner) override;
private: private:
void executeFile(const std::filesystem::path& relativePath); void executeFile (const std::filesystem::path& relativePath);
static std::optional<std::string> getComment(std::string& string); static std::optional<std::string> getComment (std::string& string);
unsigned int lastInsertedId(); unsigned int lastInsertedId ();
protected: protected:
MYSQL connection; MYSQL connection;

View File

@ -8,6 +8,7 @@ set(HEADERS
register.h register.h
login.h login.h
poll.h poll.h
listassets.h
) )
set(SOURCES set(SOURCES
@ -17,6 +18,7 @@ set(SOURCES
register.cpp register.cpp
login.cpp login.cpp
poll.cpp poll.cpp
listassets.cpp
) )
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})

45
handler/listassets.cpp Normal file
View File

@ -0,0 +1,45 @@
//SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
//SPDX-License-Identifier: GPL-3.0-or-later
#include "listassets.h"
#include "server/server.h"
#include "server/session.h"
#include "database/exceptions.h"
Handler::ListAssets::ListAssets (std::weak_ptr<Server> server):
Handler("listAssets", Request::Method::get),
server(server)
{}
void Handler::ListAssets::handle (Request& request) {
std::string access = request.getAuthorizationToken();
if (access.empty())
return error(request, Response::Status::unauthorized);
if (access.size() != 32)
return error(request, Response::Status::badRequest);
std::shared_ptr<Server> srv = server.lock();
if (!srv)
return error(request, Response::Status::internalError);
try {
Session& session = srv->getSession(access);
} catch (const DB::NoSession& e) {
return error(request, Response::Status::unauthorized);
} catch (const std::exception& e) {
std::cerr << "Exception on poll:\n\t" << e.what() << std::endl;
return error(request, Response::Status::internalError);
} catch (...) {
std::cerr << "Unknown exception on poll" << std::endl;
return error(request, Response::Status::internalError);
}
}
void Handler::ListAssets::error (Request& request, Response::Status status) {
Response& res = request.createResponse(status);
res.send();
}

21
handler/listassets.h Normal file
View File

@ -0,0 +1,21 @@
//SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
//SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <memory>
#include "handler.h"
class Server;
namespace Handler {
class ListAssets : public Handler::Handler {
public:
ListAssets (std::weak_ptr<Server> server);
void handle (Request& request) override;
static void error (Request& request, Response::Status status);
private:
std::weak_ptr<Server> server;
};
}

View File

@ -14,8 +14,8 @@ namespace Handler {
class Poll : public Handler { class Poll : public Handler {
public: public:
Poll(std::weak_ptr<Server> server); Poll (std::weak_ptr<Server> server);
void handle(Request& request) override; void handle (Request& request) override;
enum class Result { enum class Result {
success, success,
@ -25,7 +25,8 @@ public:
unknownError unknownError
}; };
static void error(Request& request, Result result, Response::Status status); static void error (Request& request, Result result, Response::Status status);
private: private:
std::weak_ptr<Server> server; std::weak_ptr<Server> server;

View File

@ -4,12 +4,15 @@
#SPDX-License-Identifier: GPL-3.0-or-later #SPDX-License-Identifier: GPL-3.0-or-later
start_service() { start_service() {
if systemctl is-active --quiet $1 if (systemctl is-active --quiet $1) then
then
echo "$1 is already running" echo "$1 is already running"
else else
sudo systemctl start $1 echo "$1 is not running, going to use \"sudo systemctl start $1\""
if (sudo systemctl start $1) then
echo "$1 started" echo "$1 started"
else
exit
fi
fi fi
} }

View File

@ -157,25 +157,26 @@ bool Server::validatePassword(const std::string& login, const std::string& passw
Session& Server::openSession(const std::string& login) { Session& Server::openSession(const std::string& login) {
std::string accessToken, renewToken; std::string accessToken, renewToken;
unsigned int sessionId = 0; DB::Session s;
s.id = 0;
int counter = 10; int counter = 10;
do { do {
try { try {
accessToken = generateRandomString(32); accessToken = generateRandomString(32);
renewToken = generateRandomString(32); renewToken = generateRandomString(32);
DB::Resource db = pool->request(); DB::Resource db = pool->request();
sessionId = db->createSession(login, accessToken, renewToken); s = db->createSession(login, accessToken, renewToken);
break; break;
} catch (const DB::Duplicate& e) { } catch (const DB::Duplicate& e) {
std::cout << "Duplicate on creating session, trying again with different tokens"; std::cout << "Duplicate on creating session, trying again with different tokens";
} }
} while (--counter != 0); } while (--counter != 0);
if (sessionId == 0) if (s.id == 0)
throw std::runtime_error("Couldn't create session, ran out of attempts"); throw std::runtime_error("Couldn't create session, ran out of attempts");
std::unique_ptr<Session>& session = sessions[accessToken] std::unique_ptr<Session>& session = sessions[accessToken]
= std::make_unique<Session>(scheduler, sessionId, accessToken, renewToken, pollTimout); = std::make_unique<Session>(scheduler, s.id, s.owner, s.accessToken, s.renewToken, pollTimout);
return *session.get(); return *session.get();
} }
@ -189,6 +190,7 @@ Session& Server::getSession (const std::string& accessToken) {
std::unique_ptr<Session>& session = sessions[accessToken] = std::make_unique<Session>( std::unique_ptr<Session>& session = sessions[accessToken] = std::make_unique<Session>(
scheduler, scheduler,
s.id, s.id,
s.owner,
s.accessToken, s.accessToken,
s.renewToken, s.renewToken,
pollTimout pollTimout

View File

@ -8,12 +8,14 @@
Session::Session( Session::Session(
std::weak_ptr<TM::Scheduler> scheduler, std::weak_ptr<TM::Scheduler> scheduler,
unsigned int id, unsigned int id,
unsigned int owner,
const std::string& access, const std::string& access,
const std::string& renew, const std::string& renew,
unsigned int timeout unsigned int timeout
): ):
scheduler(scheduler),
id(id), id(id),
owner(owner),
scheduler(scheduler),
access(access), access(access),
renew(renew), renew(renew),
polling(nullptr), polling(nullptr),

View File

@ -13,26 +13,29 @@ public:
Session( Session(
std::weak_ptr<TM::Scheduler> scheduler, std::weak_ptr<TM::Scheduler> scheduler,
unsigned int id, unsigned int id,
unsigned int owner,
const std::string& access, const std::string& access,
const std::string& renew, const std::string& renew,
unsigned int timeout unsigned int timeout
); );
Session(const Session&) = delete; Session(const Session&) = delete;
Session(Session&& other); Session(Session&& other) = delete;
~Session(); ~Session();
Session& operator = (const Session&) = delete; Session& operator = (const Session&) = delete;
Session& operator = (Session&& other); Session& operator = (Session&& other) = delete;
std::string getAccessToken() const; std::string getAccessToken() const;
std::string getRenewToken() const; std::string getRenewToken() const;
void accept(std::unique_ptr<Request> request) override; void accept(std::unique_ptr<Request> request) override;
const unsigned int id;
const unsigned int owner;
private: private:
void onTimeout(); void onTimeout();
private: private:
std::weak_ptr<TM::Scheduler> scheduler; std::weak_ptr<TM::Scheduler> scheduler;
unsigned int id;
std::string access; std::string access;
std::string renew; std::string renew;
std::unique_ptr<Request> polling; std::unique_ptr<Request> polling;

View File

@ -36,7 +36,7 @@ void initPaths(const char* programPath) {
if (cp.parent_path() == FULL_BIN_DIR) if (cp.parent_path() == FULL_BIN_DIR)
return setAbsoluteSharedPath(); return setAbsoluteSharedPath();
std::cout << cp << std::endl; //std::cout << cp << std::endl;
std::filesystem::path parent = cp.parent_path(); std::filesystem::path parent = cp.parent_path();
if (endsWith(parent.string(), BIN_DIR)) { //this is the case when the program is installed somewhere but not system root if (endsWith(parent.string(), BIN_DIR)) { //this is the case when the program is installed somewhere but not system root
std::filesystem::path bin(BIN_DIR); //so it will read from something like ../share/pica/ relative to the binary std::filesystem::path bin(BIN_DIR); //so it will read from something like ../share/pica/ relative to the binary