Merge branch 'fix/live-server-restructuring' into fix/live-server-restructuring
This commit is contained in:
@@ -85,3 +85,5 @@ deploy/selfhost/plane-app/
|
||||
## Storybook
|
||||
*storybook.log
|
||||
output.css
|
||||
|
||||
dev-editor
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import type { Request } from "express";
|
||||
import type { WebSocket as WS } from "ws";
|
||||
import type { Hocuspocus } from "@hocuspocus/server";
|
||||
import { Controller, WebSocket } from "@/lib/decorators";
|
||||
import { BaseWebSocketController } from "@/lib/base.controller";
|
||||
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 extends BaseWebSocketController {
|
||||
export class CollaborationController {
|
||||
private metrics = {
|
||||
errors: 0,
|
||||
};
|
||||
|
||||
constructor(private readonly hocusPocusServer: Hocuspocus) {
|
||||
super();
|
||||
}
|
||||
constructor(private readonly hocusPocusServer: Hocuspocus) {}
|
||||
|
||||
@WebSocket("/")
|
||||
handleConnection(ws: WS, req: Request) {
|
||||
|
||||
@@ -3,17 +3,17 @@ import type { Request, Response } from "express";
|
||||
import { convertHTMLDocumentToAllFormats } from "@/core/helpers/convert-document";
|
||||
// types
|
||||
import { TConvertDocumentRequestBody } from "@/core/types/common";
|
||||
// controller
|
||||
import { BaseController } from "@/lib/base.controller";
|
||||
// decorators
|
||||
import { Post, CatchErrors } from "@/lib/decorators";
|
||||
import { CatchErrors } from "@/lib/decorators";
|
||||
// logger
|
||||
import { logger } from "@plane/logger";
|
||||
// error handling
|
||||
import validate from "@/core/helpers/error-handling/error-validation";
|
||||
import { Controller, Post } from "@plane/decorators";
|
||||
|
||||
export class DocumentController extends BaseController {
|
||||
@Post("/convert-document")
|
||||
@Controller("/convert-document")
|
||||
export class DocumentController {
|
||||
@Post("/")
|
||||
@CatchErrors()
|
||||
async convertDocument(req: Request, res: Response) {
|
||||
const { description_html, variant } = req.body as TConvertDocumentRequestBody;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { CatchErrors } from "@/lib/decorators";
|
||||
import { Controller, Get } from "@plane/decorators";
|
||||
import type { Request, Response } from "express";
|
||||
import { Controller, Get, CatchErrors } from "@/lib/decorators";
|
||||
import { BaseController } from "@/lib/base.controller";
|
||||
|
||||
@Controller("/health")
|
||||
export class HealthController extends BaseController {
|
||||
export class HealthController {
|
||||
@Get("/")
|
||||
@CatchErrors()
|
||||
async healthCheck(_req: Request, res: Response) {
|
||||
res.status(200).json({
|
||||
res.status(200).json({
|
||||
status: "OK",
|
||||
timestamp: new Date().toISOString(),
|
||||
version: process.env.APP_VERSION || '1.0.0'
|
||||
version: process.env.APP_VERSION || "1.0.0",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,21 @@
|
||||
import { IControllerRegistry, ControllerRegistration, WebSocketControllerRegistration } from "@/lib/controller.interface";
|
||||
import { createControllerRegistry } from "@/lib/controller.utils";
|
||||
|
||||
// Import controllers
|
||||
import { HealthController } from "@/controllers/health.controller";
|
||||
import { DocumentController } from "@/controllers/document.controller";
|
||||
import { CollaborationController } from "@/controllers/collaboration.controller";
|
||||
|
||||
/**
|
||||
* Controller Registry Module
|
||||
* Centralized place to register all controllers and their dependencies
|
||||
* Controller registry exports
|
||||
* Simple grouped arrays of controller classes for better organization
|
||||
*/
|
||||
class ControllerRegistryModule {
|
||||
// Define controller groups for better organization using registration format
|
||||
private readonly CONTROLLERS = {
|
||||
// Core system controllers (health checks, status endpoints)
|
||||
CORE: [
|
||||
{ Controller: HealthController }
|
||||
],
|
||||
|
||||
// Document management controllers
|
||||
DOCUMENT: [
|
||||
{ Controller: DocumentController }
|
||||
],
|
||||
|
||||
// WebSocket controllers for real-time functionality
|
||||
WEBSOCKET: [
|
||||
{ Controller: CollaborationController, dependencies: ['hocuspocus'] }
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all REST controllers
|
||||
*/
|
||||
getAllRestControllers(): ControllerRegistration[] {
|
||||
return [...this.CONTROLLERS.CORE, ...this.CONTROLLERS.DOCUMENT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all WebSocket controllers
|
||||
*/
|
||||
getAllWebSocketControllers(): WebSocketControllerRegistration[] {
|
||||
return this.CONTROLLERS.WEBSOCKET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a controller registry with all configured controllers
|
||||
*/
|
||||
createRegistry(): IControllerRegistry {
|
||||
return createControllerRegistry(
|
||||
this.getAllRestControllers(),
|
||||
this.getAllWebSocketControllers()
|
||||
);
|
||||
}
|
||||
}
|
||||
export const CONTROLLERS = {
|
||||
// Core system controllers (health checks, status endpoints)
|
||||
CORE: [HealthController],
|
||||
|
||||
// Export a singleton instance
|
||||
export const controllerRegistry = new ControllerRegistryModule();
|
||||
// Document management controllers
|
||||
DOCUMENT: [DocumentController],
|
||||
|
||||
// WebSocket controllers for real-time functionality
|
||||
WEBSOCKET: [CollaborationController],
|
||||
};
|
||||
|
||||
// Helper to get all REST controllers
|
||||
export const getAllControllers = () => [...CONTROLLERS.CORE, ...CONTROLLERS.DOCUMENT, ...CONTROLLERS.WEBSOCKET];
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { ErrorRequestHandler, Request, Response, NextFunction } from "express";
|
||||
|
||||
import { SentryInstance, captureException } from "@/sentry-config";
|
||||
import { env } from "@/env";
|
||||
import { logger } from "@plane/logger";
|
||||
import { handleError } from "./error-factory";
|
||||
import { ErrorContext, reportError } from "./error-reporting";
|
||||
import { manualLogger } from "../logger";
|
||||
import { APIService } from "@/core/services/api.service";
|
||||
|
||||
/**
|
||||
* HTTP Status Codes
|
||||
@@ -150,18 +148,6 @@ export class AppError extends Error {
|
||||
context: this.context,
|
||||
});
|
||||
}
|
||||
|
||||
// Send to Sentry if configured
|
||||
if (SentryInstance) {
|
||||
captureException(this, {
|
||||
extra: {
|
||||
...this.context,
|
||||
isOperational: this.isOperational,
|
||||
errorCategory: this.category,
|
||||
status: this.status,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,10 +314,6 @@ export const setupGlobalErrorHandlers = (gracefulTerminationHandler: () => Promi
|
||||
process.on("unhandledRejection", (reason: unknown) => {
|
||||
logger.error("Unhandled Promise Rejection", { reason });
|
||||
|
||||
if (SentryInstance) {
|
||||
SentryInstance.captureException(reason);
|
||||
}
|
||||
|
||||
// Convert to AppError and handle
|
||||
const appError = handleError(reason, {
|
||||
errorType: "internal",
|
||||
@@ -352,10 +334,6 @@ export const setupGlobalErrorHandlers = (gracefulTerminationHandler: () => Promi
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
if (SentryInstance) {
|
||||
SentryInstance.captureException(error);
|
||||
}
|
||||
|
||||
// Convert to AppError if needed
|
||||
const appError = handleError(error, {
|
||||
errorType: "internal",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SentryInstance } from "@/sentry-config";
|
||||
import { AppError, ErrorCategory } from "./error-handler";
|
||||
import { logger } from "@plane/logger";
|
||||
import { handleError } from "./error-factory";
|
||||
@@ -27,16 +26,6 @@ export const reportError = (error: Error | unknown, context?: ErrorContext): voi
|
||||
error,
|
||||
context,
|
||||
});
|
||||
|
||||
// Send to Sentry
|
||||
if (SentryInstance) {
|
||||
SentryInstance.captureException(error, {
|
||||
extra: {
|
||||
...context,
|
||||
errorCategory: ErrorCategory.PROGRAMMING,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const handleFatalError = (error: Error | unknown, context?: ErrorContext): void => {
|
||||
|
||||
+1
-11
@@ -12,16 +12,7 @@ const envSchema = z.object({
|
||||
LIVE_BASE_PATH: z.string().default("/live"),
|
||||
|
||||
// CORS configuration
|
||||
CORS_ALLOWED_ORIGINS: z
|
||||
.string()
|
||||
.default("*"),
|
||||
// Sentry configuration
|
||||
LIVE_SENTRY_DSN: z.string().optional(),
|
||||
LIVE_SENTRY_ENVIRONMENT: z.string().default("development"),
|
||||
LIVE_SENTRY_TRACES_SAMPLE_RATE: z.string().default("0.5").transform(Number),
|
||||
LIVE_SENTRY_RELEASE: z.string().optional(),
|
||||
LIVE_SENTRY_RELEASE_VERSION: z.string().optional(),
|
||||
|
||||
CORS_ALLOWED_ORIGINS: z.string().default("*"),
|
||||
// Compression options
|
||||
COMPRESSION_LEVEL: z.string().default("6").transform(Number),
|
||||
COMPRESSION_THRESHOLD: z.string().default("5000").transform(Number),
|
||||
@@ -49,4 +40,3 @@ function validateEnv() {
|
||||
|
||||
// Export the validated environment
|
||||
export const env = validateEnv();
|
||||
|
||||
|
||||
@@ -1,52 +1,32 @@
|
||||
import { Router } from "express";
|
||||
import { IControllerRegistry, IServiceContainer, ControllerRegistration, WebSocketControllerRegistration } from "./controller.interface";
|
||||
import { registerControllers as registerRestControllers, registerWebSocketControllers } from "@plane/decorators";
|
||||
import "reflect-metadata";
|
||||
|
||||
// Define valid HTTP methods
|
||||
type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'all' | 'use' | 'options' | 'head' | 'ws';
|
||||
|
||||
/**
|
||||
* Register all controllers from a registry with dependency injection
|
||||
* Register all controllers from the controllers array
|
||||
* @param router Express router to register routes on
|
||||
* @param registry Controller registry containing all controllers
|
||||
* @param container Service container for dependency injection
|
||||
* @param controllers Array of controller classes to register
|
||||
* @param dependencies Array of dependencies to pass to controllers
|
||||
*/
|
||||
export function registerControllers(
|
||||
router: Router,
|
||||
registry: IControllerRegistry,
|
||||
container: IServiceContainer
|
||||
): void {
|
||||
// Register REST controllers
|
||||
registry.controllers.forEach((registration) => {
|
||||
const { Controller, dependencies = [] } = registration;
|
||||
const resolvedDependencies = dependencies.map(dep => container.get(dep));
|
||||
const instance = new Controller(...resolvedDependencies);
|
||||
instance.registerRoutes(router);
|
||||
});
|
||||
export function registerControllers(router: Router, controllers: any[], dependencies: any[] = []): void {
|
||||
controllers.forEach((Controller) => {
|
||||
// Create the controller instance with dependencies
|
||||
const instance = new Controller(...dependencies);
|
||||
|
||||
// Register WebSocket controllers
|
||||
registry.webSocketControllers.forEach((registration) => {
|
||||
const { Controller, dependencies = [] } = registration;
|
||||
const resolvedDependencies = dependencies.map(dep => container.get(dep));
|
||||
const instance = new Controller(...resolvedDependencies);
|
||||
|
||||
// Call the specialized WebSocket registration method
|
||||
instance.registerWebSocketRoutes(router);
|
||||
// Determine if it's a WebSocket controller or REST controller by checking
|
||||
// if it has any methods with the "ws" method metadata
|
||||
const isWebsocket = Object.getOwnPropertyNames(Controller.prototype).some((methodName) => {
|
||||
if (methodName === "constructor") return false;
|
||||
return Reflect.getMetadata("method", instance, methodName) === "ws";
|
||||
});
|
||||
|
||||
if (isWebsocket) {
|
||||
// Register as WebSocket controller
|
||||
// Pass the existing instance with dependencies to avoid creating a new instance without them
|
||||
registerWebSocketControllers(router, Controller, instance);
|
||||
} else {
|
||||
// Register as REST controller - doesn't accept an instance parameter
|
||||
registerRestControllers(router, Controller);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a controller registry with the given controllers
|
||||
* @param controllers Array of REST controller registrations
|
||||
* @param webSocketControllers Array of WebSocket controller registrations
|
||||
* @returns Controller registry
|
||||
*/
|
||||
export function createControllerRegistry(
|
||||
controllers: ControllerRegistration[],
|
||||
webSocketControllers: WebSocketControllerRegistration[] = []
|
||||
): IControllerRegistry {
|
||||
return {
|
||||
controllers,
|
||||
webSocketControllers,
|
||||
};
|
||||
}
|
||||
@@ -1,19 +1,6 @@
|
||||
import "reflect-metadata";
|
||||
import type { RequestHandler } from "express";
|
||||
import { asyncHandler } from "@/core/helpers/error-handling/error-handler";
|
||||
|
||||
// Re-export all decorators from our new package
|
||||
export {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Patch,
|
||||
Delete,
|
||||
Middleware,
|
||||
WebSocket
|
||||
} from "@plane/decorators";
|
||||
|
||||
/**
|
||||
* Decorator to wrap controller methods with error handling
|
||||
* This automatically catches and processes all errors using our error handling system
|
||||
@@ -21,14 +8,15 @@ export {
|
||||
export const CatchErrors = (): MethodDecorator => {
|
||||
return function (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
|
||||
// Only apply to methods that are not WebSocket handlers
|
||||
const isWebSocketHandler = Reflect.getMetadata("method", target, propertyKey) === "ws";
|
||||
|
||||
if (typeof originalMethod === 'function' && !isWebSocketHandler) {
|
||||
|
||||
if (typeof originalMethod === "function" && !isWebSocketHandler) {
|
||||
descriptor.value = asyncHandler(originalMethod);
|
||||
}
|
||||
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user