Lab 4 Collaborative Writing Tool (Part 2)

Realtime editing + highlights/comments + resolve workflow + clone projects

Summary (what you’re adding)

Lab 4 builds directly on Lab 3 and adds:

  1. Realtime collaboration (Socket.IO)
  • When one user edits, others see updates quickly
  1. Highlight + comment system
  • Select text → add comment
  • Comment shows when cursor is inside the highlighted range
  • Resolve comment → it disappears from open list
  1. 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:update with 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:created to 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, set resolved_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
Scroll to Top