Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
dbee748
feat: add decorator system and controller registration for live server
devin-ai-integration[bot] Dec 18, 2024
6c83a0d
feat: implement health check controller with decorator support
devin-ai-integration[bot] Dec 18, 2024
23eeb45
feat: implement collaboration controller with websocket support
devin-ai-integration[bot] Dec 18, 2024
f2a0885
feat: add start.ts entry point for live server
devin-ai-integration[bot] Dec 18, 2024
a7ab5ae
fix: resolve lint errors in decorators and collaboration controller
devin-ai-integration[bot] Dec 18, 2024
b39ce9c
fix: add eslint config and fix websocket router interface
devin-ai-integration[bot] Dec 18, 2024
5802858
fix: resolve typescript errors in server and decorators
devin-ai-integration[bot] Dec 18, 2024
146332f
chore: replace babel with esbuild for build configuration
devin-ai-integration[bot] Dec 19, 2024
d5bd4ef
chore: switch from esbuild to tsup for build configuration
devin-ai-integration[bot] Dec 19, 2024
a229508
Merge branch 'preview' into devin/1734544044-refactor-live-server
Palanikannan1437 Dec 23, 2024
1b29f65
fix: removed .js imports
Palanikannan1437 Dec 23, 2024
3d61604
Merge branch 'preview' into devin/1734544044-refactor-live-server
Palanikannan1437 Feb 8, 2025
388151b
Merge branch 'preview' into devin/1734544044-refactor-live-server
Palanikannan1437 Mar 17, 2025
6897575
fix: logger and added working global error handling with sentry
Palanikannan1437 Mar 17, 2025
3710b18
fix: tsup hot reloading
Palanikannan1437 Mar 17, 2025
38d8d3e
fix: dividing server code
Palanikannan1437 Mar 17, 2025
df35cce
fix: better error handling
Palanikannan1437 Mar 18, 2025
0d57e0a
fix: file structure for error handling
Palanikannan1437 Mar 18, 2025
c56097b
fix: better error handling for redis client
Palanikannan1437 Mar 18, 2025
3672ee4
fix: errors and imports
Palanikannan1437 Mar 18, 2025
cef4110
fix: handlers
Palanikannan1437 Mar 19, 2025
c2a3e47
fix: stop event prop on error
Palanikannan1437 Mar 19, 2025
e4f31ae
Merge branch 'preview' into devin/1734544044-refactor-live-server
Palanikannan1437 Mar 19, 2025
a9f4427
fix: seperated decorators into it's own package
Palanikannan1437 Mar 20, 2025
16d41a3
fix: not shutting down the app for any reason
Palanikannan1437 Mar 21, 2025
304ef1a
Merge branch 'preview' into fix/live-server-restructuring
Palanikannan1437 Mar 21, 2025
53efad3
chore: restructuring project-page methods, transformers and handlers
Palanikannan1437 Mar 21, 2025
3a0891e
fix: extensions index ts cleaned up
Palanikannan1437 Mar 21, 2025
d3defc9
fix: redis process improved a lot
Palanikannan1437 Mar 24, 2025
37cd01e
feat: express decorators for rest apis and websocket
Palanikannan1437 Mar 25, 2025
2db9d35
Merge branch 'preview' into devin/1734544044-refactor-live-server
Palanikannan1437 Mar 25, 2025
93066ef
Merge branch 'fix/live-server-restructuring' into fix/live-server-res…
Palanikannan1437 Mar 25, 2025
ea7ebe6
feat: express decorators for rest apis and websocket
Palanikannan1437 Mar 25, 2025
6db75fe
fix: added package dependency
Palanikannan1437 Mar 25, 2025
8a9cdc6
fix: refactor decorators
Palanikannan1437 Mar 26, 2025
c1b8fea
chore: Merge preview
Palanikannan1437 Mar 26, 2025
6c2fb4b
merge decorators
Palanikannan1437 Mar 26, 2025
3d07d0f
fix: add missing packages
Palanikannan1437 Mar 26, 2025
07bf68c
fix: exit with error 1
Palanikannan1437 Mar 26, 2025
cef60b5
fix: error factory types and document controller error handling
Palanikannan1437 Mar 26, 2025
7cbdcd4
fix: added zod validation
Palanikannan1437 Mar 26, 2025
cabd5f4
fix: moving around pages
Palanikannan1437 Apr 2, 2025
4eef115
Merge branch 'preview' into fix/live-server-restructuring
Palanikannan1437 Apr 2, 2025
ad9888c
fix: registering handlers made simplified
Palanikannan1437 Apr 2, 2025
70dbad6
chore: remove services
Palanikannan1437 Apr 2, 2025
cb3b6fb
Merge branch 'preview' into fix/live-server-restructuring
Palanikannan1437 Apr 3, 2025
728f517
fix: logger reference
Palanikannan1437 Apr 3, 2025
1315acf
fix: redis extension fixes
Palanikannan1437 Apr 3, 2025
31c09d5
Merge branch 'preview' of github.com:makeplane/plane into fix/live-se…
sriramveeraghanta Apr 3, 2025
18a3771
fix: removed unused dependencies
sriramveeraghanta Apr 3, 2025
829fc47
Merge branch 'preview' into fix/live-server-restructuring
Palanikannan1437 Jun 23, 2025
24a3c08
fix: package.json
Palanikannan1437 Jun 23, 2025
c063eae
fix: live restructuring
Palanikannan1437 Jun 24, 2025
4eb5b4f
fix: more live server stuff
Palanikannan1437 Jun 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions live/.babelrc

