CMU Coding Bootcamp
1import express from "express";
2import { readFile, writeFile, exists } from "fs/promises";
3
4const router = express.Router();
5
6interface Task {
7 id: string;
8 title: string;
9 completed: boolean;
10}
11
12const getTasks: () => Promise<Task[]> = async () => {
13 if (!(await exists("tasks.json"))) {
14 await writeFile("tasks.json", JSON.stringify([]));
15 }
16 const file = await readFile("tasks.json", "utf-8");
17 if (file.length === 0) {
18 return [];
19 }
20 return JSON.parse(file);
21};
22
23const updateTask = async (task: Task): Promise<void> => {
24 const tasks = await getTasks();
25 const index = tasks.findIndex((t) => t.id === task.id);
26 if (index !== -1) {
27 tasks[index] = task;
28 } else {
29 tasks.push(task);
30 }
31 await writeFile("tasks.json", JSON.stringify(tasks));
32};
33
34const removeTask = async (id: string): Promise<void> => {
35 const tasks = await getTasks();
36 const index = tasks.findIndex((t) => t.id === id);
37 if (index !== -1) {
38 tasks.splice(index, 1);
39 await writeFile("tasks.json", JSON.stringify(tasks));
40 }
41};
42
43router.use(express.json());
44
45router.get("/", async (_req, res) => {
46 res.json(await getTasks());
47});
48
49router.post("/", async (req, res) => {
50 if (!(typeof req.body.title === "string")) {
51 res.status(400).send("Invalid title");
52 return;
53 }
54 const newTask = {
55 id: Math.random().toString(16).substring(2, 8),
56 title: req.body.title,
57 completed: false,
58 };
59 await updateTask(newTask);
60 res.json(newTask).status(201);
61});
62
63router.get("/:id", async (req, res) => {
64 const task = (await getTasks()).find((t) => t.id === req.params.id);
65 if (!task) {
66 res.status(404).send("Task not found");
67 } else {
68 res.json(task);
69 }
70});
71
72router.put("/:id", async (req, res) => {
73 const task = (await getTasks()).find((t) => t.id === req.params.id);
74 if (!task) {
75 res.status(404).send("Task not found");
76 } else {
77 const missing = [];
78 if (req.body.title === undefined) missing.push("title");
79 if (req.body.completed === undefined) missing.push("completed");
80 if (missing.length > 0) {
81 res
82 .status(400)
83 .send(
84 `Missing field${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`,
85 );
86 }
87 const badTypes = [];
88 if (!(typeof req.body.title === "string")) badTypes.push("title");
89 if (!(typeof req.body.completed === "boolean")) badTypes.push("completed");
90 if (badTypes.length > 0) {
91 res
92 .status(400)
93 .send(
94 `Invalid type${badTypes.length > 1 ? "s" : ""}: ${badTypes.join(", ")}`,
95 );
96 return;
97 }
98 task.title = req.body.title ?? task.title;
99 task.completed = req.body.completed ?? task.completed;
100 await updateTask(task);
101 res.json(task);
102 }
103});
104
105router.patch("/:id", async (req, res) => {
106 const task = (await getTasks()).find((t) => t.id === req.params.id);
107 if (!task) {
108 res.status(404).send("Task not found");
109 } else {
110 const badTypes = [];
111 if (
112 Object.keys(req.body).includes("title") &&
113 !(typeof req.body.title === "string")
114 )
115 badTypes.push("title");
116 if (
117 Object.keys(req.body).includes("completed") &&
118 !(typeof req.body.completed === "boolean")
119 )
120 badTypes.push("completed");
121 if (badTypes.length > 0) {
122 res
123 .status(400)
124 .send(
125 `Invalid type${badTypes.length > 1 ? "s" : ""}: ${badTypes.join(", ")}`,
126 );
127 return;
128 }
129 task.title = req.body.title ?? task.title;
130 task.completed = req.body.completed ?? task.completed;
131 await updateTask(task);
132 res.json(task);
133 }
134});
135
136router.delete("/:id", async (req, res) => {
137 const task = (await getTasks()).find((t) => t.id === req.params.id);
138 if (!task) {
139 res.status(404).send("Task not found");
140 } else {
141 await removeTask(task.id);
142 res.status(204).send();
143 }
144});
145
146export default router;