Initial commit

This commit is contained in:
user
2026-07-12 14:34:52 +04:00
commit 3abf6bd9c2
557 changed files with 68706 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
#include "auth_dialog.h"
#include "repository_manager.h"
#include "repository_result.h"
#include "role_dto.h"
#include "roles_repository.h"
#include "schema_metadata.h"
#include "user_dto.h"
#include "users_repository.h"
#include <QCryptographicHash>
#include <QErrorMessage>
#include <QMessageBox>
AuthDialog::AuthDialog(QWidget *parent) : QDialog(parent) {
setupUi(this);
// WARNING: Models must be initialized before setting up table view
if (initializeRepositories()) {
setupConnections();
} else {
qWarning() << "Initialization failed";
}
}
bool AuthDialog::initializeRepositories() {
RepositoryManager &repositoryManager = RepositoryManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
rolesRepository_ = repositoryManager.getRepositoryAs<RolesRepository>(
metadata.repositoryTableName("roles"));
usersRepository_ = repositoryManager.getRepositoryAs<UsersRepository>(
metadata.repositoryTableName("users"));
return rolesRepository_ && usersRepository_;
}
void AuthDialog::setupConnections() {
connect(btnLogin, &QPushButton::clicked, this, &AuthDialog::onLoginClicked);
connect(btnCancel, &QPushButton::clicked, this, &AuthDialog::onCancelClicked);
}
void AuthDialog::onCancelClicked() {
emit loginFailed();
reject();
}
void AuthDialog::onLoginClicked() {
QString username = txtUsername->text().trimmed();
QString password = txtPassword->text();
UserDTO userDTO;
RepositoryResult result =
usersRepository_->getUserByUsername(username, userDTO);
bool userExists = result.success;
bool passwordMatch = !userExists ? false : verifyPassword(password, userDTO);
if (!userExists || !passwordMatch) {
QMessageBox::warning(this, QMessageBox::tr("Error"),
QMessageBox::tr("Wrong user name or password"));
return;
}
RoleDTO roleDTO;
result = rolesRepository_->getRoleById(userDTO.roleId(), roleDTO);
if (!result.success) {
QMessageBox::warning(this, QMessageBox::tr("Error"),
QMessageBox::tr("Role of user not found"));
return;
}
qint32 userId = userDTO.userId();
qint32 accessLevel_ = roleDTO.accessLevel();
emit loginSuccessful(userId, accessLevel_);
accept();
}
QString AuthDialog::hashPassword(const QString &password, const QString &salt) {
QByteArray data = (password + salt).toUtf8();
QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256);
return QString(hash.toHex());
}
bool AuthDialog::verifyPassword(const QString &password,
const UserDTO &userDTO) {
QString inputHash = hashPassword(password, userDTO.salt());
return (inputHash == userDTO.passwordHash());
}
+39
View File
@@ -0,0 +1,39 @@
#ifndef AUTH_DIALOG_H
#define AUTH_DIALOG_H
#include "ui_auth_dialog.h"
#include <QDialog>
#include <QSqlDatabase>
// Forward declaration
class RolesRepositoryInterface;
class UserDTO;
class UsersRepositoryInterface;
class AuthDialog : public QDialog, private Ui::AuthDialog {
Q_OBJECT
private:
RolesRepositoryInterface *rolesRepository_;
UsersRepositoryInterface *usersRepository_;
public:
explicit AuthDialog(QWidget *parent = nullptr);
signals:
void loginSuccessful(qint32 userId, qint32 accessLevel);
void loginFailed();
private slots:
void onCancelClicked();
void onLoginClicked();
private:
bool initializeRepositories();
void setupConnections();
QString hashPassword(const QString &password, const QString &salt);
bool verifyPassword(const QString &password, const UserDTO &userDTO);
};
#endif // AUTH_DIALOG_H
+96
View File
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AuthDialog</class>
<widget class="QDialog" name="AuthDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>120</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>120</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>120</height>
</size>
</property>
<property name="windowTitle">
<string>Аuthorization</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="labelUsername">
<property name="text">
<string>Login:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txtUsername"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelPassword">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="txtPassword">
<property name="echoMode">
<enum>QLineEdit::EchoMode::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btnLogin">
<property name="text">
<string>Enter</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnCancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>btnCancel</sender>
<signal>clicked()</signal>
<receiver>AuthDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>118</y>
</hint>
<hint type="destinationlabel">
<x>150</x>
<y>100</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+217
View File
@@ -0,0 +1,217 @@
#include "error_dialog.h"
#include <QCheckBox>
#include <QDebug>
#include <QErrorMessage>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
QErrorMessage *ErrorDialog::getDialog(QWidget *parent,
const QString &errorMsg) {
// Initialize the error dialog
QErrorMessage *dialog =
new QErrorMessage(parent); // parent is needed for the correct location
dialog->setAttribute(Qt::WA_DeleteOnClose); // delete the dialog when closed
// Hide the "don't show again" checkbox
#define ERROR_DIALOG_NEW_VERSION 0
#if (ERROR_DIALOG_NEW_VERSION == 1 && QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
// You need to set DontShowAgainVisible to true in Qt 6.3
errorDialog->setDontShowAgainVisible(false);
#else
QCheckBox *cb = dialog->findChild<QCheckBox *>();
if (cb) {
cb->setEnabled(false);
cb->hide();
}
#endif
#undef ERROR_DIALOG_NEW_VERSION
// Set the window title
dialog->setWindowTitle(QObject::tr("Information messages"));
// Set minimum width size
dialog->setMinimumWidth(600);
return dialog;
}
void ErrorDialog::displayError(QWidget *parent, const QString &errorMsg) {
qDebug() << "Displaying error message:" << errorMsg;
QErrorMessage *dialog = getDialog(parent, errorMsg);
if (errorMsg.contains("constraint failed")) {
QString detailedMessage = handleSqlConstraints(errorMsg);
dialog->showMessage(detailedMessage);
dialog->exec();
return;
}
if (errorMsg.contains("not found")) {
QString detailedMessage = handleNotFoundErrors(errorMsg);
dialog->showMessage(detailedMessage);
dialog->exec();
return;
}
QRegularExpression regex("Error (\\d+):");
QRegularExpressionMatch match = regex.match(errorMsg);
if (!match.hasMatch()) {
dialog->showMessage(QObject::tr(
"An error occurred from an unknown source of the problem."));
dialog->exec();
return;
}
int errorCode = match.captured(1).toInt();
QString detailedMessage = getDetailedMessage(errorCode);
dialog->showMessage(detailedMessage);
dialog->exec();
}
QString ErrorDialog::handleNotFoundErrors(const QString &errorMsg) {
if (errorMsg.contains("Genre", Qt::CaseInsensitive)) {
return QObject::tr("The specified genre was not found.");
}
if (errorMsg.contains("Hall", Qt::CaseInsensitive)) {
return QObject::tr("The specified hall was not found.");
}
if (errorMsg.contains("Movie", Qt::CaseInsensitive)) {
return QObject::tr("The specified movie was not found.");
}
if (errorMsg.contains("Refund", Qt::CaseInsensitive)) {
return QObject::tr("The specified refund was not found.");
}
if (errorMsg.contains("Session", Qt::CaseInsensitive)) {
return QObject::tr("The specified session was not found.");
}
if (errorMsg.contains("Ticket", Qt::CaseInsensitive)) {
return QObject::tr("The specified ticket was not found.");
}
// If the specific error is not recognized, return a general message
return QObject::tr("The requested object was not found in the database.");
}
QString ErrorDialog::handleSqlConstraints(const QString &errorMsg) {
if (errorMsg.contains("UNIQUE constraint failed")) {
return handleUniqueConstraint(errorMsg);
}
if (errorMsg.contains("FOREIGN KEY constraint failed")) {
return handleForeignKeyConstraint(errorMsg);
}
if (errorMsg.contains("PRIMARY KEY constraint failed")) {
return handlePrimaryKeyConstraint();
}
if (errorMsg.contains("CHECK constraint failed")) {
return handleCheckConstraint(errorMsg);
}
if (errorMsg.contains("NOT NULL constraint failed")) {
return tr("A required field is missing. Please fill all required fields.");
}
return tr("An SQL constraint violation occurred, but at the moment it has "
"not been described in detail yet.");
}
QString ErrorDialog::handleUniqueConstraint(const QString &errorMsg) {
if (errorMsg.contains("genres.genre_name")) {
return tr("A genre with the same name already exists.");
}
if (errorMsg.contains("movies.title")) {
return tr("A movie with the same title already exists.");
}
if (errorMsg.contains("halls.hall_name")) {
return tr("A hall with the same name already exists.");
}
if (errorMsg.contains("tickets.session_id, tickets.seat_number")) {
return tr("A ticket with the same session and seat number already exists.");
}
if (errorMsg.contains("refunds.ticket_id")) {
return tr("A refund for the same ticket already exists.");
}
if (errorMsg.contains("sessions.hall_id, sessions.begin_at")) {
return tr("A session is already scheduled in this hall at the same time.");
}
return tr("A unique constraint violation occurred, but at the moment it has "
"not been described in detail yet.");
}
QString ErrorDialog::handleForeignKeyConstraint(const QString &errorMsg) {
if (errorMsg.contains("sessions.movie_id")) {
return tr("The movie ID for the session does not exist.");
}
if (errorMsg.contains("sessions.hall_id")) {
return tr("The hall ID for the session does not exist.");
}
if (errorMsg.contains("movies.genre_id")) {
return tr("The genre ID for the movie does not exist.");
}
if (errorMsg.contains("tickets.session_id")) {
return tr("The session ID for the ticket does not exist.");
}
if (errorMsg.contains("refunds.ticket_id")) {
return tr("The ticket ID for the refund does not exist.");
}
return tr("Operation violates a foreign key constraint, but at the moment it "
"has not been described in detail yet.");
}
QString ErrorDialog::handlePrimaryKeyConstraint() {
return tr("Duplicate primary key value. Ensure the primary key is unique.");
}
QString ErrorDialog::handleCheckConstraint(const QString &errorMsg) {
if (errorMsg.contains("length(genre_name) > 0")) {
return tr("Genre name must not be empty.");
}
if (errorMsg.contains("length(title) > 0")) {
return tr("Movie title must not be empty.");
}
if (errorMsg.contains("duration > 0")) {
return tr("Movie duration must be a positive value.");
}
if (errorMsg.contains("length(hall_name) > 0")) {
return tr("Hall name must not be empty.");
}
if (errorMsg.contains("capacity > 0")) {
return tr("Hall capacity must be a positive number.");
}
if (errorMsg.contains("ticket_price >= 0")) {
return tr("Ticket price must be a non-negative value.");
}
if (errorMsg.contains("seat_number > 0")) {
return tr("Seat number must be a positive value.");
}
if (errorMsg.contains("refund_amount >= 0")) {
return tr("Refund amount must be a non-negative value.");
}
return tr("A value violates a CHECK constraint, but at the moment it has not "
"been described in detail yet.");
}
QString ErrorDialog::getDetailedMessage(int errorCode) {
switch (errorCode) {
case 1001:
return tr("Seat number exceeds hall capacity.");
case 1002:
return tr("Cannot purchase ticket after the session has started.");
case 1003:
return tr("Refunds cannot be made after the session has started.");
case 1004:
return tr("Refund amount cannot exceed the sale amount.");
case 1005:
return tr("Refund date cannot be earlier than the sale date.");
case 1006:
return tr("The new session overlaps with an ongoing movie in the hall.");
default:
return tr("A known error has occurred, but at the moment it has not been "
"described in detail yet.");
}
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef ERROR_DIALOG_H
#define ERROR_DIALOG_H
#include <QErrorMessage>
#include <QObject>
#include <QString>
class ErrorDialog : private QObject {
// For translation
Q_OBJECT
public:
// Method to dispose the error dialog
static void displayError(QWidget *parent, const QString &errorMsg);
ErrorDialog() = delete;
private:
// A method for initializing the error dialog
static QErrorMessage *getDialog(QWidget *parent, const QString &errorMsg);
// A method for obtaining a "human-readable" text based on an error code
static QString getDetailedMessage(int errorCode);
// Methods for handling various SQL constraints
static QString handleNotFoundErrors(const QString &errorMsg);
static QString handleCheckConstraint(const QString &errorMsg);
static QString handleForeignKeyConstraint(const QString &errorMsg);
static QString handlePrimaryKeyConstraint();
static QString handleSqlConstraints(const QString &errorMsg);
static QString handleUniqueConstraint(const QString &errorMsg);
};
#endif // ERROR_DIALOG_H
+154
View File
@@ -0,0 +1,154 @@
#include "search_dialog.h"
#include "base_sql_table_model.h"
#include "model_manager.h"
#include "multi_column_filter_proxy_model.h"
#include <QMessageBox>
SearchDialog::SearchDialog(QWidget *parent)
: QDialog(parent), originalModel_(nullptr) {
setupUi(this);
initializeProxyModel();
setupTableView(); // After initializing proxy model
setupConnections();
}
SearchDialog::SearchDialog(const QString &modelTableName,
const QString &fieldName, QWidget *parent)
: SearchDialog(parent) {
if (!setTableName(modelTableName)) {
qFatal("SearchDialog::SearchDialog: Failed to set table name");
}
if (!setFieldName(fieldName)) {
qFatal("SearchDialog::SearchDialog: Failed to set field name");
}
}
SearchDialog::~SearchDialog() {
if (originalModel_ != nullptr) {
originalModel_->resetFilter();
}
}
bool SearchDialog::setFieldName(const QString &fieldName) {
int columnIndex = originalModel_->columnIndex(fieldName);
if (columnIndex == -1) {
return false;
}
columnIndex_ = columnIndex;
return true;
}
bool SearchDialog::setTableName(const QString &modelTableName) {
ModelManager &modelManger = ModelManager::instance();
if (originalModel_ != nullptr) {
originalModel_->resetFilter();
}
modelTableName_ = modelTableName;
originalModel_ = modelManger.getModelAs<BaseSqlTableModel>(modelTableName);
proxyModel_->setSourceModel(originalModel_);
tableView->clearSelection();
columnIndex_ = -1;
if (originalModel_->isCompleteDatabaseFiltering()) {
return true;
}
#ifdef NOPTIMIZATION
while (originalModel_->canFetchMore()) {
originalModel_->fetchMore();
}
#endif
return true;
}
void SearchDialog::initializeProxyModel() {
proxyModel_ = new MultiColumnSortFilterProxyModel(this);
}
void SearchDialog::setupConnections() {
connect(lineEditSearch, &QLineEdit::textChanged, this,
&SearchDialog::onLineEditSearchTextChanged);
connect(pushButtonSelect, &QPushButton::clicked, this,
&SearchDialog::onPushButtonSelectClicked);
}
void SearchDialog::setupTableView() {
QHeaderView *horizontalHeader = tableView->horizontalHeader();
horizontalHeader->setHighlightSections(false);
horizontalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
tableView->setAlternatingRowColors(true);
tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
tableView->setFocusPolicy(Qt::NoFocus);
tableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->setSelectionMode(QAbstractItemView::SingleSelection);
tableView->setSortingEnabled(true);
tableView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
tableView->sortByColumn(0, Qt::AscendingOrder);
tableView->setModel(proxyModel_);
}
void SearchDialog::onLineEditSearchTextChanged(const QString &text) {
ModelManager &modelManger = ModelManager::instance();
#ifdef NOPTIMIZATION
if (originalModel_->isCompleteDatabaseFiltering()) {
originalModel_->setMultiColumnFilter(text);
return;
}
// Model filtering
QRegularExpression regex(text, QRegularExpression::CaseInsensitiveOption);
proxyModel_->setFilterRegularExpression(regex);
#else
// Database filtering
originalModel_->setMultiColumnFilter(text);
#endif
}
void SearchDialog::onPushButtonSelectClicked() {
if (columnIndex_ == -1) {
qFatal("SearchDialog::onPushButtonSelectClicked: Column index not set");
}
QModelIndexList selectedIndexes = tableView->selectionModel()->selectedRows();
if (selectedIndexes.isEmpty()) {
QMessageBox::warning(this, QMessageBox::tr("Error"),
QMessageBox::tr("No row selected"));
return;
}
QModelIndex selectedRowIndex = selectedIndexes.first();
emit rowSelected(selectedRowIndex.row());
QSqlRecord record = originalModel_->record(selectedRowIndex.row());
emit recordRetrieved(record);
if (columnIndex_ != -1) {
QSqlRecord record = originalModel_->record(selectedRowIndex.row());
QSqlField field = record.field(columnIndex_);
emit fieldRetrieved(field);
}
accept();
}
+48
View File
@@ -0,0 +1,48 @@
#ifndef SEARCH_DIALOG_H
#define SEARCH_DIALOG_H
#include "ui_search_dialog.h"
#include <QList>
#include <QSortFilterProxyModel>
#include <QDialog>
#include <QSqlField>
#include <QSqlRecord>
// Forward declaration
class BaseSqlTableModel;
class SearchDialog : public QDialog, private Ui::SearchDialog {
Q_OBJECT
private:
BaseSqlTableModel *originalModel_;
QSortFilterProxyModel *proxyModel_;
QString modelTableName_;
int columnIndex_;
public:
explicit SearchDialog(const QString &modelTableName, const QString &fieldName,
QWidget *parent = nullptr);
explicit SearchDialog(QWidget *parent = nullptr);
~SearchDialog();
bool setFieldName(const QString &fieldName);
bool setTableName(const QString &tableName);
signals:
void rowSelected(int rowIndex);
void recordRetrieved(QSqlRecord rowValues);
void fieldRetrieved(QSqlField field);
private slots:
void onLineEditSearchTextChanged(const QString &text);
void onPushButtonSelectClicked();
private:
void initializeProxyModel();
void setupConnections();
void setupTableView();
};
#endif // SEARCH_DIALOG_H
+70
View File
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SearchDialog</class>
<widget class="QDialog" name="SearchDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>460</width>
<height>346</height>
</rect>
</property>
<property name="windowTitle">
<string>Finding a database entry</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="lineEditSearch">
<property name="placeholderText">
<string>Enter text to search...</string>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="tableView">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pushButtonSelect">
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonCancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>pushButtonCancel</sender>
<signal>clicked()</signal>
<receiver>SearchDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>258</y>
</hint>
<hint type="destinationlabel">
<x>200</x>
<y>250</y>
</hint>
</hints>
</connection>
</connections>
</ui>