Skip to content

Commit e4f3a3b

Browse files
committed
Science Commit 5.
1 parent 8f528be commit e4f3a3b

6 files changed

Lines changed: 450 additions & 1 deletion

File tree

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
2+
<!DOCTYPE html>
3+
<html lang="en">
4+
<head>
5+
<meta charset="UTF-8"/>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7+
<title>Profile — NWE Chat™</title>
8+
<link rel="stylesheet" href="css/style.css"/>
9+
<script src="js/scroll-preserve.js"></script>
10+
</head>
11+
<body>
12+
<nav class="nav"><div class="nav-inner">
13+
<span class="nav-brand">NWE Chat™</span>
14+
<ul class="nav-links">
15+
<li><a href="index.jsp">Chat</a></li>
16+
<li><a href="account.jsp">Account</a></li>
17+
<li><a href="profile.jsp" class="active">Profile</a></li>
18+
<li><a href="federation.jsp">Federation</a></li>
19+
<li><a href="settings.jsp">Settings</a></li>
20+
<li><a href="admin.jsp">Admin</a></li>
21+
<li><a href="status.jsp">Status</a></li>
22+
</ul>
23+
</div></nav>
24+
25+
<section class="hero">
26+
<div class="hero-inner">
27+
<span class="hero-tag">Your Profile — NWE Chat™</span>
28+
<h1>Profile</h1>
29+
<p>Manage your profile picture, resume, and view other users' profiles.</p>
30+
</div>
31+
</section>
32+
33+
<section class="section">
34+
<div class="section-inner">
35+
<h2>Profile Picture</h2>
36+
<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:1.5rem;">
37+
<div style="display:flex;align-items:center;gap:1.5rem;">
38+
<div id="avatar-preview" style="width:96px;height:96px;border-radius:50%;background:var(--bg-section);border:2px solid var(--purple);display:flex;align-items:center;justify-content:center;overflow:hidden;">
39+
<span style="font-size:2rem;color:var(--text-muted);">👤</span>
40+
</div>
41+
<div>
42+
<p style="color:var(--text-muted);font-size:0.85rem;margin-bottom:0.75rem;">Upload a profile picture (jpg, png, gif, webp). Visible to other chat users.</p>
43+
<input type="file" id="profile-pic-input" accept="image/jpeg,image/png,image/gif,image/webp" onchange="previewPic(this)" style="font-size:0.8rem;color:var(--text-muted);"/>
44+
<br/><button class="btn btn-primary" style="margin-top:0.75rem;" onclick="uploadPic()">Upload Profile Picture</button>
45+
</div>
46+
</div>
47+
</div>
48+
</div>
49+
</section>
50+
51+
<section class="section">
52+
<div class="section-inner">
53+
<h2>Resume</h2>
54+
<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:1.5rem;">
55+
<p style="color:var(--text-muted);font-size:0.85rem;margin-bottom:0.75rem;">Upload your resume if wanted. Accepted formats: pdf, doc, docx, txt, rtf, odt. Viewable by other users on your profile.</p>
56+
<input type="file" id="resume-input" accept=".pdf,.doc,.docx,.txt,.rtf,.odt" style="font-size:0.8rem;color:var(--text-muted);"/>
57+
<br/><button class="btn btn-primary" style="margin-top:0.75rem;" onclick="uploadResume()">Upload Resume</button>
58+
<p id="resume-status" style="margin-top:0.5rem;font-size:0.8rem;color:var(--purple-hover);"></p>
59+
</div>
60+
</div>
61+
</section>
62+
63+
<section class="section">
64+
<div class="section-inner">
65+
<h2>My Profile Info</h2>
66+
<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:1.5rem;">
67+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;font-size:0.85rem;">
68+
<span style="color:var(--text-muted);">Username</span><span>(login required)</span>
69+
<span style="color:var(--text-muted);">Email</span><span>(login required)</span>
70+
<span style="color:var(--text-muted);">Profile Picture</span><span>(login required)</span>
71+
<span style="color:var(--text-muted);">Resume</span><span>(login required)</span>
72+
<span style="color:var(--text-muted);">Geo</span><span>(login required)</span>
73+
<span style="color:var(--text-muted);">Federated Connects</span><span>(login required)</span>
74+
<span style="color:var(--text-muted);">Rank</span><span>(login required)</span>
75+
<span style="color:var(--text-muted);">Member Since</span><span>(login required)</span>
76+
</div>
77+
</div>
78+
</div>
79+
</section>
80+
81+
<section class="section">
82+
<div class="section-inner">
83+
<h2>View Another User's Profile</h2>
84+
<form style="display:flex;gap:0.75rem;align-items:flex-end;flex-wrap:wrap;max-width:500px;">
85+
<div class="form-group" style="margin:0;flex:1;">
86+
<label>Username</label>
87+
<input type="text" id="view-user" placeholder="Enter username"/>
88+
</div>
89+
<button type="button" class="btn btn-ghost" onclick="viewProfile()">View</button>
90+
</form>
91+
<div id="profile-result" style="margin-top:1rem;"></div>
92+
</div>
93+
</section>
94+
95+
<footer class="footer">
96+
<span>NWE Chat™ — Profile — MEARVK LLC — NitroWebExpress™ 2026</span>
97+
</footer>
98+
99+
<script>
100+
function previewPic(input) {
101+
if (input.files && input.files[0]) {
102+
var reader = new FileReader();
103+
reader.onload = function(e) {
104+
document.getElementById('avatar-preview').innerHTML = '<img src="' + e.target.result + '" style="width:100%;height:100%;object-fit:cover;"/>';
105+
};
106+
reader.readAsDataURL(input.files[0]);
107+
}
108+
}
109+
function uploadPic() {
110+
var input = document.getElementById('profile-pic-input');
111+
if (!input.files || !input.files[0]) { alert('Select an image first.'); return; }
112+
alert('Profile picture "' + input.files[0].name + '" ready for upload via SET_PROFILE_PIC on port 49230.');
113+
}
114+
function uploadResume() {
115+
var input = document.getElementById('resume-input');
116+
if (!input.files || !input.files[0]) { alert('Select a resume file first.'); return; }
117+
document.getElementById('resume-status').textContent = '✓ Resume "' + input.files[0].name + '" selected. Upload via UPLOAD_RESUME on port 49230.';
118+
}
119+
function viewProfile() {
120+
var user = document.getElementById('view-user').value.trim();
121+
if (!user) { alert('Enter a username.'); return; }
122+
document.getElementById('profile-result').innerHTML = '<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:1rem;"><p style="color:var(--accent-hover);font-weight:600;">' + user + '</p><p style="color:var(--text-muted);font-size:0.85rem;">Use VIEW_PROFILE|' + user + ' via port 49230 or CD1 connector to load full profile.</p></div>';
123+
}
124+
</script>
125+
</body>
126+
</html>

