diff --git a/data/tasks.json b/data/tasks.json index 3747a16..fe1e29f 100644 --- a/data/tasks.json +++ b/data/tasks.json @@ -152,5 +152,21 @@ "subtasks": [], "createdAt": "2025-07-23T04:22:47.898Z", "updatedAt": "2025-07-23T04:22:47.899Z" + }, + { + "id": "me2rq0pr67vivr", + "title": "Write poetry for Abdul", + "description": "Express feelings through verses", + "status": "pending", + "priority": "high", + "dueDate": "2025-08-10", + "assignedTo": "Abdullahi", + "subtasks": [ + "Draft lines", + "Add metaphor", + "Review rhythm" + ], + "createdAt": "2025-08-08T11:54:18.783Z", + "updatedAt": "2025-08-08T11:54:18.783Z" } ] \ No newline at end of file diff --git a/package.json b/package.json index 2327747..314284b 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,9 @@ "author": "Duraan", "license": "MIT", "dependencies": { - "express": "^4.18.2", - "cors": "^2.8.5", "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", "node-fetch": "^3.3.2" }, "devDependencies": { diff --git a/routes/tasks.js b/routes/tasks.js index b2bcfcb..4a43217 100644 --- a/routes/tasks.js +++ b/routes/tasks.js @@ -1,232 +1,160 @@ import express from "express"; -import fs from "fs/promises"; +import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; -const router = express.Router(); - -// ES module __dirname equivalent -// In CommonJS, __dirname is automatically available, but in ES modules we need to create it manually -// import.meta.url gives us the URL of the current module file -// fileURLToPath converts the URL to a file path string -// path.dirname gets the directory name from the file path +// Recreate __dirname const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -// Path to the data file -// path.join combines the current directory with the relative path to our data file -// "../data/tasks.json" means: go up one directory, then into 'data' folder, then 'tasks.json' -const dataFilePath = path.join(__dirname, "../data/tasks.json"); +const router = express.Router(); +const tasksFilePath = path.join(__dirname, "../data/tasks.json"); +const tasksData = await fs.promises.readFile(tasksFilePath, "utf8"); +const tasks = JSON.parse(tasksData); -// Helper function to read tasks from JSON file -// fs.readFile reads the file asynchronously and returns a promise -// JSON.parse converts the string data back to a JavaScript array -// If file doesn't exist or has errors, return empty array -async function getAllTasks() { - try { - const data = await fs.readFile(dataFilePath, "utf8"); - return JSON.parse(data); - } catch (error) { - return []; + +// Simple ID generator +const generateId = () => `${Date.now()}`; + +/* GET ALL TASKS */ +router.get("/tasks", async(req, res) => { + const { status, priority, assignedTo } = req.query; + + let filteredTasks = tasks; + + if (status) { + filteredTasks = filteredTasks.filter(task => task.status === status); } -} - -// Helper function to write tasks to JSON file -// JSON.stringify converts the array back to a string with nice formatting (null, 2) -// fs.writeFile writes the data to the file asynchronously -async function writeTasks(tasks) { - await fs.writeFile(dataFilePath, JSON.stringify(tasks, null, 2)); -} - -// Helper function to validate task data -// This function checks if the required fields are present and valid -function validateTaskData(taskData) { - const requiredFields = ["title", "description", "status", "priority"]; - const validStatuses = ["pending", "in-progress", "completed", "cancelled"]; - const validPriorities = ["low", "medium", "high", "urgent"]; - - // Check if all required fields are present - for (const field of requiredFields) { - if (!taskData[field]) { - return { isValid: false, error: `Missing required field: ${field}` }; - } + if (priority) { + filteredTasks = filteredTasks.filter(task => task.priority === priority); } - - // Validate status - if (!validStatuses.includes(taskData.status)) { - return { - isValid: false, - error: `Invalid status. Must be one of: ${validStatuses.join(", ")}`, - }; + if (assignedTo) { + filteredTasks = filteredTasks.filter(task => task.assignedTo === assignedTo); } - // Validate priority - if (!validPriorities.includes(taskData.priority)) { - return { - isValid: false, - error: `Invalid priority. Must be one of: ${validPriorities.join(", ")}`, - }; - } + res.json({ success: true, data: filteredTasks }); +}); - return { isValid: true }; -} +/* GET TASK BY ID */ +router.get("/tasks/:id", async(req, res) => { + const task = tasks.find(t => t.id === req.params.id); -// GET /api/tasks - Get all tasks -// This route handles GET requests to /api/tasks -// req = request object (contains data sent by client) -// res = response object (used to send data back to client) -router.get("/tasks", async (req, res) => { - try { - const tasks = await getAllTasks(); + if (!task) { + return res.status(404).json({ success: false, error: "Task not found" }); + } - // Add query parameter support for filtering - let filteredTasks = tasks; + res.json({ success: true, data: task }); +}); - // Filter by status if provided - if (req.query.status) { - filteredTasks = filteredTasks.filter( - (task) => task.status === req.query.status - ); +/* POST TASK */ +router.post("/tasks", async(req, res) => { + try { + const { + title, + description, + status, + priority, + assignedTo, + dueDate, + subtasks, + } = req.body; + + if (!title || !description || !status || !priority) { + return res.status(400).json({ + success: false, + error: "Missing required field: title, description, status or priority", + }); } - // Filter by priority if provided - if (req.query.priority) { - filteredTasks = filteredTasks.filter( - (task) => task.priority === req.query.priority - ); - } + const now = new Date().toISOString(); + const newTask = { + id: generateId(), + title, + description, + status, + priority, + assignedTo: assignedTo || null, + dueDate: dueDate || null, + subtasks: Array.isArray(subtasks) ? subtasks : [], + createdAt: now, + updatedAt: now, + }; - // Filter by assignedTo if provided - if (req.query.assignedTo) { - filteredTasks = filteredTasks.filter((task) => - task.assignedTo - .toLowerCase() - .includes(req.query.assignedTo.toLowerCase()) - ); - } + tasks.push(newTask); + fs.writeFileSync(tasksFilePath, JSON.stringify(tasks, null, 2)); - res.json({ - success: true, - count: filteredTasks.length, - data: filteredTasks, - }); - } catch (error) { - res.status(500).json({ - success: false, - error: "Error retrieving tasks", - }); + res.status(201).json({ success: true, message: "Task created", data: newTask }); + } catch (err) { + console.error("POST error:", err); + res.status(500).json({ success: false, message: "Something went wrong!", error: err.message }); } }); -// GET /api/tasks/:id - Get task by ID -// :id is a route parameter - it captures the value from the URL -// Example: /api/tasks/1 will set req.params.id = "1" -router.get("/tasks/:id", async (req, res) => { +/* PUT TASK */ +router.put("/tasks/:id", async(req, res) => { try { - const { id } = req.params; // Extract the ID from the URL - const tasks = await getAllTasks(); - const task = tasks.find((task) => task.id === id); // Find task with matching ID + const taskIndex = tasks.findIndex(t => t.id === req.params.id); - if (!task) { - return res.status(404).json({ + if (taskIndex === -1) { + return res.status(404).json({ success: false, error: "Task not found" }); + } + + const { + title, + description, + status, + priority, + assignedTo, + dueDate, + subtasks, + } = req.body; + + if (!title || !description || !status || !priority) { + return res.status(400).json({ success: false, - error: "Task not found", - }); // 404 = Not Found + error: "Missing required field: title, description, status or priority", + }); } - res.json({ - success: true, - data: task, - }); - } catch (error) { - res.status(500).json({ - success: false, - error: "Error retrieving task", - }); // 500 = Server Error - } -}); + const updatedTask = { + id: tasks[taskIndex].id, + title, + description, + status, + priority, + assignedTo: assignedTo || null, + dueDate: dueDate || null, + subtasks: Array.isArray(subtasks) ? subtasks : [], + createdAt: tasks[taskIndex].createdAt, + updatedAt: new Date().toISOString(), + }; -// POST /api/tasks - Create new task -// POST requests are used to create new resources -// req.body contains the data sent in the request body -router.post("/tasks", async (req, res) => { - try { - // TODO: Implement task creation - // 1. Extract data from req.body (title, description, status, priority, etc.) - // 2. Validate the data using validateTaskData function - // 3. Get all existing tasks using getAllTasks() - // 4. Generate a new ID for the task - // 5. Create a new task object with all required fields - // 6. Add the task to the tasks array - // 7. Save to file using writeTasks() - // 8. Send success response with status 201 - - // Temporary response - remove this when you implement the above - res.status(501).json({ - success: false, - error: - "POST endpoint not implemented yet - implement task creation above", - }); - } catch (error) { - res.status(500).json({ - success: false, - error: "Error creating task", - }); // 500 = Server Error - } -}); + tasks[taskIndex] = updatedTask; + fs.writeFileSync(tasksFilePath, JSON.stringify(tasks, null, 2)); -// PUT /api/tasks/:id - Update task -// PUT requests are used to update existing resources -// The entire resource is replaced with the new data -router.put("/tasks/:id", async (req, res) => { - try { - // TODO: Implement task update - // 1. Extract the task ID from req.params - // 2. Get the update data from req.body - // 3. Validate the data if status or priority is being updated - // 4. Get all tasks and find the task by ID - // 5. Check if task exists, return 404 if not found - // 6. Update the task with new data - // 7. Save to file using writeTasks() - // 8. Send success response with the updated task - - // Temporary response - remove this when you implement the above - res.status(501).json({ - success: false, - error: "PUT endpoint not implemented yet - implement task update above", - }); - } catch (error) { - res.status(500).json({ - success: false, - error: "Error updating task", - }); // 500 = Server Error + res.json({ success: true, message: "Task updated", data: updatedTask }); + } catch (err) { + console.error("PUT error:", err); + res.status(500).json({ success: false, message: "Something went wrong!", error: err.message }); } }); -// DELETE /api/tasks/:id - Delete task -// DELETE requests are used to remove resources -router.delete("/tasks/:id", async (req, res) => { +/* DELETE TASK BY ID */ +router.delete("/tasks/:id", async(req, res) => { try { - // TODO: Implement task deletion - // 1. Extract the task ID from req.params - // 2. Get all tasks and find the task by ID - // 3. Check if task exists, return 404 if not found - // 4. Store the task before deletion (for response) - // 5. Remove the task from the array - // 6. Save to file using writeTasks() - // 7. Send success response with the deleted task - - // Temporary response - remove this when you implement the above - res.status(501).json({ - success: false, - error: - "DELETE endpoint not implemented yet - implement task deletion above", - }); - } catch (error) { - res.status(500).json({ - success: false, - error: "Error deleting task", - }); // 500 = Server Error + const taskIndex = tasks.findIndex(t => t.id === req.params.id); + + if (taskIndex === -1) { + return res.status(404).json({ success: false, error: "Task not found" }); + } + + const deletedTask = tasks.splice(taskIndex, 1)[0]; + fs.writeFileSync(tasksFilePath, JSON.stringify(tasks, null, 2)); + + res.json({ success: true, message: "Task deleted", data: deletedTask }); + } catch (err) { + console.error("DELETE error:", err); + res.status(500).json({ success: false, message: "Something went wrong!", error: err.message }); } });