remove blog example source.
All checks were successful
Deploy Docker Images / Build dockerfile and Server deploy (push) Successful in 17s
Deploy / Build (push) Successful in 3m11s

This commit is contained in:
luocai 2024-11-11 19:21:48 +08:00
parent f12ecefe14
commit a9640bbda5
42 changed files with 75 additions and 2619 deletions

View File

@ -79,8 +79,7 @@ int main(int argc, char const *argv[]) {
corpContext->start();
auto live2d = std::make_shared<Live2dBackend>();
LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency()
<< ",threads: " << application->getThreads();
LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency() << ",threads: " << application->getThreads();
LOG(info) << "working directory: " << prefix.generic_string();
LOG(info) << "server: " << application->getServer() << ",port: " << application->getPort();
LOG(info) << "document root: " << application->getDocumentRoot();
@ -102,7 +101,7 @@ int main(int argc, char const *argv[]) {
auto udpServer = std::make_shared<UdpServer>(application->ioContext());
auto mediaServer = std::make_shared<MediaServer>(554, false);
auto webApp = std::make_unique<WebApplication>();
auto webApp = Singleton<WebApplication>::instance<Construct>();
using namespace boost::asio::ip;
auto proxyAddress = make_address(application->getServer());

View File

@ -1,16 +0,0 @@
#include "BlogApplication.h"
#include "view/BlogView.h"
#include <Wt/WContainerWidget.h>
static const char *FeedUrl = "/blog/feed/";
BlogApplication::BlogApplication(const Wt::WEnvironment &env, Wt::Dbo::SqlConnectionPool &blogDb)
: Wt::WApplication(env) {
messageResourceBundle().use(Wt::WApplication::appRoot() + "blog");
useStyleSheet("/css/blog.css");
useStyleSheet("/css/asciidoc.css");
root()->addWidget(std::make_unique<BlogView>("/", blogDb, FeedUrl));
useStyleSheet("css/blogexample.css");
}

View File

@ -1,11 +0,0 @@
#ifndef __BLOGAPPLICATION_H__
#define __BLOGAPPLICATION_H__
#include <Wt/WApplication.h>
class BlogApplication : public Wt::WApplication {
public:
BlogApplication(const Wt::WEnvironment &env, Wt::Dbo::SqlConnectionPool &blogDb);
};
#endif // __BLOGAPPLICATION_H__

View File

@ -1,23 +1,10 @@
add_library(WebApplication
WebApplication.h WebApplication.cpp
BlogApplication.h BlogApplication.cpp
User.h User.cpp
Hello.h Hello.cpp
Restful.h Restful.cpp
Dialog.h Dialog.cpp
Session.h Session.cpp
model/asciidoc.h model/asciidoc.cpp
model/BlogSession.h model/BlogSession.cpp
model/BlogUserDatabase.h model/BlogUserDatabase.cpp
model/Comment.h model/Comment.cpp
model/Post.h model/Post.cpp
model/Tag.h model/Tag.cpp
model/Token.h model/Token.cpp
model/User.h model/User.cpp
view/BlogLoginWidget.h view/BlogLoginWidget.cpp
view/BlogView.h view/BlogView.cpp
view/CommentView.h view/CommentView.cpp
view/EditUsers.h view/EditUsers.cpp
view/PostView.h view/PostView.cpp
)
target_include_directories(WebApplication

View File

@ -2,6 +2,7 @@
#include "BoostLog.h"
#include "Dialog.h"
#include "Session.h"
#include "WebApplication.h"
#include <Wt/Auth/AuthService.h>
#include <Wt/Auth/AuthWidget.h>
#include <Wt/Auth/Identity.h>
@ -57,8 +58,9 @@ Hello::Hello(const Wt::WEnvironment &env, bool embedded) : Wt::WApplication(env)
b->clicked().connect(this, &Hello::greet);
m_nameEdit->enterPressed().connect(this, &Hello::greet);
auto authWidget = std::make_unique<Wt::Auth::AuthWidget>(Session::auth(), m_session->users(), m_session->login());
authWidget->model()->addPasswordAuth(&Session::passwordAuth());
auto app = Amass::Singleton<WebApplication>::instance();
auto authWidget = std::make_unique<Wt::Auth::AuthWidget>(app->authService(), m_session->users(), m_session->login());
authWidget->model()->addPasswordAuth(&app->passwordService());
authWidget->setRegistrationEnabled(true);
authWidget->processEnvironment();
top->addWidget(std::move(authWidget));

View File

@ -10,10 +10,6 @@
#include <Wt/Auth/PasswordVerifier.h>
#include <Wt/Dbo/backend/Sqlite3.h>
Wt::Auth::AuthService myAuthService;
Wt::Auth::PasswordService myPasswordService(myAuthService);
std::vector<std::unique_ptr<Wt::Auth::OAuthService>> myOAuthServices;
Session::Session(const std::string &sqliteDb) {
auto connection = std::make_unique<Wt::Dbo::backend::Sqlite3>(sqliteDb);
@ -45,36 +41,4 @@ Wt::Auth::AbstractUserDatabase &Session::users() {
Wt::Auth::Login &Session::login() {
return m_login;
}
void Session::configureAuth() {
myAuthService.setAuthTokensEnabled(true, "logincookie");
// myAuthService.setEmailVerificationEnabled(true);
// myAuthService.setEmailVerificationRequired(true);
auto verifier = std::make_unique<Wt::Auth::PasswordVerifier>();
verifier->addHashFunction(std::make_unique<Wt::Auth::BCryptHashFunction>(7));
myPasswordService.setVerifier(std::move(verifier));
myPasswordService.setPasswordThrottle(std::make_unique<Wt::Auth::AuthThrottle>());
myPasswordService.setStrengthValidator(std::make_unique<Wt::Auth::PasswordStrengthValidator>());
if (Wt::Auth::GoogleService::configured()) {
myOAuthServices.push_back(std::make_unique<Wt::Auth::GoogleService>(myAuthService));
}
if (Wt::Auth::FacebookService::configured()) {
myOAuthServices.push_back(std::make_unique<Wt::Auth::FacebookService>(myAuthService));
}
for (const auto &oAuthService : myOAuthServices) {
oAuthService->generateRedirectEndpoint();
}
}
const Wt::Auth::AuthService &Session::auth() {
return myAuthService;
}
const Wt::Auth::PasswordService &Session::passwordAuth() {
return myPasswordService;
}
}

View File

@ -1,11 +1,10 @@
#ifndef __SESSION_H__
#define __SESSION_H__
#include "model/User.h"
#include "User.h"
#include <Wt/Auth/Login.h>
#include <Wt/Dbo/Session.h>
using AuthInfo = Wt::Auth::Dbo::AuthInfo<User>;
using UserDatabase = Wt::Auth::Dbo::UserDatabase<AuthInfo>;
class Session : public Wt::Dbo::Session {
@ -14,9 +13,6 @@ public:
~Session();
Wt::Auth::AbstractUserDatabase &users();
Wt::Auth::Login &login();
static void configureAuth();
static const Wt::Auth::AuthService &auth();
static const Wt::Auth::PasswordService &passwordAuth();
private:
std::unique_ptr<UserDatabase> m_users;

5
WebApplication/User.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "User.h"
#include <Wt/Dbo/Impl.h>
#include <Wt/Auth/Dbo/AuthInfo.h>
DBO_INSTANTIATE_TEMPLATES(User)

17
WebApplication/User.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef __USER_H__
#define __USER_H__
#include <Wt/Auth/Dbo/AuthInfo.h>
class User;
using AuthInfo = Wt::Auth::Dbo::AuthInfo<User>;
class User {
public:
template <class Action>
void persist(Action &a) {
}
};
DBO_EXTERN_TEMPLATES(User);
#endif // __USER_H__

View File

@ -1,10 +1,13 @@
#include "WebApplication.h"
#include "BlogApplication.h"
#include "BoostLog.h"
#include "Hello.h"
#include "Restful.h"
#include "Session.h"
#include "model/BlogSession.h"
#include <Wt/Auth/AuthService.h>
#include <Wt/Auth/HashFunction.h>
#include <Wt/Auth/PasswordService.h>
#include <Wt/Auth/PasswordStrengthValidator.h>
#include <Wt/Auth/PasswordVerifier.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include <Wt/WServer.h>
@ -12,11 +15,6 @@ static std::unique_ptr<Wt::WApplication> createApplication(const Wt::WEnvironmen
return std::make_unique<Hello>(env, false);
}
std::unique_ptr<Wt::WApplication> createBlogApplication(const Wt::WEnvironment &env,
Wt::Dbo::SqlConnectionPool *blogDb) {
return std::make_unique<BlogApplication>(env, *blogDb);
}
static std::unique_ptr<Wt::WApplication> createWidgetSet(const Wt::WEnvironment &env) {
return std::make_unique<Hello>(env, true);
}
@ -28,27 +26,39 @@ WebApplication::WebApplication() {
args.push_back("--docroot=./build");
args.push_back("--http-listen=127.0.0.1:8855");
// --docroot=. --no-compression --http-listen 127.0.0.1:8855
initializeAuthenticationService();
m_server = std::make_unique<Wt::WServer>("./build", args);
m_server->addEntryPoint(Wt::EntryPointType::Application, createApplication, "/hello");
BlogSession::configureAuth();
m_blogSqlConnectionPool = BlogSession::createConnectionPool(m_server->appRoot() + "database.sqlite");
m_server->addEntryPoint(Wt::EntryPointType::Application,
std::bind(&createBlogApplication, std::placeholders::_1, m_blogSqlConnectionPool.get()),
"/blog");
m_server->addEntryPoint(Wt::EntryPointType::WidgetSet, createWidgetSet, "/gui/hello.js");
Session::configureAuth();
m_server->addResource(std::make_shared<JsonResource>(), "/json");
m_server->addResource(std::make_shared<PlaintextResource>(), "/plaintext");
m_server->addResource(std::make_shared<DbResource>("database.sqlite"), "/db");
m_server->start();
m_server->start();
} catch (const std::exception &e) {
LOG(error) << e.what();
}
}
WebApplication::~WebApplication() {
}
void WebApplication::initializeAuthenticationService() {
m_authService = std::make_unique<Wt::Auth::AuthService>();
m_authService->setAuthTokensEnabled(true, "logincookie");
m_passwordService = std::make_unique<Wt::Auth::PasswordService>(*m_authService);
auto verifier = std::make_unique<Wt::Auth::PasswordVerifier>();
verifier->addHashFunction(std::make_unique<Wt::Auth::BCryptHashFunction>(7));
m_passwordService->setVerifier(std::move(verifier));
m_passwordService->setPasswordThrottle(std::make_unique<Wt::Auth::AuthThrottle>());
m_passwordService->setStrengthValidator(std::make_unique<Wt::Auth::PasswordStrengthValidator>());
}
const Wt::Auth::AuthService &WebApplication::authService() {
return *m_authService;
}
const Wt::Auth::PasswordService &WebApplication::passwordService() {
return *m_passwordService;
}

View File

@ -2,7 +2,7 @@
#define __WEBAPPLICATION_H__
#include <memory>
#include <thread>
#include "Singleton.h"
namespace Wt {
class WServer;
@ -10,16 +10,32 @@ class WServer;
namespace Dbo {
class SqlConnectionPool;
}
namespace Auth {
class AuthService;
class PasswordService;
} // namespace Auth
}; // namespace Wt
class WebApplication {
friend class Amass::Singleton<WebApplication>;
public:
WebApplication();
~WebApplication();
void initializeAuthenticationService();
const Wt::Auth::AuthService &authService();
const Wt::Auth::PasswordService &passwordService();
protected:
WebApplication();
private:
std::unique_ptr<Wt::WServer> m_server;
std::unique_ptr<Wt::Dbo::SqlConnectionPool> m_blogSqlConnectionPool;
std::unique_ptr<Wt::Auth::AuthService> m_authService;
std::unique_ptr<Wt::Auth::PasswordService> m_passwordService;
};
#endif // __WEBAPPLICATION_H__

View File

@ -1,296 +0,0 @@
.asciidoc em {
font-style: italic;
color: #111111;
}
.asciidoc strong {
font-weight: bold;
color: #111111;
}
.asciidoc tt {
color: #111111;
}
.asciidoc h1, .asciidoc h2, .asciidoc h3, .asciidoc h4, .asciidoc h5, .asciidoc h6 {
color: #111111;
font-family: sans-serif;
margin-top: 1.2em;
margin-bottom: 0.5em;
line-height: 1.3;
}
.asciidoc h1, .asciidoc h2, .asciidoc h3 {
border-bottom: 2px solid silver;
}
.asciidoc h2 {
padding-top: 0.5em;
}
.asciidoc h3 {
float: left;
}
.asciidoc h3 + * {
clear: left;
}
.asciidoc div.sectionbody {
font-family: serif;
margin-left: 0;
}
.asciidoc hr {
border: 1px solid silver;
}
.asciidoc p {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.asciidoc ul, .asciidoc ol, .asciidoc li > p {
margin-top: 0;
}
.asciidoc pre {
padding: 0;
margin: 0;
line-height: 14px;
font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace;
font-size: 12px;
}
.asciidoc div.tableblock, .asciidoc div.imageblock, .asciidoc div.exampleblock,
.asciidoc div.verseblock, .asciidoc div.quoteblock, .asciidoc div.literalblock,
.asciidoc div.listingblock, .asciidoc div.sidebarblock,
.asciidoc div.admonitionblock {
margin-top: 1.5em;
margin-bottom: 1.5em;
}
.asciidoc div.admonitionblock {
margin-top: 2.5em;
margin-bottom: 2.5em;
}
.asciidoc div.content { /* Block element content. */
padding: 0;
}
/* Block element titles. */
.asciidoc div.title, .asciidoc caption.title, .asciidoc div.sidebar-title {
color: #111111;
font-family: sans-serif;
font-weight: bold;
text-align: left;
margin-top: 1.0em;
margin-bottom: 0.5em;
}
.asciidoc div.title + * {
margin-top: 0;
}
.asciidoc td div.title:first-child {
margin-top: 0.0em;
}
.asciidoc div.content div.title:first-child {
margin-top: 0.0em;
}
.asciidoc div.content + div.title {
margin-top: 0.0em;
}
.asciidoc div.sidebarblock > div.sidebar-content {
background: #ffffee;
border: 1px solid silver;
padding: 0.5em;
}
.asciidoc div.listingblock > div.content {
border: 1px solid silver;
background: #f4f4f4;
padding: 0.5em;
}
.asciidoc div.quoteblock {
padding-left: 2.0em;
margin-right: 10%;
}
.asciidoc div.quoteblock > div.attribution {
padding-top: 0.5em;
text-align: right;
}
.asciidoc div.verseblock {
padding-left: 2.0em;
margin-right: 10%;
}
.asciidoc div.verseblock > div.content {
white-space: pre;
}
.asciidoc div.verseblock > div.attribution {
padding-top: 0.75em;
text-align: left;
}
/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
.asciidoc div.verseblock + div.attribution {
text-align: left;
}
.asciidoc div.admonitionblock .icon {
vertical-align: top;
font-size: 1.1em;
font-weight: bold;
text-decoration: underline;
color: #111111;
padding-right: 0.5em;
}
.asciidoc div.admonitionblock td.content {
padding-left: 0.5em;
border-left: 2px solid silver;
}
.asciidoc div.exampleblock > div.content {
border-left: 2px solid silver;
padding: 0.5em;
}
.asciidoc div.imageblock div.content { padding-left: 0; }
.asciidoc span.image img { border-style: none; }
.asciidoc a.image:visited { color: white; }
.asciidoc dl {
margin-top: 0.8em;
margin-bottom: 0.8em;
}
.asciidoc dt {
margin-top: 0.5em;
margin-bottom: 0;
font-style: normal;
color: #111111;
}
.asciidoc dd > *:first-child {
margin-top: 0.1em;
}
.asciidoc ul, ol {
list-style-position: outside;
}
.asciidoc ol.arabic {
list-style-type: decimal;
}
.asciidoc ol.loweralpha {
list-style-type: lower-alpha;
}
.asciidoc ol.upperalpha {
list-style-type: upper-alpha;
}
.asciidoc ol.lowerroman {
list-style-type: lower-roman;
}
.asciidoc ol.upperroman {
list-style-type: upper-roman;
}
.asciidoc div.compact ul, .asciidoc div.compact ol,
.asciidoc div.compact p, .asciidoc div.compact p,
.asciidoc div.compact div, .asciidoc div.compact div {
margin-top: 0.1em;
margin-bottom: 0.1em;
}
.asciidoc div.tableblock > table {
border: 3px solid #527bbd;
}
.asciidoc thead {
font-family: sans-serif;
font-weight: bold;
}
.asciidoc tfoot {
font-weight: bold;
}
.asciidoc td > div.verse {
white-space: pre;
}
.asciidoc p.table {
margin-top: 0;
}
/* Because the table frame attribute is overriden by CSS in most browsers. */
.asciidoc div.tableblock > table[frame="void"] {
border-style: none;
}
.asciidoc div.tableblock > table[frame="hsides"] {
border-left-style: none;
border-right-style: none;
}
.asciidoc div.tableblock > table[frame="vsides"] {
border-top-style: none;
border-bottom-style: none;
}
.asciidoc div.hdlist {
margin-top: 0.8em;
margin-bottom: 0.8em;
}
.asciidoc div.hdlist tr {
padding-bottom: 15px;
}
.asciidoc dt.hdlist1.strong, .asciidoc td.hdlist1.strong {
font-weight: bold;
}
.asciidoc td.hdlist1 {
vertical-align: top;
font-style: normal;
padding-right: 0.8em;
color: #111111;
}
.asciidoc td.hdlist2 {
vertical-align: top;
}
.asciidoc div.hdlist.compact tr {
margin: 0;
padding-bottom: 0;
}
.asciidoc .comment {
background: yellow;
}
@media print {
div#footer-badges { display: none; }
}
.asciidoc div#toctitle {
color: #111111;
font-family: sans-serif;
font-size: 1.1em;
font-weight: bold;
margin-top: 1.0em;
margin-bottom: 0.1em;
}
.asciidoc div.toclevel1,
.asciidoc div.toclevel2,
.asciidoc div.toclevel3,
.asciidoc div.toclevel4 {
margin-top: 0;
margin-bottom: 0;
}
.asciidoc div.toclevel2 {
margin-left: 2em;
font-size: 0.9em;
}
.asciidoc div.toclevel3 {
margin-left: 4em;
font-size: 0.9em;
}
.asciidoc div.toclevel4 {
margin-left: 6em;
font-size: 0.9em;
}

View File

@ -1,128 +0,0 @@
.login-box {
margin-bottom: -10px;
height: 25px;
}
.user-menu {
}
.login-menu {
float: right;
white-space: nowrap;
}
.Wt-auth-logged-in {
display: inline;
}
.link {
text-decoration: underline;
cursor: pointer;
}
.invalid {
background-color: #EE9999;
}
.comment-icon {
float: left;
margin-top: 3px;
width: 20px;
height: 20px;
background-image: url(comment.png);
background-repeat: no-repeat;
}
.comment-edit-icon {
float: left;
margin-top: 3px;
width: 20px;
height: 20px;
background-image: url(comment_edit.png);
background-repeat: no-repeat;
}
.comment-info {
color: rgb(136,136,136);
}
.poster {
color: rgb(51, 102, 53);
font-weight: bold;
}
.author-panel {
margin-top: 10px;
border: 1px #528B12 dashed;
padding: 5px;
}
.user-editor {
margin-top: 10px;
border: 1px #528B12 dashed;
padding: 5px;
}
.profile-panel {
margin-top: 10px;
border: 1px #528B12 dashed;
padding: 5px;
}
.comment-body {
overflow: hidden; /* trick that makes alignment work properly */
margin-left: 3px;
}
.comment-body .vspace {
margin: 10px 0px;
}
.comment-body pre {
line-height: 140%;
border: 1px solid silver;
background: #f4f4f4;
padding: 0.5em;
}
.comment-edit {
width: 400px;
}
.comment-edit textarea {
width: 396px;
}
.comment-links {
margin-bottom: 3px;
}
.blogpost-edit {
width: 500px;
margin: 10x;
}
.blogpost-edit div {
margin: 5px 0px;
}
.blogpost-edit input {
width: 450px;
}
.blogpost-edit textarea {
width: 496px;
height: 150px;
}
.archive-month-title {
color: #528B12;
display: block;
font-size: 1.3em;
line-height: 1.8;
margin-top: 15px;
font-weight: bold;
}
.asciidoc .subtitle {
font-size: 1.4em;
}

View File

@ -1,258 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<messages xmlns:if="Wt.WTemplate.conditions">
<message id="blog-login">
${user-name} ${password} ${remember-me} Remember me
${login}${<if:oauth>}, or use ${icons}${</if:oauth>}
</message>
<message id="blog-login-status">
<div class="login-box">
<div class="login-menu">
${login} ${login-link} | ${register-link}
<a href="${feed-url}">
<img src="/css/rss.png" alt="Rss Feed"
style="margin-left: 6px; vertical-align: top;"/>
</a>
</div>
<div class="user-menu">
${archive-link} ${profile-link} ${author-panel-link} ${userlist-link}
</div>
</div>
</message>
<message id="edit-users-list">
<h4>Registered users</h4>
${user-list}
Limit list to user names containing : ${limit-edit} ${limit-button}
</message>
<message id="edit-user">
<div class="profile-panel">
<h4>Edit user ${username}</h4>
${role-button}
</div>
</message>
<message id="edit-one-user">
<h4>Editing user ${user}</h4>
${save-button} ${cancel-button}
</message>
<message id="blog-invaliduser">
<div class="profile-panel">
<h4>This user id is invalid</h4>
</div>
</message>
<message id="blog-mustlogin">
<div class="profile-panel">
<h4>You need to log in to access this function</h4>
</div>
</message>
<message id="blog-mustbeadministrator">
<div class="profile-panel">
<h4>You need to be administrator to access this function</h4>
</div>
</message>
<message id="blog-register">
<h4>Register</h4>
<p>
To register as a user to post comments with your own login,
please fill out the following form.
</p>
<table style="margin: 5px auto;">
<tr>
<td>Login:</td>
<td>${name}</td>
</tr>
<tr>
<td>Password:</td>
<td>${passwd}</td>
</tr>
<tr>
<td>Repeat password:</td>
<td>${passwd2}</td>
</tr>
<tr>
<td colspan="2" style="text-align: center">
${ok-button}
${cancel-button}
</td>
</tr>
<tr>
<td colspan="2" class="login-error">
${error}
</td>
</tr>
</table>
</message>
<message id="blog-userslist">
<div class="profile-panel">
<h4>Registered users</h4>
Search for ${searchstring} ${search-button}<br/>
</div>
</message>
<message id="blog-profile">
<div class="profile-panel">
<h4>Profile panel for <span class="poster">${user}</span></h4>
<table style="margin: 5px auto;">
<tr>
<td>New password:</td>
<td>${passwd}</td>
</tr>
<tr>
<td>Repeat password:</td>
<td>${passwd2}</td>
</tr>
<tr>
<td colspan="2" style="text-align: center">
${ok-button}
${cancel-button}
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center; color: red;" class="login-error">
${error}
</td>
</tr>
</table>
</div>
</message>
<message id="blog-author-panel">
<div class="author-panel">
<h4>Author panel for <span class="poster">${user}</span></h4>
Statistics:
<ul>
<li>${unpublished-count} unpublished post(s)</li>
<li>${published-count} published post(s)</li>
</ul>
${new-post}
${unpublished-posts}
</div>
</message>
<message id="blog-post">
<h4>${title}</h4>
<div id="${anchor}">
<div>by ${author} on ${date}</div>
<div class="asciidoc">
${brief+body}
</div>
<div style="margin-top: 10px;">${comment-count}</div>
<div>
${publish} ${edit} ${delete}
</div>
</div>
<div id="${anchor}/comments">
${comments}
</div>
</message>
<message id="blog-post-brief">
<h4>${title}</h4>
<div>by ${author} on ${date}</div>
<div class="asciidoc">
${brief}
</div>
<div>${read-more}</div>
<div style="margin-top: 10px">
${publish} ${edit} ${delete}
</div>
<div style="margin-top: 10px;">${comment-count}</div>
</message>
<message id="blog-post-edit">
<h4></h4>
<div class="blogpost-edit">
<div>Title: ${title-edit}</div>
<div>${brief-edit}</div>
<div>${body-edit}</div>
<div>
${save} ${cancel}
</div>
</div>
</message>
<message id="blog-comment">
<div class="comment-icon" />
<div class="comment-body">
<div class="comment-info">
<span class="poster">${author}</span>
${date} ${collapse-expand}
</div>
${contents}
<div class="comment-links">
${reply} ${edit} ${delete}
</div>
${children}
</div>
</message>
<message id="blog-root-comment">
<div class="comment-links">
${reply}
</div>
${children}
</message>
<message id="blog-edit-comment">
<div class="comment-edit-icon" />
<div class="comment-body comment-edit">
<div>${area}</div>
<div style="float: right"><i>plain text</i> or (&lt;code&gt;...&lt;/code&gt;)</div>
${save}
${cancel}
</div>
</message>
<message id="blog-no-post">
<h4>No posts found</h4>
Sorry, no blog posts found that match your selection.
</message>
<message id="blog-no-author">
<h4>No author</h4>
Sorry, {1} is not a registered blog author.
</message>
<message id="archive-title">
<h4>Archive</h4>
</message>
<message id="login">Login</message>
<message id="login-tooshort">Login too short (must be at least {1} characters)</message>
<message id="passwd-mismatch">Passwords don't match.</message>
<message id="register">Register</message>
<message id="logout">Logout</message>
<message id="archive">Archive</message>
<message id="profile">Profile</message>
<message id="author-post">Authoring panel</message>
<message id="edit-users">Edit users</message>
<message id="comment-add">Add comment</message>
<message id="comment-reply">Reply</message>
<message id="comment-edit">Edit</message>
<message id="comment-delete">Delete</message>
<message id="comment-deleted"><i>[[Comment deleted]]</i></message>
<message id="blog-read-more">Read the rest of this post &gt;&gt;</message>
<message id="new-post">New post</message>
<message id="publish">Publish</message>
<message id="retract">Retract</message>
<message id="delete">Delete</message>
<message id="edit">Edit</message>
<message id="save">Save</message>
<message id="cancel">Cancel</message>
<message id="go-limit">Search</message>
<message id="demote-admin">Demote this administrator to a regular visitor</message>
<message id="promote-user">Promote this user to administrator</message>
<message id="no-users-found">No users found</message>
</messages>

View File

@ -1,52 +0,0 @@
body {
color: #333333;
font-family: arial,sans-serif;
font-size: 80%;
line-height:1.5em;
background-color:#FFF;
min-width:750px;
overflow-x: hidden;
}
a {
text-decoration: underline;
color: #528B12; /*#70BD1A;*/
}
.link {
color: #528B12; /*#70BD1A;*/
}
a.blank {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h4 {
font-size: 1.3em;
line-height: 1.8;
border-top : 1px solid #528B12;
padding-bottom:10px;
margin-top:15px;
color: #528B12;
display: block;
}
p {
font-size: 1.1em;
font-weight: normal;
line-height: 1.4;
margin-bottom : 15px;
color: #333333;
}
p.intro {
font-size: 1.3em;
font-weight: normal;
line-height: 1.4;
padding-bottom : 15px;
color: #333333;
}

View File

@ -1,118 +0,0 @@
#include "BlogSession.h"
#include "BoostLog.h"
#include "Post.h"
#include "Token.h"
#include "User.h"
#include "asciidoc.h"
#include <Wt/Auth/AuthService.h>
#include <Wt/Auth/GoogleService.h>
#include <Wt/Auth/HashFunction.h>
#include <Wt/Auth/PasswordService.h>
#include <Wt/Auth/PasswordStrengthValidator.h>
#include <Wt/Auth/PasswordVerifier.h>
#include <Wt/Dbo/FixedSqlConnectionPool.h>
const std::string ADMIN_USERNAME = "admin";
const std::string ADMIN_PASSWORD = "admin";
class BlogOAuth : public std::vector<const Wt::Auth::OAuthService *> {
public:
~BlogOAuth() {
for (unsigned i = 0; i < size(); ++i) delete (*this)[i];
}
};
Wt::Auth::AuthService blogAuth;
Wt::Auth::PasswordService blogPasswords(blogAuth);
BlogOAuth blogOAuth;
BlogSession::BlogSession(Wt::Dbo::SqlConnectionPool &connectionPool)
: m_connectionPool(connectionPool), m_users(*this) {
setConnectionPool(m_connectionPool);
mapClass<Comment>("comment");
mapClass<Post>("post");
mapClass<Tag>("tag");
mapClass<Token>("token");
mapClass<User>("user");
try {
Wt::Dbo::Transaction t(*this);
createTables();
Wt::Dbo::ptr<User> admin = add(std::make_unique<User>());
User *a = admin.modify();
a->name = ADMIN_USERNAME;
a->role = User::Admin;
Wt::Auth::User authAdmin = m_users.findWithIdentity(Wt::Auth::Identity::LoginName, a->name);
blogPasswords.updatePassword(authAdmin, ADMIN_PASSWORD);
Wt::Dbo::ptr<Post> post = add(std::make_unique<Post>());
Post *p = post.modify();
p->state = Post::Published;
p->author = admin;
p->title = "Welcome!";
p->briefSrc = "Welcome to your own blog.";
p->bodySrc = "We have created for you an " + ADMIN_USERNAME + " user with password " + ADMIN_PASSWORD;
p->briefHtml = asciidoc(p->briefSrc);
p->bodyHtml = asciidoc(p->bodySrc);
p->date = Wt::WDateTime::currentDateTime();
Wt::Dbo::ptr<Comment> rootComment = add(std::make_unique<Comment>());
rootComment.modify()->post = post;
t.commit();
LOG(info) << "Created database, and user " << ADMIN_USERNAME << " / " << ADMIN_PASSWORD << std::endl;
} catch (std::exception &e) {
LOG(error) << e.what() << std::endl;
LOG(info) << "Using existing database";
}
}
void BlogSession::configureAuth() {
blogAuth.setAuthTokensEnabled(true, "bloglogin");
std::unique_ptr<Wt::Auth::PasswordVerifier> verifier = std::make_unique<Wt::Auth::PasswordVerifier>();
verifier->addHashFunction(std::make_unique<Wt::Auth::BCryptHashFunction>(7));
#ifdef WT_WITH_SSL
verifier->addHashFunction(std::make_unique<Wt::Auth::SHA1HashFunction>());
#endif
#ifdef HAVE_CRYPT
verifier->addHashFunction(std::make_unique<UnixCryptHashFunction>());
#endif
blogPasswords.setVerifier(std::move(verifier));
blogPasswords.setPasswordThrottle(std::make_unique<Wt::Auth::AuthThrottle>());
blogPasswords.setStrengthValidator(std::make_unique<Wt::Auth::PasswordStrengthValidator>());
if (Wt::Auth::GoogleService::configured()) blogOAuth.push_back(new Wt::Auth::GoogleService(blogAuth));
}
std::unique_ptr<Wt::Dbo::SqlConnectionPool> BlogSession::createConnectionPool(const std::string &sqlite3) {
auto connection = std::make_unique<Wt::Dbo::backend::Sqlite3>(sqlite3);
connection->setProperty("show-queries", "true");
connection->setDateTimeStorage(Wt::Dbo::SqlDateTimeType::DateTime,
Wt::Dbo::backend::DateTimeStorage::PseudoISO8601AsText);
return std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), 10);
}
Wt::Dbo::ptr<User> BlogSession::user() const {
if (m_login.loggedIn())
return m_users.find(m_login.user());
else
return Wt::Dbo::ptr<User>();
}
Wt::Signal<Wt::Dbo::ptr<Comment>> &BlogSession::commentsChanged() {
return m_commentsChanged;
}
Wt::Auth::PasswordService *BlogSession::passwordAuth() const {
return &blogPasswords;
}
const std::vector<const Wt::Auth::OAuthService *> &BlogSession::oAuth() const {
return blogOAuth;
}

View File

@ -1,35 +0,0 @@
#ifndef __BLOGSESSION_H__
#define __BLOGSESSION_H__
#include "BlogUserDatabase.h"
#include <Wt/Auth/Login.h>
#include <Wt/Auth/OAuthService.h>
#include <Wt/Dbo/Session.h>
#include <Wt/Dbo/backend/Sqlite3.h>
class Comment;
class BlogSession : public Wt::Dbo::Session {
public:
BlogSession(Wt::Dbo::SqlConnectionPool &connectionPool);
static void configureAuth();
static std::unique_ptr<Wt::Dbo::SqlConnectionPool> createConnectionPool(const std::string &sqlite3);
Wt::Auth::Login &login() {
return m_login;
}
Wt::Dbo::ptr<User> user() const;
Wt::Signal<Wt::Dbo::ptr<Comment>> &commentsChanged();
BlogUserDatabase &users() {
return m_users;
}
Wt::Auth::PasswordService *passwordAuth() const;
const std::vector<const Wt::Auth::OAuthService *> &oAuth() const;
private:
Wt::Dbo::SqlConnectionPool &m_connectionPool;
BlogUserDatabase m_users;
Wt::Auth::Login m_login;
Wt::Signal<Wt::Dbo::ptr<Comment>> m_commentsChanged;
};
#endif // __BLOGSESSION_H__

View File

@ -1,140 +0,0 @@
#include "User.h"
#include "BlogUserDatabase.h"
#include <Wt/Auth/Identity.h>
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() {
}
Wt::Dbo::ptr<User> BlogUserDatabase::find(const Wt::Auth::User &user) const {
getUser(user.id());
return m_user;
}
Wt::Auth::User BlogUserDatabase::find(Wt::Dbo::ptr<User> user) const {
m_user = user;
return Wt::Auth::User(std::to_string(m_user.id()), *this);
}
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<User>().where("name = ?").bind(identity);
} else
m_user = m_session.find<User>().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;
}
Wt::Auth::PasswordHash BlogUserDatabase::password(const Wt::Auth::User &user) const {
WithUser find(*this, user);
return Wt::Auth::PasswordHash(m_user->passwordMethod, m_user->passwordSalt, m_user->password);
}
void BlogUserDatabase::setPassword(const Wt::Auth::User &user, const Wt::Auth::PasswordHash &password) {
WithUser find(*this, user);
m_user.modify()->password = password.value();
m_user.modify()->passwordMethod = password.function();
m_user.modify()->passwordSalt = password.salt();
}
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<User>().where("id = ?").bind(User::stringToId(id));
t.commit();
}
}