modules/chat/servlets/setup-db.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ CREATE TABLE IF NOT EXISTS users (
2020
password_hash VARCHAR(128) NOT NULL,
2121
salt VARCHAR(64) NOT NULL,
2222
email VARCHAR(256),
23+
profile_picture VARCHAR(512),
24+
resume_path VARCHAR(512),
2325
ip_address VARCHAR(45),
2426
last_ip VARCHAR(45),
2527
geo_city VARCHAR(128) DEFAULT 'Unknown',

modules/chat/source/ChatServer.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ private String processCommand(String input, ChatSession session) {
184184
// ── Require login for everything below ──
185185
if (session.username == null) return "ERROR|Not logged in. Use LOGIN|username|password or REGISTER|username|password|email";
186186

187+
// ── Profile Picture & Resume ──
188+
if (upper.startsWith("SET_PROFILE_PIC|")) return setProfilePic(input, session);
189+
if (upper.startsWith("UPLOAD_RESUME|")) return uploadResume(input, session);
190+
if (upper.equals("MY_PROFILE")) return getMyProfile(session);
191+
if (upper.startsWith("VIEW_PROFILE|")) return viewUserProfile(input, session);
192+
187193
// ── Chat Commands ──
188194
if (upper.startsWith("MSG|")) return sendMessage(input, session);
189195
if (upper.startsWith("BROADCAST|")) return broadcast(input, session);
@@ -367,6 +373,81 @@ private String changeUsername(String input, ChatSession session) {
367373
}
368374
}
369375

376+
// ── Profile Picture & Resume ───────────────────────────────────────────────
377+
378+
private String setProfilePic(String input, ChatSession session) {
379+
// SET_PROFILE_PIC|filename|base64Data
380+
String[] parts = input.split("\\|", 3);
381+
if (parts.length < 3) return "ERROR|Usage: SET_PROFILE_PIC|filename.jpg|base64Data";
382+
String filename = parts[1].trim();
383+
String ext = filename.contains(".") ? filename.substring(filename.lastIndexOf('.') + 1).toLowerCase() : "";
384+
if (!java.util.Set.of("jpg", "jpeg", "png", "gif", "webp", "bmp").contains(ext))
385+
return "ERROR|Profile picture must be jpg, jpeg, png, gif, webp, or bmp.";
386+
try {
387+
java.nio.file.Path picDir = java.nio.file.Paths.get("modules/chat/user-files/" + session.username + "/profile");
388+
java.nio.file.Files.createDirectories(picDir);
389+
java.nio.file.Path picPath = picDir.resolve("avatar." + ext);
390+
java.nio.file.Files.write(picPath, java.util.Base64.getDecoder().decode(parts[2].trim()));
391+
try (Connection c = DriverManager.getConnection(DB_URL, DB_USER, getPassword())) {
392+
PreparedStatement ps = c.prepareStatement("UPDATE users SET profile_picture = ? WHERE id = ?");
393+
ps.setString(1, picPath.toString()); ps.setInt(2, session.userId); ps.executeUpdate();
394+
}
395+
return "PROFILE_PIC|OK|Profile picture set: " + filename;
396+
} catch (Exception e) { return "ERROR|" + e.getMessage(); }
397+
}
398+
399+
private String uploadResume(String input, ChatSession session) {
400+
// UPLOAD_RESUME|filename|base64Data
401+
String[] parts = input.split("\\|", 3);
402+
if (parts.length < 3) return "ERROR|Usage: UPLOAD_RESUME|resume.pdf|base64Data";
403+
String filename = parts[1].trim();
404+
String ext = filename.contains(".") ? filename.substring(filename.lastIndexOf('.') + 1).toLowerCase() : "";
405+
if (!java.util.Set.of("pdf", "doc", "docx", "txt", "rtf", "odt").contains(ext))
406+
return "ERROR|Resume must be pdf, doc, docx, txt, rtf, or odt.";
407+
try {
408+
java.nio.file.Path resumeDir = java.nio.file.Paths.get("modules/chat/user-files/" + session.username + "/resume");
409+
java.nio.file.Files.createDirectories(resumeDir);
410+
java.nio.file.Path resumePath = resumeDir.resolve(filename);
411+
java.nio.file.Files.write(resumePath, java.util.Base64.getDecoder().decode(parts[2].trim()));
412+
try (Connection c = DriverManager.getConnection(DB_URL, DB_USER, getPassword())) {
413+
PreparedStatement ps = c.prepareStatement("UPDATE users SET resume_path = ? WHERE id = ?");
414+
ps.setString(1, resumePath.toString()); ps.setInt(2, session.userId); ps.executeUpdate();
415+
}
416+
return "RESUME|OK|Resume uploaded: " + filename;
417+
} catch (Exception e) { return "ERROR|" + e.getMessage(); }
418+
}
419+
420+
private String getMyProfile(ChatSession session) {
421+
try (Connection c = DriverManager.getConnection(DB_URL, DB_USER, getPassword())) {
422+
PreparedStatement ps = c.prepareStatement("SELECT username, email, profile_picture, resume_path, geo_city, geo_country, federated_connects, created_at FROM users WHERE id = ?");
423+
ps.setInt(1, session.userId); ResultSet rs = ps.executeQuery();
424+
if (!rs.next()) return "ERROR|Profile not found.";
425+
return "PROFILE|username=" + rs.getString("username") +
426+
"|email=" + rs.getString("email") +
427+
"|profilePic=" + (rs.getString("profile_picture") != null ? "SET" : "NOT_SET") +
428+
"|resume=" + (rs.getString("resume_path") != null ? "SET" : "NOT_SET") +
429+
"|geo=" + rs.getString("geo_city") + "/" + rs.getString("geo_country") +
430+
"|federatedConnects=" + rs.getInt("federated_connects") +
431+
"|joined=" + rs.getTimestamp("created_at");
432+
} catch (Exception e) { return "ERROR|" + e.getMessage(); }
433+
}
434+
435+
private String viewUserProfile(String input, ChatSession session) {
436+
String[] parts = input.split("\\|", 2);
437+
if (parts.length < 2) return "ERROR|Usage: VIEW_PROFILE|username";
438+
try (Connection c = DriverManager.getConnection(DB_URL, DB_USER, getPassword())) {
439+
PreparedStatement ps = c.prepareStatement("SELECT username, profile_picture, resume_path, geo_city, geo_country, federated_connects, created_at FROM users WHERE username = ? AND is_deleted = FALSE");
440+
ps.setString(1, parts[1].trim()); ResultSet rs = ps.executeQuery();
441+
if (!rs.next()) return "ERROR|User not found.";
442+
return "PROFILE|username=" + rs.getString("username") +
443+
"|profilePic=" + (rs.getString("profile_picture") != null ? "SET" : "NOT_SET") +
444+
"|resume=" + (rs.getString("resume_path") != null ? "AVAILABLE" : "NONE") +
445+
"|geo=" + rs.getString("geo_city") + "/" + rs.getString("geo_country") +
446+
"|federatedConnects=" + rs.getInt("federated_connects") +
447+
"|joined=" + rs.getTimestamp("created_at");
448+
} catch (Exception e) { return "ERROR|" + e.getMessage(); }
449+
}
450+
370451
// ── Chat Operations ────────────────────────────────────────────────────────
371452

372453
private String sendMessage(String input, ChatSession session) {
@@ -799,6 +880,8 @@ private void initDatabase() {
799880
"password_hash VARCHAR(128) NOT NULL, " +
800881
"salt VARCHAR(64) NOT NULL, " +
801882
"email VARCHAR(256), " +
883+
"profile_picture VARCHAR(512), " +
884+
"resume_path VARCHAR(512), " +
802885
"ip_address VARCHAR(45), " +
803886
"last_ip VARCHAR(45), " +
804887
"geo_city VARCHAR(128) DEFAULT 'Unknown', " +

0 commit comments

Comments
 (0)