This repository was archived by the owner on Nov 5, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathattendance.cpp
More file actions
502 lines (463 loc) · 21.4 KB
/
Copy pathattendance.cpp
File metadata and controls
502 lines (463 loc) · 21.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
#include "httplib.h"
#include <nlohmann/json.hpp>
#include <sqlite3.h>
#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <chrono>
#include <thread>
#include <mutex>
#include <fstream>
#include <regex>
#include <ctime>
#include <sstream>
#include <iomanip>
using json = nlohmann::json;
struct AttendanceRecord {
int userId;
std::string timestamp;
double latitude, longitude;
std::string role;
};
struct CampusBoundary {
double minLat;
double maxLat;
double minLon;
double maxLon;
void loadFromJSON(const json& j) {
if (!j.contains("minLat") || !j.contains("maxLat") || !j.contains("minLon") || !j.contains("maxLon")) {
std::cerr << "Error: Geofence JSON missing required fields\n";
throw std::invalid_argument("Invalid geofence configuration: missing required fields");
}
minLat = j["minLat"].get<double>();
maxLat = j["maxLat"].get<double>();
minLon = j["minLon"].get<double>();
maxLon = j["maxLon"].get<double>();
if (minLat >= maxLat || minLon >= maxLon || minLat < -90.0 || maxLat > 90.0 || minLon < -180.0 || maxLon > 180.0) {
std::cerr << "Error: Invalid geofence boundaries - minLat: " << minLat << ", maxLat: " << maxLat
<< ", minLon: " << minLon << ", maxLon: " << maxLon << "\n";
throw std::invalid_argument("Invalid geofence boundaries");
}
}
bool isWithinBounds(double lat, double lon) const {
bool within = (lat >= minLat && lat <= maxLat && lon >= minLon && lon <= maxLon);
if (!within) {
std::cerr << "Geofence check failed: lat=" << lat << ", lon=" << lon
<< " not within bounds (minLat=" << minLat << ", maxLat=" << maxLat
<< ", minLon=" << minLon << ", maxLon=" << maxLon << ")\n";
}
return within;
}
json toJSON() const {
return {{"minLat", minLat}, {"maxLat", maxLat}, {"minLon", minLon}, {"maxLon", maxLon}};
}
};
class AcademicCalendar {
private:
std::vector<std::string> holidays;
std::vector<std::string> extraClasses;
public:
void loadFromJSON(const json& j) {
holidays = j.value("holidays", std::vector<std::string>{});
extraClasses = j.value("extraClasses", std::vector<std::string>{});
}
bool isHoliday(const std::string& date) const {
return std::find(holidays.begin(), holidays.end(), date) != holidays.end();
}
bool hasExtraClass(const std::string& date) const {
return std::find(extraClasses.begin(), extraClasses.end(), date) != extraClasses.end();
}
AcademicCalendar& operator+(const std::string& extraClassDate) {
extraClasses.push_back(extraClassDate);
return *this;
}
};
// Simple JWT implementation (for demo; use cpp-jwt in production)
std::string base64_encode(const std::string& in) {
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string out;
int val = 0, valb = -6;
for (unsigned char c : in) {
val = (val << 8) + c;
valb += 8;
while (valb >= 0) {
out.push_back(base64_chars[(val >> valb) & 0x3F]);
valb -= 6;
}
}
if (valb > -6) out.push_back(base64_chars[((val << 8) >> (valb + 8)) & 0x3F]);
while (out.size() % 4) out.push_back('=');
return out;
}
std::string generate_jwt(int userId, const std::string& role, const std::string& secret) {
json header = {{"alg", "HS256"}, {"typ", "JWT"}};
auto now = std::time(nullptr);
json payload = {{"userId", userId}, {"role", role}, {"exp", now + 3600}}; // 1 hour expiry
std::string header_b64 = base64_encode(header.dump());
std::string payload_b64 = base64_encode(payload.dump());
std::string signature_input = header_b64 + "." + payload_b64;
std::string signature = base64_encode(signature_input + secret); // Simplified for demo
return signature_input + "." + signature;
}
bool verify_jwt(const std::string& token, const std::string& secret, int& userId, std::string& role) {
try {
auto parts = httplib::detail::split(token, '.');
if (parts.size() != 3) return false;
std::string header_b64 = parts[0], payload_b64 = parts[1], signature = parts[2];
std::string expected_signature = base64_encode(header_b64 + "." + payload_b64 + secret);
if (signature != expected_signature) return false;
// Assume base64_decode exists (simplified; implement or use library)
json payload = json::parse(base64_decode(payload_b64));
auto now = std::time(nullptr);
if (payload["exp"].get<int64_t>() < now) return false;
userId = payload["userId"].get<int>();
role = payload["role"].get<std::string>();
return true;
} catch (...) {
return false;
}
}
class User {
protected:
int userId;
std::string name, role;
public:
User(int id, const std::string& n, const std::string& r) : userId(id), name(n), role(r) {}
virtual ~User() = default;
virtual void markAttendance(double lat, double lon, const AcademicCalendar& cal, const std::string& date, const CampusBoundary& boundary) = 0;
virtual json generateReport() const = 0;
static bool isValidUserId(const std::string& id) {
std::regex pattern("(STU|TCH)[0-9]+");
return std::regex_match(id, pattern);
}
};
class Student : public User {
public:
Student(int id, const std::string& n) : User(id, n, "Student") {}
void markAttendance(double lat, double lon, const AcademicCalendar& cal, const std::string& date, const CampusBoundary& boundary) override {
if (cal.isHoliday(date) && !cal.hasExtraClass(date)) {
throw std::invalid_argument("Cannot mark attendance on holiday without extra class (Student)");
}
if (lat < -90.0 || lat > 90.0 || lon < -180.0 || lon > 180.0) {
throw std::invalid_argument("Invalid geolocation coordinates");
}
if (!boundary.isWithinBounds(lat, lon)) {
throw std::invalid_argument("User is outside campus boundaries");
}
}
json generateReport() const override {
return {{"id", userId}, {"name", name}, {"role", role}, {"type", "Student Report"}};
}
};
class Teacher : public User {
public:
Teacher(int id, const std::string& n) : User(id, n, "Teacher") {}
void markAttendance(double lat, double lon, const AcademicCalendar& cal, const std::string& date, const CampusBoundary& boundary) override {
if (lat < -90.0 || lat > 90.0 || lon < -180.0 || lon > 180.0) {
throw std::invalid_argument("Invalid geolocation coordinates");
}
if (!boundary.isWithinBounds(lat, lon)) {
throw std::invalid_argument("User is outside campus boundaries");
}
}
json generateReport() const override {
return {{"id", userId}, {"name", name}, {"role", role}, {"type", "Teacher Report"}};
}
};
template <typename T>
class AttendanceQueue {
private:
std::queue<T> records;
std::mutex mtx;
public:
void enqueue(const T& record) {
std::lock_guard<std::mutex> lock(mtx);
records.push(record);
}
std::vector<T> flush() {
std::lock_guard<std::mutex> lock(mtx);
std::vector<T> flushed;
while (!records.empty()) {
flushed.push_back(records.front());
records.pop();
}
return flushed;
}
};
std::string getCurrentDate() {
auto now = std::chrono::system_clock::now();
std::time_t time = std::chrono::system_clock::to_time_t(now);
std::tm local = *std::localtime(&time);
std::ostringstream oss;
oss << std::put_time(&local, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
int main() {
httplib::Server svr;
sqlite3* db;
const std::string jwt_secret = "my-secret-key"; // Change in production
if (sqlite3_open("attendance.db", &db) != SQLITE_OK) {
std::cerr << "Failed to open database: " << sqlite3_errmsg(db) << "\n";
return 1;
}
// Create tables
const char* createTables =
"CREATE TABLE IF NOT EXISTS attendance (userId INTEGER, timestamp TEXT, lat REAL, lon REAL, role TEXT, PRIMARY KEY (userId, timestamp));"
"CREATE TABLE IF NOT EXISTS users (userId INTEGER PRIMARY KEY, username TEXT UNIQUE, password TEXT, role TEXT, name TEXT);";
char* errMsg = nullptr;
if (sqlite3_exec(db, createTables, nullptr, nullptr, &errMsg) != SQLITE_OK) {
std::cerr << "Failed to create tables: " << errMsg << "\n";
sqlite3_free(errMsg);
sqlite3_close(db);
return 1;
}
// Insert sample users (for testing; remove in production)
const char* insertUsers =
"INSERT OR IGNORE INTO users (userId, username, password, role, name) VALUES "
"(1, 'stu123', 'password123', 'Student', 'Student One'),"
"(2, 'tch456', 'password456', 'Teacher', 'Teacher One');";
if (sqlite3_exec(db, insertUsers, nullptr, nullptr, &errMsg) != SQLITE_OK) {
std::cerr << "Failed to insert sample users: " << errMsg << "\n";
sqlite3_free(errMsg);
}
AcademicCalendar cal;
try {
std::ifstream f("calendar.json");
if (!f.is_open()) {
std::cerr << "Error: calendar.json not found\n";
throw std::runtime_error("Calendar file not found");
}
json j;
f >> j;
cal.loadFromJSON(j);
} catch (const std::exception& e) {
std::cerr << "Failed to load calendar: " << e.what() << "\n";
}
CampusBoundary boundary;
try {
std::ifstream f("geofence.json");
if (!f.is_open()) {
std::cerr << "Error: geofence.json not found\n";
throw std::runtime_error("Geofence configuration file not found");
}
json j;
f >> j;
boundary.loadFromJSON(j);
std::cout << "Geofence loaded: minLat=" << boundary.minLat << ", maxLat=" << boundary.maxLat
<< ", minLon=" << boundary.minLon << ", maxLon=" << boundary.maxLon << "\n";
} catch (const std::exception& e) {
std::cerr << "Failed to load geofence configuration: " << e.what() << "\n";
boundary.minLat = 37.7749;
boundary.maxLat = 37.7849;
boundary.minLon = -122.4194;
boundary.maxLon = -122.4094;
std::cout << "Using default geofence boundaries\n";
}
AttendanceQueue<AttendanceRecord> queue;
// Add CORS headers
svr.set_pre_routing_handler([](const httplib::Request& req, httplib::Response& res) {
res.set_header("Access-Control-Allow-Origin", "https://atomicintel.vercel.app");
res.set_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (req.method == "OPTIONS") {
res.status = 200;
return httplib::Server::HandlerResponse::Handled;
}
return httplib::Server::HandlerResponse::Unhandled;
});
svr.Post("/login", [&](const httplib::Request& req, httplib::Response& res) {
try {
json data = json::parse(req.body);
if (!data.contains("username") || !data.contains("password")) {
throw std::invalid_argument("Missing username or password");
}
std::string username = data["username"].get<std::string>();
std::string password = data["password"].get<std::string>();
sqlite3_stmt* stmt;
const char* sql = "SELECT userId, role, name FROM users WHERE username = ? AND password = ?;";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
throw std::runtime_error("Failed to prepare login statement");
}
sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, password.c_str(), -1, SQLITE_STATIC);
if (sqlite3_step(stmt) != SQLITE_ROW) {
sqlite3_finalize(stmt);
throw std::invalid_argument("Invalid username or password");
}
int userId = sqlite3_column_int(stmt, 0);
std::string role = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
std::string name = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
sqlite3_finalize(stmt);
std::string token = generate_jwt(userId, role, jwt_secret);
res.set_content(json({{"status", "success"}, {"token", token}, {"userId", userId}, {"role", role}, {"name", name}}).dump(), "application/json");
} catch (const std::exception& e) {
res.status = 401;
res.set_content(json({{"error", e.what()}}).dump(), "application/json");
}
});
auto authenticate = [&](const httplib::Request& req, httplib::Response& res, auto callback) {
if (!req.has_header("Authorization")) {
res.status = 401;
res.set_content(json({{"error", "Missing Authorization header"}}).dump(), "application/json");
return false;
}
std::string token = req.get_header_value("Authorization");
if (token.substr(0, 7) == "Bearer ") token = token.substr(7);
int userId;
std::string role;
if (!verify_jwt(token, jwt_secret, userId, role)) {
res.status = 401;
res.set_content(json({{"error", "Invalid or expired token"}}).dump(), "application/json");
return false;
}
return callback(req, res, userId, role);
};
svr.Get("/calendar", [&](const httplib::Request& req, httplib::Response& res) {
authenticate(req, res, [&](const httplib::Request& req, httplib::Response& res, int userId, std::string role) {
try {
json j = {{"holidays", cal.holidays}, {"extraClasses", cal.extraClasses}};
res.set_content(j.dump(), "application/json");
} catch (const std::exception& e) {
res.status = 500;
res.set_content(json({{"error", "Failed to fetch calendar: " + std::string(e.what())}}).dump(), "application/json");
}
return true;
});
});
svr.Get("/geofence", [&](const httplib::Request& req, httplib::Response& res) {
authenticate(req, res, [&](const httplib::Request& req, httplib::Response& res, int userId, std::string role) {
try {
res.set_content(boundary.toJSON().dump(), "application/json");
} catch (const std::exception& e) {
res.status = 500;
res.set_content(json({{"error", "Failed to fetch geofence: " + std::string(e.what())}}).dump(), "application/json");
}
return true;
});
});
svr.Post("/mark-attendance", [&](const httplib::Request& req, httplib::Response& res) {
authenticate(req, res, [&](const httplib::Request& req, httplib::Response& res, int userId, std::string role) {
try {
json data = json::parse(req.body);
if (!data.contains("lat") || !data.contains("lon")) {
throw std::invalid_argument("Missing required fields");
}
double lat = data["lat"].get<double>();
double lon = data["lon"].get<double>();
std::string date = getCurrentDate();
User* user;
if (role == "Student") {
user = new Student(userId, "Name");
} else if (role == "Teacher") {
user = new Teacher(userId, "Name");
} else {
throw std::invalid_argument("Invalid role");
}
user->markAttendance(lat, lon, cal, date.substr(0, 10), boundary);
sqlite3_stmt* stmt;
const char* sql = "INSERT INTO attendance (userId, timestamp, lat, lon, role) VALUES (?, ?, ?, ?, ?);";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
throw std::runtime_error("Failed to prepare statement: " + std::string(sqlite3_errmsg(db)));
}
sqlite3_bind_int(stmt, 1, userId);
sqlite3_bind_text(stmt, 2, date.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_double(stmt, 3, lat);
sqlite3_bind_double(stmt, 4, lon);
sqlite3_bind_text(stmt, 5, role.c_str(), -1, SQLITE_STATIC);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
throw std::runtime_error("Failed to insert attendance: " + std::string(sqlite3_errmsg(db)));
}
sqlite3_finalize(stmt);
json response = {{"status", "success"}, {"report", user->generateReport()}};
res.set_content(response.dump(), "application/json");
delete user;
} catch (const std::exception& e) {
res.status = 400;
res.set_content(json({{"error", e.what()}}).dump(), "application/json");
}
return true;
});
});
svr.Get("/reports", [&](const httplib::Request& req, httplib::Response& res) {
authenticate(req, res, [&](const httplib::Request& req, httplib::Response& res, int userId, std::string role) {
try {
json reports = json::array();
const char* sql = "SELECT userId, timestamp, lat, lon, role FROM attendance WHERE userId = ? ORDER BY timestamp DESC;";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
throw std::runtime_error("Failed to prepare statement: " + std::string(sqlite3_errmsg(db)));
}
sqlite3_bind_int(stmt, 1, userId);
while (sqlite3_step(stmt) == SQLITE_ROW) {
json record = {
{"userId", sqlite3_column_int(stmt, 0)},
{"timestamp", reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1))},
{"lat", sqlite3_column_double(stmt, 2)},
{"lon", sqlite3_column_double(stmt, 3)},
{"role", reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4))}
};
reports.push_back(record);
}
sqlite3_finalize(stmt);
res.set_content(reports.dump(), "application/json");
} catch (const std::exception& e) {
res.status = 500;
res.set_content(json({{"error", "Failed to fetch reports: " + std::string(e.what())}}).dump(), "application/json");
}
return true;
});
});
svr.Post("/sync", [&](const httplib::Request& req, httplib::Response& res) {
authenticate(req, res, [&](const httplib::Request& req, httplib::Response& res, int userId, std::string role) {
try {
json data = json::parse(req.body);
if (!data.is_array()) {
throw std::invalid_argument("Expected array of records");
}
sqlite3_stmt* stmt;
const char* sql = "INSERT OR IGNORE INTO attendance (userId, timestamp, lat, lon, role) VALUES (?, ?, ?, ?, ?);";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
throw std::runtime_error("Failed to prepare sync statement: " + std::string(sqlite3_errmsg(db)));
}
for (const auto& item : data) {
if (!item.contains("userId") || !item.contains("timestamp") || !item.contains("lat") || !item.contains("lon") || !item.contains("role")) {
continue;
}
int recordUserId = item["userId"].get<int>();
if (recordUserId != userId) continue;
std::string timestamp = item["timestamp"].get<std::string>();
double lat = item["lat"].get<double>();
double lon = item["lon"].get<double>();
std::string recordRole = item["role"].get<std::string>();
if (!boundary.isWithinBounds(lat, lon)) {
std::cerr << "Sync skipped record outside geofence: userId=" << recordUserId << ", lat=" << lat << ", lon=" << lon << "\n";
continue;
}
sqlite3_bind_int(stmt, 1, recordUserId);
sqlite3_bind_text(stmt, 2, timestamp.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_double(stmt, 3, lat);
sqlite3_bind_double(stmt, 4, lon);
sqlite3_bind_text(stmt, 5, recordRole.c_str(), -1, SQLITE_STATIC);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
throw std::runtime_error("Failed to sync record: " + std::string(sqlite3_errmsg(db)));
}
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
res.set_content(json({{"status", "synced"}}).dump(), "application/json");
} catch (const std::exception& e) {
res.status = 400;
res.set_content(json({{"error", e.what()}}).dump(), "application/json");
}
return true;
});
});
std::cout << "Backend running on port 8080\n";
svr.listen("0.0.0.0", 8080);
sqlite3_close(db);
return 0;
}