Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: implement class-based live server with decorator support #6226

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 5 additions & 8 deletions live/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"private": true,
"type": "module",
"scripts": {
"dev": "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": "concurrently \"tsup src/start.ts --watch\" \"nodemon --env-file=.env dist/start.js\"",
"build": "tsup src/start.ts",
"start": "node --env-file=.env dist/start.js",
"lint": "eslint src --ext .ts,.tsx",
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
},
Expand Down Expand Up @@ -39,23 +39,20 @@
"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.9",
"y-protocols": "^1.0.6",
"yjs": "^13.6.14"
},
"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/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",
"@types/reflect-metadata": "^0.1.0",
"concurrently": "^9.0.1",
"nodemon": "^3.1.7",
"ts-node": "^10.9.2",
Expand Down
2 changes: 1 addition & 1 deletion live/src/ce/lib/fetch-document.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// types
import { TDocumentTypes } from "@/core/types/common.js";
import { TDocumentTypes } from "@/core/types/common";

type TArgs = {
cookie: string | undefined;
Expand Down
2 changes: 1 addition & 1 deletion live/src/ce/lib/update-document.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// types
import { TDocumentTypes } from "@/core/types/common.js";
import { TDocumentTypes } from "@/core/types/common";

type TArgs = {
cookie: string | undefined;
Expand Down
19 changes: 19 additions & 0 deletions live/src/controllers/collaboration.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Request } from "express";
import type { WebSocket as WS } from "ws";
import { Server } from "@hocuspocus/server";
import { Controller, WebSocket } from "@/lib/decorators";

@Controller("/collaboration")
export class CollaborationController {
constructor(private readonly hocusPocusServer: typeof Server) {}

@WebSocket("/")
handleConnection(ws: WS, req: Request) {
try {
this.hocusPocusServer.handleConnection(ws, req);
} catch (err) {
console.error("WebSocket connection error:", err);
ws.close();
}
}
}
10 changes: 10 additions & 0 deletions live/src/controllers/health.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Request, Response } from "express";
import { Controller, Get } from "@/lib/decorators";

@Controller("/health")
export class HealthController {
@Get("/")
async healthCheck(_req: Request, res: Response) {
res.status(200).json({ status: "OK" });
}
}
12 changes: 6 additions & 6 deletions live/src/core/extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ import { Extension } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";
import { Redis as HocusPocusRedis } from "@hocuspocus/extension-redis";
// core helpers and utilities
import { manualLogger } from "@/core/helpers/logger.js";
import { getRedisUrl } from "@/core/lib/utils/redis-url.js";
import { manualLogger } from "@/core/helpers/logger";
import { getRedisUrl } from "@/core/lib/utils/redis-url";
// core libraries
import {
fetchPageDescriptionBinary,
updatePageDescription,
} from "@/core/lib/page.js";
} from "@/core/lib/page";
// plane live libraries
import { fetchDocument } from "@/plane-live/lib/fetch-document.js";
import { updateDocument } from "@/plane-live/lib/update-document.js";
import { fetchDocument } from "@/plane-live/lib/fetch-document";
import { updateDocument } from "@/plane-live/lib/update-document";
// types
import {
type HocusPocusServerContext,
type TDocumentTypes,
} from "@/core/types/common.js";
} from "@/core/types/common";