View File

@ -1,40 +0,0 @@
#ifndef __BLOGUSERDATABASE_H__
#define __BLOGUSERDATABASE_H__
#include <Wt/Auth/AbstractUserDatabase.h>
#include <Wt/Dbo/Transaction.h>
#include <Wt/Dbo/Types.h>
class User;
class BlogUserDatabase : public Wt::Auth::AbstractUserDatabase {
public:
BlogUserDatabase(Wt::Dbo::Session &session);
~BlogUserDatabase();
Wt::Dbo::ptr<User> find(const Wt::Auth::User &user) const;
Wt::Auth::User find(Wt::Dbo::ptr<User> user) const;
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;
Wt::Auth::PasswordHash password(const Wt::Auth::User &user) const final;
void setPassword(const Wt::Auth::User &user, const Wt::Auth::PasswordHash &password) 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<User> m_user;
};
#endif // __BLOGUSERDATABASE_H__

View File

@ -1,76 +0,0 @@
#include "Comment.h"
#include "Post.h"
#include "Tag.h"
#include "User.h"
#include <Wt/Dbo/Impl.h>
#include <Wt/WWebWidget.h>
DBO_INSTANTIATE_TEMPLATES(Comment)
static std::string &replace(std::string &s, const std::string &k, const std::string &r) {
std::string::size_type p = 0;
while ((p = s.find(k, p)) != std::string::npos) {
s.replace(p, k.length(), r);
p += r.length();
}
return s;
}
void Comment::setText(const Wt::WString &text) {
textSrc_ = text;
std::string html = Wt::WWebWidget::escapeText(text, true).toUTF8();
std::string::size_type b = 0;
// Replace &lt;code&gt;...&lt/code&gt; with <pre>...</pre>
// This is kind of very ad-hoc!
while ((b = html.find("&lt;code&gt;", b)) != std::string::npos) {
std::string::size_type e = html.find("&lt;/code&gt;", b);
if (e == std::string::npos)
break;
else {
if (b > 6 && html.substr(b - 6, 6) == "<br />") {
html.erase(b - 6, 6);
b -= 6;
e -= 6;
}
html.replace(b, 12, "<pre>");
e -= 7;
if (html.substr(b + 5, 6) == "<br />") {
html.erase(b + 5, 6);
e -= 6;
}
if (html.substr(e - 6, 6) == "<br />") {
html.erase(e - 6, 6);
e -= 6;
}
html.replace(e, 13, "</pre>");
e += 6;
if (e + 6 <= html.length() && html.substr(e, 6) == "<br />") {
html.erase(e, 6);
e -= 6;
}
b = e;
}
}
// We would also want to replace <br /><br /> (empty line) with
// <div class="vspace"></div>
replace(html, "<br /><br />", "<div class=\"vspace\"></div>");
textHtml_ = Wt::WString(html);
}
void Comment::setDeleted() {
textHtml_ = Wt::WString::tr("comment-deleted");
}

