164 lines
4.4 KiB
JavaScript
164 lines
4.4 KiB
JavaScript
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();
|