Compare commits

..

10 Commits
main ... master

8 changed files with 486 additions and 0 deletions

82
.gitignore vendored Normal file
View File

@ -0,0 +1,82 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
*.qbs.user*
CMakeLists.txt.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe
# Directories with generated files
.moc/
.obj/
.pch/
.rcc/
.uic/
/build*/

73
CMakeLists.txt Normal file
View File

@ -0,0 +1,73 @@
cmake_minimum_required(VERSION 3.16)
project(mysitenews VERSION 0.1 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Sql)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Sql)
set(PROJECT_SOURCES
main.cpp
widget.cpp
widget.h
auth.h auth.cpp
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(mysitenews
MANUAL_FINALIZATION
${PROJECT_SOURCES}
auth.h auth.cpp
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET mysitenews APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(mysitenews SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(mysitenews
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(mysitenews PRIVATE Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Sql
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.mysitenews)
endif()
set_target_properties(mysitenews PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
include(GNUInstallDirs)
install(TARGETS mysitenews
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(mysitenews)
endif()

107
README.md
View File

@ -1,2 +1,109 @@
# mysitenews # mysitenews
Графическое приложение на Qt для заполнения таблицы `random_data` в PostgreSQL.
При запуске открывается окно подключения к БД. После успешного подключения
появляется форма добавления записи с полями `name` и `text`, а также счётчик
текущего количества записей в таблице.
## Возможности
- подключение к PostgreSQL (host, port, user, password);
- сохранение последней успешной конфигурации подключения;
- отображение количества записей в `public.random_data`;
- добавление записей (`name`, `text`);
- показ ошибки при неудачном подключении.
## Требования
- CMake 3.16+;
- компилятор с поддержкой C++17;
- Qt 5 или Qt 6 (модули `Widgets` и `Sql`);
- драйвер PostgreSQL для Qt (`QPSQL`);
- сервер PostgreSQL.
Проверить наличие драйвера можно так:
```bash
ls /usr/lib/x86_64-linux-gnu/qt6/plugins/sqldrivers/
```
В каталоге должен быть `libqsqlpsql.so` (для Qt 5 — `libqsqlpsql.so` в
соответствующем каталоге плагинов). В Debian/Ubuntu драйвер ставится пакетом
`libqt6sql6-psql` (для Qt 5 — `libqt5sql5-psql`).
## Сборка и запуск
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/mysitenews
```
При сборке из Qt Creator используется каталог `build/Desktop-Debug`.
## База данных
Приложение обращается к БД `cards` и таблице `public.random_data`.
Ожидаемая структура таблицы:
```sql
CREATE TABLE public.random_data (
id serial PRIMARY KEY,
name varchar(100) NOT NULL,
text text NOT NULL,
description text,
created_at timestamp DEFAULT CURRENT_TIMESTAMP
);
```
### Права пользователя
Пользователю БД нужны права на таблицу и на последовательность `id`:
```sql
GRANT SELECT, INSERT ON public.random_data TO cards_user;
GRANT USAGE, SELECT ON SEQUENCE public.random_data_id_seq TO cards_user;
```
Без прав на последовательность вставка завершится ошибкой
`permission denied for sequence random_data_id_seq`.
## Использование
1. Запустите приложение.
2. Введите адрес сервера, порт (по умолчанию `5432`), имя пользователя и пароль.
3. Нажмите **OK**. При ошибке подключения будет показано сообщение.
4. Заполните поля `name` и `text` и нажмите **ADD**.
Поле `text` сохраняется как HTML (`QTextEdit::toHtml()`). Оба поля обязательны.
Длина `name` ограничена таблицей — 100 символов.
## Конфигурация
После успешного подключения параметры сохраняются через `QSettings`
(`QSettings("mysitenews", "mysitenews")`). В Linux файл находится по пути:
```
~/.config/mysitenews/mysitenews.conf
```
Сохраняются `db/host`, `db/port`, `db/database`, `db/user`, `db/password`.
При следующем запуске поля формы заполняются этими значениями.
**Внимание:** пароль хранится в открытом виде. Не используйте этот способ
для продакшн-окружений; предпочтительнее `~/.pgpass` или переменные окружения.
## Структура проекта
| Файл | Назначение |
| --- | --- |
| `main.cpp` | точка входа, связывает окно авторизации и рабочее окно |
| `auth.h` / `auth.cpp` | окно подключения к БД, сохранение конфигурации |
| `widget.h` / `widget.cpp` | форма добавления записей и подсчёт количества |
| `CMakeLists.txt` | конфигурация сборки |
## Лицензия
Проект распространяется под GNU General Public License v3. Подробности — в
файле [LICENSE](LICENSE).

71
auth.cpp Normal file
View File

@ -0,0 +1,71 @@
#include "auth.h"
#include <QMessageBox>
#include <QSettings>
#include <QSqlError>
auth::auth(QWidget *parent)
: QWidget{parent}
, _nicknameLabel(new QLabel("nickname:"))
, _nicknameEdit(new QLineEdit())
, _passwordLabel(new QLabel("passwprd:"))
, _passwordEdit(new QLineEdit())
, _addressLabel(new QLabel("address:"))
, _addressEdit(new QLineEdit())
, _portLabel(new QLabel("port:"))
, _portEdit(new QLineEdit())
, _okButton(new QPushButton("&OK"))
, _quitButton(new QPushButton("&QUIT"))
, _vbox(new QVBoxLayout(this))
, _db(QSqlDatabase::addDatabase("QPSQL"))
{
_nicknameLabel->setAlignment (Qt::AlignHCenter);
_passwordLabel->setAlignment (Qt::AlignHCenter);
_addressLabel->setAlignment (Qt::AlignHCenter);
_portLabel->setAlignment (Qt::AlignHCenter);
_passwordEdit->setEchoMode (QLineEdit::Password);
QSettings settings ("mysitenews", "mysitenews");
_nicknameEdit->setText (settings.value ("db/user").toString ());
_passwordEdit->setText (settings.value ("db/password").toString ());
_addressEdit->setText (settings.value ("db/host").toString ());
_portEdit->setText (settings.value ("db/port", "5432").toString ());
_vbox->addWidget (_nicknameLabel);
_vbox->addWidget (_nicknameEdit);
_vbox->addWidget (_passwordLabel);
_vbox->addWidget (_passwordEdit);
_vbox->addWidget (_addressLabel);
_vbox->addWidget (_addressEdit);
_vbox->addWidget (_portLabel);
_vbox->addWidget (_portEdit);
_vbox->addWidget (_okButton);
_vbox->addWidget (_quitButton);
connect (_okButton, &QPushButton::clicked, this, &auth::onAuth);
connect (_quitButton, &QPushButton::clicked, this, &auth::close);
setLayout (_vbox);
}
void
auth::onAuth ()
{
QSettings settings ("mysitenews", "mysitenews");
_db.setHostName(_addressEdit->text());
_db.setPort(_portEdit->text().toInt());
_db.setDatabaseName(settings.value ("db/database", "cards").toString ());
_db.setUserName(_nicknameEdit->text());
_db.setPassword(_passwordEdit->text());
if (!_db.open()) {
QMessageBox::critical(this, tr("Ошибка подключения"),
_db.lastError().text());
return;
}
settings.setValue ("db/host", _db.hostName ());
settings.setValue ("db/port", _db.port ());
settings.setValue ("db/database", _db.databaseName ());
settings.setValue ("db/user", _db.userName ());
settings.setValue ("db/password", _db.password ());
emit dbOpenned(_db);
close();
}

36
auth.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef AUTH_H
#define AUTH_H
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSqlDatabase>
#include <QVBoxLayout>
#include <QWidget>
class auth : public QWidget
{
Q_OBJECT
public:
explicit auth (QWidget *parent = nullptr);
private:
QLabel *_nicknameLabel = nullptr;
QLineEdit *_nicknameEdit = nullptr;
QLabel *_passwordLabel = nullptr;
QLineEdit *_passwordEdit = nullptr;
QLabel *_addressLabel = nullptr;
QLineEdit *_addressEdit = nullptr;
QLabel *_portLabel = nullptr;
QLineEdit *_portEdit = nullptr;
QPushButton *_okButton = nullptr;
QPushButton *_quitButton = nullptr;
QVBoxLayout *_vbox = nullptr;
QSqlDatabase _db;
public slots:
void onAuth ();
signals:
void dbOpenned(const QSqlDatabase &);
};
#endif // AUTH_H

15
main.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "auth.h"
#include "widget.h"
#include <QApplication>
int
main (int argc, char *argv[])
{
QApplication app (argc, argv);
auth a;
Widget w;
QObject::connect(&a, &auth::dbOpenned, &w, &Widget::init);
a.show();
return app.exec ();
}

68
widget.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "widget.h"
#include <QSqlQuery>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, _grid(new QGridLayout(this))
, _countId(new QLabel("NULL"))
, _nameLabel(new QLabel("name: "))
, _nameEdit(new QLineEdit())
, _textLabel(new QLabel("text:"))
, _textEdit(new QTextEdit())
, _addBtn(new QPushButton("&ADD"))
{
_grid->addWidget(_countId, 0, 0, Qt::AlignHCenter);
_grid->addWidget(_nameLabel, 1, 0, Qt::AlignRight);
_grid->addWidget(_nameEdit, 1, 1, Qt::AlignVCenter);
_grid->addWidget(_textLabel, 2, 0, Qt::AlignRight);
_grid->addWidget(_textEdit, 2, 1, Qt::AlignHCenter);
_grid->addWidget(_addBtn, 3, 0, Qt::AlignHCenter);
setLayout(_grid);
connect(_addBtn, &QPushButton::clicked, this, &Widget::addRecord);
}
Widget::~Widget () {}
void Widget::init(const QSqlDatabase &db)
{
_db = db;
if (!_db.isOpen()) {
close();
return;
}
updateCount();
show();
}
void Widget::updateCount()
{
if (!_db.isOpen()) {
return;
}
QSqlQuery query(_db);
if (query.exec("SELECT COUNT(*) FROM random_data") && query.next()) {
_countId->setText(query.value(0).toString());
}
}
void Widget::addRecord()
{
if (_nameEdit->displayText().isEmpty() || _textEdit->toPlainText().isEmpty()) {
return;
}
if (!_db.isOpen()) {
return;
}
QSqlQuery query(_db);
query.prepare("INSERT INTO public.random_data (name, text) "
"VALUES (?, ?)");
query.addBindValue(_nameEdit->displayText());
query.addBindValue(_textEdit->toPlainText());
if (!query.exec()) {
return;
}
_nameEdit->clear();
_textEdit->clear();
updateCount();
}

34
widget.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef WIDGET_H
#define WIDGET_H
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSqlDatabase>
#include <QTextEdit>
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
QSqlDatabase _db;
QGridLayout *_grid = nullptr;
QLabel *_countId = nullptr;
QLabel *_nameLabel = nullptr;
QLineEdit *_nameEdit = nullptr;
QLabel *_textLabel = nullptr;
QTextEdit *_textEdit = nullptr;
QPushButton *_addBtn = nullptr;
void updateCount();
public slots:
void init(const QSqlDatabase &);
void addRecord();
};
#endif // WIDGET_H