zkt26/z1/api/server.js

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();