Skip to content

Commit

Permalink
Ask/find questions and Question model.
Browse files Browse the repository at this point in the history
The user can search and if there are no related topics, a new question can be created. Otherwise, the user is redirected to the results.
  • Loading branch information
cristianfrasineanu committed Nov 30, 2016
1 parent a98f06b commit 284d7b8
Show file tree
Hide file tree
Showing 16 changed files with 177 additions and 56 deletions.
2 changes: 1 addition & 1 deletion app/Bootstrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ void main()
clearScreen();
toast(string(e.what()), string("error"));
}
}
}
42 changes: 32 additions & 10 deletions app/Console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,11 @@ void Console::handleView()
this->theController = Controller(this->currentView.getViewName(), buffer.str(), View::getViewExtension());
if (!Controller::getErrorBag().empty())
{
toast(string("There were some issues:"), string("error"));
printString("\n");
printVector(Controller::getErrorBag());

// Empty the bag and provide a retry step.
Controller::pushError(string(""));
this->provideRetry();
this->handleErrors();
}
else if (Controller::hasRedirectTo != "")
{
this->handleRedirect();
}

buffer.clear();
Expand All @@ -170,7 +168,30 @@ void Console::handleView()
}
}

// Prepare the next view and also cache the current one.
void Console::handleErrors()
{
toast(string("There were some issues:"), string("error"));
printString("\n");
printVector(Controller::getErrorBag());

Controller::pushError(string(""));

this->provideRetry();
}

void Console::handleRedirect()
{
toast(string("Redirecting..."), string("notification"));
printString("\n");
sleepAndFlushInput(this->delay);

string redirectTo = Controller::hasRedirectTo;
Controller::hasRedirectTo = "";

this->renderNextView(redirectTo);
}

// Prepare the next view and cache the current one.
void Console::renderNextView()
{
string nextView = this->currentView.getAvailableOptions().find(this->getLastInput())->second;
Expand All @@ -183,13 +204,14 @@ void Console::renderNextView()
this->handleView();
}

