const express = require('express'); const cors = require('cors'); const pool = require('./db'); const { createClient } = require('redis'); const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(cors()); app.use(express.json()); // Redis client let redisClient; const CACHE_TTL = 30; // seconds async function initRedis() { try { redisClient = createClient({ url: `redis://${process.env.REDIS_HOST || 'redis'}:${process.env.REDIS_PORT || 6379}` }); redisClient.on('error', (err) => console.error('Redis error:', err)); await redisClient.connect(); console.log('Connected to Redis'); } catch (err) { console.error('Failed to connect to Redis:', err.message); redisClient = null; } } // Helper: invalidate cache async function invalidateCache() { if (redisClient && redisClient.isOpen) { try { await redisClient.del('tasks:all'); } catch (err) { console.error('Cache invalidation error:', err.message); } } } // Health check app.get('/api/health', async (req, res) => { try { await pool.query('SELECT 1'); const redisOk = redisClient && redisClient.isOpen; res.json({ status: 'healthy', database: 'connected', cache: redisOk ? 'connected' : 'disconnected', timestamp: new Date().toISOString() }); } catch (err) { res.status(503).json({ status: 'unhealthy', error: err.message }); } }); // GET /api/tasks — List all tasks (with Redis caching) app.get('/api/tasks', async (req, res) => { try { // Try cache first if (redisClient && redisClient.isOpen) { const cached = await redisClient.get('tasks:all'); if (cached) { return res.json({ tasks: JSON.parse(cached), source: 'cache' }); } } const result = await pool.query( 'SELECT * FROM tasks ORDER BY created_at DESC' ); // Store in cache if (redisClient && redisClient.isOpen) { await redisClient.setEx('tasks:all', CACHE_TTL, JSON.stringify(result.rows)); } res.json({ tasks: result.rows, source: 'database' }); } catch (err) { console.error('Error fetching tasks:', err); res.status(500).json({ error: 'Failed to fetch tasks' }); } }); // POST /api/tasks — Create a new task app.post('/api/tasks', async (req, res) => { try { const { title, description } = req.body; if (!title || title.trim() === '') { return res.status(400).json({ error: 'Title is required' }); } const result = await pool.query( 'INSERT INTO tasks (title, description) VALUES ($1, $2) RETURNING *', [title.trim(), (description || '').trim()] ); await invalidateCache(); res.status(201).json({ task: result.rows[0] }); } catch (err) { console.error('Error creating task:', err); res.status(500).json({ error: 'Failed to create task' }); } }); // PUT /api/tasks/:id — Update a task app.put('/api/tasks/:id', async (req, res) => { try { const { id } = req.params; const { title, description, completed } = req.body; const result = await pool.query( `UPDATE tasks SET title = COALESCE($1, title), description = COALESCE($2, description), completed = COALESCE($3, completed), updated_at = CURRENT_TIMESTAMP WHERE id = $4 RETURNING *`, [title, description, completed, id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Task not found' }); } await invalidateCache(); res.json({ task: result.rows[0] }); } catch (err) { console.error('Error updating task:', err); res.status(500).json({ error: 'Failed to update task' }); } }); // DELETE /api/tasks/:id — Delete a task app.delete('/api/tasks/:id', async (req, res) => { try { const { id } = req.params; const result = await pool.query( 'DELETE FROM tasks WHERE id = $1 RETURNING *', [id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Task not found' }); } await invalidateCache(); res.json({ message: 'Task deleted', task: result.rows[0] }); } catch (err) { console.error('Error deleting task:', err); res.status(500).json({ error: 'Failed to delete task' }); } }); // Start server async function start() { await initRedis(); app.listen(PORT, '0.0.0.0', () => { console.log(`Task Manager API running on port ${PORT}`); }); } start();