Skip to content

Fixes #41: Allow Users to View, Edit, and Delete Their Own Complaints #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
247 changes: 227 additions & 20 deletions ComplaintBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,45 @@
#include <fstream>
#include <sstream>
#include <bits/stdc++.h>
#include <openssl/sha.h>
#include <conio.h>
#include "globals.h"


std::string logged_in_username = "random";

using namespace std;

string getHiddenPassword() {
string pass;
char ch;
while ((ch = _getch()) != '\r') { // Enter key
if (ch == '\b') { // Backspace
if (!pass.empty()) {
cout << "\b \b";
pass.pop_back();
}
} else {
pass += ch;
cout << '*';
}
}
cout << endl;
return pass;
}

string hashPassword(const string& password) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((const unsigned char*)password.c_str(), password.size(), hash);

stringstream ss;
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
ss << hex << setw(2) << setfill('0') << (int)hash[i];

return ss.str();
}


ComplaintBox::ComplaintBox() {
sqlite3_open("complaints.db", &db);
createTables();
Expand All @@ -19,18 +55,21 @@ void ComplaintBox::createTables() {
string sqlUsers = "CREATE TABLE IF NOT EXISTS users (username TEXT PRIMARY KEY, password TEXT);";
string sqlAdmins = "CREATE TABLE IF NOT EXISTS adminusers (username TEXT PRIMARY KEY, password TEXT);";
string sqlComplaints = "CREATE TABLE IF NOT EXISTS complaints ("
"complaint_id INTEGER PRIMARY KEY AUTOINCREMENT, "
"category TEXT, "
"subCategory TEXT, "
"message TEXT);";
"complaint_id INTEGER PRIMARY KEY AUTOINCREMENT, "
"category TEXT, "
"subCategory TEXT, "
"message TEXT, "
"status TEXT DEFAULT 'Pending', "
"timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, "
"notified INTEGER DEFAULT 0);";

sqlite3_exec(db, sqlUsers.c_str(), 0, 0, &errMsg);
sqlite3_exec(db, sqlAdmins.c_str(), 0, 0, &errMsg);
sqlite3_exec(db, sqlComplaints.c_str(), 0, 0, &errMsg);
}

void ComplaintBox::registerUser(bool isAdmin) {
string uname, pass;
string uname;
cout << PURPLE << "Enter username: " << RESET;
cin >> uname;

Expand All @@ -48,28 +87,40 @@ void ComplaintBox::registerUser(bool isAdmin) {
}

cout << PURPLE << "Enter password: " << RESET;
cin >> pass;
string pass1 = getHiddenPassword();

cout << PURPLE << "Confirm password: " << RESET;
string pass2 = getHiddenPassword();

if (pass1 != pass2) {
cout << RED << "Passwords do not match. Registration failed.\n" << RESET;
return;
}

string table = isAdmin ? "adminusers" : "users";
string sql = "INSERT INTO " + table + " (username, password) VALUES ('" + uname + "', '" + pass + "');";
string hashedPass = hashPassword(pass1);
string insertSql = "INSERT INTO " + table + " (username, password) VALUES ('" + uname + "', '" + hashedPass + "');";

if (sqlite3_exec(db, sql.c_str(), 0, 0, &errMsg) != SQLITE_OK) {
if (sqlite3_exec(db, insertSql.c_str(), 0, 0, &errMsg) != SQLITE_OK) {
cout << RED << "Error: " << errMsg << RESET << endl;
sqlite3_free(errMsg);
} else {
cout << BOLDGREEN << "Registration successful!\n" << RESET;
}
}


bool ComplaintBox::loginUser(bool isAdmin) {
string uname, pass;
cout << CYAN << "Enter username: " << RESET;
cin >> uname;
cout << CYAN << "Enter password: " << RESET;
cin >> pass;
pass = getHiddenPassword();

string table = isAdmin ? "adminusers" : "users";
string sql = "SELECT * FROM " + table + " WHERE username = '" + uname + "' AND password = '" + pass + "';";
string hashedPass = hashPassword(pass);
string sql = "SELECT * FROM " + table + " WHERE username = '" + uname + "' AND password = '" + hashedPass + "';";

bool success = false;

sqlite3_exec(db, sql.c_str(), [](void *successPtr, int, char **, char **) -> int {
Expand All @@ -79,13 +130,35 @@ bool ComplaintBox::loginUser(bool isAdmin) {

if (success) {
cout << GREEN << "Login successful!\n" << RESET;
admin_logged_in = isAdmin;
logged_in_username = uname;

if (isAdmin) {
const char* countSQL = "SELECT COUNT(*) FROM complaints WHERE notified = 0;";
int newComplaints = 0;
sqlite3_exec(db, countSQL, [](void* data, int argc, char** argv, char**) -> int {
*(int*)data = atoi(argv[0]);
return 0;
}, &newComplaints, &errMsg);

if (newComplaints > 0) {
cout << YELLOW << "You have " << newComplaints << " new complaint(s)!\n" << RESET;

const char* updateSQL = "UPDATE complaints SET notified = 1 WHERE notified = 0;";
sqlite3_exec(db, updateSQL, 0, 0, &errMsg);
} else {
cout << GREEN << "No new complaints since your last login.\n" << RESET;
}
}

return true;
} else {
cout << RED << "Invalid credentials!\n" << RESET;
return false;
}
}


void ComplaintBox::fileComplaint() {
string category, subCategory, message;
cout << YELLOW << "Enter category: " << RESET;
Expand All @@ -96,52 +169,68 @@ void ComplaintBox::fileComplaint() {
cout << YELLOW << "Enter complaint message: " << RESET;
getline(cin, message);

string sql = "INSERT INTO complaints (category, subCategory, message) VALUES ('" + category + "', '" + subCategory + "', '" + message + "');";
string filedBy = logged_in_username.empty() ? "random" : logged_in_username;

string sql = "INSERT INTO complaints (category, subCategory, message, status, filed_by) VALUES ('"
+ category + "', '" + subCategory + "', '" + message + "', 'Pending', '" + filedBy + "');";

if (sqlite3_exec(db, sql.c_str(), 0, 0, &errMsg) != SQLITE_OK) {
cout << RED << "Error: " << errMsg << RESET << endl;
sqlite3_free(errMsg);
} else {
cout << BOLDGREEN << "Complaint filed successfully!\n" << RESET;
cout << BOLDGREEN << "Complaint filed successfully by '" << filedBy << "' with status 'Pending'!\n" << RESET;
}
}



void ComplaintBox::exportComplaintsToCSV() {
if (!admin_logged_in) {
cout << RED << "Only admins are allowed to export complaints.\n" << RESET;
return;
}

ofstream file("complaints_export.csv");
if (!file.is_open()) {
cout << RED << "Failed to create CSV file.\n" << RESET;
return;
}

file << "complaint_id,category,subCategory,message\n";
string sql = "SELECT complaint_id, category, subCategory, message FROM complaints;";

file << "complaint_id,filed_by,category,subCategory,message,status,timestamp\n";

string sql = "SELECT complaint_id, filed_by, category, subCategory, message, status, timestamp FROM complaints;";

auto callback = [](void *data, int argc, char **argv, char **colName) -> int {
ofstream *f = static_cast<ofstream *>(data);
for (int i = 0; i < argc; i++) {
*f << (argv[i] ? argv[i] : "") << (i < argc - 1 ? "," : "\n");
}
return 0;
};

if (sqlite3_exec(db, sql.c_str(), callback, &file, &errMsg) != SQLITE_OK) {
cout << RED << "Export failed: " << errMsg << RESET << endl;
sqlite3_free(errMsg);
} else {
cout << BOLDGREEN << "Complaints exported to 'complaints_export.csv'!\n" << RESET;
cout << BOLDGREEN << "Complaints exported to 'complaints_export.csv'\n" << RESET;
}

file.close();
}



void ComplaintBox::searchComplaints() {
string keyword;
cout << YELLOW << "Enter keyword to search for: " << RESET;
cin.ignore();
getline(cin, keyword);

string sql = "SELECT complaint_id, category, subCategory, message FROM complaints "
string sql = "SELECT complaint_id, category, subCategory, message, status FROM complaints "
"WHERE category LIKE '%" + keyword + "%' OR "
"subCategory LIKE '%" + keyword + "%' OR "
"message LIKE '%" + keyword + "%';";
"message LIKE '%" + keyword + "%' OR "
"status LIKE '%" + keyword + "%';";

cout << CYAN << "\nSearch Results:\n" << RESET;
auto callback = [](void *data, int argc, char **argv, char **colName) -> int {
Expand All @@ -157,3 +246,121 @@ void ComplaintBox::searchComplaints() {
sqlite3_free(errMsg);
}
}


void ComplaintBox::updateComplaintStatus(int complaint_id, const std::string& new_status) {
if (!admin_logged_in) {
cout << RED << "Only admins can update complaint status.\n" << RESET;
return;
}

sqlite3_stmt* stmt;
string sql = "UPDATE complaints SET status = ? WHERE complaint_id = ?";

if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, new_status.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, complaint_id);

if (sqlite3_step(stmt) == SQLITE_DONE) {
cout << GREEN << "Status updated successfully.\n" << RESET;
} else {
cerr << RED << "Failed to update status.\n" << RESET;
}
} else {
cerr << RED << "SQL Prepare Failed: " << sqlite3_errmsg(db) << "\n" << RESET;
}

sqlite3_finalize(stmt);
}

void ComplaintBox::viewMyComplaints() {
if (logged_in_username.empty()) {
cout << RED << "Please log in to view your complaints.\n" << RESET;
return;
}

string sql = "SELECT complaint_id, category, subCategory, message, status, timestamp FROM complaints WHERE filed_by = '" + logged_in_username + "';";

cout << BOLDGREEN << "Your Complaints:\n" << RESET;

auto callback = [](void* unused, int argc, char** argv, char** colName) -> int {
cout << "\nComplaint ID: " << argv[0]
<< "\nCategory: " << argv[1]
<< "\nSub-category: " << argv[2]
<< "\nMessage: " << argv[3]
<< "\nStatus: " << argv[4]
<< "\nTimestamp: " << argv[5] << "\n";
return 0;
};

if (sqlite3_exec(db, sql.c_str(), callback, 0, &errMsg) != SQLITE_OK) {
cout << RED << "Failed to fetch complaints: " << errMsg << RESET << endl;
sqlite3_free(errMsg);
}
}

void ComplaintBox::editComplaint() {
int id;
cout << CYAN << "Enter Complaint ID to edit: " << RESET;
cin >> id;
cin.ignore();

string sql = "SELECT status FROM complaints WHERE complaint_id = " + to_string(id) + " AND filed_by = '" + logged_in_username + "';";
string status;
bool found = false;

sqlite3_exec(db, sql.c_str(), [](void* s, int argc, char** argv, char**) -> int {
if (argc > 0 && argv[0]) {
*(string*)s = argv[0];
}
return 0;
}, &status, &errMsg);

if (status != "Pending") {
cout << RED << "You can only edit complaints in 'Pending' status.\n" << RESET;
return;
}

string newMsg;
cout << YELLOW << "Enter new complaint message: " << RESET;
getline(cin, newMsg);

sql = "UPDATE complaints SET message = '" + newMsg + "' WHERE complaint_id = " + to_string(id) + " AND filed_by = '" + logged_in_username + "';";

if (sqlite3_exec(db, sql.c_str(), 0, 0, &errMsg) == SQLITE_OK) {
cout << GREEN << "Complaint updated successfully.\n" << RESET;
} else {
cout << RED << "Update failed: " << errMsg << RESET << endl;
sqlite3_free(errMsg);
}
}

void ComplaintBox::deleteComplaint() {
int id;
cout << CYAN << "Enter Complaint ID to delete: " << RESET;
cin >> id;

string sql = "SELECT status FROM complaints WHERE complaint_id = " + to_string(id) + " AND filed_by = '" + logged_in_username + "';";
string status;

sqlite3_exec(db, sql.c_str(), [](void* s, int argc, char** argv, char**) -> int {
if (argc > 0 && argv[0]) {
*(string*)s = argv[0];
}
return 0;
}, &status, &errMsg);

if (status != "Pending") {
cout << RED << "Only 'Pending' complaints can be deleted.\n" << RESET;
return;
}

sql = "DELETE FROM complaints WHERE complaint_id = " + to_string(id) + " AND filed_by = '" + logged_in_username + "';";

if (sqlite3_exec(db, sql.c_str(), 0, 0, &errMsg) == SQLITE_OK) {
cout << GREEN << "Complaint deleted successfully.\n" << RESET;
} else {
cout << RED << "Delete failed: " << errMsg << RESET << endl;
sqlite3_free(errMsg);
}
}
7 changes: 7 additions & 0 deletions ComplaintBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ class ComplaintBox {
void fileComplaint();
void exportComplaintsToCSV();
void searchComplaints();
void updateComplaintStatus(int complaint_id, const string& new_status);
bool isAdminLoggedIn() const { return admin_logged_in; }
void viewMyComplaints();
void editComplaint();
void deleteComplaint();


private:
sqlite3 *db;
char *errMsg;
bool admin_logged_in = false;
void createTables();
};

Expand Down
10 changes: 6 additions & 4 deletions complaints_export.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
complaint_id,category,subCategory,message
1,Infrastructural,Hostel,Washrooms are not being regularly cleaned.
2,Management,College Events,Admin paisa kha gaya bc
3,Infrastructure,Hostel,MMC change karo mc
complaint_id,filed_by,category,subCategory,message,status,timestamp
1,random,College,Fests,Admin chor mc,Pending,2025-04-11 14:43:14
2,random,College,Infrastructure,Add AC in class,Pending,2025-04-11 16:46:37
3,Kartik,College,Faculty,B Nath,Pending,2025-04-11 17:19:58
4,XD,Admin,Admin,Salary nahi milti sed,Pending,2025-04-11 17:21:05
5,random,College,Gymkhana,Why,Pending,2025-04-11 17:21:58
8 changes: 8 additions & 0 deletions globals.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef GLOBALS_H
#define GLOBALS_H

#include <string>

extern std::string logged_in_username;

#endif
Loading