From 27ffee57be497f60b393cd194bbca0cf7c3d975c Mon Sep 17 00:00:00 2001 From: luocai Date: Fri, 1 Nov 2024 19:05:20 +0800 Subject: [PATCH] add blog code. --- Server/Application.h | 3 +- Server/main.cpp | 2 +- WebApplication/BlogApplication.cpp | 11 ++ WebApplication/BlogApplication.h | 11 ++ WebApplication/CMakeLists.txt | 10 +- WebApplication/Dialog.cpp | 25 +++++ WebApplication/Dialog.h | 3 +- WebApplication/Restful.cpp | 53 +++++++++ WebApplication/Restful.h | 63 +++++++++++ WebApplication/Session.h | 3 +- WebApplication/User.cpp | 4 - WebApplication/User.h | 22 ---- WebApplication/WebApplication.cpp | 18 +++- WebApplication/model/BlogSession.cpp | 16 +++ WebApplication/model/BlogSession.h | 17 +++ WebApplication/model/BlogUserDatabase.cpp | 124 ++++++++++++++++++++++ WebApplication/model/BlogUserDatabase.h | 35 ++++++ WebApplication/model/User.cpp | 15 +++ WebApplication/model/User.h | 38 +++++++ WebApplication/view/BlogLoginWidget.cpp | 14 +++ WebApplication/view/BlogLoginWidget.h | 12 +++ WebApplication/view/BlogView.cpp | 115 ++++++++++++++++++++ WebApplication/view/BlogView.h | 16 +++ WebApplication/view/EditUsers.cpp | 40 +++++++ WebApplication/view/EditUsers.h | 35 ++++++ 25 files changed, 672 insertions(+), 33 deletions(-) create mode 100644 WebApplication/BlogApplication.cpp create mode 100644 WebApplication/BlogApplication.h create mode 100644 WebApplication/Restful.cpp create mode 100644 WebApplication/Restful.h delete mode 100644 WebApplication/User.cpp delete mode 100644 WebApplication/User.h create mode 100644 WebApplication/model/BlogSession.cpp create mode 100644 WebApplication/model/BlogSession.h create mode 100644 WebApplication/model/BlogUserDatabase.cpp create mode 100644 WebApplication/model/BlogUserDatabase.h create mode 100644 WebApplication/model/User.cpp create mode 100644 WebApplication/model/User.h create mode 100644 WebApplication/view/BlogLoginWidget.cpp create mode 100644 WebApplication/view/BlogLoginWidget.h create mode 100644 WebApplication/view/BlogView.cpp create mode 100644 WebApplication/view/BlogView.h create mode 100644 WebApplication/view/EditUsers.cpp create mode 100644 WebApplication/view/EditUsers.h diff --git a/Server/Application.h b/Server/Application.h index 867efb3..78657dc 100644 --- a/Server/Application.h +++ b/Server/Application.h @@ -23,6 +23,7 @@ public: BUILD_SETTING(uint16_t, Port, 8081); BUILD_SETTING(uint32_t, Threads, std::thread::hardware_concurrency()); BUILD_SETTING(std::string, DocumentRoot, "."); + BUILD_SETTING(std::string, Sqlte3Path, "database.sqlite"); BUILD_SETTING(std::string, HomeAssistantAccessToken, ""); BUILD_SETTING(std::string, Live2dModelsRoot, "./live2d"); @@ -33,7 +34,7 @@ public: const RequestHandler *find(boost::urls::segments_encoded_view path, boost::urls::matches_base &matches) const noexcept; - void insertUrl(std::string_view url,RequestHandler &&handler); + void insertUrl(std::string_view url, RequestHandler &&handler); protected: void alarmTask(); diff --git a/Server/main.cpp b/Server/main.cpp index 5c1af18..5f36c69 100644 --- a/Server/main.cpp +++ b/Server/main.cpp @@ -61,7 +61,7 @@ int main(int argc, char const *argv[]) { auto application = Singleton::instance("settings.ini"); auto database = Singleton::instance(); - database->open("database.sqlite"); + database->open(application->getSqlte3Path()); if (!std::filesystem::exists(application->getDocumentRoot())) { LOG(fatal) << "document root: " << application->getDocumentRoot() << " is not exists..."; diff --git a/WebApplication/BlogApplication.cpp b/WebApplication/BlogApplication.cpp new file mode 100644 index 0000000..c1a69ae --- /dev/null +++ b/WebApplication/BlogApplication.cpp @@ -0,0 +1,11 @@ +#include "BlogApplication.h" +#include "view/BlogView.h" +#include + +static const char *FeedUrl = "/blog/feed/"; + +BlogApplication::BlogApplication(const Wt::WEnvironment &env, Wt::Dbo::SqlConnectionPool &blogDb) + : Wt::WApplication(env) { + root()->addWidget(std::make_unique("/", blogDb, FeedUrl)); + useStyleSheet("css/blogexample.css"); +} diff --git a/WebApplication/BlogApplication.h b/WebApplication/BlogApplication.h new file mode 100644 index 0000000..77a4b91 --- /dev/null +++ b/WebApplication/BlogApplication.h @@ -0,0 +1,11 @@ +#ifndef __BLOGAPPLICATION_H__ +#define __BLOGAPPLICATION_H__ + +#include + +class BlogApplication : public Wt::WApplication { +public: + BlogApplication(const Wt::WEnvironment &env, Wt::Dbo::SqlConnectionPool &blogDb); +}; + +#endif // __BLOGAPPLICATION_H__ \ No newline at end of file diff --git a/WebApplication/CMakeLists.txt b/WebApplication/CMakeLists.txt index 14c4d09..95cdc73 100644 --- a/WebApplication/CMakeLists.txt +++ b/WebApplication/CMakeLists.txt @@ -1,13 +1,19 @@ add_library(WebApplication WebApplication.h WebApplication.cpp + BlogApplication.h BlogApplication.cpp Hello.h Hello.cpp + Restful.h Restful.cpp Dialog.h Dialog.cpp Session.h Session.cpp - User.h User.cpp + model/BlogSession.h model/BlogSession.cpp + model/BlogUserDatabase.h model/BlogUserDatabase.cpp + model/User.h model/User.cpp + view/BlogView.h view/BlogView.cpp + view/EditUsers.h view/EditUsers.cpp ) target_include_directories(WebApplication - INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE ${WT_INCLUDE_DIR} ) diff --git a/WebApplication/Dialog.cpp b/WebApplication/Dialog.cpp index 10f4122..5017c85 100644 --- a/WebApplication/Dialog.cpp +++ b/WebApplication/Dialog.cpp @@ -1,4 +1,5 @@ #include "Dialog.h" +#include #include #include @@ -22,6 +23,9 @@ Dialog::Dialog() { button = buttons->addWidget(std::make_unique("Discard")); button->clicked().connect(this, &Dialog::messageBox4); + button = buttons->addWidget(std::make_unique("Familiar")); + button->clicked().connect(this, &Dialog::custom); + textdiv = addWidget(std::make_unique()); textdiv->setStyleClass("text"); m_status = textdiv->addWidget(std::make_unique("Go ahead...")); @@ -74,6 +78,27 @@ void Dialog::messageBox4() { Wt::TimingFunction::Linear, 250)); } +void Dialog::custom() { + Wt::WDialog dialog("个人信息"); + dialog.setClosable(true); + dialog.setResizable(true); + dialog.rejectWhenEscapePressed(true); + + dialog.contents()->addWidget(std::make_unique("请输入你的名字: ")); + Wt::WLineEdit *edit = dialog.contents()->addWidget(std::make_unique()); + Wt::WPushButton *ok = dialog.footer()->addWidget(std::make_unique("确认")); + ok->setDefault(true); + + edit->setFocus(); + ok->clicked().connect(&dialog, &Wt::WDialog::accept); + + if (dialog.exec() == Wt::DialogCode::Accepted) { + setStatus("欢迎, " + edit->text()); + } else { + setStatus("Oh nevermind!"); + } +} + void Dialog::messageBoxDone(Wt::StandardButton result) { switch (result) { case Wt::StandardButton::Ok: diff --git a/WebApplication/Dialog.h b/WebApplication/Dialog.h index a4bba8b..505b378 100644 --- a/WebApplication/Dialog.h +++ b/WebApplication/Dialog.h @@ -12,7 +12,8 @@ protected: void messageBox1(); void messageBox2(); void messageBox3(); - void messageBox4(); + void messageBox4(); + void custom(); void messageBoxDone(Wt::StandardButton result); private: diff --git a/WebApplication/Restful.cpp b/WebApplication/Restful.cpp new file mode 100644 index 0000000..b7a3c29 --- /dev/null +++ b/WebApplication/Restful.cpp @@ -0,0 +1,53 @@ +#include "Restful.h" +#include +#include +#include +#include + +DBO_INSTANTIATE_TEMPLATES(MyMessage) + +DbStruct *m_dbStruct; + +void JsonResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) { + response.setMimeType("application/json"); + response.addHeader("Server", "Wt"); + + MyMessage message; + message.message = "Hello, World!"; + + Wt::Dbo::JsonSerializer writer(response.out()); + writer.serialize(message); +} + +void PlaintextResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) { + response.setMimeType("text/plain"); + response.addHeader("Server", "Wt"); + response.out() << "Hello, World!"; +} + +DbResource::DbResource(const std::string &db) { + if (!m_dbStruct) { + m_dbStruct = new DbStruct(db); + } +} + +void DbResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) { + response.setMimeType("application/json"); + response.addHeader("Server", "Wt"); + + Wt::Dbo::Transaction transaction(m_dbStruct->session); + Wt::Dbo::ptr entry = m_dbStruct->session.load(m_dbStruct->rand()); + + Wt::Dbo::JsonSerializer writer(response.out()); + writer.serialize(entry); +} + +int DbStruct::rand() { + return distribution(rng); +} + +DbStruct::DbStruct(const std::string &db) : rng(clock()), distribution(1, 10000) { + session.setConnection(std::make_unique(db)); + session.mapClass("world"); + session.mapClass("fortune"); +} diff --git a/WebApplication/Restful.h b/WebApplication/Restful.h new file mode 100644 index 0000000..d966c68 --- /dev/null +++ b/WebApplication/Restful.h @@ -0,0 +1,63 @@ +#ifndef __RESTFUL_H__ +#define __RESTFUL_H__ + +#include +#include +#include +#include + +class MyMessage { +public: + std::string message; + + template + void persist(Action &a) { + Wt::Dbo::field(a, message, "message"); + } +}; + +class World { +public: + int randomNumber; + + template + void persist(Action &a) { + Wt::Dbo::field(a, randomNumber, "randomnumber"); + } +}; + +class Fortune { +public: + std::string message; + + template + void persist(Action &a) { + Wt::Dbo::field(a, message, "message"); + } +}; + +struct DbStruct { + DbStruct(const std::string &db); + int rand(); + Wt::Dbo::Session session; + std::default_random_engine rng; + std::uniform_int_distribution distribution; +}; + +class JsonResource : public Wt::WResource { +public: + void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) final; +}; + +class PlaintextResource : public Wt::WResource { +public: + void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) final; +}; + +class DbResource : public Wt::WResource { +public: + DbResource(const std::string &db); + void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) final; +}; + +#endif // __RESTFUL_H__ \ No newline at end of file diff --git a/WebApplication/Session.h b/WebApplication/Session.h index 6adddcd..5b800e8 100644 --- a/WebApplication/Session.h +++ b/WebApplication/Session.h @@ -1,10 +1,11 @@ #ifndef __SESSION_H__ #define __SESSION_H__ -#include "User.h" +#include "model/User.h" #include #include +using AuthInfo = Wt::Auth::Dbo::AuthInfo; using UserDatabase = Wt::Auth::Dbo::UserDatabase; class Session : public Wt::Dbo::Session { diff --git a/WebApplication/User.cpp b/WebApplication/User.cpp deleted file mode 100644 index 1ff4e06..0000000 --- a/WebApplication/User.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#include "User.h" -#include - -DBO_INSTANTIATE_TEMPLATES(User) \ No newline at end of file diff --git a/WebApplication/User.h b/WebApplication/User.h deleted file mode 100644 index 3d76d6b..0000000 --- a/WebApplication/User.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef __USER_H__ -#define __USER_H__ - -#include -#include - -class User; -using AuthInfo = Wt::Auth::Dbo::AuthInfo; - -class User { -public: - template - void persist(Action &a) { - // Wt::Dbo::field(a, name, "name"); - } - // std::string name; - // int age; -}; - -DBO_EXTERN_TEMPLATES(User) - -#endif // __USER_H__ \ No newline at end of file diff --git a/WebApplication/WebApplication.cpp b/WebApplication/WebApplication.cpp index 154d193..ce6daf6 100644 --- a/WebApplication/WebApplication.cpp +++ b/WebApplication/WebApplication.cpp @@ -1,13 +1,22 @@ #include "WebApplication.h" +#include "BlogApplication.h" #include "BoostLog.h" #include "Hello.h" +#include "Restful.h" #include "Session.h" +#include "model/BlogSession.h" +#include #include static std::unique_ptr createApplication(const Wt::WEnvironment &env) { return std::make_unique(env, false); } +std::unique_ptr createBlogApplication(const Wt::WEnvironment &env, + Wt::Dbo::SqlConnectionPool *blogDb) { + return std::make_unique(env, *blogDb); +} + static std::unique_ptr createWidgetSet(const Wt::WEnvironment &env) { return std::make_unique(env, true); } @@ -19,9 +28,16 @@ WebApplication::WebApplication() { args.push_back("--http-listen=127.0.0.1:8855"); // --docroot=. --no-compression --http-listen 127.0.0.1:8855 m_server = std::make_unique("./build", args); - m_server->addEntryPoint(Wt::EntryPointType::Application, createApplication); + m_server->addEntryPoint(Wt::EntryPointType::Application, createApplication, "/hello"); + + auto blogDb = BlogSession::createConnectionPool(m_server->appRoot() + "blog.db"); + m_server->addEntryPoint(Wt::EntryPointType::Application, + std::bind(&createBlogApplication, std::placeholders::_1, blogDb.get()), "/blog"); m_server->addEntryPoint(Wt::EntryPointType::WidgetSet, createWidgetSet, "/gui/hello.js"); Session::configureAuth(); + m_server->addResource(std::make_shared(), "/json"); + m_server->addResource(std::make_shared(), "/plaintext"); + m_server->addResource(std::make_shared("database.sqlite"), "/db"); m_thread = std::thread(&WebApplication::run, this); } catch (const std::exception &e) { diff --git a/WebApplication/model/BlogSession.cpp b/WebApplication/model/BlogSession.cpp new file mode 100644 index 0000000..2200723 --- /dev/null +++ b/WebApplication/model/BlogSession.cpp @@ -0,0 +1,16 @@ +#include "BlogSession.h" +#include + +BlogSession::BlogSession(Wt::Dbo::SqlConnectionPool &connectionPool) + : m_connectionPool(connectionPool), m_users(*this) { +} + +std::unique_ptr BlogSession::createConnectionPool(const std::string &sqlite3) { + auto connection = std::make_unique(sqlite3); + + connection->setProperty("show-queries", "true"); + connection->setDateTimeStorage(Wt::Dbo::SqlDateTimeType::DateTime, + Wt::Dbo::backend::DateTimeStorage::PseudoISO8601AsText); + + return std::make_unique(std::move(connection), 10); +} diff --git a/WebApplication/model/BlogSession.h b/WebApplication/model/BlogSession.h new file mode 100644 index 0000000..b19b8c3 --- /dev/null +++ b/WebApplication/model/BlogSession.h @@ -0,0 +1,17 @@ +#ifndef __BLOGSESSION_H__ +#define __BLOGSESSION_H__ + +#include "BlogUserDatabase.h" +#include +#include + +class BlogSession : public Wt::Dbo::Session { +public: + BlogSession(Wt::Dbo::SqlConnectionPool &connectionPool); + static std::unique_ptr createConnectionPool(const std::string &sqlite3); + +private: + Wt::Dbo::SqlConnectionPool &m_connectionPool; + BlogUserDatabase m_users; +}; +#endif // __BLOGSESSION_H__ \ No newline at end of file diff --git a/WebApplication/model/BlogUserDatabase.cpp b/WebApplication/model/BlogUserDatabase.cpp new file mode 100644 index 0000000..ec322c1 --- /dev/null +++ b/WebApplication/model/BlogUserDatabase.cpp @@ -0,0 +1,124 @@ +#include "User.h" + +#include "BlogUserDatabase.h" + +#include + +class TransactionImpl : public Wt::Auth::AbstractUserDatabase::Transaction, public Wt::Dbo::Transaction { +public: + TransactionImpl(Wt::Dbo::Session &session) : Wt::Dbo::Transaction(session) { + } + + virtual ~TransactionImpl() noexcept(false) { + } + + virtual void commit() { + Wt::Dbo::Transaction::commit(); + } + + virtual void rollback() { + Wt::Dbo::Transaction::rollback(); + } +}; + +class InvalidUser : public std::runtime_error { +public: + InvalidUser(const std::string &id) : std::runtime_error("Invalid user: " + id) { + } +}; + +BlogUserDatabase::BlogUserDatabase(Wt::Dbo::Session &session) : m_session(session) { +} + +BlogUserDatabase::~BlogUserDatabase() { +} + +BlogUserDatabase::Transaction *BlogUserDatabase::startTransaction() { + return new TransactionImpl(m_session); +} + +Wt::Auth::User BlogUserDatabase::findWithId(const std::string &id) const { + getUser(id); + + if (m_user) + return Wt::Auth::User(id, *this); + else + return Wt::Auth::User(); +} + +Wt::Auth::User BlogUserDatabase::findWithIdentity(const std::string &provider, const Wt::WString &identity) const { + Wt::Dbo::Transaction t(m_session); + if (provider == Wt::Auth::Identity::LoginName) { + if (!m_user || m_user->name != identity) m_user = m_session.find().where("name = ?").bind(identity); + } else + m_user = m_session.find() + .where("oauth_id = ?") + .bind(identity.toUTF8()) + .where("oauth_provider = ?") + .bind(provider); + t.commit(); + + if (m_user) + return Wt::Auth::User(std::to_string(m_user.id()), *this); + else + return Wt::Auth::User(); +} + +void BlogUserDatabase::addIdentity(const Wt::Auth::User &user, const std::string &provider, + const Wt::WString &identity) { + WithUser find(*this, user); + + if (provider == Wt::Auth::Identity::LoginName) + m_user.modify()->name = identity; + else { + m_user.modify()->oAuthProvider = provider; + m_user.modify()->oAuthId = identity.toUTF8(); + } +} + +void BlogUserDatabase::removeIdentity(const Wt::Auth::User &user, const std::string &provider) { + WithUser find(*this, user); + + if (provider == Wt::Auth::Identity::LoginName) + m_user.modify()->name = ""; + else if (provider == m_user->oAuthProvider) { + m_user.modify()->oAuthProvider = std::string(); + m_user.modify()->oAuthId = std::string(); + } +} + +Wt::WString BlogUserDatabase::identity(const Wt::Auth::User &user, const std::string &provider) const { + WithUser find(*this, user); + + if (provider == Wt::Auth::Identity::LoginName) + return m_user->name; + else if (provider == m_user->oAuthProvider) + return Wt::WString(m_user->oAuthId); + else + return Wt::WString::Empty; +} + +void BlogUserDatabase::setLastLoginAttempt(const Wt::Auth::User &user, const Wt::WDateTime &t) { + WithUser find(*this, user); + + m_user.modify()->lastLoginAttempt = t; +} + +BlogUserDatabase::WithUser::WithUser(const BlogUserDatabase &self, const Wt::Auth::User &user) + : transaction(self.m_session) { + self.getUser(user.id()); + + if (!self.m_user) throw InvalidUser(user.id()); +} + +BlogUserDatabase::WithUser::~WithUser() { + transaction.commit(); +} + +void BlogUserDatabase::getUser(const std::string &id) const { + if (!m_user || std::to_string(m_user.id()) != id) { + Wt::Dbo::Transaction t(m_session); + m_user = m_session.find().where("id = ?").bind(User::stringToId(id)); + t.commit(); + } +} diff --git a/WebApplication/model/BlogUserDatabase.h b/WebApplication/model/BlogUserDatabase.h new file mode 100644 index 0000000..3af3e25 --- /dev/null +++ b/WebApplication/model/BlogUserDatabase.h @@ -0,0 +1,35 @@ +#ifndef __BLOGUSERDATABASE_H__ +#define __BLOGUSERDATABASE_H__ + +#include +#include +#include + +class User; + +class BlogUserDatabase : public Wt::Auth::AbstractUserDatabase { +public: + BlogUserDatabase(Wt::Dbo::Session &session); + ~BlogUserDatabase(); + Transaction *startTransaction() final; + Wt::Auth::User findWithId(const std::string &id) const final; + Wt::Auth::User findWithIdentity(const std::string &provider, const Wt::WString &identity) const final; + void addIdentity(const Wt::Auth::User &user, const std::string &provider, const Wt::WString &identity) final; + void removeIdentity(const Wt::Auth::User &user, const std::string &provider) final; + Wt::WString identity(const Wt::Auth::User &user, const std::string &provider) const final; + void setLastLoginAttempt(const Wt::Auth::User &user, const Wt::WDateTime &t) final; + +protected: + struct WithUser { + WithUser(const BlogUserDatabase &self, const Wt::Auth::User &user); + ~WithUser(); + + Wt::Dbo::Transaction transaction; + }; + void getUser(const std::string &id) const; + +private: + Wt::Dbo::Session &m_session; + mutable Wt::Dbo::ptr m_user; +}; +#endif // __BLOGUSERDATABASE_H__ \ No newline at end of file diff --git a/WebApplication/model/User.cpp b/WebApplication/model/User.cpp new file mode 100644 index 0000000..f8b815a --- /dev/null +++ b/WebApplication/model/User.cpp @@ -0,0 +1,15 @@ +#include "User.h" +#include + +DBO_INSTANTIATE_TEMPLATES(User) +Wt::Dbo::dbo_traits::IdType User::stringToId(const std::string &s) { + std::size_t pos = std::string::npos; + auto result = std::stoll(s, &pos); + if (pos != s.size()) + return Wt::Dbo::dbo_traits::invalidId(); + else + return result; +} + +User::User() : role(Visitor), failedLoginAttempts(0) { +} diff --git a/WebApplication/model/User.h b/WebApplication/model/User.h new file mode 100644 index 0000000..d4c86e5 --- /dev/null +++ b/WebApplication/model/User.h @@ -0,0 +1,38 @@ +#ifndef __USER_H__ +#define __USER_H__ + +#include +#include +#include +#include +#include + +class User { +public: + enum Role { + Visitor = 0, + Admin = 1, + }; + User(); + + static Wt::Dbo::dbo_traits::IdType stringToId(const std::string &s); + Wt::WString name; + Role role; + int failedLoginAttempts; + Wt::WDateTime lastLoginAttempt; + std::string oAuthId; + std::string oAuthProvider; + + template + void persist(Action &a) { + Wt::Dbo::field(a, name, "name"); + Wt::Dbo::field(a, failedLoginAttempts, "failed_login_attempts"); + Wt::Dbo::field(a, lastLoginAttempt, "last_login_attempt"); + Wt::Dbo::field(a, oAuthId, "oauth_id"); + Wt::Dbo::field(a, oAuthProvider, "oauth_provider"); + } +}; + +DBO_EXTERN_TEMPLATES(User) + +#endif // __USER_H__ \ No newline at end of file diff --git a/WebApplication/view/BlogLoginWidget.cpp b/WebApplication/view/BlogLoginWidget.cpp new file mode 100644 index 0000000..2befed7 --- /dev/null +++ b/WebApplication/view/BlogLoginWidget.cpp @@ -0,0 +1,14 @@ +#include "BlogLoginWidget.h" +#include "model/BlogSession.h" + +BlogLoginWidget::BlogLoginWidget(BlogSession &session, const std::string &basePath) : AuthWidget(session.login()) { + setInline(true); + + auto model = std::make_unique(session.passwordAuth()->baseAuth(), session.users()); + model->addPasswordAuth(session.passwordAuth()); + model->addOAuth(session.oAuth()); + + setModel(std::move(model)); + + setInternalBasePath(basePath + "login"); +} diff --git a/WebApplication/view/BlogLoginWidget.h b/WebApplication/view/BlogLoginWidget.h new file mode 100644 index 0000000..e497ef9 --- /dev/null +++ b/WebApplication/view/BlogLoginWidget.h @@ -0,0 +1,12 @@ +#ifndef __BLOGLOGINWIDGET_H__ +#define __BLOGLOGINWIDGET_H__ + +#include + +class BlogSession; + +class BlogLoginWidget : public Wt::Auth::AuthWidget { +public: + BlogLoginWidget(BlogSession &session, const std::string &basePath); +}; +#endif // __BLOGLOGINWIDGET_H__ \ No newline at end of file diff --git a/WebApplication/view/BlogView.cpp b/WebApplication/view/BlogView.cpp new file mode 100644 index 0000000..b760098 --- /dev/null +++ b/WebApplication/view/BlogView.cpp @@ -0,0 +1,115 @@ +#include "BlogView.h" +#include "EditUsers.h" +#include "model/BlogSession.h" +#include +#include +#include + +class BlogImpl : public Wt::WContainerWidget { +public: + BlogImpl(const std::string &basePath, Wt::Dbo::SqlConnectionPool &connectionPool, const std::string &rssFeedUrl, + BlogView *blogView) + : m_basePath(basePath), m_rssFeedUrl(rssFeedUrl), m_session(connectionPool) { + Wt::WApplication *app = Wt::WApplication::instance(); + + app->messageResourceBundle().use(Wt::WApplication::appRoot() + "blog"); + app->useStyleSheet("/css/blog.css"); + app->useStyleSheet("/css/asciidoc.css"); + app->internalPathChanged().connect(this, &BlogImpl::handlePathChange); + + m_loginStatus = this->addWidget(std::make_unique(tr("blog-login-status"))); + m_panel = this->addWidget(std::make_unique()); + m_items = this->addWidget(std::make_unique()); + + m_session.login().changed().connect(this, &BlogImpl::onUserChanged); + + auto loginWidget = std::make_unique(m_session, basePath); + m_loginWidget = loginWidget.get(); + m_loginWidget->hide(); + + auto loginLink = std::make_unique(tr("login")); + auto lPtr = loginLink.get(); + loginLink->setStyleClass("link"); + loginLink->clicked().connect(loginWidget_, &WWidget::show); + loginLink->clicked().connect(lPtr, &WWidget::hide); + + auto registerLink = std::make_unique(tr("Wt.Auth.register")); + registerLink->setStyleClass("link"); + registerLink->clicked().connect(loginWidget_, &BlogLoginWidget::registerNewUser); + + auto archiveLink = + std::make_unique(Wt::WLink(Wt::LinkType::InternalPath, basePath_ + "all"), tr("archive")); + + loginStatus_->bindWidget("login", std::move(loginWidget)); + loginStatus_->bindWidget("login-link", std::move(loginLink)); + loginStatus_->bindWidget("register-link", std::move(registerLink)); + loginStatus_->bindString("feed-url", rssFeedUrl_); + loginStatus_->bindWidget("archive-link", std::move(archiveLink)); + + onUserChanged(); + + loginWidget_->processEnvironment(); + } + +protected: + void handlePathChange(const std::string &) { + Wt::WApplication *app = Wt::WApplication::instance(); + + if (app->internalPathMatches(basePath_)) { + dbo::Transaction t(session_); + + std::string path = app->internalPathNextPart(basePath_); + + items_->clear(); + + if (users_) { + users_ = 0; + } + + if (path.empty()) + showPosts(session_ + .find("where state = ? " + "order by date desc " + "limit 10") + .bind(Post::Published), + items_); + + else if (path == "author") { + std::string author = app->internalPathNextPart(basePath_ + path + '/'); + dbo::ptr user = findUser(author); + + if (user) + showPosts(user); + else + showError(tr("blog-no-author").arg(author)); + } else if (path == "edituser") { + editUser(app->internalPathNextPart(basePath_ + path + '/')); + } else if (path == "all") { + showArchive(items_); + } else { + std::string remainder = app->internalPath().substr(basePath_.length()); + showPostsByDateTopic(remainder, items_); + } + + t.commit(); + } + } + +private: + std::string m_basePath, m_rssFeedUrl; + BlogSession m_session; BlogLoginWidget *m_loginWidget=nullptr; + Wt::WStackedWidget *m_panel = nullptr; + Wt::WTemplate *m_authorPanel = nullptr; + EditUsers *m_users = nullptr; + EditUser *m_userEditor = nullptr; + Wt::WTemplate *m_mustLoginWarning = nullptr; + Wt::WTemplate *m_mustBeAdministratorWarning = nullptr; + Wt::WTemplate *m_invalidUser = nullptr; + Wt::WTemplate *m_loginStatus = nullptr; + WContainerWidget *m_items = nullptr; +}; + +BlogView::BlogView(const std::string &basePath, Wt::Dbo::SqlConnectionPool &db, const std::string &rssFeedUrl) + : WCompositeWidget(), m_userChanged() { + m_impl = setImplementation(std::make_unique(basePath, db, rssFeedUrl, this)); +} diff --git a/WebApplication/view/BlogView.h b/WebApplication/view/BlogView.h new file mode 100644 index 0000000..0c21a9e --- /dev/null +++ b/WebApplication/view/BlogView.h @@ -0,0 +1,16 @@ +#ifndef __BLOGVIEW_H__ +#define __BLOGVIEW_H__ + +#include + +class BlogImpl; + +class BlogView : public Wt::WCompositeWidget { +public: + BlogView(const std::string &basePath, Wt::Dbo::SqlConnectionPool &db, const std::string &rssFeedUrl); + +private: + BlogImpl *m_impl; + Wt::Signal m_userChanged; +}; +#endif // __BLOGVIEW_H__ \ No newline at end of file diff --git a/WebApplication/view/EditUsers.cpp b/WebApplication/view/EditUsers.cpp new file mode 100644 index 0000000..4ef40f2 --- /dev/null +++ b/WebApplication/view/EditUsers.cpp @@ -0,0 +1,40 @@ +#include "EditUsers.h" +#include +#include + +EditUsers::EditUsers(Wt::Dbo::Session &aSession, const std::string &basePath) + : m_session(aSession), m_basePath(basePath) { + setStyleClass("user-editor"); + setTemplateText(tr("edit-users-list")); + auto limitEdit = std::make_unique(); + auto goLimit = std::make_unique(tr("go-limit")); + goLimit->clicked().connect(this, &EditUsers::limitList); + + m_limitEdit = bindWidget("limit-edit", std::move(limitEdit)); + bindWidget("limit-button", std::move(goLimit)); + limitList(); +} + +void EditUsers::limitList() { + auto listPtr = std::make_unique(); + auto list = listPtr.get(); + bindWidget("user-list", std::move(listPtr)); + + typedef Wt::Dbo ::collection> UserList; + Wt::Dbo ::Transaction t(m_session); + UserList users = m_session.find().where("name like ?").bind("%" + m_limitEdit->text() + "%").orderBy("name"); + + for (auto user : users) { + Wt::WText *t = list->addWidget(std::make_unique(user->name)); + t->setStyleClass("link"); + list->addWidget(std::make_unique()); + t->clicked().connect(std::bind(&EditUsers::onUserClicked, this, user.id())); + } + if (!users.size()) list->addWidget(std::make_unique(tr("no-users-found"))); +} + +EditUser::EditUser(Wt::Dbo::Session &aSession) : WTemplate(tr("edit-user")), session_(aSession) { + auto roleButton = std::make_unique(); + roleButton_ = bindWidget("role-button", std::move(roleButton)); + roleButton_->clicked().connect(this, &EditUser::switchRole); +} diff --git a/WebApplication/view/EditUsers.h b/WebApplication/view/EditUsers.h new file mode 100644 index 0000000..8b982e0 --- /dev/null +++ b/WebApplication/view/EditUsers.h @@ -0,0 +1,35 @@ +#ifndef __EDITUSERS_H__ +#define __EDITUSERS_H__ + +#include "model/User.h" +#include +#include + +class EditUsers : public Wt::WTemplate { +public: + EditUsers(Wt::Dbo::Session &aSession, const std::string &basePath); + +private: + void onUserClicked(Wt::Dbo::dbo_traits::IdType id); + void limitList(); + + Wt::Dbo::Session &m_session; + std::string m_basePath; + Wt::WLineEdit *m_limitEdit; +}; + +class EditUser : public Wt::WTemplate { +public: + EditUser(Wt::Dbo::Session &aSession); + void switchUser(Wt::Dbo::ptr target); + +private: + void bindTemplate(); + void switchRole(); + + Wt::Dbo::Session &session_; + Wt::Dbo::ptr target_; + Wt::WPushButton *roleButton_; +}; + +#endif // __EDITUSERS_H__ \ No newline at end of file