View File

@ -1,50 +0,0 @@
#ifndef __COMMENT_H__
#define __COMMENT_H__
#include <Wt/Dbo/Types.h>
#include <Wt/Dbo/WtSqlTraits.h>
#include <Wt/WDateTime.h>
class Comment;
using Comments = Wt::Dbo::collection<Wt::Dbo::ptr<Comment>>;
class Post;
class User;
class Comment {
public:
Wt::Dbo::ptr<User> author;
Wt::Dbo::ptr<Post> post;
Wt::Dbo::ptr<Comment> parent;
Wt::WDateTime date;
void setText(const Wt::WString &text);
void setDeleted();
const Wt::WString &textSrc() const {
return textSrc_;
}
const Wt::WString &textHtml() const {
return textHtml_;
}
Comments children;
template <class Action>
void persist(Action &a) {
Wt::Dbo::field(a, date, "date");
Wt::Dbo::field(a, textSrc_, "text_source");
Wt::Dbo::field(a, textHtml_, "text_html");
Wt::Dbo::belongsTo(a, post, "post", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::belongsTo(a, author, "author");
Wt::Dbo::belongsTo(a, parent, "parent", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, children, Wt::Dbo::ManyToOne, "parent");
}
private:
Wt::WString textSrc_;
Wt::WString textHtml_;
};
#endif // __COMMENT_H__

View File

@ -1,36 +0,0 @@
#include "Post.h"
#include "User.h"
#include <Wt/Dbo/Impl.h>
DBO_INSTANTIATE_TEMPLATES(Post)
std::string Post::permaLink() const {
return date.toString("yyyy/MM/dd/'" + titleToUrl() + '\'').toUTF8();
}
std::string Post::commentCount() const {
int count = (int)comments.size() - 1;
if (count == 1)
return "1 comment";
else
return std::to_string(count) + " comments";
}
std::string Post::titleToUrl() const {
std::string result = title.narrow();
for (unsigned i = 0; i < result.length(); ++i) {
if (!isalnum(result[i]))
result[i] = '_';
else
result[i] = tolower(result[i]);
}
return result;
}
Wt::Dbo::ptr<Comment> Post::rootComment() const {
if (session())
return session()->find<Comment>().where("post_id = ?").bind(id()).where("parent_id is null");
else
return Wt::Dbo::ptr<Comment>();
}

View File

@ -1,58 +0,0 @@
#ifndef __POST_H__
#define __POST_H__
#include "Comment.h"
#include "Tag.h"
#include <Wt/Dbo/WtSqlTraits.h>
#include <Wt/WDateTime.h>
#include <Wt/WString.h>
class User;
typedef Wt::Dbo::collection<Wt::Dbo::ptr<Comment>> Comments;
typedef Wt::Dbo::collection<Wt::Dbo::ptr<Tag>> Tags;
class Post : public Wt::Dbo::Dbo<Post> {
public:
enum State {
Unpublished = 0,
Published = 1,
};
std::string permaLink() const;
std::string commentCount() const;
std::string titleToUrl() const;
Wt::Dbo::ptr<Comment> rootComment() const;
template <class Action>
void persist(Action &a) {
Wt::Dbo::field(a, state, "state");
Wt::Dbo::field(a, date, "date");
Wt::Dbo::field(a, title, "title");
Wt::Dbo::field(a, briefSrc, "brief_src");
Wt::Dbo::field(a, briefHtml, "brief_html");
Wt::Dbo::field(a, bodySrc, "body_src");
Wt::Dbo::field(a, bodyHtml, "body_html");
Wt::Dbo::belongsTo(a, author, "author");
Wt::Dbo::hasMany(a, comments, Wt::Dbo::ManyToOne, "post");
Wt::Dbo::hasMany(a, tags, Wt::Dbo::ManyToMany, "post_tag");
}
Wt::Dbo::ptr<User> author;
State state;
Wt::WDateTime date;
Wt::WString title;
Wt::WString briefSrc;
Wt::WString briefHtml;
Wt::WString bodySrc;
Wt::WString bodyHtml;
Comments comments;
Tags tags;
};
DBO_EXTERN_TEMPLATES(Post)
#endif // __POST_H__

View File

@ -1,7 +0,0 @@
#include "Tag.h"
#include "Comment.h"
#include "Post.h"
#include "User.h"
#include <Wt/Dbo/Impl.h>
DBO_INSTANTIATE_TEMPLATES(Tag)

View File

@ -1,26 +0,0 @@
#ifndef __TAG_H__
#define __TAG_H__
#include <Wt/Dbo/Types.h>
class Post;
using Posts= Wt::Dbo::collection<Wt::Dbo::ptr<Post>> ;
class Tag {
public:
Tag() = default;
Tag(const std::string &aName) : name(aName) {
}
template <class Action>
void persist(Action &a) {
Wt::Dbo::field(a, name, "name");
Wt::Dbo::hasMany(a, posts, Wt::Dbo::ManyToMany, "post_tag");
}
std::string name;
Posts posts;
};
DBO_EXTERN_TEMPLATES(Tag)
#endif // __TAG_H__

View File

@ -1,11 +0,0 @@
#include "Token.h"
#include "User.h"
#include <Wt/Dbo/Impl.h>
DBO_INSTANTIATE_TEMPLATES(Token)
Token::Token() {
}
Token::Token(const std::string &v, const Wt::WDateTime &e) : value(v), expires(e) {
}

View File

@ -1,30 +0,0 @@
#ifndef __TOKENS_H__
#define __TOKENS_H__
#include <Wt/Dbo/Types.h>
#include <Wt/WDateTime.h>
class User;
class Token : public Wt::Dbo::Dbo<Token> {
public:
Token();
Token(const std::string &value, const Wt::WDateTime &expires);
Wt::Dbo::ptr<User> user;
std::string value;
Wt::WDateTime expires;
template <class Action>
void persist(Action &a) {
Wt::Dbo::field(a, value, "value");
Wt::Dbo::field(a, expires, "expires");
Wt::Dbo::belongsTo(a, user, "user");
}
};
DBO_EXTERN_TEMPLATES(Token)
#endif // __TOKENS_H__

View File

@ -1,23 +0,0 @@
#include "User.h"
#include <Wt/Dbo/Impl.h>
DBO_INSTANTIATE_TEMPLATES(User)
Wt::Dbo::dbo_traits<User>::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<User>::invalidId();
else
return result;
}
Posts User::latestPosts(int count) const {
return posts.find().where("state = ?").bind(Post::Published).orderBy("date desc").limit(count);
}
Posts User::allPosts(Post::State state) const {
return posts.find().where("state = ?").bind(state).orderBy("date desc");
}
User::User() : role(Visitor), failedLoginAttempts(0) {
}

