UNPKG

@masuidrive/ticket

Version:

Real-time ticket tracking viewer with Vite + Express

187 lines (163 loc) 5.3 kB
import express from 'express'; import cors from 'cors'; import { createServer } from 'http'; import { WebSocketServer, WebSocket } from 'ws'; import { FileService } from './fileService'; import { FileWatcher } from './fileWatcher'; import { TicketContent } from './types'; export class TicketServer { private app: express.Application; private server: any; private wss!: WebSocketServer; private fileService: FileService; private fileWatcher: FileWatcher; private clients: Set<WebSocket> = new Set(); private port: number; constructor(port: number = 4932, projectRoot?: string) { this.port = port; this.app = express(); this.fileService = new FileService(projectRoot); this.fileWatcher = new FileWatcher(projectRoot); this.setupMiddleware(); this.setupRoutes(); this.setupWebSocket(); this.setupFileWatcher(); } private setupMiddleware(): void { this.app.use(cors()); this.app.use(express.json()); } private setupRoutes(): void { // Root endpoint - API documentation this.app.get('/', (req, res) => { res.json({ name: 'Ticket Viewer Server', version: '1.0.0', endpoints: { 'GET /': 'This documentation', 'GET /health': 'Health check endpoint', 'GET /api/ticket': 'Get current ticket content (YAML frontmatter + markdown)', 'GET /api/ticket/exists': 'Check if current-ticket.md exists', 'WS ws://localhost:4932': 'WebSocket endpoint for real-time updates' }, description: 'Server that serves current-ticket.md content with real-time updates' }); }); // Health check endpoint this.app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Get current ticket content this.app.get('/api/ticket', async (req, res) => { try { const content = await this.fileService.readTicketFile(); if (!content) { return res.status(404).json({ error: 'Ticket file not found', message: 'No current-ticket.md file exists in the project root' }); } res.json(content); } catch (error) { console.error('Error reading ticket file:', error); res.status(500).json({ error: 'Internal server error', message: 'Failed to read ticket file' }); } }); // Check if ticket file exists this.app.get('/api/ticket/exists', async (req, res) => { try { const exists = await this.fileService.fileExists('current-ticket.md'); res.json({ exists }); } catch (error) { console.error('Error checking file existence:', error); res.status(500).json({ error: 'Internal server error', message: 'Failed to check file existence' }); } }); } private setupWebSocket(): void { this.server = createServer(this.app); this.wss = new WebSocketServer({ server: this.server }); this.wss.on('connection', (ws: WebSocket) => { console.log('New WebSocket client connected'); this.clients.add(ws); ws.on('close', () => { console.log('WebSocket client disconnected'); this.clients.delete(ws); }); ws.on('error', (error) => { console.error('WebSocket error:', error); this.clients.delete(ws); }); // Send initial content this.fileService.readTicketFile().then(content => { if (content) { ws.send(JSON.stringify({ type: 'update', data: content })); } }).catch(error => { console.error('Error sending initial content:', error); }); }); } private setupFileWatcher(): void { this.fileWatcher.watchFile('current-ticket.md'); this.fileWatcher.on('fileChanged', async () => { console.log('Ticket file changed'); await this.broadcastUpdate(); }); this.fileWatcher.on('fileAdded', async () => { console.log('Ticket file added'); await this.broadcastUpdate(); }); this.fileWatcher.on('fileRemoved', () => { console.log('Ticket file removed'); this.broadcast({ type: 'removed', data: null }); }); this.fileWatcher.on('error', (error) => { console.error('File watcher error:', error); }); } private async broadcastUpdate(): Promise<void> { try { const content = await this.fileService.readTicketFile(); if (content) { this.broadcast({ type: 'update', data: content }); } } catch (error) { console.error('Error broadcasting update:', error); } } private broadcast(message: any): void { const messageStr = JSON.stringify(message); this.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(messageStr); } }); } start(): void { this.server.listen(this.port, () => { console.log(`Ticket server running on http://localhost:${this.port}`); console.log(`WebSocket endpoint: ws://localhost:${this.port}`); }); } stop(): void { this.fileWatcher.stop(); this.wss.close(); this.server.close(); } }