Realtime editing + highlights/comments + resolve workflow + clone projects
Summary (what you’re adding)
Lab 4 builds directly on Lab 3 and adds:
- Realtime collaboration (Socket.IO)
- When one user edits, others see updates quickly
- Highlight + comment system
- Select text → add comment
- Comment shows when cursor is inside the highlighted range
- Resolve comment → it disappears from open list
- Project clone
- Owner can clone a project into a new one
- New join code
- Copied document text
No AI yet.
Learning goals
- Add Socket.IO realtime events to a full-stack app
- Maintain presence lists per project
- Store comment ranges and status in the DB
- Broadcast comment creation/resolution to connected clients
- Implement clone workflow with correct permissions
Lab 4 Deliverables
- Working realtime editing between two browsers
- DB migration for comments
- Comment UI + resolve
- Clone project endpoint + UI
- README demo steps: (two browser demo + comments + clone)
Lab 4 Step-by-step
Part A — Add DB table for comments (migration)
Create server/sql/002_comments.sql:
CREATE TABLE IF NOT EXISTS project_comments (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
author_user_id INT NOT NULL,
start_index INT NOT NULL,
end_index INT NOT NULL,
comment_text TEXT NOT NULL,
status ENUM('open','resolved') NOT NULL DEFAULT 'open',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
resolved_at TIMESTAMP NULL,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (author_user_id) REFERENCES users(id) ON DELETE CASCADE
);
Then run it (simple method):
mysql -u <your_db_user> -p -h localhost <your_db_name> < server/sql/002_comments.sql
Part B — Add Socket.IO (server)
Install:
cd server npm install socket.io
Convert Express to use an HTTP server
In server/src/index.js (example structure):
import http from "http";
import { Server as SocketIOServer } from "socket.io";
const httpServer = http.createServer(app);
const io = new SocketIOServer(httpServer, {
cors: { origin: process.env.CORS_ORIGIN }
});
httpServer.listen(port, "0.0.0.0", () => console.log(`API listening on ${port}`));
Socket authentication (required)
Clients must send their JWT token. You’ll verify it on connect.
Server-side pattern:
import jwt from "jsonwebtoken";
io.use((socket, next) => {
const token = socket.handshake.auth?.token;
if (!token) return next(new Error("Missing token"));
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
socket.user = { id: payload.userId, email: payload.email };
next();
} catch {
next(new Error("Invalid token"));
}
});
Rooms (per project)
When a user opens a project page, the client emits project:join with projectId.
Server:
socket.join("project:"+projectId)- emit presence updates
Part C — Realtime editing model (simple and acceptable)
You are NOT building Google Docs. Use this simpler model:
- Client emits
doc:updatewith full text (throttled) - Server broadcasts to everyone else in project room
- DB save still occurs with “Save” button (or you may autosave every N seconds)
Throttle (client)
If you’ve never done throttling, the simple approach:
- Use a timer that sends at most every 200ms–500ms
Part D — Presence list (who is currently here)
Server keeps a map:
- key: project room
- value: set of connected users
Broadcast presence:update to room whenever:
- someone joins
- someone leaves
Client displays list of display names (you may need to request display name from DB via a /api/projects/:id/presence helper, or include it in JWT if you add it at login time).
Part E — Comments (create, show, resolve)
E1) Create comment
Backend:
POST /api/projects/:projectId/comments- membership required
- body:
{ startIndex, endIndex, commentText } - store in DB
- broadcast
comment:createdto room
Client:
- In ProjectPage textarea:
- detect selectionStart/selectionEnd
- enable “Add Comment”
- prompt for comment text
- send POST
- update local comment list
E2) Show comment when cursor is inside range
Track cursor position in textarea:
- onClick / onKeyUp → read
selectionStart - if cursor is within any open comment range, show comment
E3) Resolve comment
Backend:
POST /api/projects/:projectId/comments/:commentId/resolve- member allowed OR owner-only (you choose; document it)
- update status to
resolved, setresolved_at - broadcast
comment:resolved
Client:
- Remove from open list immediately
Part F — Clone project (owner-only)
Backend:
POST /api/projects/:projectId/clone- owner required
- creates new project with new join code
- copies document text
- membership policy: you choose one:
- Option A: clone includes only owner
- Option B: clone includes all members
- return new project id
Frontend:
- owner sees “Clone Project”
- after clone, navigate to the new project
Starter project updates you should apply (so scaffold matches these labs)
Client packages
Lab 3 requires:
cd client npm install react-router-dom
Lab 4 requires:
cd client npm install socket.io-client
Client socket connection (Lab 4)
Example:
import { io } from "socket.io-client";
const socket = io(import.meta.env.VITE_API_BASE, {
auth: { token: localStorage.getItem("token") }
});
Instructor tool: DB provisioning script (already fits this server)
Your earlier provisioning script approach works well on 10.192.145.179. Keep these notes in the instructor instructions:
- Run
provision_dbs.sh roster.txt - It prompts for DB prefix (like
comp318lab3) - It creates:
- database:
comp318lab3-username - mysql user:
username - password: student ID
- database:
