214 lines
5.1 KiB
JavaScript
214 lines
5.1 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const morgan = require('morgan');
|
|
const { Pool } = require('pg');
|
|
|
|
const app = express();
|
|
|
|
const PORT = process.env.PORT || 10000;
|
|
const DATABASE_URL = process.env.DATABASE_URL;
|
|
const CORS_ORIGIN = process.env.CORS_ORIGIN || 'http://localhost:8080';
|
|
|
|
if (!DATABASE_URL) {
|
|
console.error('DATABASE_URL environment variable is required');
|
|
process.exit(1);
|
|
}
|
|
|
|
const allowedOrigins = CORS_ORIGIN.split(',').map((origin) => origin.trim());
|
|
|
|
app.use(
|
|
cors({
|
|
origin(origin, callback) {
|
|
if (!origin || allowedOrigins.includes(origin)) {
|
|
callback(null, true);
|
|
} else {
|
|
callback(new Error(`CORS not allowed for origin: ${origin}`));
|
|
}
|
|
},
|
|
})
|
|
);
|
|
|
|
app.use(express.json());
|
|
app.use(morgan('combined'));
|
|
|
|
const useSsl = process.env.NODE_ENV === 'production';
|
|
|
|
const pool = new Pool({
|
|
connectionString: DATABASE_URL,
|
|
ssl: useSsl ? { rejectUnauthorized: false } : false,
|
|
});
|
|
|
|
async function initDb() {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
id SERIAL PRIMARY KEY,
|
|
title VARCHAR(200) NOT NULL,
|
|
description TEXT DEFAULT '',
|
|
subject VARCHAR(120) DEFAULT '',
|
|
task_type VARCHAR(50) DEFAULT 'assignment',
|
|
priority VARCHAR(20) DEFAULT 'normal',
|
|
deadline DATE,
|
|
completed BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`);
|
|
}
|
|
|
|
function normalizeTask(row) {
|
|
return {
|
|
id: row.id,
|
|
title: row.title,
|
|
description: row.description || '',
|
|
subject: row.subject || '',
|
|
taskType: row.task_type || 'assignment',
|
|
priority: row.priority || 'normal',
|
|
deadline: row.deadline,
|
|
completed: row.completed,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
app.get('/health', async (req, res) => {
|
|
try {
|
|
await pool.query('SELECT 1');
|
|
res.json({
|
|
status: 'ok',
|
|
database: 'connected',
|
|
app: 'Student Study Planner',
|
|
});
|
|
} catch (error) {
|
|
console.error('Health check failed:', error);
|
|
res.status(500).json({
|
|
status: 'error',
|
|
database: 'disconnected',
|
|
message: error.message,
|
|
});
|
|
}
|
|
});
|
|
|
|
app.get('/api/tasks', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(`
|
|
SELECT *
|
|
FROM tasks
|
|
ORDER BY completed ASC, deadline ASC NULLS LAST, created_at DESC;
|
|
`);
|
|
|
|
res.json(result.rows.map(normalizeTask));
|
|
} catch (error) {
|
|
console.error('Error getting tasks:', error);
|
|
res.status(500).json({ message: 'Error getting tasks' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/tasks', async (req, res) => {
|
|
try {
|
|
const {
|
|
title,
|
|
description = '',
|
|
subject = '',
|
|
taskType = 'assignment',
|
|
priority = 'normal',
|
|
deadline = null,
|
|
} = req.body;
|
|
|
|
if (!title || !title.trim()) {
|
|
return res.status(400).json({ message: 'Title is required' });
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`
|
|
INSERT INTO tasks
|
|
(title, description, subject, task_type, priority, deadline)
|
|
VALUES
|
|
($1, $2, $3, $4, $5, $6)
|
|
RETURNING *;
|
|
`,
|
|
[
|
|
title.trim(),
|
|
description,
|
|
subject,
|
|
taskType,
|
|
priority,
|
|
deadline || null,
|
|
]
|
|
);
|
|
|
|
res.status(201).json(normalizeTask(result.rows[0]));
|
|
} catch (error) {
|
|
console.error('Error creating task:', error);
|
|
res.status(500).json({ message: 'Error creating task' });
|
|
}
|
|
});
|
|
|
|
app.patch('/api/tasks/:id/toggle', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const result = await pool.query(
|
|
`
|
|
UPDATE tasks
|
|
SET completed = NOT completed,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $1
|
|
RETURNING *;
|
|
`,
|
|
[id]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ message: 'Task not found' });
|
|
}
|
|
|
|
res.json(normalizeTask(result.rows[0]));
|
|
} catch (error) {
|
|
console.error('Error updating task:', error);
|
|
res.status(500).json({ message: 'Error updating 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({ message: 'Task not found' });
|
|
}
|
|
|
|
res.json({
|
|
message: 'Task deleted',
|
|
task: normalizeTask(result.rows[0]),
|
|
});
|
|
} catch (error) {
|
|
console.error('Error deleting task:', error);
|
|
res.status(500).json({ message: 'Error deleting task' });
|
|
}
|
|
});
|
|
|
|
app.use((req, res) => {
|
|
res.status(404).json({ message: 'Route not found' });
|
|
});
|
|
|
|
initDb()
|
|
.then(() => {
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`Student Study Planner backend running on port ${PORT}`);
|
|
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
console.log(`SSL for database: ${useSsl ? 'enabled' : 'disabled'}`);
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
console.error('Database initialization failed:', error);
|
|
process.exit(1);
|
|
}); |