@masuidrive/ticket
Version:
Real-time ticket tracking viewer with Vite + Express
187 lines (163 loc) • 5.3 kB
text/typescript
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();
}
}