diff --git a/TODO b/TODO index b7e27b7c..4548ddab 100644 --- a/TODO +++ b/TODO @@ -10,7 +10,7 @@ [Users] - Handle login lifetime? -- Disable multi login? +- Limit multi login from UI and remote interface (limit per interface is OK) [Cover] - Scaling: find something more "reliable" than GIL and its customs extensions (adobe work, io_new)? @@ -25,15 +25,20 @@ - Use size limits for strings (artist, release, genre)? - Process only files whose extensions are well known in audio/video world (avoid useless parsing/errors) +[Transcode] +- some zombies seem to be remaining after a week of use + [UI] [Settings] + - logout users that are being changed (loss of admin admin rights), or make sure they are still admin when they make changes + - "signal not exposed" problem if a user logout and login again. bad resource destruction? [admin/Users] - Add/remove users - max audio/video bitrates for user - capabilities (admin) [admin/DB] - - Add path to Audio/Video files - - Do not make the update start time active when update perdiod is "Never" + - Add a dedicated Menu to add/delete and view the Media pathes (see Users for the idea) + - Do not make the update start time field active when update perdiod is "Never" - Do not restart the db update service if user applied no changes - Uncheck the "Request immediate scan" once setins are applied [user/transcoding] @@ -47,7 +52,7 @@ - TrackView : handle original release date - TrackView : handle duration > 1 hour - TrackView : select only relevant columns to speed up queries (do not get eveything) - - TrackView : Reselect the current playing song when displaying the updated search results + - TrackView : Reselect the current selected item when displaying the updated search results - ReleaseView: display the release's publication year - OGG metadata -> properly handle metadata nested in the audio stream - TrackView : when udating the view, reselect the current playing track diff --git a/database/DatabaseHandler.cpp b/database/DatabaseHandler.cpp index 262a5bc7..693119af 100644 --- a/database/DatabaseHandler.cpp +++ b/database/DatabaseHandler.cpp @@ -103,6 +103,12 @@ Handler::getCurrentUser() User::pointer Handler::getUser(const Wt::Auth::User& authUser) { + + if (!authUser.isValid()) { + std::cerr << "Handler::getUser: invalid authUser" << std::endl; + return User::pointer(); + } + Wt::Dbo::ptr authInfo = _users->find(authUser); User::pointer user = authInfo->user(); diff --git a/database/DatabaseHandler.hpp b/database/DatabaseHandler.hpp index 55a37301..1b49a5ee 100644 --- a/database/DatabaseHandler.hpp +++ b/database/DatabaseHandler.hpp @@ -15,7 +15,7 @@ namespace Database { typedef Wt::Auth::Dbo::UserDatabase UserDatabase; -// Session living class handling the database +// Session living class handling the database and the login class Handler { public: diff --git a/database/User.cpp b/database/User.cpp index 7df00512..a1697f8a 100644 --- a/database/User.cpp +++ b/database/User.cpp @@ -21,6 +21,11 @@ User::getAll(Wt::Dbo::Session& session) return std::vector(res.begin(), res.end()); } +User::pointer +User::getById(Wt::Dbo::Session& session, std::string id) +{ + return session.find().where("id = ?").bind( id ); +} } // namespace Database diff --git a/database/User.hpp b/database/User.hpp index 9288ff46..10a167bf 100644 --- a/database/User.hpp +++ b/database/User.hpp @@ -12,15 +12,25 @@ typedef Wt::Auth::Dbo::AuthInfo AuthInfo; class User { public: + static const std::size_t MaxNameLength = 15; User(); typedef Wt::Dbo::ptr pointer; // accessors + static pointer getById(Wt::Dbo::Session& session, std::string id); static std::vector getAll(Wt::Dbo::Session& session); + // write + void setAdmin(bool admin) { _isAdmin = admin; } + void setMaxAudioBitrate(std::size_t bitrate) { _maxAudioBitrate = bitrate; } + void setMaxVideoBitrate(std::size_t bitrate) { _maxVideoBitrate = bitrate; } + + // read bool isAdmin() const {return _isAdmin;} + std::size_t getMaxAudioBitrate() const { return _maxAudioBitrate; } + std::size_t getMaxVideoBitrate() const { return _maxVideoBitrate; } template void persist(Action& a) diff --git a/ui/approot/templates.xml b/ui/approot/templates.xml index 9c972819..160ea844 100644 --- a/ui/approot/templates.xml +++ b/ui/approot/templates.xml @@ -2,6 +2,104 @@ + + ${title} +
+
+ +
+ ${name} +
+
+ ${name-info} +
+
+
+ +
+ ${email} +
+
+ ${email-info} +
+
+
+ +
+ ${password} +
+
+ ${password-info} +
+
+
+ +
+ ${password-confirm} +
+
+ ${password-confirm-info} +
+
+
+ Access +
+
+ +
+ ${admin} +
+
+ ${admin-info} +
+
+
+ +
+
+ ${audio-bitrate-limit} + kbps +
+
+
+ ${audio-bitrate-limit-info} +
+
+
+ +
+
+ ${video-bitrate-limit} + kbps +
+
+
+ ${video-bitrate-limit-info} +
+
+
+
+ ${save-button} ${cancel-button} +
+
+
+ +
+ ${title}
diff --git a/ui/common/EmailValidator.hpp b/ui/common/EmailValidator.hpp new file mode 100644 index 00000000..aaf92a22 --- /dev/null +++ b/ui/common/EmailValidator.hpp @@ -0,0 +1,10 @@ +#include + +namespace UserInterface { + +static inline Wt::WValidator *createEmailValidator() +{ + return new Wt::WRegExpValidator("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}"); +} + +} // namespace UserInterface diff --git a/ui/settings/SettingsDatabaseFormView.cpp b/ui/settings/SettingsDatabaseFormView.cpp index 58c68498..f475e3f4 100644 --- a/ui/settings/SettingsDatabaseFormView.cpp +++ b/ui/settings/SettingsDatabaseFormView.cpp @@ -40,10 +40,10 @@ class DatabaseFormModel : public Wt::WFormModel addField(UpdateStartTimeField); addField(UpdateRequestImmediateField); - setValidator(PathField, createPathValidator(PathField)); - setValidator(UpdatePeriodField, createUpdatePeriodValidator(UpdatePeriodField)); - setValidator(UpdateStartTimeField, createStartTimeValidator(UpdateStartTimeField)); - setValidator(UpdateRequestImmediateField, createRequestImmediateFieldValidator(UpdateRequestImmediateField)); + setValidator(PathField, createPathValidator()); + setValidator(UpdatePeriodField, createUpdatePeriodValidator()); + setValidator(UpdateStartTimeField, createStartTimeValidator()); + setValidator(UpdateRequestImmediateField, createRequestImmediateFieldValidator()); // populate the model with initial data loadData(); @@ -206,25 +206,25 @@ class DatabaseFormModel : public Wt::WFormModel } - Wt::WValidator *createPathValidator(const std::string& field) { + Wt::WValidator *createPathValidator() { DirectoryValidator* v = new DirectoryValidator(); v->setMandatory(true); return v; } - Wt::WValidator *createUpdatePeriodValidator(const std::string& field) { + Wt::WValidator *createUpdatePeriodValidator() { Wt::WValidator* v = new Wt::WValidator(); v->setMandatory(true); return v; } - Wt::WValidator *createStartTimeValidator(const std::string& field) { + Wt::WValidator *createStartTimeValidator() { Wt::WValidator* v = new Wt::WValidator(); v->setMandatory(true); return v; } - Wt::WValidator *createRequestImmediateFieldValidator(const std::string& field) { + Wt::WValidator *createRequestImmediateFieldValidator() { Wt::WValidator* v = new Wt::WValidator(); return v; } diff --git a/ui/settings/SettingsDatabaseFormView.hpp b/ui/settings/SettingsDatabaseFormView.hpp index fcedc0a4..75255c17 100644 --- a/ui/settings/SettingsDatabaseFormView.hpp +++ b/ui/settings/SettingsDatabaseFormView.hpp @@ -18,6 +18,7 @@ class DatabaseFormView : public Wt::WTemplateFormView DatabaseFormView(SessionData& sessionData, Wt::WContainerWidget *parent = 0); private: + void processSave(); void processDiscard(); diff --git a/ui/settings/SettingsUserFormView.cpp b/ui/settings/SettingsUserFormView.cpp new file mode 100644 index 00000000..221e9b7b --- /dev/null +++ b/ui/settings/SettingsUserFormView.cpp @@ -0,0 +1,451 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/EmailValidator.hpp" + +#include "SettingsUserFormView.hpp" + +namespace UserInterface { +namespace Settings { + +class UserFormModel : public Wt::WFormModel +{ + public: + + // Associate each field with a unique string literal. + static const Field NameField; + static const Field EmailField; + static const Field PasswordField; + static const Field PasswordConfirmField; + static const Field AdminField; + static const Field AudioBitrateLimitField; + static const Field VideoBitrateLimitField; + + UserFormModel(SessionData& sessionData, std::string userId, Wt::WObject *parent = 0) + : Wt::WFormModel(parent), + _sessionData(sessionData), + _userId(userId) + { + initializeModels(); + + addField(NameField); + addField(EmailField); + addField(PasswordField); + addField(PasswordConfirmField); + addField(AdminField); + addField(AudioBitrateLimitField); + addField(VideoBitrateLimitField); + + setValidator(NameField, createNameValidator()); + Wt::WValidator *emailValidator = createEmailValidator(); + emailValidator->setMandatory(true); + setValidator(EmailField, emailValidator); + // If creating a user, passwords are mandatory + if (_userId.empty()) + { + setValidator(PasswordField, new Wt::WValidator(true)); // mandatory + setValidator(PasswordConfirmField, new Wt::WValidator(true)); // mandatory + } + setValidator(AudioBitrateLimitField, new Wt::WValidator(true)); // mandatory + setValidator(VideoBitrateLimitField, new Wt::WValidator(true)); // mandatory + + // populate the model with initial data + loadData(userId); + } + + Wt::WAbstractItemModel *audioBitrateModel() { return _audioBitrateModel; } + Wt::WAbstractItemModel *videoBitrateModel() { return _videoBitrateModel; } + + void loadData(std::string userId) + { + + if (!userId.empty()) + { + Database::Handler& db = _sessionData.getDatabaseHandler(); + + Wt::Dbo::Transaction transaction(db.getSession()); + + Wt::Auth::User authUser = db.getUserDatabase().findWithId( userId ); + Database::User::pointer user = db.getUser(authUser); + + Wt::Auth::User currentUser = _sessionData.getDatabaseHandler().getLogin().user(); + + if (user && user->isAdmin()) + { + setValue(AdminField, true); + + // We can cannot remove admin rights to ourselves + if (currentUser == authUser) + setReadOnly(AdminField, true); + + // if the user is admin, no need to limit it + setReadOnly(AudioBitrateLimitField, true); + setValidator(AudioBitrateLimitField, nullptr); + + setReadOnly(VideoBitrateLimitField, true); + setValidator(VideoBitrateLimitField, nullptr); + } + + + if (user && authUser.isValid()) + { + setValue(NameField, authUser.identity(Wt::Auth::Identity::LoginName)); + if (!authUser.email().empty()) + setValue(EmailField, authUser.email()); + else + setValue(EmailField, authUser.unverifiedEmail()); + } + + if (user) + { + setValue(AudioBitrateLimitField, user->getMaxAudioBitrate() / 1000); // in kbps + setValue(VideoBitrateLimitField, user->getMaxVideoBitrate() / 1000); // in kbps + } + + } + } + + bool saveData() + { + // DBO transaction active here + try { + Database::Handler& db = _sessionData.getDatabaseHandler(); + Wt::Dbo::Transaction transaction(db.getSession()); + + if (_userId.empty()) + { + // Create user + Wt::Auth::User authUser = db.getUserDatabase().registerNew(); + Database::User::pointer user = db.getUser(authUser); + + // Account + authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); + authUser.setEmail(valueText(EmailField).toUTF8()); + db.getPasswordService().updatePassword(authUser, valueText(PasswordField)); + + // Access + { + boost::any v = value(AdminField); + if (!v.empty() && boost::any_cast(v) == true) + user.modify()->setAdmin( true ); + else + user.modify()->setAdmin( false ); + } + + if (!isReadOnly(AudioBitrateLimitField)) + user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps + + if (!isReadOnly(VideoBitrateLimitField)) + user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps + + transaction.commit(); + } + else + { + + // Update user + Wt::Auth::User authUser = db.getUserDatabase().findWithId(_userId); + Database::User::pointer user = db.getUser( authUser ); + + // user may have been deleted by someone else + if (!authUser.isValid()) { + std::cerr << "user identity does not exist!" << std::endl; + return false; + } + else if(!user) + { + std::cerr << "User not found!" << std::endl; + return false; + } + + // Account + authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); + authUser.setEmail(valueText(EmailField).toUTF8()); + + // Password + if (!valueText(PasswordField).empty()) + db.getPasswordService().updatePassword(authUser, valueText(PasswordField)); + + // Access + if (!isReadOnly(AdminField)) + { + boost::any v = value(AdminField); + if (!v.empty() && boost::any_cast(v) == true) + user.modify()->setAdmin( true ); + else + user.modify()->setAdmin( false ); + } + + if (!isReadOnly(AudioBitrateLimitField)) + user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps + + if (!isReadOnly(VideoBitrateLimitField)) + user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps + + transaction.commit(); + } + } + catch(Wt::Dbo::Exception& exception) + { + std::cerr << "Dbo exception: " << exception.what() << std::endl; + return false; + } + + return true; + } + + bool validateField(Field field) + { + // DBO transaction active here + + Wt::WString error; + + if (field == NameField) + { + Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession()); + // Must be unique since used as LoginIdentity + Wt::Auth::User user = _sessionData.getDatabaseHandler().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field)); + if (user.isValid() && user.id() != _userId) + error = "Already exists"; + else + return Wt::WFormModel::validateField(field); + } + else if (field == PasswordField) + { + // Password is mandatory if we create the user + if (!valueText(PasswordField).empty()) + { + // Evaluate the strength of the password + Wt::Auth::PasswordStrengthValidator validator; + + // Reduce some constraints... + validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::TwoCharClass, 11); + validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::ThreeCharClass, 8 ); + validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::FourCharClass, 6 ); + + Wt::Auth::AbstractPasswordService::StrengthValidatorResult res + = validator.evaluateStrength(valueText(PasswordField), + valueText(NameField), + valueText(EmailField).toUTF8()); + + if (!res.isValid()) + error = res.message(); + } + else + return Wt::WFormModel::validateField(field); + } + else if (field == PasswordConfirmField) + { + if (validation(PasswordField).state() == Wt::WValidator::Valid) + { + if (valueText(PasswordField) != valueText(PasswordConfirmField)) + error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); + } + } + else + { + // Apply validators + return Wt::WFormModel::validateField(field); + } + + setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); + + return validation(field).state() == Wt::WValidator::Valid; + } + + + private: + + static Wt::WValidator *createNameValidator() { + Wt::WLengthValidator *v = new Wt::WLengthValidator(); + v->setMandatory(true); + v->setMinimumLength(3); + v->setMaximumLength(::Database::User::MaxNameLength); + return v; + } + + void initializeModels() + { + + // AUDIO + // TODO move defaults somewhere else? + static const std::vector + audioBitrateLimits = + { + 64, + 96, + 128, + 160, + 192, + 224, + 256, + 320, + 512 + }; + + _audioBitrateModel = new Wt::WStringListModel(); + for (std::size_t i = 0; i < audioBitrateLimits.size(); ++i) + _audioBitrateModel->addString( Wt::WString("{1}").arg( audioBitrateLimits[i] ) ); + + + // VIDEO + // TODO move defaults somewhere else? + static const std::vector + videoBitrateLimits = + { + 256, + 512, + 1024, + 2048, + 4096, + 8192 + }; + + _videoBitrateModel = new Wt::WStringListModel(); + for (std::size_t i = 0; i < videoBitrateLimits.size(); ++i) + _videoBitrateModel->addString( Wt::WString("{1}").arg( videoBitrateLimits[i] ) ); + + } + + SessionData& _sessionData; + std::string _userId; + Wt::WStringListModel* _audioBitrateModel; + Wt::WStringListModel* _videoBitrateModel; +}; + +const Wt::WFormModel::Field UserFormModel::NameField = "name"; +const Wt::WFormModel::Field UserFormModel::EmailField = "email"; +const Wt::WFormModel::Field UserFormModel::PasswordField = "password"; +const Wt::WFormModel::Field UserFormModel::PasswordConfirmField = "password-confirm"; +const Wt::WFormModel::Field UserFormModel::AdminField = "admin"; +const Wt::WFormModel::Field UserFormModel::AudioBitrateLimitField = "audio-bitrate-limit"; +const Wt::WFormModel::Field UserFormModel::VideoBitrateLimitField = "video-bitrate-limit"; + + +UserFormView::UserFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent) +: Wt::WTemplateFormView(parent), + _sessionData(sessionData) +{ + + _model = new UserFormModel(sessionData, userId, this); + + setTemplateText(tr("userForm-template")); + addFunction("id", &WTemplate::Functions::id); + addFunction("block", &WTemplate::Functions::id); + + // Name + setFormWidget(UserFormModel::NameField, new Wt::WLineEdit()); + + // Email + setFormWidget(UserFormModel::EmailField, new Wt::WLineEdit()); + + // Password + Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); + setFormWidget(UserFormModel::PasswordField, passwordEdit ); + passwordEdit->setEchoMode(Wt::WLineEdit::Password); + + // Password confirmation + Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); + setFormWidget(UserFormModel::PasswordConfirmField, passwordConfirmEdit); + passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); + + // Admin Field + Wt::WCheckBox *admin = new Wt::WCheckBox(); + setFormWidget(UserFormModel::AdminField, admin); + + // AudioBitrate + Wt::WComboBox *audioBitrateCB = new Wt::WComboBox(); + setFormWidget(UserFormModel::AudioBitrateLimitField, audioBitrateCB); + audioBitrateCB->setStyleClass("span2"); + audioBitrateCB->setModel(_model->audioBitrateModel()); + + // VideoBitrate + Wt::WComboBox *videoBitrateCB = new Wt::WComboBox(); + setFormWidget(UserFormModel::VideoBitrateLimitField, videoBitrateCB); + videoBitrateCB->setStyleClass("span2"); + videoBitrateCB->setModel(_model->videoBitrateModel()); + + // Title & Buttons + Wt::WString title; + if (userId.empty()) { + title = Wt::WString("Create user"); + } + else { + Database::Handler &db = sessionData.getDatabaseHandler(); + Wt::Dbo::Transaction transaction (db.getSession()); + Wt::Auth::User authUser = db.getUserDatabase().findWithId( userId ); + + Wt::WString userName; + if (authUser.isValid()) + userName = authUser.identity(Wt::Auth::Identity::LoginName); + else + ; // TODO display user deleted and close the widget + + title = Wt::WString("Edit user {1}").arg(userName); + } + + bindString("title", title); + + Wt::WPushButton *saveButton = new Wt::WPushButton(); + if (userId.empty()) { + saveButton->setText("Create user"); + saveButton->setStyleClass("btn-success"); + } + else + { + saveButton->setText("Save"); + saveButton->setStyleClass("btn-primary"); + } + bindWidget("save-button", saveButton); + saveButton->clicked().connect(this, &UserFormView::processSave); + + Wt::WPushButton *cancelButton = new Wt::WPushButton("Cancel"); + bindWidget("cancel-button", cancelButton); + cancelButton->clicked().connect(this, &UserFormView::processCancel); + + updateView(_model); + +} + +void +UserFormView::processCancel() +{ + // parent widget will delete this widget + completed().emit(false); +} + +void +UserFormView::processSave() +{ + updateModel(_model); + + + if (_model->validate()) + { + // commit model into DB + if (_model->saveData() ) + { + // parent widget will delete this widget + completed().emit(true); + } + // else TODO display a nice error message + } + else + { + updateView(_model); + } +} + +} // namespace Settings +} // namespace UserInterface diff --git a/ui/settings/SettingsUserFormView.hpp b/ui/settings/SettingsUserFormView.hpp new file mode 100644 index 00000000..c66d8bd5 --- /dev/null +++ b/ui/settings/SettingsUserFormView.hpp @@ -0,0 +1,39 @@ +#ifndef UI_SETTINGS_USER_FORM_VIEW_HPP +#define UI_SETTINGS_USER_FORM_VIEW_HPP + +#include +#include +#include + +#include "common/SessionData.hpp" + +namespace UserInterface { +namespace Settings { + +class UserFormModel; + +class UserFormView : public Wt::WTemplateFormView +{ + public: + UserFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent = 0); + + // Signal emitted once the form is completed + Wt::Signal& completed() { return _sigCompleted; } + + private: + + Wt::Signal _sigCompleted; + + SessionData& _sessionData; + + void processSave(); + void processCancel(); + + UserFormModel* _model; + +}; + +} // namespace Settings +} // namespace UserInterface + +#endif diff --git a/ui/settings/SettingsUsers.cpp b/ui/settings/SettingsUsers.cpp index 8f123a73..4f252ecf 100644 --- a/ui/settings/SettingsUsers.cpp +++ b/ui/settings/SettingsUsers.cpp @@ -6,6 +6,8 @@ #include +#include "SettingsUserFormView.hpp" + #include "SettingsUsers.hpp" namespace UserInterface { @@ -39,12 +41,7 @@ _sessionData(sessionData) Wt::WPushButton* addBtn = new Wt::WPushButton("Add User"); addBtn->setStyleClass("btn-success"); container->addWidget( addBtn ); - addBtn->clicked().connect(this, &Users::handleAddUser); - } - - // 2/ the user form - { - new Wt::WText("This is the user form!", _stack); + addBtn->clicked().connect(boost::bind(&Users::handleCreateUser, this, "")); } refresh(); @@ -55,7 +52,7 @@ Users::refresh(void) { assert(_table->rowCount() > 0); - for (int i = _table->rowCount() - 1; i > 0; ++i) + for (int i = _table->rowCount() - 1; i > 0; --i) _table->deleteRow(i); Database::Handler& db = _sessionData.getDatabaseHandler(); @@ -66,6 +63,7 @@ Users::refresh(void) std::vector users = Database::User::getAll(db.getSession()); + std::size_t userIndex = 1; for (std::size_t i = 0; i < users.size(); ++i) { @@ -75,10 +73,25 @@ Users::refresh(void) userId = oss.str(); } - Wt::Auth::User authUser = db.getUserDatabase().findWithId( userId ); + Wt::Auth::User authUser; - _table->elementAt(i + 1, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(i+1))); - _table->elementAt(i + 1, 1)->addWidget(new Wt::WText( authUser.identity(Wt::Auth::Identity::LoginName)) ); + // Hack try/catch here since it may fail! + try { + authUser = db.getUserDatabase().findWithId( userId ); + } + catch(Wt::Dbo::Exception& e) + { + std::cerr << "Caught exception when getting userId=" << userId << ": " << e.code() << std::endl; + continue; + } + + if (!authUser.isValid()) { + std::cerr << "Users::refresh: skipping invalid userId = " << userId << std::endl; + continue; + } + + _table->elementAt(userIndex, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(userIndex))); + _table->elementAt(userIndex, 1)->addWidget(new Wt::WText( authUser.identity(Wt::Auth::Identity::LoginName)) ); Wt::WText* email = new Wt::WText(); if (!authUser.email().empty()) { @@ -88,21 +101,23 @@ Users::refresh(void) email->setStyleClass("alert-danger"); email->setText(authUser.unverifiedEmail()); } - _table->elementAt(i + 1, 2)->addWidget( email ) ; - _table->elementAt(i + 1, 3)->addWidget(new Wt::WText( users[i]->isAdmin() ? "Yes" : "No" )); + _table->elementAt(userIndex, 2)->addWidget( email ) ; + _table->elementAt(userIndex, 3)->addWidget(new Wt::WText( users[i]->isAdmin() ? "Yes" : "No" )); Wt::WPushButton* editBtn = new Wt::WPushButton("Edit"); - _table->elementAt(i + 1, 4)->addWidget(editBtn); - editBtn->clicked().connect(boost::bind( &Users::handleEditUser, this, userId)); + _table->elementAt(userIndex, 4)->addWidget(editBtn); + editBtn->clicked().connect(boost::bind( &Users::handleCreateUser, this, userId)); if (currentUser != authUser) { Wt::WPushButton* delBtn = new Wt::WPushButton("Delete"); delBtn->setStyleClass("btn-danger"); delBtn->setMargin(5, Wt::Left); - _table->elementAt(i + 1, 4)->addWidget(delBtn); + _table->elementAt(userIndex, 4)->addWidget(delBtn); delBtn->clicked().connect(boost::bind( &Users::handleDelUser, this, authUser.identity(Wt::Auth::Identity::LoginName), userId)); } + + ++userIndex; } } @@ -122,36 +137,51 @@ Users::handleDelUser(Wt::WString loginNameIdentity, std::string id) { Database::Handler& db = _sessionData.getDatabaseHandler(); + Wt::Dbo::Transaction transaction(db.getSession()); + // Delete the user Wt::Auth::User authUser = db.getUserDatabase().findWithId( id ); db.getUserDatabase().deleteUser( authUser ); - // TODO remove Database::User too? + Database::User::pointer user = Database::User::getById(db.getSession(), id); + if (user) + user.remove(); - // TODO Update the view + refresh(); } delete messageBox; })); messageBox->show(); + } void -Users::handleAddUser(void) +Users::handleCreateUser(std::string id) { - // TODO refresh the User Form for a new user - //_stack->setCurrentIndex(1); + assert(_stack->count() == 1); + + UserFormView* userFormView = new UserFormView(_sessionData, id, _stack); + userFormView->completed().connect(this, &Users::handleUserFormCompleted); + + _stack->setCurrentIndex(1); } void -Users::handleEditUser(std::string id) +Users::handleUserFormCompleted(bool changed) { - //TODO refresh the User Form to edit the given user - //_stack->setCurrentIndex(1); -} + _stack->setCurrentIndex(0); + // Refresh the user table if a change has been made + if (changed) + refresh(); + + // Delete the form view + delete _stack->widget(1); + +} } // namespace UserInterface } // namespace Settings diff --git a/ui/settings/SettingsUsers.hpp b/ui/settings/SettingsUsers.hpp index a096c679..306b1f6e 100644 --- a/ui/settings/SettingsUsers.hpp +++ b/ui/settings/SettingsUsers.hpp @@ -19,9 +19,10 @@ class Users : public Wt::WContainerWidget private: + void handleUserFormCompleted(bool changed); + void handleDelUser(Wt::WString loginNameIdentity, std::string id); - void handleEditUser(std::string id); - void handleAddUser(void); + void handleCreateUser(std::string id); // set the id in order to edit the user SessionData& _sessionData;