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 ADDED
@@ -0,0 +1,16 @@
1
+ # Codeland Client Application
2
+
3
+ A client application for Codeland.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install codeland
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```javascript
14
+ const codeland = require("codeland");
15
+ // usage here
16
+ ```
package/config.js ADDED
@@ -0,0 +1,6 @@
1
+ require("dotenv").config();
2
+
3
+ module.exports = {
4
+ port: 3210,
5
+ serverUrl: "",
6
+ };
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
+ }
@@ -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>
@@ -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
+ };