Merge pull request #56 from gradido/stage0-update_user_search

Add Feature in user search old frontend because Support has requested the feature long ago
This commit is contained in:
Ulf Gebhardt 2021-03-23 17:47:51 +01:00 committed by GitHub
commit 483a68078b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1698 additions and 1498 deletions

View File

@ -55,7 +55,7 @@ class JsonRequestClientComponent extends Component
]), '/getRunningUserTasks');
}
public function getUsers($session_id, $searchString)
public function getUsers($session_id, $searchString, $accountState)
{
if($searchString == "") {
return ['state' => 'error', 'type' => 'parameter error', 'msg' => 'search string is empty'];
@ -66,7 +66,8 @@ class JsonRequestClientComponent extends Component
return $this->sendRequest(json_encode([
'session_id' => $session_id,
'search' => $searchString
'search' => $searchString,
'account_state' => $accountState,
]), '/getUsers');
}

View File

@ -99,9 +99,14 @@ class StateUsersController extends AppController
//$this->set('timeUsed', $timeUsed);
$csfr_token = $this->request->getParam('_csrfToken');
$this->set(compact('timeUsed', 'searchForm', 'csfr_token'));
$empty_string = '... empty ...';
if ($this->request->is('post')) {
$finalUserEntrys = [];
$requestData = $this->request->getData();
$account_state = $requestData['account_state'];
if($requestData['search'] == '' && $account_state != 'all') {
$requestData['search'] = $empty_string;
}
if ($searchForm->validate($requestData)) {
//var_dump($requestData);
@ -111,7 +116,7 @@ class StateUsersController extends AppController
$searchType = 'email';
}
// find users on login server
$resultJson = $this->JsonRequestClient->getUsers($session->read('session_id'), $searchString);
$resultJson = $this->JsonRequestClient->getUsers($session->read('session_id'), $searchString, $account_state);
$loginServerUser = [];
if ($resultJson['state'] == 'success') {
$dataJson = $resultJson['data'];
@ -139,28 +144,42 @@ class StateUsersController extends AppController
}
}
// find user on community server db
$globalSearch = '%' . $searchString . '%';
$communityUsers = $this->StateUsers
->find('all')
->contain(['StateBalances' => ['fields' => ['amount', 'state_user_id']]]);
$communityUsers->where(['OR' => [
'first_name LIKE' => $globalSearch,
'last_name LIKE' => $globalSearch,
//'username LIKE' => $globalSearch,
'email LIKE' => $globalSearch
]]);
//var_dump($communityUsers->toArray());
foreach ($communityUsers as $u) {
$pubkey_hex = bin2hex(stream_get_contents($u->public_key));
$u->public_hex = $pubkey_hex;
if (!isset($pubkeySorted[$pubkey_hex])) {
$pubkeySorted[$pubkey_hex] = ['login' => [], 'community' => []];
if($account_state == 'email not activated') {
if(count($pubkeySorted) > 0) {
$communityUsers->where(['hex(public_key) IN' => array_keys($pubkeySorted)]);
} else {
$communityUsers = null;
}
array_push($pubkeySorted[$pubkey_hex]['community'], $u);
} else {
$globalSearch = '%' . $searchString . '%';
$communityUsers->where(['OR' => [
'first_name LIKE' => $globalSearch,
'last_name LIKE' => $globalSearch,
//'username LIKE' => $globalSearch,
'email LIKE' => $globalSearch
]]);
}
$finalUserEntrys = [];
//var_dump($communityUsers->toArray());
if($communityUsers) {
foreach ($communityUsers as $u) {
$pubkey_hex = bin2hex(stream_get_contents($u->public_key));
$u->public_hex = $pubkey_hex;
if (!isset($pubkeySorted[$pubkey_hex])) {
$pubkeySorted[$pubkey_hex] = ['login' => [], 'community' => []];
}
array_push($pubkeySorted[$pubkey_hex]['community'], $u);
}
}
// detect states
foreach ($pubkeySorted as $pubhex => $user) {
$finalUser = [];

View File

@ -11,7 +11,9 @@ class UserSearchForm extends Form
protected function _buildSchema(Schema $schema)
{
return $schema->addField('search', ['type' => 'string']);
return $schema
->addField('search', ['type' => 'string'])
->addField('account_state', ['type' => 'select']);
}
function validationDefault(Validator $validator)

View File

@ -11,6 +11,19 @@ $this->assign('title', __('Benutzer suchen'));
$this->loadHelper('Form', [
'templates' => 'horizontal_form',
]);
$stateOptions = [
'all' => __('Alle'),
//'account created'=>__('Konto angelegt'),
//'account not on login-server' => __('Konto nicht auf Login-Server'),
//'email activated' => __('Konto aktiviert'),
//'account copied to community' => __('Konto auf Gemeinschafts-Server'),
'email not activated' => __('Konto nicht aktiviert'),
//'account multiple times on login-server' => __('Konto mehrfach vorhanden'),
//'account not on community server' => __('Konto nicht auf Gemeinschafts-Server'),
//'no keys' => __('Keine Schlüssel generiert')
];
?>
<?= $this->Html->css([
'loginServer/style.css',
@ -41,7 +54,8 @@ $this->loadHelper('Form', [
<p class="form-header">Benutzer suchen</p>
<div class="form-body">
<?= $this->Form->create($searchForm, []) ?>
<?= $this->Form->control('search', ['label' => __('Suchbegriff'), 'class' => 'form-control', 'id' => 'inlineFormInputGroup', 'placeholder' => __('Vorname/Nachname/E-Mail')]) ?>
<?= $this->Form->control('search', ['label' => __('Suchbegriff'), 'class' => 'form-control', 'id' => 'inlineFormInputGroup', 'placeholder' => __('Vorname/Nachname/E-Mail'), 'required' => false]) ?>
<?= $this->Form->control('account_state', ['label' => __('Konto Status'), 'class' => 'form-control', 'type' => 'select', 'options' => $stateOptions]) ?>
<?= $this->Form->button('<i class="material-icons-outlined">search</i>&nbsp;' . __('Suchen'), ['class' => 'form-button']) ?>
<?= $this->Form->hidden('order_row', ['id' => 'input-order-row']) ?>
</div>
@ -59,7 +73,7 @@ $this->loadHelper('Form', [
csfr_token = '<?= $csfr_token ?>';
</script>
<?= $this->Html->script('userSearch') ?>
<?= $this->Html->script('userSearch.min') ?>
<!-- npm run build im mithril client! -->
<!-- keybase://team/gradido/gradido_mithril_user_search -->

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -71,11 +71,15 @@ Poco::JSON::Object* JsonGetLogin::handle(Poco::Dynamic::Var params)
em->addError(new Error("JsonGetLogin::handle", "generic exception calling userModel->getJson: "));
em->sendErrorsAsEmail();
}
// use both variants for compatibility reasons, but final version is Transactions.pending
result->set("Transaction.pending", session->getProcessingTransactionCount());
result->set("Transactions.pending", session->getProcessingTransactionCount());
auto executing = observer->getTaskCount(userModel->getEmail(), TASK_OBSERVER_SIGN_TRANSACTION);
if (executing < 0) {
executing = 0;
}
// use both variants for compatibility reasons, but final version is Transactions.executing
result->set("Transaction.executing", executing);
result->set("Transactions.executing", executing);
//printf("pending: %d\n", session->getProcessingTransactionCount());
//std::string user_string = userModel->toString();

View File

@ -12,7 +12,8 @@ Poco::JSON::Object* JsonGetUsers::handle(Poco::Dynamic::Var params)
int session_id = 0;
std::string searchString;
std::string accountState = "";
static std::string emptySearchString = "... empty ...";
// if is json object
if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
Poco::JSON::Object::Ptr paramJsonObject = params.extract<Poco::JSON::Object::Ptr>();
@ -23,6 +24,9 @@ Poco::JSON::Object* JsonGetUsers::handle(Poco::Dynamic::Var params)
/// Throws InvalidAccessException if Var is empty.
try {
paramJsonObject->get("search").convert(searchString);
if (paramJsonObject->has("account_state")) {
paramJsonObject->get("account_state").convert(accountState);
}
paramJsonObject->get("session_id").convert(session_id);
}
catch (Poco::Exception& ex) {
@ -67,17 +71,20 @@ Poco::JSON::Object* JsonGetUsers::handle(Poco::Dynamic::Var params)
}
auto user = session->getNewUser();
if (searchString == emptySearchString) {
searchString = "";
}
if (user.isNull()) {
return customStateError("not found", "Session didn't contain user");
}
else if (searchString == "") {
return customStateError("not found", "Search string is empty");
else if (searchString == "" && (accountState == "" || accountState == "all")) {
return customStateError("not found", "Search string is empty and account_state is all or empty");
}
else if (user->getModel()->getRole() != model::table::ROLE_ADMIN) {
return customStateError("wrong role", "User hasn't correct role");
}
auto results = controller::User::search(searchString);
auto results = controller::User::search(searchString, accountState);
if (!results.size()) {
return stateSuccess();
}

View File

@ -92,6 +92,8 @@ int EmailManager::ThreadFunction()
if (mPendingEmails.empty()) return 0;
auto lm = LanguageManager::getInstance();
ErrorList errors;
static const char* function_name = "PrepareEmailTask";
Poco::Net::SecureSMTPClientSession mailClientSession(mEmailAccount.url, mEmailAccount.port);
mailClientSession.login();
@ -100,6 +102,7 @@ int EmailManager::ThreadFunction()
mailClientSession.login(Poco::Net::SMTPClientSession::AUTH_LOGIN, mEmailAccount.username, mEmailAccount.password);
}
catch (Poco::Net::SSLException& ex) {
errors.addError(new ParamError(function_name, "ssl certificate error", ex.displayText()));
printf("[PrepareEmailTask] ssl certificate error: %s\nPlease make sure you have cacert.pem (CA/root certificates) next to binary from https://curl.haxx.se/docs/caextract.html\n", ex.displayText().data());
return -1;
}
@ -151,6 +154,7 @@ int EmailManager::ThreadFunction()
else {
// error drafting email, shouldn't happend
printf("[EmailManager::ThreadFunction] Error drafting email\n");
errors.addError(new Error(function_name, "Error drafting email"));
}
delete email;
email = nullptr;

View File

@ -54,9 +54,15 @@ namespace controller {
Poco::AutoPtr<EmailVerificationCode> EmailVerificationCode::load(int user_id, model::table::EmailOptInType type) {
auto db = new model::table::EmailOptIn();
std::vector<std::string> fields = { "user_id", "email_opt_in_type_id" };
if (db->loadFromDB(fields, user_id, (int)type) == 1) {
return Poco::AutoPtr<EmailVerificationCode>(new EmailVerificationCode(db));
std::vector<int> field_values = { user_id, (int)type };
auto results = db->loadFromDB<int, model::table::EmailOptInTuple>(fields, field_values);
if (results.size() > 0) {
db->release();
return Poco::AutoPtr<EmailVerificationCode>(new EmailVerificationCode(new model::table::EmailOptIn(results[0])));
}
/*if (db->loadFromDB(fields, user_id, (int)type) == 1) {
return Poco::AutoPtr<EmailVerificationCode>(new EmailVerificationCode(db));
}*/
db->release();
return nullptr;
}

View File

@ -46,24 +46,49 @@ namespace controller {
return Poco::AutoPtr<User>(user);
}
std::vector<User*> User::search(const std::string& searchString)
std::vector<User*> User::search(const std::string& searchString, const std::string& accountState /* = "all" */)
{
auto sm = SessionManager::getInstance();
auto cm = ConnectionManager::getInstance();
auto db = new model::table::User();
static const char* functionName = "User::search";
std::string globalSearch = "%" + searchString + "%";
std::vector<model::table::UserTuple> resultFromDB;
// check if search string is email
/*if (sm->isValid(searchString, VALIDATE_EMAIL)) {
resultFromDB = db->loadFromDB <std::string, model::table::UserTuple>("email", globalSearch);
if (accountState == "email not activated") {
std::vector<std::string> fieldNames = { "first_name", "last_name", "email", "email_checked" };
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
std::vector<model::table::UserTuple> results;
using namespace Poco::Data::Keywords;
Poco::Data::Statement select(session);
// typedef Poco::Tuple<std::string, std::string, std::string, Poco::Nullable<Poco::Data::BLOB>, int> UserTuple;
select << "SELECT id, first_name, last_name, email, pubkey, created, email_checked, disabled FROM " << db->getTableName();
select << " where email_checked = 0 ";
select, into(resultFromDB);
if (searchString != "") {
select << "AND (first_name LIKE ? OR last_name LIKE ? OR email LIKE ?)";
select, useRef(globalSearch), useRef(globalSearch), useRef(globalSearch);
}
try {
select.execute();
}
catch (Poco::Exception& ex) {
ErrorList errors;
errors.addError(new ParamError(functionName, "mysql error ", ex.displayText()));
errors.addError(new ParamError(functionName, "search string", searchString));
errors.addError(new ParamError(functionName, "account state", accountState));
errors.sendErrorsAsEmail();
}
}
else {*/
else {
std::vector<std::string> fieldNames = { "first_name", "last_name", "email" };
std::vector<std::string> fieldValues = { globalSearch, globalSearch, globalSearch };
resultFromDB = db->loadFromDB<std::string, model::table::UserTuple>(fieldNames, fieldValues, model::table::MYSQL_CONDITION_OR);
//}
}
db->release();
db = nullptr;

View File

@ -28,7 +28,7 @@ namespace controller {
static Poco::AutoPtr<User> create();
static Poco::AutoPtr<User> create(const std::string& email, const std::string& first_name, const std::string& last_name, Poco::UInt64 passwordHashed = 0, std::string languageKey = "de");
static std::vector<User*> search(const std::string& searchString);
static std::vector<User*> search(const std::string& searchString, const std::string& accountState = "all");
//! \brief go through whole db and search users with email_checked = false and schedule resend 7 days after email_opt_in created date
//!

View File

@ -90,6 +90,33 @@ namespace model {
return select;
}
Poco::Data::Statement EmailOptIn::_loadMultipleFromDB(Poco::Data::Session session, const std::vector<std::string> fieldNames, MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/)
{
Poco::Data::Statement select(session);
if (fieldNames.size() <= 1) {
throw Poco::NullValueException("EmailOptIn::_loadFromDB fieldNames empty or contain only one field");
}
select << "SELECT id, user_id, verification_code, email_opt_in_type_id, created, resend_count, updated FROM " << getTableName()
<< " where " << fieldNames[0] << " = ? ";
if (conditionType == MYSQL_CONDITION_AND) {
for (int i = 1; i < fieldNames.size(); i++) {
select << " AND " << fieldNames[i] << " = ? ";
}
}
else if (conditionType == MYSQL_CONDITION_OR) {
for (int i = 1; i < fieldNames.size(); i++) {
select << " OR " << fieldNames[i] << " = ? ";
}
}
else {
addError(new ParamError("EmailOptIn::_loadFromDB", "condition type not implemented", conditionType));
}
return select;
}
Poco::Data::Statement EmailOptIn::_loadFromDB(Poco::Data::Session session, const std::vector<std::string>& fieldNames, MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/)
{
Poco::Data::Statement select(session);

View File

@ -48,6 +48,7 @@ namespace model {
Poco::Data::Statement _loadFromDB(Poco::Data::Session session, const std::string& fieldName);
Poco::Data::Statement _loadIdFromDB(Poco::Data::Session session);
Poco::Data::Statement _loadMultipleFromDB(Poco::Data::Session session, const std::string& fieldName);
Poco::Data::Statement _loadMultipleFromDB(Poco::Data::Session session, const std::vector<std::string> fieldNames, MysqlConditionType conditionType = MYSQL_CONDITION_AND);
Poco::Data::Statement _loadFromDB(Poco::Data::Session session, const std::vector<std::string>& fieldNames, MysqlConditionType conditionType = MYSQL_CONDITION_AND);
Poco::Data::Statement _insertIntoDB(Poco::Data::Session session);

View File

@ -40,10 +40,19 @@ namespace model {
size_t loadFromDB(const std::string& fieldName, const T& fieldValue);
template<class T>
bool isExistInDB(const std::string& fieldName, const T& fieldValue);
template<class WhereFieldType, class Tuple>
std::vector<Tuple> loadFromDB(const std::string& fieldName, const WhereFieldType& fieldValue, int expectedResults = 0);
template<class T1, class T2>
size_t loadFromDB(const std::vector<std::string>& fieldNames, const T1& field1Value, const T2& field2Value, MysqlConditionType conditionType = MYSQL_CONDITION_AND);
template<class Tuple, class T1, class T2, class T3, class T4>
std::vector<Tuple> loadMultipleFromDB(
const std::vector<std::string>& fieldNames,
const T1& field1Value, const T2& field2Value, const T3& field3Value, const T4& field4Value,
MysqlConditionType conditionType = MYSQL_CONDITION_AND);
template<class WhereFieldType, class Tuple>
std::vector<Tuple> loadFromDB(const std::vector<std::string>& fieldNames, const std::vector<WhereFieldType>& fieldValues, MysqlConditionType conditionType = MYSQL_CONDITION_AND, int expectedResults = 0);
bool insertIntoDB(bool loadId);
@ -211,6 +220,43 @@ namespace model {
return resultCount;
}
template<class Tuple, class T1, class T2, class T3, class T4>
std::vector<Tuple> ModelBase::loadMultipleFromDB(
const std::vector<std::string>& fieldNames,
const T1& field1Value, const T2& field2Value, const T3& field3Value, const T4& field4Value,
MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/)
{
auto cm = ConnectionManager::getInstance();
std::vector<Tuple> results;
if (fieldNames.size() != 4) {
addError(new Error(getTableName(), "error in loadFromDB with 4 different field values, fieldNames count isn't 4"));
return results;
}
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
Poco::Data::Statement select = _loadMultipleFromDB(session, fieldNames, conditionType);
select, Poco::Data::Keywords::into(results),
Poco::Data::Keywords::useRef(field1Value), Poco::Data::Keywords::useRef(field2Value),
Poco::Data::Keywords::useRef(field3Value), Poco::Data::Keywords::useRef(field4Value);
size_t resultCount = 0;
try {
resultCount = select.execute();
}
catch (Poco::Exception& ex) {
lock();
addError(new ParamError(getTableName(), "mysql error by selecting with 4 different field types", ex.displayText()));
int count = 0;
for (auto it = fieldNames.begin(); it != fieldNames.end(); it++) {
addError(new ParamError(getTableName(), "field name for select: ", *it));
}
//addError(new ParamError(getTableName(), "field name for select: ", fieldName.data()));
unlock();
}
return resultCount;
}
template<class T>
size_t ModelBase::updateIntoDB(const std::string& fieldName, const T& fieldValue)

View File

@ -1,6 +1,7 @@
//#include "lib/Thread.h"
//#include "UniversumLib.h"
#include "Thread.h"
#include "../lib/ErrorList.h"
namespace UniLib {
namespace lib {
@ -55,6 +56,8 @@ namespace UniLib {
void Thread::run()
{
static const char* function_name = "Thread::run";
ErrorList errors;
//Thread* t = this;
while (true) {
try {
@ -77,6 +80,7 @@ namespace UniLib {
{
//EngineLog.writeToLog("error-code: %d", ret);
printf("[Thread::%s] error running thread functon: %d, exit thread\n", __FUNCTION__, ret);
errors.addError(new ParamError(function_name, "error running thread function, exit thread", mPocoThread->getName()));
return;
}
}
@ -85,13 +89,19 @@ namespace UniLib {
threadUnlock();
//LOG_ERROR("Fehler in Thread, exit", -1);
printf("[Thread::%s] exception: %s\n", __FUNCTION__, e.message().data());
errors.addError(new ParamError(function_name, "poco exception", e.message()));
errors.addError(new ParamError(function_name, "thread name", mPocoThread->getName()));
return;
}
} catch (Poco::TimeoutException& e) {
printf("[Thread::%s] timeout exception\n", __FUNCTION__);
errors.addError(new ParamError(function_name, "poco timeout exception", e.message()));
errors.addError(new ParamError(function_name, "thread name", mPocoThread->getName()));
} catch (Poco::Exception& e) {
printf("[Thread::%s] exception: %s\n", __FUNCTION__, e.message().data());
errors.addError(new ParamError(function_name, "poco exception 2", e.message()));
errors.addError(new ParamError(function_name, "thread name", mPocoThread->getName()));
return;
}
}