View File

@ -1,60 +0,0 @@
#ifndef __USER_H__
#define __USER_H__
#include "Post.h"
#include "Token.h"
#include <Wt/Dbo/WtSqlTraits.h>
#include <Wt/WDateTime.h>
#include <Wt/WGlobal.h>
#include <Wt/WString.h>
using Tokens = Wt::Dbo::collection<Wt::Dbo::ptr<Token>>;
class User {
public:
enum Role {
Visitor = 0,
Admin = 1,
};
User();
static Wt::Dbo::dbo_traits<User>::IdType stringToId(const std::string &s);
Posts latestPosts(int count = 10) const;
Posts allPosts(Post::State state) const;
Wt::WString name;
Role role;
std::string password;
std::string passwordMethod;
std::string passwordSalt;
int failedLoginAttempts;
Wt::WDateTime lastLoginAttempt;
std::string oAuthId;
std::string oAuthProvider;
Tokens authTokens;
Comments comments;
Posts posts;
template <class Action>
void persist(Action &a) {
Wt::Dbo::field(a, name, "name");
Wt::Dbo::field(a, password, "password");
Wt::Dbo::field(a, passwordMethod, "password_method");
Wt::Dbo::field(a, passwordSalt, "password_salt");
Wt::Dbo::field(a, role, "role");
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");
Wt::Dbo::hasMany(a, comments, Wt::Dbo::ManyToOne, "author");
Wt::Dbo::hasMany(a, posts, Wt::Dbo::ManyToOne, "author");
Wt::Dbo::hasMany(a, authTokens, Wt::Dbo::ManyToOne, "user");
}
};
DBO_EXTERN_TEMPLATES(User)
#endif // __USER_H__

