coderland 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/config.js +6 -0
- package/index.js +52 -0
- package/package.json +37 -0
- package/public/client.js +133 -0
- package/public/dashboard.html +54 -0
- package/public/index.html +37 -0
- package/public/style.css +95 -0
- package/routes/action.routes.js +68 -0
- package/routes/auth.routes.js +39 -0
- package/routes/system.routes.js +33 -0
- package/services/api.service.js +72 -0
- package/services/auth.service.js +24 -0
- package/services/system.service.js +38 -0
- package/utils/logger.util.js +17 -0
- package/utils/uid.util.js +27 -0
package/README.md
ADDED
package/config.js
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const config = require("./config");
|
|
4
|
+
const logger = require("./utils/logger.util");
|
|
5
|
+
|
|
6
|
+
const authRoutes = require("./routes/auth.routes");
|
|
7
|
+
const systemRoutes = require("./routes/system.routes");
|
|
8
|
+
const actionRoutes = require("./routes/action.routes");
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
|
|
12
|
+
// Middleware
|
|
13
|
+
app.use(express.json());
|
|
14
|
+
app.use(express.static(path.join(__dirname, "public")));
|
|
15
|
+
|
|
16
|
+
// Routes
|
|
17
|
+
app.use("/api/auth", authRoutes);
|
|
18
|
+
app.use("/api/system", systemRoutes);
|
|
19
|
+
app.use("/api/action", actionRoutes);
|
|
20
|
+
|
|
21
|
+
// Root route to serve index.html (redundant with static, but explicit is good)
|
|
22
|
+
app.get("/", (req, res) => {
|
|
23
|
+
res.sendFile(path.join(__dirname, "public", "index.html"));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Dashboard route
|
|
27
|
+
app.get("/dashboard", (req, res) => {
|
|
28
|
+
res.sendFile(path.join(__dirname, "public", "dashboard.html"));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const readline = require("readline");
|
|
32
|
+
|
|
33
|
+
// Interactive startup to get Server URL
|
|
34
|
+
const rl = readline.createInterface({
|
|
35
|
+
input: process.stdin,
|
|
36
|
+
output: process.stdout,
|
|
37
|
+
});
|
|
38
|
+
// (default: ${config.serverUrl})
|
|
39
|
+
rl.question(`Enter Server URL : `, (url) => {
|
|
40
|
+
if (url && url.trim()) {
|
|
41
|
+
config.serverUrl = url.trim().replace(/\/$/, "");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`Using Server URL: ${config.serverUrl}`);
|
|
45
|
+
rl.close();
|
|
46
|
+
|
|
47
|
+
// Start server after configuration
|
|
48
|
+
app.listen(config.port, () => {
|
|
49
|
+
logger.info(`Client running on http://localhost:${config.port}`);
|
|
50
|
+
// logger.info(`Connected to Server at: ${config.serverUrl}`);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coderland",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Coderland works to bridge the gap between trainer and students",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node index.js",
|
|
8
|
+
"dev": "nodemon index.js",
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/programmerrush/coderland.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"coderland",
|
|
17
|
+
"client",
|
|
18
|
+
"collaboration",
|
|
19
|
+
"student",
|
|
20
|
+
"trainer"
|
|
21
|
+
],
|
|
22
|
+
"author": "Rushikesh",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/programmerrush/coderland/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/programmerrush/coderland#readme",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"dotenv": "^16.3.1",
|
|
30
|
+
"express": "^4.18.2",
|
|
31
|
+
"node-fetch": "^2.7.0",
|
|
32
|
+
"systeminformation": "^5.21.22"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"nodemon": "^3.0.1"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/public/client.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Client-side shared logic
|
|
2
|
+
|
|
3
|
+
// Helper Status Fetcher
|
|
4
|
+
async function getStatus() {
|
|
5
|
+
try {
|
|
6
|
+
const response = await fetch("/api/system/status");
|
|
7
|
+
if (!response.ok) {
|
|
8
|
+
throw new Error("Failed to fetch status");
|
|
9
|
+
}
|
|
10
|
+
return await response.json();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error("Error fetching status:", error);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
18
|
+
// --- JOIN PAGE LOGIC (index.html) ---
|
|
19
|
+
const joinForm = document.getElementById("join-form");
|
|
20
|
+
if (joinForm) {
|
|
21
|
+
joinForm.addEventListener("submit", async (e) => {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
const uid = document.getElementById("uid").value;
|
|
24
|
+
// Removed classCode input, handled by backend default
|
|
25
|
+
const consent = document.getElementById("consent").checked;
|
|
26
|
+
const msgEl = document.getElementById("message");
|
|
27
|
+
|
|
28
|
+
if (!consent) {
|
|
29
|
+
msgEl.textContent = "You must agree to continue.";
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch("/api/auth/join", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json" },
|
|
37
|
+
body: JSON.stringify({ uid, consent }), // Removed classCode from body
|
|
38
|
+
});
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
|
|
41
|
+
if (data.redirect) {
|
|
42
|
+
window.location.href = data.redirect;
|
|
43
|
+
} else {
|
|
44
|
+
msgEl.textContent = data.message || "Join failed.";
|
|
45
|
+
msgEl.className = "error";
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
msgEl.textContent = "Error connecting to server.";
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- DASHBOARD PAGE LOGIC (dashboard.html) ---
|
|
54
|
+
const uidDisplay = document.getElementById("uid-display");
|
|
55
|
+
if (uidDisplay) {
|
|
56
|
+
const status = await getStatus();
|
|
57
|
+
if (status && status.uid) {
|
|
58
|
+
uidDisplay.textContent = status.uid;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Fetch Trainer Messages
|
|
62
|
+
const msgContainer = document.getElementById("trainer-messages");
|
|
63
|
+
if (msgContainer) {
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch("/api/action/messages");
|
|
66
|
+
const data = await res.json();
|
|
67
|
+
|
|
68
|
+
msgContainer.innerHTML = ""; // Clear loading
|
|
69
|
+
|
|
70
|
+
if (data.success && data.messages && data.messages.length > 0) {
|
|
71
|
+
data.messages.forEach((msg) => {
|
|
72
|
+
const div = document.createElement("div");
|
|
73
|
+
div.className = "message-item";
|
|
74
|
+
|
|
75
|
+
const text = document.createElement("div");
|
|
76
|
+
text.textContent = msg.text;
|
|
77
|
+
|
|
78
|
+
const time = document.createElement("span");
|
|
79
|
+
time.className = "message-time";
|
|
80
|
+
time.textContent = new Date(msg.timestamp).toLocaleTimeString();
|
|
81
|
+
|
|
82
|
+
div.appendChild(text);
|
|
83
|
+
div.appendChild(time);
|
|
84
|
+
msgContainer.appendChild(div);
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
msgContainer.innerHTML =
|
|
88
|
+
'<div style="color: #666;">No messages from trainer.</div>';
|
|
89
|
+
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
msgContainer.innerHTML =
|
|
92
|
+
'<div style="color: red;">Failed to load messages.</div>';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const raiseHandBtn = document.getElementById("raise-hand-btn");
|
|
98
|
+
if (raiseHandBtn) {
|
|
99
|
+
raiseHandBtn.addEventListener("click", async () => {
|
|
100
|
+
const msgEl = document.getElementById("hand-message");
|
|
101
|
+
msgEl.textContent = "Raising hand...";
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch("/api/action/raise-hand", { method: "POST" });
|
|
104
|
+
const data = await res.json();
|
|
105
|
+
msgEl.textContent = data.message;
|
|
106
|
+
setTimeout(() => (msgEl.textContent = ""), 3000);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
msgEl.textContent = "Failed to raise hand.";
|
|
109
|
+
msgEl.className = "error";
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Removed messageForm logic
|
|
115
|
+
|
|
116
|
+
const resetBtn = document.getElementById("reset-btn");
|
|
117
|
+
if (resetBtn) {
|
|
118
|
+
resetBtn.addEventListener("click", async () => {
|
|
119
|
+
if (!confirm("Are you sure you want to reset? This will sign you out."))
|
|
120
|
+
return;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const res = await fetch("/api/action/reset", { method: "POST" });
|
|
124
|
+
const data = await res.json();
|
|
125
|
+
if (data.redirect) {
|
|
126
|
+
window.location.href = data.redirect;
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
alert("Reset failed.");
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Classroom Client - Dashboard</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css" />
|
|
8
|
+
<style>
|
|
9
|
+
#trainer-messages {
|
|
10
|
+
background: #e9ecef;
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
padding: 10px;
|
|
13
|
+
margin: 1.5rem 0;
|
|
14
|
+
text-align: left;
|
|
15
|
+
min-height: 100px;
|
|
16
|
+
max-height: 300px;
|
|
17
|
+
overflow-y: auto;
|
|
18
|
+
}
|
|
19
|
+
.message-item {
|
|
20
|
+
background: white;
|
|
21
|
+
padding: 8px;
|
|
22
|
+
margin-bottom: 8px;
|
|
23
|
+
border-radius: 4px;
|
|
24
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
25
|
+
}
|
|
26
|
+
.message-time {
|
|
27
|
+
font-size: 0.75rem;
|
|
28
|
+
color: #888;
|
|
29
|
+
margin-top: 4px;
|
|
30
|
+
display: block;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<div class="container">
|
|
36
|
+
<h1>Dashboard</h1>
|
|
37
|
+
<div class="input-group">
|
|
38
|
+
<label>Welcome, <span id="uid-display">Student</span></label>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<button id="raise-hand-btn">✋ Raise Hand</button>
|
|
42
|
+
<div id="hand-message" class="success"></div>
|
|
43
|
+
|
|
44
|
+
<div id="trainer-messages">
|
|
45
|
+
<div style="color: #666; font-style: italic">Loading messages...</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<button id="reset-btn" class="secondary" style="margin-top: 2rem">
|
|
49
|
+
Reset Client
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
<script src="client.js"></script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Classroom Client - Join</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>Join Class</h1>
|
|
12
|
+
|
|
13
|
+
<form id="join-form">
|
|
14
|
+
<div class="input-group">
|
|
15
|
+
<label for="uid">Name</label>
|
|
16
|
+
<input
|
|
17
|
+
type="text"
|
|
18
|
+
id="uid"
|
|
19
|
+
name="uid"
|
|
20
|
+
required
|
|
21
|
+
placeholder="Enter your name"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="input-group">
|
|
26
|
+
<label>
|
|
27
|
+
<input type="checkbox" id="consent" required />
|
|
28
|
+
I agree to send my system data for academic purposes
|
|
29
|
+
</label>
|
|
30
|
+
</div>
|
|
31
|
+
<button type="submit">Join Class</button>
|
|
32
|
+
<div id="message" class="error"></div>
|
|
33
|
+
</form>
|
|
34
|
+
</div>
|
|
35
|
+
<script src="client.js"></script>
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
package/public/style.css
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* Basic Clean Style */
|
|
2
|
+
body {
|
|
3
|
+
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
|
4
|
+
background-color: #f4f4f9;
|
|
5
|
+
color: #333;
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
align-items: center;
|
|
9
|
+
height: 100vh;
|
|
10
|
+
margin: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.container {
|
|
14
|
+
background: white;
|
|
15
|
+
padding: 2rem;
|
|
16
|
+
border-radius: 8px;
|
|
17
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
18
|
+
width: 100%;
|
|
19
|
+
max-width: 400px;
|
|
20
|
+
text-align: center;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
h1 {
|
|
24
|
+
margin-bottom: 1.5rem;
|
|
25
|
+
color: #444;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.input-group {
|
|
29
|
+
margin-bottom: 1rem;
|
|
30
|
+
text-align: left;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
label {
|
|
34
|
+
display: block;
|
|
35
|
+
margin-bottom: 0.5rem;
|
|
36
|
+
font-weight: bold;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
input[type="text"],
|
|
40
|
+
textarea {
|
|
41
|
+
width: 100%;
|
|
42
|
+
padding: 0.75rem;
|
|
43
|
+
border: 1px solid #ddd;
|
|
44
|
+
border-radius: 4px;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
font-size: 1rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
input[type="checkbox"] {
|
|
50
|
+
margin-right: 0.5rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
button {
|
|
54
|
+
background-color: #007bff;
|
|
55
|
+
color: white;
|
|
56
|
+
border: none;
|
|
57
|
+
padding: 0.75rem 1.5rem;
|
|
58
|
+
font-size: 1rem;
|
|
59
|
+
border-radius: 4px;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
transition: background-color 0.2s;
|
|
62
|
+
width: 100%;
|
|
63
|
+
margin-top: 1rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
button:hover {
|
|
67
|
+
background-color: #0056b3;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
button.secondary {
|
|
71
|
+
background-color: #6c757d;
|
|
72
|
+
margin-top: 0.5rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
button.secondary:hover {
|
|
76
|
+
background-color: #545b62;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.info {
|
|
80
|
+
margin-top: 1rem;
|
|
81
|
+
font-size: 0.9rem;
|
|
82
|
+
color: #666;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.error {
|
|
86
|
+
color: #dc3545;
|
|
87
|
+
margin-top: 0.5rem;
|
|
88
|
+
font-size: 0.9rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.success {
|
|
92
|
+
color: #28a745;
|
|
93
|
+
margin-top: 0.5rem;
|
|
94
|
+
font-size: 0.9rem;
|
|
95
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const apiService = require("../services/api.service");
|
|
4
|
+
const uidUtil = require("../utils/uid.util");
|
|
5
|
+
|
|
6
|
+
// Raise Hand
|
|
7
|
+
router.post("/raise-hand", async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
await apiService.post("/raise-hand", { timestamp: Date.now() });
|
|
10
|
+
res.json({ success: true, message: "Hand raised!" });
|
|
11
|
+
} catch (error) {
|
|
12
|
+
res.status(500).json({ success: false, message: error.message });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Send Message
|
|
17
|
+
router.post("/message", async (req, res) => {
|
|
18
|
+
const { title, message } = req.body;
|
|
19
|
+
|
|
20
|
+
if (!title || title.length > 5) {
|
|
21
|
+
return res
|
|
22
|
+
.status(400)
|
|
23
|
+
.json({ success: false, message: "Title must be 1-5 characters." });
|
|
24
|
+
}
|
|
25
|
+
if (!message || message.length > 200) {
|
|
26
|
+
return res
|
|
27
|
+
.status(400)
|
|
28
|
+
.json({ success: false, message: "Message must be 1-200 characters." });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await apiService.post("/message", {
|
|
33
|
+
title,
|
|
34
|
+
message,
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
});
|
|
37
|
+
res.json({ success: true, message: "Message sent!" });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
res.status(500).json({ success: false, message: error.message });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Reset Client
|
|
44
|
+
router.post("/reset", (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
uidUtil.resetData();
|
|
47
|
+
res.json({ success: true, redirect: "/" });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
res
|
|
50
|
+
.status(500)
|
|
51
|
+
.json({ success: false, message: "Failed to reset client." });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Get Trainer Messages
|
|
56
|
+
router.get("/messages", async (req, res) => {
|
|
57
|
+
try {
|
|
58
|
+
// In real scenario, we might pass a timestamp or since param
|
|
59
|
+
const response = await apiService.get("/messages");
|
|
60
|
+
const messages = response.messages || [];
|
|
61
|
+
res.json({ success: true, messages });
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// Fallback or empty if error
|
|
64
|
+
res.json({ success: false, messages: [] });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
module.exports = router;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const authService = require("../services/auth.service");
|
|
4
|
+
const systemService = require("../services/system.service");
|
|
5
|
+
|
|
6
|
+
router.post("/join", async (req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
let { uid, consent } = req.body;
|
|
9
|
+
|
|
10
|
+
if (!uid || typeof uid !== "string") {
|
|
11
|
+
return res
|
|
12
|
+
.status(400)
|
|
13
|
+
.json({ success: false, message: "Name/UID is required." });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Alphanumeric check - relaxed to allow spaces for Names if needed,
|
|
17
|
+
// but user requirement was previously alphanumeric.
|
|
18
|
+
// "Name of the user" implies spaces might be needed.
|
|
19
|
+
// Let's allow spaces for now.
|
|
20
|
+
if (!/^[a-zA-Z0-9 ]+$/.test(uid)) {
|
|
21
|
+
return res
|
|
22
|
+
.status(400)
|
|
23
|
+
.json({ success: false, message: "Name must be alphanumeric." });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const uidUtil = require("../utils/uid.util");
|
|
27
|
+
uidUtil.saveUID(uid);
|
|
28
|
+
|
|
29
|
+
await authService.joinClass(consent);
|
|
30
|
+
// After joining, send system info immediately
|
|
31
|
+
await systemService.sendSystemInfo();
|
|
32
|
+
|
|
33
|
+
res.json({ success: true, redirect: "/dashboard.html" });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
res.status(400).json({ success: false, message: error.message });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
module.exports = router;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const systemService = require("../services/system.service");
|
|
4
|
+
const uidUtil = require("../utils/uid.util");
|
|
5
|
+
|
|
6
|
+
router.get("/status", (req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
const uid = uidUtil.getUID();
|
|
9
|
+
res.json({ uid });
|
|
10
|
+
} catch (error) {
|
|
11
|
+
res.status(500).json({ success: false, message: "Failed to get status." });
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
router.post("/info", async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
await systemService.sendSystemInfo();
|
|
18
|
+
res.json({ success: true, message: "System info sent." });
|
|
19
|
+
} catch (error) {
|
|
20
|
+
res.status(500).json({ success: false, message: error.message });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
router.get("/preview", async (req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const details = await systemService.getSystemDetails();
|
|
27
|
+
res.json(details);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
res.status(500).json({ success: false, message: error.message });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
module.exports = router;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const fetch = require("node-fetch");
|
|
2
|
+
const config = require("../config");
|
|
3
|
+
const uidUtil = require("../utils/uid.util");
|
|
4
|
+
const logger = require("../utils/logger.util");
|
|
5
|
+
|
|
6
|
+
async function post(endpoint, body) {
|
|
7
|
+
const uid = uidUtil.getUID();
|
|
8
|
+
const url = `${config.serverUrl}${endpoint}`;
|
|
9
|
+
|
|
10
|
+
// Ensure UID is in body for identification
|
|
11
|
+
const finalBody = { ...body };
|
|
12
|
+
if (!finalBody.uid && uid) {
|
|
13
|
+
finalBody.uid = uid;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(url, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
body: JSON.stringify(finalBody),
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
"x-uid": uid || "",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const errorText = await response.text();
|
|
28
|
+
logger.error(
|
|
29
|
+
`API Error: ${response.status} ${response.statusText} - ${errorText}`,
|
|
30
|
+
);
|
|
31
|
+
throw new Error(`API Error: ${response.statusText}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return await response.json();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
logger.error(`Network Error: ${error.message}`);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function get(endpoint) {
|
|
42
|
+
const uid = uidUtil.getUID();
|
|
43
|
+
const url = `${config.serverUrl}${endpoint}`;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch(url, {
|
|
47
|
+
method: "GET",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"x-uid": uid || "",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const errorText = await response.text();
|
|
56
|
+
logger.error(
|
|
57
|
+
`API Error: ${response.status} ${response.statusText} - ${errorText}`,
|
|
58
|
+
);
|
|
59
|
+
throw new Error(`API Error: ${response.statusText}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return await response.json();
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.error(`Network Error: ${error.message}`);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
post,
|
|
71
|
+
get,
|
|
72
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const apiService = require("./api.service");
|
|
2
|
+
const uidUtil = require("../utils/uid.util");
|
|
3
|
+
|
|
4
|
+
async function joinClass(consent) {
|
|
5
|
+
if (!consent) {
|
|
6
|
+
throw new Error("You must agree to send system data.");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const uid = uidUtil.getUID();
|
|
10
|
+
if (!uid) {
|
|
11
|
+
throw new Error("UID not found. Please restart the application.");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await apiService.post("/join", { uid });
|
|
16
|
+
return { success: true, message: "Joined class successfully." };
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error("Failed to join class: " + error.message);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
joinClass,
|
|
24
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const si = require("systeminformation");
|
|
2
|
+
const apiService = require("./api.service");
|
|
3
|
+
const uidUtil = require("../utils/uid.util");
|
|
4
|
+
|
|
5
|
+
async function getSystemDetails() {
|
|
6
|
+
const osInfo = await si.osInfo();
|
|
7
|
+
const cpu = await si.cpu();
|
|
8
|
+
const mem = await si.mem();
|
|
9
|
+
const nodeVersion = process.version;
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
os: `${osInfo.platform} ${osInfo.release}`,
|
|
13
|
+
cpu: `${cpu.manufacturer} ${cpu.brand}`,
|
|
14
|
+
totalRam: `${(mem.total / 1024 / 1024 / 1024).toFixed(2)} GB`,
|
|
15
|
+
nodeVersion: nodeVersion,
|
|
16
|
+
platform: process.platform,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function sendSystemInfo() {
|
|
21
|
+
try {
|
|
22
|
+
const details = await getSystemDetails();
|
|
23
|
+
const systemData = {
|
|
24
|
+
...details,
|
|
25
|
+
uid: uidUtil.getUID(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await apiService.post("/system-info", systemData);
|
|
29
|
+
return { success: true, message: "System info sent successfully." };
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error("Failed to send system info: " + error.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
sendSystemInfo,
|
|
37
|
+
getSystemDetails,
|
|
38
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function log(message, type = "INFO") {
|
|
2
|
+
const timestamp = new Date().toISOString();
|
|
3
|
+
console.log(`[${timestamp}] [${type}] ${message}`);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function info(message) {
|
|
7
|
+
log(message, "INFO");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function error(message) {
|
|
11
|
+
log(message, "ERROR");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
info,
|
|
16
|
+
error,
|
|
17
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// In-memory storage for UID (no file persistence)
|
|
2
|
+
let currentUid = null;
|
|
3
|
+
|
|
4
|
+
function getUID() {
|
|
5
|
+
return currentUid;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function saveUID(uid) {
|
|
9
|
+
currentUid = uid;
|
|
10
|
+
return currentUid;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function resetData() {
|
|
14
|
+
currentUid = null;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getUIDData() {
|
|
19
|
+
return { uid: currentUid };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
getUID,
|
|
24
|
+
saveUID,
|
|
25
|
+
resetData,
|
|
26
|
+
getUIDData,
|
|
27
|
+
};
|