export const getExtensions: () => Promise<Extension[]> = async () => {
const extensions: Extension[] = [
Expand Down
4 changes: 2 additions & 2 deletions live/src/core/helpers/error-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ErrorRequestHandler } from "express";
import { manualLogger } from "@/core/helpers/logger.js";
import { manualLogger } from "@/core/helpers/logger";

export const errorHandler: ErrorRequestHandler = (err, _req, res) => {
// Log the error
Expand All @@ -9,7 +9,7 @@ export const errorHandler: ErrorRequestHandler = (err, _req, res) => {
res.status(err.status || 500);

// Send the response
res.json({
reson({
error: {
message:
process.env.NODE_ENV === "production"
Expand Down
6 changes: 3 additions & 3 deletions live/src/core/hocuspocus-server.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Server } from "@hocuspocus/server";
import { v4 as uuidv4 } from "uuid";
// lib
import { handleAuthentication } from "@/core/lib/authentication.js";
import { handleAuthentication } from "@/core/lib/authentication";
// extensions
import { getExtensions } from "@/core/extensions/index.js";
import { getExtensions } from "@/core/extensions/index";
import {
DocumentCollaborativeEvents,
TDocumentEventsServer,
} from "@plane/editor/lib";
// editor types
import { TUserDetails } from "@plane/editor";
// types
import { type HocusPocusServerContext } from "@/core/types/common.js";
import { type HocusPocusServerContext } from "@/core/types/common";

export const getHocusPocusServer = async () => {
const extensions = await getExtensions();
Expand Down
4 changes: 2 additions & 2 deletions live/src/core/lib/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// services
import { UserService } from "@/core/services/user.service.js";
import { UserService } from "@/core/services/user.service";
// core helpers
import { manualLogger } from "@/core/helpers/logger.js";
import { manualLogger } from "@/core/helpers/logger";

const userService = new UserService();

Expand Down
6 changes: 3 additions & 3 deletions live/src/core/lib/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import {
getAllDocumentFormatsFromBinaryData,
getBinaryDataFromHTMLString,
} from "@/core/helpers/page.js";
} from "@/core/helpers/page";
// services
import { PageService } from "@/core/services/page.service.js";
import { manualLogger } from "../helpers/logger.js";
import { PageService } from "@/core/services/page.service";
import { manualLogger } from "@/core/helpers/logger";
const pageService = new PageService();

export const updatePageDescription = async (
Expand Down
2 changes: 1 addition & 1 deletion live/src/core/services/page.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// types
import { TPage } from "@plane/types";
// services
import { API_BASE_URL, APIService } from "@/core/services/api.service.js";
import { API_BASE_URL, APIService } from "@/core/services/api.service";

export class PageService extends APIService {
constructor() {
Expand Down
2 changes: 1 addition & 1 deletion live/src/core/services/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// types
import type { IUser } from "@plane/types";
// services
import { API_BASE_URL, APIService } from "@/core/services/api.service.js";
import { API_BASE_URL, APIService } from "@/core/services/api.service";

export class UserService extends APIService {
constructor() {
Expand Down
2 changes: 1 addition & 1 deletion live/src/core/types/common.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// types
import { TAdditionalDocumentTypes } from "@/plane-live/types/common.js";
import { TAdditionalDocumentTypes } from "@/plane-live/types/common";

export type TDocumentTypes = "project_page" | TAdditionalDocumentTypes;

Expand Down
3 changes: 2 additions & 1 deletion live/src/ee/lib/authentication.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "../../ce/lib/authentication.js"
export * from "../../core/lib/authentication";

3 changes: 2 additions & 1 deletion live/src/ee/lib/fetch-document.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "../../ce/lib/fetch-document.js"
export * from "../../ce/lib/fetch-document";

3 changes: 2 additions & 1 deletion live/src/ee/lib/update-document.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "../../ce/lib/update-document.js"
export * from "../../ce/lib/update-document";

2 changes: 1 addition & 1 deletion live/src/ee/types/common.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "../../ce/types/common.js"
export * from "../../ce/types/common"
24 changes: 24 additions & 0 deletions live/src/lib/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import "reflect-metadata";
import type { Router } from "express";

type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';

export const registerControllers = (router: Router, Controller: any): void => {
const instance = new Controller();
const baseRoute = Reflect.getMetadata("baseRoute", Controller) || "";

const proto = Object.getPrototypeOf(instance);
const methods = Object.getOwnPropertyNames(proto).filter(
(item) => item !== "constructor" && typeof instance[item] === "function"
);

methods.forEach((methodName) => {
const route = Reflect.getMetadata("route", proto, methodName) || "";
const method = Reflect.getMetadata("method", proto, methodName) as HttpMethod;
const middlewares = Reflect.getMetadata("middlewares", proto, methodName) || [];

if (route && method) {
router[method](baseRoute + route, ...middlewares, instance[methodName].bind(instance));
}
});
};
58 changes: 58 additions & 0 deletions live/src/lib/decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import "reflect-metadata";
import type { RequestHandler } from "express";

export const Controller = (baseRoute = ""): ClassDecorator => {
return function (target: object) {
Reflect.defineMetadata("baseRoute", baseRoute, target);
};
};

export const Get = (route: string): MethodDecorator => {
return function (target: object, propertyKey: string | symbol, _descriptor: PropertyDescriptor) {
Reflect.defineMetadata("route", route, target, propertyKey);
Reflect.defineMetadata("method", "get", target, propertyKey);
};
};

export const Post = (route: string): MethodDecorator => {
return function (target: object, propertyKey: string | symbol, _descriptor: PropertyDescriptor) {
Reflect.defineMetadata("route", route, target, propertyKey);
Reflect.defineMetadata("method", "post", target, propertyKey);
};
};

export const Put = (route: string): MethodDecorator => {
return function (target: object, propertyKey: string | symbol, _descriptor: PropertyDescriptor) {
Reflect.defineMetadata("route", route, target, propertyKey);
Reflect.defineMetadata("method", "put", target, propertyKey);
};
};

export const Patch = (route: string): MethodDecorator => {
return function (target: object, propertyKey: string | symbol, _descriptor: PropertyDescriptor) {
Reflect.defineMetadata("route", route, target, propertyKey);
Reflect.defineMetadata("method", "patch", target, propertyKey);
};
};

export const Delete = (route: string): MethodDecorator => {
return function (target: object, propertyKey: string | symbol, _descriptor: PropertyDescriptor) {
Reflect.defineMetadata("route", route, target, propertyKey);
Reflect.defineMetadata("method", "delete", target, propertyKey);
};
};

export const Middleware = (middleware: RequestHandler): MethodDecorator => {
return function (target: object, propertyKey: string | symbol, _descriptor: PropertyDescriptor) {
const middlewares = Reflect.getMetadata("middlewares", target, propertyKey) || [];
middlewares.push(middleware);
Reflect.defineMetadata("middlewares", middlewares, target, propertyKey);
};
};

export const WebSocket = (route: string): MethodDecorator => {
return function (target: object, propertyKey: string | symbol, _descriptor: PropertyDescriptor) {
Reflect.defineMetadata("route", route, target, propertyKey);
Reflect.defineMetadata("method", "ws", target, propertyKey);
};
};
Loading
Loading