View File

@ -1,94 +0,0 @@
#include "asciidoc.h"
#include <fstream>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include "Wt/WString.h"
#ifndef WT_WIN32
#include <unistd.h>
#endif
namespace {
std::string tempFileName()
{
#ifndef WT_WIN32
char spool[20];
strcpy(spool, "/tmp/wtXXXXXX");
int i = mkstemp(spool);
close(i);
#else
char spool[2 * L_tmpnam];
tmpnam(spool);
#endif
return std::string(spool);
}
std::string readFileToString(const std::string& fileName)
{
std::fstream file(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate);
int length = file.tellg();
file.seekg(0, std::ios::beg);
std::unique_ptr<char[]> buf(new char[length]);
file.read(buf.get(), length);
file.close();
return std::string(buf.get(), length);
}
}
Wt::WString asciidoc(const Wt::WString& src)
{
std::string srcFileName = tempFileName();
std::string htmlFileName = tempFileName();
{
std::ofstream srcFile(srcFileName.c_str(), std::ios::out);
std::string ssrc = src.toUTF8();
srcFile.write(ssrc.c_str(), (std::streamsize)ssrc.length());
srcFile.close();
}
#if defined(ASCIIDOCTOR_EXECUTABLE)
#define xstr(s) str(s)
#define str(s) #s
std::string cmd = xstr(ASCIIDOCTOR_EXECUTABLE);
#else
std::string cmd = "asciidoctor";
#endif
std::string command = cmd + " -a htmlsyntax=xml -o " + htmlFileName + " -s " + srcFileName;
#ifndef WT_WIN32
/*
* So, asciidoc apparently sends a SIGINT which is caught by its parent
* process.. So we have to temporarily ignore it.
*/
struct sigaction newAction, oldAction;
newAction.sa_handler = SIG_IGN;
newAction.sa_flags = 0;
sigemptyset(&newAction.sa_mask);
sigaction(SIGINT, &newAction, &oldAction);
#endif
bool ok = system(command.c_str()) == 0;
#ifndef WT_WIN32
sigaction(SIGINT, &oldAction, 0);
#endif
Wt::WString result;
if (ok) {
result = Wt::WString(readFileToString(htmlFileName));
} else
result = Wt::WString("<i>Could not execute asciidoc</i>");
unlink(srcFileName.c_str());
unlink(htmlFileName.c_str());
return result;
}

View File