// Render a custom view without caching the previous one as it breaks the normal flow.
// Render a custom view without caching the previous one.
void Console::renderNextView(string &viewName)
{
map<char, string> nextOptions = View::getViewsOptions().find(viewName)->second;

this->previousViews.erase(this->previousViews.begin(), this->previousViews.end());
//this->previousViews.erase(this->previousViews.end() - 1, this->previousViews.end());
this->currentView = View(viewName, nextOptions);
this->lastInput = '\0';

clearScreen();
this->handleView();
Expand Down
3 changes: 3 additions & 0 deletions app/Console.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class Console {

void renderPreviousView();
void handleView();

void handleErrors();
void handleRedirect();
public:
static void initTerminal();

Expand Down
1 change: 1 addition & 0 deletions app/Controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ string Controller::userOutputString = "@output-";
string Controller::actionString = "@action-";

vector<string> Controller::errorBag = {};
string Controller::hasRedirectTo = "";

void Controller::justShow()
{
Expand Down
1 change: 1 addition & 0 deletions app/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Controller {
public:
static void pushError(string &);
static vector<string> getErrorBag();
static string hasRedirectTo;

Controller();
Controller(char *, string &, string &);
Expand Down
46 changes: 38 additions & 8 deletions app/QuestionModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ Question QuestionModel::setAfterId(int id)
return this->question;
}

vector<Question> QuestionModel::retrieveAll()
{
vector<Question> questions;

this->io.seekg(0, this->io.beg);

while (this->io.read(reinterpret_cast<char *>(&this->question), sizeof(Question)))
{
if (string(this->question.deleted_at) == "")
{
questions.push_back(this->question);
}
}

return questions;
}

bool QuestionModel::questionTitleExists(string &title)
{
Question question;
Expand Down Expand Up @@ -87,13 +104,24 @@ void QuestionModel::setAttributes(map<string, string> &cleanInputs)
{
strcpy(this->question.body, it->second.c_str());
}
else if (it->first == "userId")
{
this->question.user_id = atoi(it->second.c_str());
}
else if (it->first == "category")
{
this->question.category_id = 1;

// TODO: get the id from the static map defined in the category model.
}
}

// If there's a new user, assign created_at with the current date.
if (cleanInputs.find("action")->second == "create")
{
time_t t = time(nullptr);
strftime(this->question.created_at, sizeof(this->question.created_at), "%c", localtime(&t));
this->question.active = false;
}

this->question.id = ++this->lastId;
Expand All @@ -112,14 +140,16 @@ void QuestionModel::dumpFile()

while (db.read(reinterpret_cast<char *>(&question), sizeof(Question)))
{
dump << question.id << endl
<< question.user_id << endl
<< question.category_id << endl
<< question.title << endl
<< question.body << endl
<< question.created_at << endl
<< question.deleted_at << endl
<< question.hasAnswer << endl;
dump << "Id: " << question.id << endl
<< "User ID: " << question.user_id << endl
<< "Category ID: " << question.category_id << endl
<< "Votes: " << question.votes << endl
<< "Title: " << question.title << endl
<< "Body: " << question.body << endl
<< "Created at: " << question.created_at << endl
<< "Deleted at: " << question.deleted_at << endl
<< "Has answer: " << question.hasAnswer << endl
<< "Active: " << question.active << endl << endl;
}

db.close();
Expand Down
1 change: 1 addition & 0 deletions app/QuestionModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class QuestionModel : public ModelInterface {

Question setAfterUserId(int);
Question setAfterId(int);
vector<Question> retrieveAll();

bool questionTitleExists(string &);
void markAnswered(int);
Expand Down
53 changes: 49 additions & 4 deletions app/QuestionRepository.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ void QuestionRepository::defineValidation()
this->ValidationRules = {
{ "title", "^.*$" },
{ "body", "^.*$" },
{ "keyword", "^.*$" }
{ "category", "^.*$" },
{ "keyword", "^.*$" },
{ "action", "(?!.*\\W+)(?!.*[A-Z]+)[a-z]+$" }
};

this->ValidationErrors = {
{ "title", "..." },
{ "body", "..." },
{ "category", "..." },
{ "keyword", "..." }
};

Expand All @@ -40,16 +43,49 @@ void QuestionRepository::receiveCleanInput(map<string, string> &cleanInput)
if (action == "search")
{
string keyword = cleanInput.find("keyword")->second;
bool foundAnyRelated = false;

toast(string("Search!"), string("notice"));
vector<Question> results = this->model.retrieveAll();
vector<Question>::iterator it = results.begin();

while (it != results.end())
{
if (string((*it).title).find(keyword) != string::npos || string((*it).body).find(keyword) != string::npos)
{
foundAnyRelated = true;
++it;
}
else
{
it = results.erase(it);
}
}

if (!foundAnyRelated)
{
Controller::hasRedirectTo = "create.view";

toast(string("No related questions found!"), string("notification"));
printString("\n");
}
else
{
Controller::hasRedirectTo = "search-results.view";

toast(string("Found ") + to_string(results.size()) + string(" similar questions."), string("notification"));
printString("\n");
}
}
else if (action == "create")
{
toast(string("Create!"), string("notice"));
this->model.save();

toast(string("Your question was created!"), string("notification"));
printString("\n");
}
else
{
Controller::pushError(string("Please provide a valid action!"));
Controller::pushError(string("Please provide a valid action in your view!"));
}
}

Expand All @@ -76,10 +112,19 @@ void QuestionRepository::validateItems(map<string, string> &truncatedInput)
{
toast("\n\n" + string(e.what()) + "\n", string("error"));
}

if (it->first == "category" && !isInVector(vector<string>({ "something", "tada", "c++" }), it->second))
{
Controller::pushError(string("Please provide a valid category."));
}
}

if (Controller::getErrorBag().empty())
{
if (truncatedInput.find("action")->second == "create")
{
truncatedInput["userId"] = to_string(this->users.setActive().id);
}
this->receiveCleanInput(truncatedInput);
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/QuestionRepository.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "RepositoryInterface.h"
#include "QuestionModel.h"
#include "UserModel.h"

using namespace std;

Expand All @@ -12,6 +13,7 @@ class QuestionRepository : public RepositoryInterface {
static string alias;

QuestionModel model;
UserModel users;

void defineValidation();
void receiveCleanInput(map<string, string> &);
Expand Down
20 changes: 10 additions & 10 deletions app/UserModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,16 @@ void UserModel::dumpFile()

while (db.read(reinterpret_cast<char *>(&user), sizeof(User)))
{
dump << user.id << endl
<< user.full_name << endl
<< user.email << endl
<< user.username << endl
<< user.password << endl
<< user.created_at << endl
<< user.deleted_at << endl
<< user.role << endl
<< user.active << endl
<< user.banned << endl << endl;
dump << "Id: " << user.id << endl
<< "Full name: " << user.full_name << endl
<< "Email: " << user.email << endl
<< "Username: " << user.username << endl
<< "Password: " << user.password << endl
<< "Created at: " << user.created_at << endl
<< "Delete at: " << user.deleted_at << endl
<< "Role: " << user.role << endl
<< "Active: " << user.active << endl
<< "Banned: " << user.banned << endl << endl;
}

db.close();
Expand Down
34 changes: 21 additions & 13 deletions app/View.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,27 +54,35 @@ void View::loadViewsOptions()
helpView = "help.view",
dashboardView = "dashboard.view",
logoutView = "logout.view",
findOrAsk = "search-or-ask.view";
findOrAskView = "find-or-ask.view",
createView = "create.view",
searchResultsView = "search-results.view";

// Create non-linkable views, e.g. search results

View::ViewsOptions = {
{ homeView, { { '1', "login.view" }, { '2', "signup.view" }, { '3', "browse-index.view" },
{ '4', "faq.view" }, { '5', "help.view" }, { 'q', "quit" } } },
{ homeView, { { '1', loginView }, { '2', signupView }, { '3', browseIndexView },
{ '4', faqView }, { '5', helpView }, { 'q', "" } } },

{ browseIndexView, { { 'q', "" }, { 'b', "" } } },

{ loginView, { { 'c', dashboardView } } },

{ signupView, { { 'c', dashboardView } } },

{ dashboardView, { { '1', findOrAskView }, { '4', logoutView } } },

{ browseIndexView, { { 'q', "quit" }, { 'b', "back" } } },
{ logoutView, { { 'y', "" }, { 'b', "" } } },

{ loginView, { { 'c', "dashboard.view" } } },
{ faqView, { { 'q', "" }, { 'b', "" } } },

{ signupView, { { 'c', "dashboard.view" } } },
{ helpView, { { 'q', "" }, { 'b', "" } } },

{ dashboardView, { { '1', "find-or-ask.view" }, { '4', "logout.view" } } },
{ findOrAskView, { { 'b', "" } } },

{ logoutView, { { 'y', "confirm" }, { 'b', "back" } } },

{ faqView, { { 'q', "quit" }, { 'b', "back" } } },
{ searchResultsView, { { '1', createView }, { '3', dashboardView } } },

{ helpView, { { 'q', "quit" }, { 'b', "back" } } },

{ findOrAsk, { { 'c', "search-results.view" } } }
{ createView, { { '1', dashboardView } } }
};
}

Expand Down
11 changes: 7 additions & 4 deletions views/questions/create.view
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
_\ \ || (_| | (__| </ ___/| | |_| \__ \/ ___/| | |_| \__ \
\__/\__\__,_|\___|_|\_\/ |_|\__,_|___/\/ |_|\__,_|___/

~~~ If you cannot find any questions, then you can open a new one.
~~~ Seek and you will find...

--> @input-question-keyword
@action-seach

Title: @input-question-title
Description: @input-question-body
Category: @input-question-category
@action-create

===========================================================
# (c) 2016 Cristian-Adrian Frasineanu | GNU GPLv3 licence #
===========================================================
===========================================================
Loading

0 comments on commit 284d7b8

Please sign in to comment.