This file was deleted.

30 changes: 15 additions & 15 deletions live/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
"version": "0.26.1",
"license": "AGPL-3.0",
"description": "A realtime collaborative server powers Plane's rich text editor",
"main": "./src/server.ts",
"main": "./dist/start.js",
"module": "./dist/start.mjs",
"types": "./dist/start.d.ts",
"private": true,
"type": "module",
"scripts": {
"dev": "PORT=3100 concurrently \"babel src --out-dir dist --extensions '.ts,.js' --watch\" \"nodemon dist/server.js\"",
"build": "babel src --out-dir dist --extensions \".ts,.js\"",
"start": "node dist/server.js",
"dev": "tsup --watch --onSuccess 'node --env-file=.env dist/start.js'",
"build": "tsup",
"start": "node --env-file=.env dist/start.js",
"lint": "eslint src --ext .ts,.tsx",
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
},
Expand All @@ -21,14 +23,17 @@
"@hocuspocus/extension-redis": "^2.15.0",
"@hocuspocus/server": "^2.15.0",
"@plane/constants": "*",
"@plane/decorators": "*",
"@plane/editor": "*",
"@plane/logger": "*",
"@plane/types": "*",
"@tiptap/core": "2.10.4",
"@tiptap/html": "2.11.0",
"axios": "^1.8.3",
"compression": "^1.7.4",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-ws": "^5.0.2",
"helmet": "^7.1.0",
Expand All @@ -37,26 +42,21 @@
"morgan": "^1.10.0",
"pino-http": "^10.3.0",
"pino-pretty": "^11.2.2",
"reflect-metadata": "^0.2.2",
"uuid": "^10.0.0",
"y-prosemirror": "^1.2.15",
"y-protocols": "^1.0.6",
"yjs": "^13.6.20"
"yjs": "^13.6.20",
"zod": "^3.24.2"
},
"devDependencies": {
"@babel/cli": "^7.25.6",
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4",
"@babel/preset-typescript": "^7.24.7",
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.8",
"@types/cors": "^2.8.17",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.21",
"@types/express-ws": "^3.0.4",
"@types/node": "^20.14.9",
"babel-plugin-module-resolver": "^5.0.2",
"concurrently": "^9.0.1",
"nodemon": "^3.1.7",
"ts-node": "^10.9.2",
"@types/node": "22.5.4",
"tsup": "8.4.0",
"typescript": "5.8.3"
}
Expand Down
96 changes: 96 additions & 0 deletions live/src/ce/controllers/collaboration.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { Request } from "express";
import type { WebSocket as WS } from "ws";
import type { Hocuspocus } from "@hocuspocus/server";
import { ErrorCategory } from "@/core/helpers/error-handling/error-handler";
import { logger } from "@plane/logger";
import Errors from "@/core/helpers/error-handling/error-factory";
import { Controller, WebSocket } from "@plane/decorators";