@ -1,7 +0,0 @@
#ifndef __ASCIIDOC_H__
#define __ASCIIDOC_H__
#include <Wt/WString.h>
Wt::WString asciidoc(const Wt::WString &src);
#endif // __ASCIIDOC_H__

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,40 +0,0 @@
#include "BlogLoginWidget.h"
#include "model/BlogSession.h"
#include <Wt/Auth/PasswordService.h>
#include <Wt/WLineEdit.h>
#include <Wt/WText.h>
BlogLoginWidget::BlogLoginWidget(BlogSession &session, const std::string &basePath) : AuthWidget(session.login()) {
setInline(true);
auto model = std::make_unique<Wt::Auth::AuthModel>(session.passwordAuth()->baseAuth(), session.users());
model->addPasswordAuth(session.passwordAuth());
model->addOAuth(session.oAuth());
setModel(std::move(model));
setInternalBasePath(basePath + "login");
}
void BlogLoginWidget::createLoginView() {
AuthWidget::createLoginView();
setTemplateText(tr("blog-login"));
Wt::WLineEdit *userName = resolve<Wt::WLineEdit *>("user-name");
userName->setPlaceholderText("login");
userName->setToolTip("login");
Wt::WLineEdit *password = resolve<Wt::WLineEdit *>("password");
password->setPlaceholderText("password");
password->setToolTip("password");
}
void BlogLoginWidget::createLoggedInView() {
AuthWidget::createLoggedInView();
auto logout = std::make_unique<Wt::WText>(tr("logout"));
logout->setStyleClass("link");
logout->clicked().connect(&login(), &Wt::Auth::Login::logout);
bindWidget("logout", std::move(logout));
}

View File

@ -1,14 +0,0 @@
#ifndef __BLOGLOGINWIDGET_H__
#define __BLOGLOGINWIDGET_H__
#include <Wt/Auth/AuthWidget.h>
class BlogSession;
class BlogLoginWidget : public Wt::Auth::AuthWidget {
public:
BlogLoginWidget(BlogSession &session, const std::string &basePath);
void createLoginView() final;
void createLoggedInView() final;
};
#endif // __BLOGLOGINWIDGET_H__

View File

@ -1,392 +0,0 @@
#include "BlogView.h"
#include "BlogLoginWidget.h"
#include "EditUsers.h"
#include "PostView.h"
#include "model/BlogSession.h"
#include "model/Post.h"
#include "model/Tag.h"
#include <Wt/WApplication.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WPushButton.h>
#include <Wt/WStackedWidget.h>
#include <Wt/WText.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>
static int try_stoi(const std::string &v) {
std::size_t pos;
auto result = std::stoi(v, &pos);
if (pos != v.length()) throw std::invalid_argument("stoi() of " + v + " failed");
return result;
}
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::instance()->internalPathChanged().connect(this, &BlogImpl::handlePathChange);
m_loginStatus = this->addWidget(std::make_unique<Wt::WTemplate>(tr("blog-login-status")));
m_panel = this->addWidget(std::make_unique<Wt::WStackedWidget>());
m_items = this->addWidget(std::make_unique<Wt::WContainerWidget>());
m_session.login().changed().connect(this, &BlogImpl::onUserChanged);
auto loginWidget = std::make_unique<BlogLoginWidget>(m_session, basePath);
m_loginWidget = loginWidget.get();
m_loginWidget->hide();
auto loginLink = std::make_unique<Wt::WText>(tr("login"));
auto lPtr = loginLink.get();
loginLink->setStyleClass("link");
loginLink->clicked().connect(m_loginWidget, &BlogLoginWidget::show);
loginLink->clicked().connect(lPtr, &WWidget::hide);
auto registerLink = std::make_unique<Wt::WText>(tr("Wt.Auth.register"));
registerLink->setStyleClass("link");
registerLink->clicked().connect(m_loginWidget, &BlogLoginWidget::registerNewUser);
auto archiveLink =
std::make_unique<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, m_basePath + "all"), tr("archive"));
m_loginStatus->bindWidget("login", std::move(loginWidget));
m_loginStatus->bindWidget("login-link", std::move(loginLink));
m_loginStatus->bindWidget("register-link", std::move(registerLink));
m_loginStatus->bindString("feed-url", m_rssFeedUrl);
m_loginStatus->bindWidget("archive-link", std::move(archiveLink));
onUserChanged();
m_loginWidget->processEnvironment();
}
protected:
void handlePathChange(const std::string &) {
Wt::WApplication *app = Wt::WApplication::instance();
if (app->internalPathMatches(m_basePath)) {
Wt::Dbo::Transaction t(m_session);
std::string path = app->internalPathNextPart(m_basePath);
m_items->clear();
if (m_users) {
m_users = 0;
}
if (path.empty())
showPosts(m_session
.find<Post>("where state = ? "
"order by date desc "
"limit 10")
.bind(Post::Published),
m_items);
else if (path == "author") {
std::string author = app->internalPathNextPart(m_basePath + path + '/');
Wt::Dbo::ptr<User> user = findUser(author);
if (user)
showPosts(user);
else
showError(tr("blog-no-author").arg(author));
} else if (path == "edituser") {
editUser(app->internalPathNextPart(m_basePath + path + '/'));
} else if (path == "all") {
showArchive(m_items);
} else {
std::string remainder = app->internalPath().substr(m_basePath.length());
showPostsByDateTopic(remainder, m_items);
}
t.commit();
}
}
void showArchive(WContainerWidget *parent) {
static const char *dateFormat = "MMMM yyyy";
parent->addWidget(std::make_unique<Wt::WText>(tr("archive-title")));
Posts posts = m_session.find<Post>("order by date desc");
Wt::WDateTime formerDate;
for (auto post : posts) {
if (post->state != Post::Published) continue;
if (formerDate.isNull() || yearMonthDiffer(formerDate, post->date)) {
Wt::WText *title =
parent->addWidget(std::make_unique<Wt::WText>(post->date.date().toString(dateFormat)));
title->setStyleClass("archive-month-title");
}
Wt::WAnchor *a = parent->addWidget(std::make_unique<Wt::WAnchor>(
Wt::WLink(Wt::LinkType::InternalPath, m_basePath + post->permaLink()), post->title));
a->setInline(false);
formerDate = post->date;
}
}
bool yearMonthDiffer(const Wt::WDateTime &dt1, const Wt::WDateTime &dt2) {
return dt1.date().year() != dt2.date().year() || dt1.date().month() != dt2.date().month();
}
Wt::Dbo::ptr<User> findUser(const std::string &name) {
return m_session.find<User>("where name = ?").bind(name);
}
bool checkLoggedIn() {
if (m_session.user()) return true;
m_panel->show();
if (!m_mustLoginWarning) {
m_mustLoginWarning = m_panel->addWidget(std::make_unique<Wt::WTemplate>(tr("blog-mustlogin")));
}
m_panel->setCurrentWidget(m_mustLoginWarning);
return false;
}
bool checkAdministrator() {
if (m_session.user() && (m_session.user()->role == User::Admin)) return true;
m_panel->show();
if (!m_mustBeAdministratorWarning) {
m_mustBeAdministratorWarning =
m_panel->addWidget(std::make_unique<Wt::WTemplate>(tr("blog-mustbeadministrator")));
}
m_panel->setCurrentWidget(m_mustBeAdministratorWarning);
return false;
}
void editUser(const std::string &ids) {
if (!checkLoggedIn()) return;
if (!checkAdministrator()) return;
Wt::Dbo::dbo_traits<User>::IdType id = User::stringToId(ids);
m_panel->show();
try {
Wt::Dbo::Transaction t(m_session);
Wt::Dbo::ptr<User> target(m_session.load<User>(id));
if (!m_userEditor) {
m_userEditor = m_panel->addWidget(std::make_unique<EditUser>(m_session));
}
m_userEditor->switchUser(target);
m_panel->setCurrentWidget(m_userEditor);
} catch (Wt::Dbo::ObjectNotFoundException &) {
if (!m_invalidUser) {
m_invalidUser = m_panel->addWidget(std::make_unique<Wt::WTemplate>(tr("blog-invaliduser")));
}
m_panel->setCurrentWidget(m_invalidUser);
}
}
void showPostsByDateTopic(const std::string &path, WContainerWidget *parent) {
std::vector<std::string> parts;
boost::split(parts, path, boost::is_any_of("/"));
Wt::WDate lower, upper;
try {
int year = try_stoi(parts[0]);
if (parts.size() > 1) {
int month = try_stoi(parts[1]);
if (parts.size() > 2) {
int day = try_stoi(parts[2]);
lower.setDate(year, month, day);
upper = lower.addDays(1);
} else {
lower.setDate(year, month, 1);
upper = lower.addMonths(1);
}
} else {
lower.setDate(year, 1, 1);
upper = lower.addYears(1);
}
} catch (std::invalid_argument &) {
showError(tr("blog-no-post"));
return;
}
Posts posts = m_session
.find<Post>("where date >= ? "
"and date < ? "
"and (state = ? or author_id = ?)")
.bind(Wt::WDateTime(lower))
.bind(Wt::WDateTime(upper))
.bind(Post::Published)
.bind(m_session.user().id());
if (parts.size() > 3) {
std::string title = parts[3];
for (auto post : posts)
if (post->titleToUrl() == title) {
showPost(post, PostView::Detail, parent);
return;
}
showError(tr("blog-no-post"));
} else {
showPosts(posts, parent);
}
}
void showPosts(Wt::Dbo::ptr<User> user) {
showPosts(user->latestPosts(), m_items);
}
void showPosts(const Posts &posts, WContainerWidget *parent) {
for (auto post : posts) showPost(post, PostView::Brief, parent);
}
void onUserChanged() {
if (m_session.login().loggedIn())
loggedIn();
else
loggedOut();
}
void editUsers() {
m_panel->show();
if (!m_users) {
m_users = m_panel->addWidget(std::make_unique<EditUsers>(m_session, m_basePath));
bindPanelTemplates();
}
m_panel->setCurrentWidget(m_users);
}
BlogSession &session() {
return m_session;
}
void loggedIn() {
Wt::WApplication::instance()->changeSessionId();
refresh();
m_loginStatus->resolveWidget("login")->show();
m_loginStatus->resolveWidget("login-link")->hide();
m_loginStatus->resolveWidget("register-link")->hide();
auto profileLink = std::make_unique<Wt::WText>(tr("profile"));
profileLink->setStyleClass("link");
profileLink->clicked().connect(this, &BlogImpl::editProfile);
Wt::Dbo::ptr<User> user = session().user();
if (user->role == User::Admin) {
auto editUsersLink = std::make_unique<Wt::WText>(tr("edit-users"));
editUsersLink->setStyleClass("link");
editUsersLink->clicked().connect(this, &BlogImpl::editUsers);
m_loginStatus->bindWidget("userlist-link", std::move(editUsersLink));
auto authorPanelLink = std::make_unique<Wt::WText>(tr("author-post"));
authorPanelLink->setStyleClass("link");
authorPanelLink->clicked().connect(this, &BlogImpl::authorPanel);
m_loginStatus->bindWidget("author-panel-link", std::move(authorPanelLink));
} else {
m_loginStatus->bindEmpty("userlist-link");
m_loginStatus->bindEmpty("author-panel-link");
}
m_loginStatus->bindWidget("profile-link", std::move(profileLink));
bindPanelTemplates();
}
void loggedOut() {
m_loginStatus->bindEmpty("profile-link");
m_loginStatus->bindEmpty("author-panel-link");
m_loginStatus->bindEmpty("userlist-link");
m_loginStatus->resolveWidget("login")->hide();
m_loginStatus->resolveWidget("login-link")->show();
m_loginStatus->resolveWidget("register-link")->show();
refresh();
m_panel->hide();
}
void editProfile() {
m_loginWidget->letUpdatePassword(m_session.login().user(), true);
}
void showError(const Wt::WString &msg) {
m_items->addWidget(std::make_unique<Wt::WText>(msg));
}
void authorPanel() {
m_panel->show();
if (!m_authorPanel) {
m_authorPanel = m_panel->addWidget(std::make_unique<Wt::WTemplate>(tr("blog-author-panel")));
bindPanelTemplates();
}
m_panel->setCurrentWidget(m_authorPanel);
}
void showPost(const Wt::Dbo::ptr<Post> post, PostView::RenderType type, Wt::WContainerWidget *parent) {
parent->addWidget(std::make_unique<PostView>(m_session, m_basePath, post, type));
}
void newPost() {
Wt::Dbo::Transaction t(m_session);
authorPanel();
WContainerWidget *unpublishedPosts = m_authorPanel->resolve<WContainerWidget *>("unpublished-posts");
Wt::Dbo::ptr<Post> post(std::make_unique<Post>());
Post *p = post.modify();
p->state = Post::Unpublished;
p->author = m_session.user();
p->title = "Title";
p->briefSrc = "Brief ...";
p->bodySrc = "Body ...";
showPost(post, PostView::Edit, unpublishedPosts);
t.commit();
}
void bindPanelTemplates() {
if (!m_session.user()) return;
Wt::Dbo::Transaction t(m_session);
if (m_authorPanel) {
auto newPost = std::make_unique<Wt::WPushButton>(tr("new-post"));
newPost->clicked().connect(this, &BlogImpl::newPost);
auto unpublishedPosts = std::make_unique<Wt::WContainerWidget>();
showPosts(m_session.user()->allPosts(Post::Unpublished), unpublishedPosts.get());
m_authorPanel->bindString("user", m_session.user()->name);
m_authorPanel->bindInt("unpublished-count", (int)m_session.user()->allPosts(Post::Unpublished).size());
m_authorPanel->bindInt("published-count", (int)m_session.user()->allPosts(Post::Published).size());
m_authorPanel->bindWidget("new-post", std::move(newPost));
m_authorPanel->bindWidget("unpublished-posts", std::move(unpublishedPosts));
}
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<BlogImpl>(basePath, db, rssFeedUrl, this));
}

