diff --git a/apps/live/src/controllers/collaboration.controller.ts b/apps/live/src/controllers/collaboration.controller.ts index d51297c5a2..59bfe7b0c1 100644 --- a/apps/live/src/controllers/collaboration.controller.ts +++ b/apps/live/src/controllers/collaboration.controller.ts @@ -22,11 +22,11 @@ export class CollaborationController { // Set up error handling for the connection ws.on("error", (error: Error) => { - logger.error("WebSocket connection error:", error); + logger.error("COLLABORATION_CONTROLLER: WebSocket connection error:", error); ws.close(1011, "Internal server error"); }); } catch (error) { - logger.error("WebSocket connection error:", error); + logger.error("COLLABORATION_CONTROLLER: WebSocket connection error:", error); ws.close(1011, "Internal server error"); } } diff --git a/apps/live/src/controllers/convert-document.controller.ts b/apps/live/src/controllers/convert-document.controller.ts deleted file mode 100644 index 49e71e5d4b..0000000000 --- a/apps/live/src/controllers/convert-document.controller.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Request, Response } from "express"; -// plane imports -import { Controller, Post } from "@plane/decorators"; -import { logger } from "@plane/logger"; -// types -import type { TConvertDocumentRequestBody } from "@/types"; -// utils -import { convertHTMLDocumentToAllFormats } from "@/utils"; - -@Controller("/convert-document") -export class ConvertDocumentController { - @Post("/") - handleConvertDocument(req: Request, res: Response) { - const { description_html, variant } = req.body as TConvertDocumentRequestBody; - try { - if (typeof description_html !== "string" || variant === undefined) { - res.status(400).json({ - message: "Missing required fields", - }); - return; - } - const { description, description_binary } = convertHTMLDocumentToAllFormats({ - document_html: description_html, - variant, - }); - res.status(200).json({ - description, - description_binary, - }); - } catch (error) { - logger.error("Error in /convert-document endpoint:", error); - res.status(500).json({ - message: `Internal server error.`, - }); - } - } -} diff --git a/apps/live/src/controllers/document.controller.ts b/apps/live/src/controllers/document.controller.ts new file mode 100644 index 0000000000..3b45c4e92b --- /dev/null +++ b/apps/live/src/controllers/document.controller.ts @@ -0,0 +1,63 @@ +import type { Request, Response } from "express"; +import { z } from "zod"; +// helpers +import { Controller, Post } from "@plane/decorators"; +import { convertHTMLDocumentToAllFormats } from "@plane/editor"; +// logger +import { logger } from "@plane/logger"; +import { type TConvertDocumentRequestBody } from "@/types"; + +// 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 { + @Post("/") + async convertDocument(req: Request, res: Response) { + try { + // Validate request body + const validatedData = convertDocumentSchema.parse(req.body as TConvertDocumentRequestBody); + const { description_html, variant } = validatedData; + + // Process document conversion + const { description, description_binary } = convertHTMLDocumentToAllFormats({ + document_html: description_html, + variant, + }); + + // Return successful response + res.status(200).json({ + description, + description_binary, + }); + } catch (error) { + if (error instanceof z.ZodError) { + const validationErrors = error.errors.map((err) => ({ + path: err.path.join("."), + message: err.message, + })); + logger.error("DOCUMENT_CONTROLLER: Validation error", { + validationErrors, + }); + return res.status(400).json({ + message: `Validation error`, + context: { + validationErrors, + }, + }); + } else { + logger.error("DOCUMENT_CONTROLLER: Internal server error", error); + return res.status(500).json({ + message: `Internal server error.`, + }); + } + } + } +} diff --git a/apps/live/src/controllers/index.ts b/apps/live/src/controllers/index.ts index f2ba83e336..3b45cb1ed9 100644 --- a/apps/live/src/controllers/index.ts +++ b/apps/live/src/controllers/index.ts @@ -1,5 +1,5 @@ import { CollaborationController } from "./collaboration.controller"; -import { ConvertDocumentController } from "./convert-document.controller"; +import { DocumentController } from "./document.controller"; import { HealthController } from "./health.controller"; -export const CONTROLLERS = [CollaborationController, ConvertDocumentController, HealthController]; +export const CONTROLLERS = [CollaborationController, DocumentController, HealthController]; diff --git a/apps/live/src/env.ts b/apps/live/src/env.ts index 3c1a91ec9a..062ab578f9 100644 --- a/apps/live/src/env.ts +++ b/apps/live/src/env.ts @@ -1,5 +1,6 @@ import * as dotenv from "@dotenvx/dotenvx"; import { z } from "zod"; +import { logger } from "@plane/logger"; dotenv.config(); @@ -27,7 +28,7 @@ const envSchema = z.object({ const validateEnv = () => { const result = envSchema.safeParse(process.env); if (!result.success) { - console.error("❌ Invalid environment variables:", JSON.stringify(result.error.format(), null, 4)); + logger.error("❌ Invalid environment variables:", JSON.stringify(result.error.format(), null, 4)); process.exit(1); } return result.data; diff --git a/apps/live/src/extensions/database.ts b/apps/live/src/extensions/database.ts index d23fbd8fcb..a4b18ad9e1 100644 --- a/apps/live/src/extensions/database.ts +++ b/apps/live/src/extensions/database.ts @@ -38,7 +38,7 @@ const fetchDocument = async ({ context, documentName: pageId }: FetchPayloadWith // return binary data return binaryData; } catch (error) { - logger.error("Error in fetching document", error); + logger.error("DATABASE_EXTENSION: Error in fetching document", error); throw normalizeToError(error, `Failed to fetch document: ${pageId}`); } }; @@ -57,7 +57,7 @@ const storeDocument = async ({ context, state: pageBinaryData, documentName: pag }; await service.updateDescriptionBinary(pageId, payload); } catch (error) { - logger.error("Error in updating document:", error); + logger.error("DATABASE_EXTENSION: Error in updating document:", error); throw normalizeToError(error, `Failed to update document: ${pageId}`); } }; diff --git a/apps/live/src/lib/auth.ts b/apps/live/src/lib/auth.ts index 5fd3e0cf42..8b5c37d94f 100644 --- a/apps/live/src/lib/auth.ts +++ b/apps/live/src/lib/auth.ts @@ -36,7 +36,7 @@ export const onAuthenticate = async ({ cookie = parsedToken.cookie; } catch (error) { // If token parsing fails, fallback to request headers - logger.error("Token parsing failed, using request headers:", error); + logger.error("AUTH: Token parsing failed, using request headers:", error); } finally { // If cookie is still not found, fallback to request headers if (!cookie) { @@ -76,7 +76,8 @@ export const handleAuthentication = async ({ cookie, userId }: { cookie: string; name: user.display_name, }, }; - } catch (_error) { + } catch (error) { + logger.error("AUTH: Token parsing failed, using request headers:", error); throw Error("Authentication unsuccessful!"); } }; diff --git a/apps/live/src/redis.ts b/apps/live/src/redis.ts index bd9bc4175b..f1dacd64a5 100644 --- a/apps/live/src/redis.ts +++ b/apps/live/src/redis.ts @@ -19,12 +19,12 @@ export class RedisManager { public async initialize(): Promise { if (this.redisClient && this.isConnected) { - logger.info("Redis client already initialized and connected"); + logger.info("REDIS_MANAGER: client already initialized and connected"); return; } if (this.connectionPromise) { - logger.info("Redis connection already in progress, waiting..."); + logger.info("REDIS_MANAGER: Redis connection already in progress, waiting..."); await this.connectionPromise; return; } @@ -54,7 +54,7 @@ export class RedisManager { const redisUrl = this.getRedisUrl(); if (!redisUrl) { - logger.warn("No Redis URL provided, Redis functionality will be disabled"); + logger.warn("REDIS_MANAGER: No Redis URL provided, Redis functionality will be disabled"); this.isConnected = false; return; } @@ -70,27 +70,27 @@ export class RedisManager { // Set up event listeners this.redisClient.on("connect", () => { - logger.info("Redis client connected"); + logger.info("REDIS_MANAGER: Redis client connected"); this.isConnected = true; }); this.redisClient.on("ready", () => { - logger.info("Redis client ready"); + logger.info("REDIS_MANAGER: Redis client ready"); this.isConnected = true; }); this.redisClient.on("error", (error) => { - logger.error("Redis client error:", error); + logger.error("REDIS_MANAGER: Redis client error:", error); this.isConnected = false; }); this.redisClient.on("close", () => { - logger.warn("Redis client connection closed"); + logger.warn("REDIS_MANAGER: Redis client connection closed"); this.isConnected = false; }); this.redisClient.on("reconnecting", () => { - logger.info("Redis client reconnecting..."); + logger.info("REDIS_MANAGER: Redis client reconnecting..."); this.isConnected = false; }); @@ -99,9 +99,9 @@ export class RedisManager { // Test the connection await this.redisClient.ping(); - logger.info("Redis connection test successful"); + logger.info("REDIS_MANAGER: Redis connection test successful"); } catch (error) { - logger.error("Failed to initialize Redis client:", error); + logger.error("REDIS_MANAGER: Failed to initialize Redis client:", error); this.isConnected = false; throw error; } finally { @@ -111,7 +111,7 @@ export class RedisManager { public getClient(): Redis | null { if (!this.redisClient || !this.isConnected) { - logger.warn("Redis client not available or not connected"); + logger.warn("REDIS_MANAGER: Redis client not available or not connected"); return null; } return this.redisClient; @@ -125,9 +125,9 @@ export class RedisManager { if (this.redisClient) { try { await this.redisClient.quit(); - logger.info("Redis client disconnected gracefully"); + logger.info("REDIS_MANAGER: Redis client disconnected gracefully"); } catch (error) { - logger.error("Error disconnecting Redis client:", error); + logger.error("REDIS_MANAGER: Error disconnecting Redis client:", error); // Force disconnect if quit fails this.redisClient.disconnect(); } finally { @@ -150,7 +150,7 @@ export class RedisManager { } return true; } catch (error) { - logger.error(`Error setting Redis key ${key}:`, error); + logger.error(`REDIS_MANAGER: Error setting Redis key ${key}:`, error); return false; } } @@ -162,7 +162,7 @@ export class RedisManager { try { return await client.get(key); } catch (error) { - logger.error(`Error getting Redis key ${key}:`, error); + logger.error(`REDIS_MANAGER: Error getting Redis key ${key}:`, error); return null; } } @@ -175,7 +175,7 @@ export class RedisManager { await client.del(key); return true; } catch (error) { - logger.error(`Error deleting Redis key ${key}:`, error); + logger.error(`REDIS_MANAGER: Error deleting Redis key ${key}:`, error); return false; } } @@ -188,7 +188,7 @@ export class RedisManager { const result = await client.exists(key); return result === 1; } catch (error) { - logger.error(`Error checking Redis key ${key}:`, error); + logger.error(`REDIS_MANAGER: Error checking Redis key ${key}:`, error); return false; } } @@ -201,7 +201,7 @@ export class RedisManager { const result = await client.expire(key, ttl); return result === 1; } catch (error) { - logger.error(`Error setting expiry for Redis key ${key}:`, error); + logger.error(`REDIS_MANAGER: Error setting expiry for Redis key ${key}:`, error); return false; } } diff --git a/apps/live/src/server.ts b/apps/live/src/server.ts index 1e2910eb4d..6f8a40f254 100644 --- a/apps/live/src/server.ts +++ b/apps/live/src/server.ts @@ -35,15 +35,15 @@ export class Server { public async initialize(): Promise { try { await redisManager.initialize(); - logger.info("Redis setup completed"); + logger.info("SERVER: Redis setup completed"); const manager = HocusPocusServerManager.getInstance(); this.hocuspocusServer = await manager.initialize(); - logger.info("HocusPocus setup completed"); + logger.info("SERVER: HocusPocus setup completed"); this.setupRoutes(this.hocuspocusServer); this.setupNotFoundHandler(); } catch (error) { - logger.error("Failed to initialize live server dependencies:", error); + logger.error("SERVER: Failed to initialize live server dependencies:", error); throw error; } } @@ -89,10 +89,10 @@ export class Server { public listen() { this.httpServer = this.app .listen(this.app.get("port"), () => { - logger.info(`Plane Live server has started at port ${this.app.get("port")}`); + logger.info(`SERVER: Express server has started at port ${this.app.get("port")}`); }) .on("error", (err) => { - logger.error("Failed to start server:", err); + logger.error("SERVER: Failed to start server:", err); throw err; }); } @@ -100,11 +100,11 @@ export class Server { public async destroy() { if (this.hocuspocusServer) { this.hocuspocusServer.closeConnections(); - logger.info("HocusPocus connections closed gracefully."); + logger.info("SERVER: HocusPocus connections closed gracefully."); } await redisManager.disconnect(); - logger.info("Redis connection closed gracefully."); + logger.info("SERVER: Redis connection closed gracefully."); if (this.httpServer) { await new Promise((resolve, reject) => { @@ -112,7 +112,7 @@ export class Server { if (err) { reject(err); } else { - logger.info("Express server closed gracefully."); + logger.info("SERVER: Express server closed gracefully."); resolve(); } }); diff --git a/apps/live/src/services/api.service.ts b/apps/live/src/services/api.service.ts index 02e8a53594..a5531d6b8b 100644 --- a/apps/live/src/services/api.service.ts +++ b/apps/live/src/services/api.service.ts @@ -1,4 +1,5 @@ import axios, { AxiosInstance } from "axios"; +import { logger } from "@plane/logger"; import { env } from "@/env"; export abstract class APIService { @@ -13,6 +14,17 @@ export abstract class APIService { withCredentials: true, timeout: 20000, }); + this.setupInterceptors(); + } + + private setupInterceptors() { + this.axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + logger.error("AXIOS_ERROR:", error); + return Promise.reject(error); + } + ); } setHeader(key: string, value: string) {