diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fd9156..20b19cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,14 @@ set(CMAKE_CXX_STANDARD 14) include_directories(.) add_executable(stackoverflow_in_cpp - AbstractUser.h - Exceptions.h - main.cpp - User.cpp - User.h) + AbstractUser.h + Exceptions.h + Content.h + Content.cpp + ContentRelation.h + ContentRelation.cpp + Logger.cpp + Logger.h + main.cpp + User.cpp + User.h) diff --git a/Content.cpp b/Content.cpp new file mode 100644 index 0000000..d12ae40 --- /dev/null +++ b/Content.cpp @@ -0,0 +1,28 @@ +#include "Content.h" + +Content::Content(std::string &body, ContentType type): visits(0), type(type), body(body) {} + +Content::~Content() { + for(int i = 0; i < relations.size(); i++) + delete relations[0]; +} + +void Content::add_relation(ContentRelationType type, Content &destination) { + auto relation_ptr = new ContentRelation(&destination, this, type); + relations.push_back(relation_ptr); + destination.relations.push_back(relation_ptr); +} + +void Content::edit_content(std::string &body) { + this->body = body; +} + +void Content::print_answers() { + int answer_number = 1; + for(int i = 0; i < relations.size(); i++) { + if(relations[i]->type == ANSWER_TO) { + std::cout << answer_number++ << ". " << relations[i]->source->body << std::endl; + } + } + std::cout << std::endl; +} diff --git a/Content.h b/Content.h new file mode 100644 index 0000000..0681543 --- /dev/null +++ b/Content.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "ContentRelation.h" + +enum ContentType { + QUESTION, + ANSWER +}; + +class Content { +public: + std::string body; + ContentType type; + int visits; + std::vector relations; + +public: + Content(std::string &body, ContentType type); + ~Content(); + void add_relation(ContentRelationType type, Content &destination); + void edit_content(std::string &body); + void print_answers(); +}; diff --git a/ContentRelation.cpp b/ContentRelation.cpp new file mode 100644 index 0000000..60ab2fc --- /dev/null +++ b/ContentRelation.cpp @@ -0,0 +1,25 @@ +#include "ContentRelation.h" +#include "Content.h" + +ContentRelation::ContentRelation(Content *destination, Content *source, ContentRelationType type): + destination(destination), + source(source), + type(type) {} + +ContentRelation::~ContentRelation() { + auto relations = &(this->destination->relations); + + for(auto it = relations->begin(); it < relations->end(); it++) { + if(*it == this) { + relations->erase(it); + } + } + + relations = &(this->source->relations); + + for (auto it = relations->begin(); it < relations->end(); it++) { + if (*it == this) { + relations->erase(it); + } + } +} diff --git a/ContentRelation.h b/ContentRelation.h new file mode 100644 index 0000000..c0f1cb2 --- /dev/null +++ b/ContentRelation.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +enum ContentRelationType { + DUPLICATE_OF, + ANSWER_TO +}; + +class Content; + +class ContentRelation { +public: + ContentRelation(Content* destination, Content* source, ContentRelationType type); + ~ContentRelation(); + +public: + Content* destination; + Content* source ; + ContentRelationType type; +}; diff --git a/Exceptions.h b/Exceptions.h index f304c0b..8bce64f 100644 --- a/Exceptions.h +++ b/Exceptions.h @@ -11,7 +11,6 @@ class UsernameAlreadyExistsException : public std::exception { private: const std::string message = "Error: username already exists"; - }; class EmailAlreadyExistsException : public std::exception { @@ -22,16 +21,38 @@ class EmailAlreadyExistsException : public std::exception { private: const std::string message = "Error: email already exists"; +}; +class InvalidUsernameException : public std::exception { +public: + const char *what() const throw() { + return message.c_str(); + } + +private: + const std::string message = "Error: invalid username\n" + "You can use a-z, 0-9 and underscores\n" + "Username must be at least 5 and at most 32 characters"; }; -class WrongUsernameOrPasswordException : public std::exception { +class InvalidEmailException : public std::exception { +public: + const char *what() const throw() { + return message.c_str(); + } + private: - const std::string message = "Error: wrong username or password!"; + const std::string message = "Error: invalid email address"; +}; + +class WrongUsernameOrPasswordException : public std::exception { public: const char *what() const throw() { return message.c_str(); } + +private: + const std::string message = "Error: wrong username or password!"; }; class DeleteAdminException : public std::exception { @@ -42,5 +63,4 @@ class DeleteAdminException : public std::exception { private: const std::string message = "Error: can't delete admin account!"; - }; diff --git a/Logger.cpp b/Logger.cpp new file mode 100644 index 0000000..c23e90c --- /dev/null +++ b/Logger.cpp @@ -0,0 +1,45 @@ +#include "Logger.h" +#include "User.h" +#include +#include +#include +#include + +Logger::Logger() { + std::ifstream file; + int num = 1; + while(true) { + file.open("log." + to_string(num) + ".txt"); + if(file.is_open()) { + file.close(); + num++; + continue; + } + log_num = num; + std::ofstream log_file("log." + std::to_string(log_num) + ".txt"); + break; + } +} + +Logger& Logger::getInstance() { + static Logger lg; + return lg; +} + +void Logger::log(User& user) { + std::ofstream log_file("log." + std::to_string(Logger::getInstance().log_num) + ".txt", std::ios::app); + time_t now = time(0); + std::string date_time(ctime(&now)); + log_file << user << " | " << date_time; + this->logs.push_back(user.toString() + " | " + date_time); +} + +void Logger::printLogs() { + for(auto &log : logs) { + std::cout << log << std::endl; + } +} + +std::vector& Logger::getLogs() { + return this->logs; +} diff --git a/Logger.h b/Logger.h new file mode 100644 index 0000000..1e0f446 --- /dev/null +++ b/Logger.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include "User.h" + +#define _Log(x) Logger::getInstance().log((x)) + + +class Logger { +public: + static Logger& getInstance(); +public: + void printLogs(); + void log(User& user); + std::vector& getLogs (); +private: + Logger(); + std::vector logs; +private: + int log_num; +}; diff --git a/User.cpp b/User.cpp index 7aa9f46..88d12a0 100644 --- a/User.cpp +++ b/User.cpp @@ -1,14 +1,20 @@ #include -// -// Created by spsina on 11/8/18. -// - #include #include "User.h" #include "Exceptions.h" #include +#include + +bool is_username_valid(const string& username) { + const std::regex pattern("\\w{5,32}"); + return std::regex_match(username, pattern); +} +bool is_email_valid(const string& email) { + const std::regex pattern("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+"); + return std::regex_match(email, pattern); +} vector User::users; string User::salt; @@ -71,7 +77,15 @@ User& User::signup(string username, string password, string email){ throw EmailAlreadyExistsException(); } } - //Create user + // Check username validatin + if(not is_username_valid(username)) + throw InvalidUsernameException(); + + // Check email validatin + if(not is_email_valid(email)) + throw InvalidEmailException(); + + // Create user users.emplace_back(username, password, email, UserType::MEMBER); return users[users.size() - 1]; } @@ -80,4 +94,55 @@ void User::init(const string &salt) { User::salt = salt; users.reserve(20); users.emplace_back("admin", "admin", "admin@stackoverflow.com", UserType::ADMIN); +} + +bool User::is_admin() { + return this->type == UserType::ADMIN; +} + +string User::toString() { + return email + " | " + username; +} + +std::ostream& operator<<(std::ostream &os, User &user) { + os << user.toString(); + return os; +} + +void User::create(std::string &body, ContentType type) { + contents.emplace_back(body, type); +} + +void User::print_questions() { + for(const auto &user : users) { + for(const auto &content : user.contents) { + if(content.type == ContentType::QUESTION) { + std::cout << user.username << ": " << content.body << std::endl << std::endl; + } + } + } +} + +void User::print_content(int num) { + num %= contents.size(); + std::cout << "Question " << num+1 << ": " << contents[num].body << std::endl + << "_______________________________" << std::endl + << "Answers:" << std::endl; + for(const auto &relation : contents[num].relations) { + if(relation->type == ContentRelationType::ANSWER_TO) + std::cout << relation->destination->body << std::endl; + } + std::cout << "_______________________________" << std::endl; +} + +void User::edit_content(int num, string &body) { + num %= contents.size(); + contents[num].body = body; +} + +void User::delete_content(int num) { + num %= contents.size(); + auto it = contents.begin(); + it += num; + contents.erase(it); } \ No newline at end of file diff --git a/User.h b/User.h index 6b239d9..276fafe 100644 --- a/User.h +++ b/User.h @@ -2,11 +2,11 @@ #include #include "AbstractUser.h" +#include "Content.h" class User : public AbstractUser { public: User(string username, string password, string email, UserType type); - static void init(const string &salt); public: @@ -21,6 +21,23 @@ class User : public AbstractUser { static User& login(string username, string password); static User& signup(string username, string password, string email); +public: + bool is_admin(); + +public: + string toString(); + friend std::ostream& operator<<(std::ostream& os, User& user); + +public: + vector contents; + void create(std::string &body, ContentType type); + +public: + static void print_questions(); + void print_content(int num); + void edit_content(int num, string& body); + void delete_content(int num); + private: static string salt; static vector users; diff --git a/main.cpp b/main.cpp index 01eac03..9f557d6 100755 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,11 @@ #include +#include #include #include "AbstractUser.h" #include "Exceptions.h" #include "User.h" +#include "Logger.h" +#include "Content.h" #ifdef _WIN32 #define CLEAR "cls" @@ -16,21 +19,27 @@ using namespace std; enum MenuState { START, LOGGED_IN, + QUESTIONS, END }; int main() { + Logger::getInstance(); User::init("SECRET_KEY"); User * loggedInUser = nullptr; MenuState menuState = MenuState::START; string last_message; - + bool show_logs = false; char choice; + int content_num = 0; while(menuState != MenuState::END) { - system(CLEAR); - if (!last_message.empty()) + if(not show_logs) + system(CLEAR); + show_logs = false; + if (not last_message.empty()) { cout << last_message << endl; - last_message = ""; + last_message = ""; + } switch (menuState) { case MenuState::START: { cout << "1. login\n2. signup\ne. exit\n"; @@ -43,8 +52,9 @@ int main() { cin >> username; cout << "Enter Password: "; cin >> password; - loggedInUser = &User::login(username,password); + loggedInUser = &User::login(username, password); menuState = MenuState::LOGGED_IN; + _Log(*loggedInUser); } catch (WrongUsernameOrPasswordException &e) { last_message = e.what(); } @@ -62,11 +72,7 @@ int main() { loggedInUser = &User::signup(username, password, email); menuState = MenuState::LOGGED_IN; last_message = "User signed up!\n"; - } catch (UsernameAlreadyExistsException &e) { - last_message = e.what(); - break; - - } catch (EmailAlreadyExistsException &e) { + } catch (exception &e) { last_message = e.what(); } break; @@ -83,10 +89,25 @@ int main() { break; } case MenuState::LOGGED_IN: { - cout << "d.delete account\nl. logout\ne. exit\n"; + if(loggedInUser->is_admin()) + cout << "s. show logs\n"; + cout << "a. add question\nq. all questions\nd. delete account\nl. logout\ne. exit\n"; cin >> choice; switch (choice) { - case 'd': { + case 'a': { // add question + cout << "Enter your question: "; + string question; + cin.ignore(1); + getline(cin, question); + loggedInUser->create(question, ContentType::QUESTION); + break; + } + case 'q': { // all questions + content_num = 0; + menuState = MenuState::QUESTIONS; + break; + } + case 'd': { // delete account try { loggedInUser->deleteAccount(); cout << "Account successfully deleted\n"; @@ -98,10 +119,19 @@ int main() { } break; } + case 's': { // show logs + if(loggedInUser->is_admin()) { + system(CLEAR); + Logger::getInstance().printLogs(); + show_logs = true; + } + else + last_message = "Unknown Input\n"; + break; + } case 'l': { // logout loggedInUser = nullptr; menuState = MenuState::START; - last_message = "GOOD BYE\n"; break; } case 'e': { // exit @@ -114,6 +144,43 @@ int main() { } } + break; + } + case MenuState::QUESTIONS: { + loggedInUser->print_content(content_num); + cout << "p. previous question\nn. next question\ne. edit question\nd. delete question\nb. back to main menu\n"; + cin >> choice; + switch (choice) { + case 'p': { // previous question + content_num--; + break; + } + case 'n': { // next question + content_num++; + break; + } + case 'e': { // edit question + cout << "Enter your question: "; + string question; + cin.ignore(1); + getline(cin, question); + loggedInUser->edit_content(content_num, question); + break; + } + case 'd': { // delete question + loggedInUser->delete_content(content_num); + break; + } + case 'b': { // back to main menu + menuState = MenuState::LOGGED_IN; + break; + } + default: { // unknown input + last_message = "Unknown Input\n"; + break; + } + } + break; } } }