import express, { type Request, Response, NextFunction } from "express"; import { registerRoutes } from "./routes.js"; import { initializeDatabase, closeDatabase } from "./db.js"; import { storage } from "./storage"; import { initializeVapid } from "./push.js"; import { apiKeyAuth } from "./api-key-auth.js"; const app = express(); app.set("trust proxy", true); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use((req, res, next) => { const start = Date.now(); const path = req.path; let capturedJsonResponse: Record | undefined = undefined; const originalResJson = res.json; res.json = function (bodyJson, ...args) { capturedJsonResponse = bodyJson; return originalResJson.apply(res, [bodyJson, ...args]); }; res.on("finish", () => { const duration = Date.now() - start; if (path.startsWith("/api")) { const formattedTime = new Date().toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", second: "2-digit", hour12: true, }); let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`; if (capturedJsonResponse) { logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`; } if (logLine.length > 80) { logLine = logLine.slice(0, 79) + "…"; } console.log(`${formattedTime} [express] ${logLine}`); } }); // Ensure no caching for API routes to prevent sticky sessions if (path.startsWith("/api")) { res.header('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); res.header('Pragma', 'no-cache'); res.header('Expires', '0'); } next(); }); (async () => { // Import utilities based on environment // Use require-style imports to avoid bundler issues let log: (message: string, source?: string) => void; let serveStatic: (app: express.Express) => void; let setupVite: ((app: express.Express, server: any) => Promise) | undefined; if (app.get("env") === "development") { try { const viteModule = await import("./vite.js"); log = viteModule.log; setupVite = viteModule.setupVite; serveStatic = viteModule.serveStatic; } catch (error) { console.error("Failed to load vite module:", error); process.exit(1); } } else { const staticModule = await import("./static.js"); log = staticModule.log; serveStatic = staticModule.serveStatic; } // Initialize database if using production mode or USE_DB is true if (process.env.NODE_ENV === 'production' || process.env.USE_DB === 'true') { try { await initializeDatabase(); // Initialize Web Push VAPID keys after database is ready await initializeVapid(); } catch (error) { console.error('Failed to initialize database:', error); process.exit(1); } } // Seed default AI System Prompt if missing const currentPrompt = await storage.getSystemSettings("ai_system_prompt"); if (!currentPrompt) { const defaultPrompt = `You are TaskFlow AI, an intelligent assistant for the TaskFlow application. You have access to the user's current tasks and context. User Name: \${user.username} Current Date/Time: \${currentDate.toLocaleString('de-DE')} (Day: \${currentDate.toLocaleDateString('en-US', { weekday: 'long' })}) Current Context: \${context} Answer the user's questions based on this context. Be concise, helpful, and friendly. ### 🛠️ AVAILABLE TOOLS You can create, search, update, and delete tasks using the provided tools. **1. Task Management** - **Create**: Use 'createTask'. Title is required. - *Bulk Creation*: If the user provides a list of tasks, call 'createTask' multiple times in parallel. - *Relative Dates*: Understand natural language! "Morgen" = Tomorrow, "Next Friday" = Date of next Friday. Always calculate the specific ISO string based on 'Current Date/Time'. - *Returns*: The tool returns the created Task ID. Remember this ID for immediate edits. - **Update/Delete**: First SEARCH for the task ID using 'searchTasks' (search by title), then use 'updateTask' or 'deleteTask'. - *Editing Recently Created*: If the user says "Change that to...", refer to the ID of the task you just created. - *Delete All*: Search all, then delete each. - **Labels**: Use 'getLabels' tags. **2. 🧠 Smart Planning & Scheduling** - **"Break this down"**: Create subtasks with 'parentTaskId'. - **"Find time for this"**: Use 'scheduleTask'. - **Time Boxing**: Set 'estimatedDuration' (minutes) if mentioned. **3. Gamification** - Check achievements/XP with 'getAchievements'. - Check highscores with 'getLeaderboard'. ### 📅 DATE & TIME RULES - **"Today"**: Use the 'Current Date/Time' context. - **"Morgen" / "Tomorrow"**: Add 1 day to Current Date. - **Scheduling**: When using 'scheduleTask', inform the user specifically *when* you scheduled it. IMPORTANT: Do NOT show Task IDs to the user. Reference tasks by Title. CRITICAL: After executing tools, provide a concise summary of your actions.`; await storage.setSystemSettings("ai_system_prompt", defaultPrompt); console.log("Seeded default AI System Prompt"); } const server = await registerRoutes(app); app.use((err: any, _req: Request, res: Response, _next: NextFunction) => { const status = err.status || err.statusCode || 500; const message = err.message || "Internal Server Error"; res.status(status).json({ message }); // Don't throw err here, it crashes the server/socket after response is sent }); // importantly only setup vite in development and after // setting up all the other routes so the catch-all route // doesn't interfere with the other routes if (app.get("env") === "development") { if (setupVite) { await setupVite(app, server); } } else { serveStatic(app); } // ALWAYS serve the app on the port specified in the environment variable PORT // Other ports are firewalled. Default to 5000 if not specified. // this serves both the API and the client. // It is the only port that is not firewalled. const port = parseInt(process.env.PORT || '5001', 10); server.listen({ port, host: "0.0.0.0", }, () => { log(`serving on port ${port}`); }); // Graceful shutdown const gracefulShutdown = async (signal: string) => { log(`${signal} received, closing server gracefully...`); server.close(async () => { log('HTTP server closed'); await closeDatabase(); process.exit(0); }); // Force close after 30 seconds setTimeout(() => { console.error('Forced shutdown after timeout'); process.exit(1); }, 30000); }; process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGINT', () => gracefulShutdown('SIGINT')); })();