@Controller("/collaboration")
export class CollaborationController {
private metrics = {
errors: 0,
};

constructor(private readonly hocusPocusServer: Hocuspocus) {}

@WebSocket("/")
handleConnection(ws: WS, req: Request) {
const clientInfo = {
ip: req.ip,
userAgent: req.get("user-agent"),
requestId: req.id || crypto.randomUUID(),
};

try {
// Initialize the connection with Hocuspocus
this.hocusPocusServer.handleConnection(ws, req);

// Set up error handling for the connection
ws.on("error", (error) => {
this.handleConnectionError(error, clientInfo, ws);
});
} catch (error) {
this.handleConnectionError(error, clientInfo, ws);
}
}

private handleConnectionError(error: unknown, clientInfo: Record<string, any>, ws: WS) {
// Convert to AppError if needed
const appError = Errors.convertError(error instanceof Error ? error : new Error(String(error)), {
context: {
...clientInfo,
component: "WebSocketConnection",
},
});

// Log at appropriate level based on error category
if (appError.category === ErrorCategory.OPERATIONAL) {
logger.info(`WebSocket operational error: ${appError.message}`, {
error: appError,
clientInfo,
});
} else {
logger.error(`WebSocket error: ${appError.message}`, {
error: appError,
clientInfo,
stack: appError.stack,
});
}

// Alert if error threshold is reached
if (this.metrics.errors % 10 === 0) {
logger.warn(`High WebSocket error rate detected: ${this.metrics.errors} total errors`);
}

// Try to send error to client before closing
try {
if (ws.readyState === ws.OPEN) {
ws.send(
JSON.stringify({
type: "error",
message: appError.category === ErrorCategory.OPERATIONAL ? appError.message : "Internal server error",
})
);
}
} catch (sendError) {
// Ignore send errors at this point
}

// Close with informative message if connection is still open
if (ws.readyState === ws.OPEN) {
ws.close(
1011,
appError.category === ErrorCategory.OPERATIONAL
? `Error: ${appError.message}. Reconnect with exponential backoff.`
: "Internal server error. Please retry in a few moments."
);
}
}

getErrorMetrics() {
return {
errors: this.metrics.errors,
};
}
}
128 changes: 128 additions & 0 deletions live/src/ce/controllers/document.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import type { Request, Response } from "express";
import { z } from "zod";
// helpers
import { convertHTMLDocumentToAllFormats } from "@plane/editor";
// types
import { TConvertDocumentRequestBody } from "@/core/types/common";
// decorators
import { CatchErrors } from "@/lib/decorators";
// logger
import { logger } from "@plane/logger";
import { Controller, Post } from "@plane/decorators";
import { AppError } from "@/core/helpers/error-handling/error-handler";
import { handleError } from "@/core/helpers/error-handling/error-factory";

// Define the schema with more robust validation
const convertDocumentSchema = z.object({
description_html: z
.string()
.min(1, "HTML content cannot be empty")
.refine((html) => html.trim().length > 0, "HTML content cannot be just whitespace")
.refine((html) => html.includes("<") && html.includes(">"), "Content must be valid HTML"),
variant: z.enum(["rich", "document"]),
});

@Controller("/convert-document")
export class DocumentController {
private metrics = {
conversions: 0,
errors: 0,
};

@Post("/")
@CatchErrors()
async convertDocument(req: Request, res: Response) {
const requestId = req.id || crypto.randomUUID();
const clientInfo = {
ip: req.ip,
userAgent: req.get("user-agent"),
requestId,
};

try {
// Validate request body
const validatedData = convertDocumentSchema.parse(req.body as TConvertDocumentRequestBody);
const { description_html, variant } = validatedData;

// Log validated data
logger.info("Validated document conversion request", {
...clientInfo,
variant,
contentLength: description_html.length,
});

// Process document conversion
const { description, description_binary } = convertHTMLDocumentToAllFormats({
document_html: description_html,
variant,
});

// Update metrics
this.metrics.conversions++;

// Log successful conversion
logger.info("Document conversion successful", {
...clientInfo,
variant,
outputLength: description_html.length,
});

// Return successful response
res.status(200).json({
description,
description_binary,
});
} catch (error) {
// Update error metrics
this.metrics.errors++;

let appError: AppError;

if (error instanceof z.ZodError) {
// Handle validation errors
appError = handleError(error, {
errorType: "unprocessable-entity",
message: "Invalid request data",
component: "document-conversion-controller",
operation: "convertDocument",
extraContext: {
...clientInfo,
validationErrors: error.errors.map((err) => ({
path: err.path.join("."),
message: err.message,
})),
},
});
} else {
// Handle other errors
appError = handleError(error, {
errorType: "internal",
message: "Internal server error",
component: "document-conversion-controller",
operation: "convertDocument",
extraContext: clientInfo,
});
}

// Log the error
logger.error("Document conversion failed", {
error: appError,
status: appError.status,
context: appError.context,
});

res.status(appError.status).json({
message: appError.message,
status: appError.status,
context: appError.context,
});
}
}

getMetrics() {
return {
conversions: this.metrics.conversions,
errors: this.metrics.errors,
};
}
}
16 changes: 16 additions & 0 deletions live/src/ce/controllers/health.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { CatchErrors } from "@/lib/decorators";
import { Controller, Get } from "@plane/decorators";
import type { Request, Response } from "express";

@Controller("/health")
export class HealthController {
@Get("/")
@CatchErrors()
async healthCheck(_req: Request, res: Response) {
res.status(200).json({
status: "OK",
timestamp: new Date().toISOString(),
version: process.env.APP_VERSION || "1.0.0",
});
}
}
17 changes: 17 additions & 0 deletions live/src/ce/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Export all controllers from this barrel file
import { CollaborationController } from "./collaboration.controller";
import { LiveDocumentController } from "./live-document.controller";
import { DocumentController } from "./document.controller";
import { HealthController } from "./health.controller";

export const CONTROLLERS = {
// Core system controllers (health checks, status endpoints)
CORE: [HealthController],

// Document management controllers
DOCUMENT: [DocumentController, LiveDocumentController],

// WebSocket controllers for real-time functionality
WEBSOCKET: [CollaborationController],
};

Loading