View File

@ -1,16 +0,0 @@
#ifndef __BLOGVIEW_H__
#define __BLOGVIEW_H__
#include <Wt/WCompositeWidget.h>
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<Wt::WString> m_userChanged;
};
#endif // __BLOGVIEW_H__

View File

@ -1,146 +0,0 @@
#include "CommentView.h"
#include "model/BlogSession.h"
#include "model/Comment.h"
#include "model/User.h"
#include <Wt/WPushButton.h>
#include <Wt/WTextArea.h>
CommentView::CommentView(BlogSession &session, long long parentId) : session_(session) {
Wt::Dbo::ptr<Comment> parent = session_.load<Comment>(parentId);
comment_ = std::make_unique<Comment>();
comment_.modify()->parent = parent;
comment_.modify()->post = parent->post;
edit();
}
CommentView::CommentView(BlogSession &session, Wt::Dbo::ptr<Comment> comment) : session_(session), comment_(comment) {
comment_ = comment;
renderView();
}
void CommentView::cancel() {
if (isNew())
removeFromParent();
else {
Wt::Dbo::Transaction t(session_);
renderView();
t.commit();
}
}
void CommentView::renderView() {
clear();
bool isRootComment = !comment_->parent;
setTemplateText(isRootComment ? tr("blog-root-comment") : tr("blog-comment"));
bindString("collapse-expand", Wt::WString::Empty); // NYI
auto replyText = std::make_unique<Wt::WText>(isRootComment ? tr("comment-add") : tr("comment-reply"));
replyText->setStyleClass("link");
replyText->clicked().connect(this, &CommentView::reply);
bindWidget("reply", std::move(replyText));
bool mayEdit = session_.user() && (comment_->author == session_.user() || session_.user()->role == User::Admin);
if (mayEdit) {
auto editText = std::make_unique<Wt::WText>(tr("comment-edit"));
editText->setStyleClass("link");
editText->clicked().connect(this, &CommentView::edit);
bindWidget("edit", std::move(editText));
} else
bindString("edit", Wt::WString::Empty);
bool mayDelete =
(session_.user() && session_.user() == comment_->author) || session_.user() == comment_->post->author;
if (mayDelete) {
auto deleteText = std::make_unique<Wt::WText>(tr("comment-delete"));
deleteText->setStyleClass("link");
deleteText->clicked().connect(this, &CommentView::rm);
bindWidget("delete", std::move(deleteText));
} else
bindString("delete", Wt::WString::Empty);
typedef std::vector<Wt::Dbo::ptr<Comment>> CommentVector;
CommentVector comments;
{
Wt::Dbo::collection<Wt::Dbo::ptr<Comment>> cmts = comment_->children.find().orderBy("date");
comments.insert(comments.end(), cmts.begin(), cmts.end());
}
auto children = std::make_unique<Wt::WContainerWidget>();
for (int i = (int)comments.size() - 1; i >= 0; --i)
children->addWidget(std::make_unique<CommentView>(session_, comments[i]));
bindWidget("children", std::move(children));
}
bool CommentView::isNew() const {
return comment_.id() == -1;
}
void CommentView::rm() {
Wt::Dbo::Transaction t(session_);
comment_.modify()->setDeleted();
renderView();
t.commit();
}
void CommentView::reply() {
Wt::Dbo::Transaction t(session_);
Wt::WContainerWidget *c = resolve<Wt::WContainerWidget *>("children");
c->insertWidget(0, std::make_unique<CommentView>(session_, comment_.id()));
t.commit();
}
void CommentView::save() {
Wt::Dbo::Transaction t(session_);
bool isNew = comment_.id() == -1;
Comment *comment = comment_.modify();
comment->setText(editArea_->text());
if (isNew) {
session_.add(comment_);
comment->date = Wt::WDateTime::currentDateTime();
comment->author = session_.user();
session_.commentsChanged().emit(comment_);
}
renderView();
t.commit();
}
void CommentView::edit() {
clear();
Wt::Dbo::Transaction t(session_);
setTemplateText(tr("blog-edit-comment"));
auto editArea = std::make_unique<Wt::WTextArea>();
editArea_ = editArea.get();
editArea_->setText(comment_->textSrc());
editArea_->setFocus();
auto save = std::make_unique<Wt::WPushButton>(tr("save"));
save->clicked().connect(this, &CommentView::save);
auto cancel = std::make_unique<Wt::WPushButton>(tr("cancel"));
cancel->clicked().connect(this, &CommentView::cancel);
bindWidget("area", std::move(editArea));
bindWidget("save", std::move(save));
bindWidget("cancel", std::move(cancel));
t.commit();
}

View File

@ -1,27 +0,0 @@
#ifndef __COMMENTVIEW_H__
#define __COMMENTVIEW_H__
#include <Wt/Dbo/ptr.h>
#include <Wt/WTemplate.h>
class BlogSession;
class Comment;
class CommentView : public Wt::WTemplate {
public:
CommentView(BlogSession &session, long long parentId);
CommentView(BlogSession& session, Wt::Dbo::ptr<Comment> comment);
protected:
void edit();
void save();
void cancel();
void renderView();
bool isNew() const;
void reply();
void rm();
private:
BlogSession &session_;
Wt::Dbo::ptr<Comment> comment_;
Wt::WTextArea *editArea_;
};
#endif // __COMMENTVIEW_H__

View File

@ -1,70 +0,0 @@
#include "EditUsers.h"
#include <Wt/WApplication.h>
#include <Wt/WBreak.h>
#include <Wt/WLineEdit.h>
#include <Wt/WPushButton.h>
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<Wt::WLineEdit>();
auto goLimit = std::make_unique<Wt::WPushButton>(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::onUserClicked(Wt::Dbo::dbo_traits<User>::IdType id) {
Wt::WApplication::instance()->setInternalPath(m_basePath + "edituser/" + std::to_string(id), true);
}
void EditUsers::limitList() {
auto listPtr = std::make_unique<Wt::WContainerWidget>();
auto list = listPtr.get();
bindWidget("user-list", std::move(listPtr));
typedef Wt::Dbo ::collection<Wt::Dbo ::ptr<User>> UserList;
Wt::Dbo ::Transaction t(m_session);
UserList users = m_session.find<User>().where("name like ?").bind("%" + m_limitEdit->text() + "%").orderBy("name");
for (auto user : users) {
Wt::WText *t = list->addWidget(std::make_unique<Wt::WText>(user->name));
t->setStyleClass("link");
list->addWidget(std::make_unique<Wt::WBreak>());
t->clicked().connect(std::bind(&EditUsers::onUserClicked, this, user.id()));
}
if (!users.size()) list->addWidget(std::make_unique<Wt::WText>(tr("no-users-found")));
}
EditUser::EditUser(Wt::Dbo::Session &aSession) : WTemplate(tr("edit-user")), session_(aSession) {
auto roleButton = std::make_unique<Wt::WPushButton>();
roleButton_ = bindWidget("role-button", std::move(roleButton));
roleButton_->clicked().connect(this, &EditUser::switchRole);
}
void EditUser::switchUser(Wt::Dbo::ptr<User> target) {
target_ = target;
bindTemplate();
}
void EditUser::bindTemplate() {
bindString("username", target_->name);
if (target_->role == User::Admin)
roleButton_->setText(tr("demote-admin"));
else
roleButton_->setText(tr("promote-user"));
}
void EditUser::switchRole() {
Wt::Dbo::Transaction t(session_);
target_.reread();
if (target_->role == User::Admin)
target_.modify()->role = User::Visitor;
else
target_.modify()->role = User::Admin;
t.commit();
bindTemplate();
}

View File

@ -1,35 +0,0 @@
#ifndef __EDITUSERS_H__
#define __EDITUSERS_H__
#include "model/User.h"
#include <Wt/Dbo/Types.h>
#include <Wt/WTemplate.h>
class EditUsers : public Wt::WTemplate {
public:
EditUsers(Wt::Dbo::Session &aSession, const std::string &basePath);
private:
void onUserClicked(Wt::Dbo::dbo_traits<User>::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<User> target);
private:
void bindTemplate();
void switchRole();
Wt::Dbo::Session &session_;
Wt::Dbo::ptr<User> target_;
Wt::WPushButton *roleButton_;
};
#endif // __EDITUSERS_H__

View File

@ -1,188 +0,0 @@
#include "PostView.h"
#include "CommentView.h"
#include "model/BlogSession.h"
#include "model/User.h"
#include "model/asciidoc.h"
#include <Wt/WAnchor.h>
#include <Wt/WLineEdit.h>
#include <Wt/WLink.h>
#include <Wt/WPushButton.h>
#include <Wt/WText.h>
#include <Wt/WTextArea.h>
PostView::PostView(BlogSession &session, const std::string &basePath, Wt::Dbo::ptr<Post> post, RenderType type)
: session_(session), basePath_(basePath), post_(post) {
viewType_ = Brief;
render(type);
}
void PostView::render(RenderType type) {
if (type != Edit) viewType_ = type;
clear();
switch (type) {
case Detail: {
setTemplateText(tr("blog-post"));
session_.commentsChanged().connect(this, &PostView::updateCommentCount);
commentCount_ = bindWidget("comment-count", std::make_unique<Wt::WText>(post_->commentCount()));
bindWidget("comments", std::make_unique<CommentView>(session_, post_->rootComment()));
bindString("anchor", basePath_ + post_->permaLink());
break;
}
case Brief: {
setTemplateText(tr("blog-post-brief"));
auto titleAnchor = std::make_unique<Wt::WAnchor>(
Wt::WLink(Wt::LinkType::InternalPath, basePath_ + post_->permaLink()), post_->title);
bindWidget("title", std::move(titleAnchor));
if (!post_->briefSrc.empty()) {
auto moreAnchor = std::make_unique<Wt::WAnchor>(
Wt::WLink(Wt::LinkType::InternalPath, basePath_ + post_->permaLink() + "/more"), tr("blog-read-more"));
bindWidget("read-more", std::move(moreAnchor));
} else {
bindString("read-more", Wt::WString::Empty);
}
auto commentsAnchor = std::make_unique<Wt::WAnchor>(
Wt::WLink(Wt::LinkType::InternalPath, basePath_ + post_->permaLink() + "/comments"));
commentCount_ = commentsAnchor->addWidget(std::make_unique<Wt::WText>("(" + post_->commentCount() + ")"));
bindWidget("comment-count", std::move(commentsAnchor));
break;
}
case Edit: {
setTemplateText(tr("blog-post-edit"));
titleEdit_ = bindWidget("title-edit", std::make_unique<Wt::WLineEdit>(post_->title));
briefEdit_ = bindWidget("brief-edit", std::make_unique<Wt::WTextArea>(post_->briefSrc));
bodyEdit_ = bindWidget("body-edit", std::make_unique<Wt::WTextArea>(post_->bodySrc));
auto saveButton = bindWidget("save", std::make_unique<Wt::WPushButton>(tr("save")));
auto cancelButton = bindWidget("cancel", std::make_unique<Wt::WPushButton>(tr("cancel")));
saveButton->clicked().connect(this, &PostView::saveEdit);
cancelButton->clicked().connect(this, &PostView::showView);
break;
}
}
if (type == Detail || type == Brief) {
if (session_.user() == post_->author) {
std::unique_ptr<Wt::WPushButton> publishButton;
if (post_->state != Post::Published) {
publishButton = std::make_unique<Wt::WPushButton>(tr("publish"));
publishButton->clicked().connect(this, &PostView::publish);
} else {
publishButton = std::make_unique<Wt::WPushButton>(tr("retract"));
publishButton->clicked().connect(this, &PostView::retract);
}
bindWidget("publish", std::move(publishButton));
auto editButton(std::make_unique<Wt::WPushButton>(tr("edit")));
editButton->clicked().connect(this, &PostView::showEdit);
bindWidget("edit", std::move(editButton));
auto deleteButton(std::make_unique<Wt::WPushButton>(tr("delete")));
deleteButton->clicked().connect(this, &PostView::rm);
bindWidget("delete", std::move(deleteButton));
} else {
bindString("publish", Wt::WString::Empty);
bindString("edit", Wt::WString::Empty);
bindString("delete", Wt::WString::Empty);
}
}
auto postAnchor = std::make_unique<Wt::WAnchor>(
Wt::WLink(Wt::LinkType::InternalPath, basePath_ + "author/" + post_->author->name.toUTF8()),
post_->author->name);
bindWidget("author", std::move(postAnchor));
}
void PostView::publish() {
setState(Post::Published);
}
void PostView::showEdit() {
Wt::Dbo::Transaction t(session_);
render(Edit);
t.commit();
}
void PostView::rm() {
Wt::Dbo::Transaction t(session_);
post_.remove();
t.commit();
this->removeFromParent();
}
void PostView::retract() {
setState(Post::Unpublished);
}
void PostView::setState(Post::State state) {
Wt::Dbo::Transaction t(session_);
post_.modify()->state = state;
if (state == Post::Published) post_.modify()->date = Wt::WDateTime::currentDateTime();
render(viewType_);
t.commit();
}
void PostView::saveEdit() {
Wt::Dbo::Transaction t(session_);
bool newPost = post_.id() == -1;
Post *post = post_.modify();
post->title = titleEdit_->text();
post->briefSrc = briefEdit_->text();
post->bodySrc = bodyEdit_->text();
post->briefHtml = asciidoc(post->briefSrc);
post->bodyHtml = asciidoc(post->bodySrc);
if (newPost) {
session_.add(post_);
post->date = Wt::WDateTime::currentDateTime();
post->state = Post::Unpublished;
post->author = session_.user();
Wt::Dbo::ptr<Comment> rootComment = session_.add(std::make_unique<Comment>());
rootComment.modify()->post = post_;
}
session_.flush();
render(viewType_);
t.commit();
}
void PostView::showView() {
if (post_.id() == -1)
this->removeFromParent();
else {
Wt::Dbo::Transaction t(session_);
render(viewType_);
t.commit();
}
}
void PostView::updateCommentCount(Wt::Dbo::ptr<Comment> comment) {
if (comment->post == post_) {
std::string count = comment->post->commentCount();
if (commentCount_->text().toUTF8()[0] == '(')
commentCount_->setText("(" + count + ")");
else
commentCount_->setText(count);
}
}

View File

@ -1,40 +0,0 @@
#ifndef __POSTVIEW_H__
#define __POSTVIEW_H__
#include "model/Post.h"
#include <Wt/WTemplate.h>
class BlogSession;
class PostView : public Wt::WTemplate {
public:
enum RenderType {
Brief,
Detail,
Edit,
};
PostView(BlogSession &session, const std::string &basePath, Wt::Dbo::ptr<Post> post, RenderType type);
protected:
void render(RenderType type);
void updateCommentCount(Wt::Dbo::ptr<Comment> comment);
void saveEdit();
void showView();
void publish();
void retract();
void setState(Post::State state);
void showEdit();
void rm();
private:
BlogSession &session_;
std::string basePath_;
Wt::Dbo::ptr<Post> post_;
RenderType viewType_;
Wt::WText *commentCount_;
Wt::WLineEdit *titleEdit_;
Wt::WTextArea *briefEdit_, *bodyEdit_;
};
#endif // __POSTVIEW_H__