+38
-3
react/src/App.tsx
+38
-3
react/src/App.tsx
···
1
1
import { posts } from "./lib/post";
2
2
import { BlogPostList } from "./components/BlogPostList";
3
3
import { Link } from "react-router";
4
+
import { useState } from "react";
4
5
5
6
export function App() {
7
+
const [searchBarDisplay, displaySearchBar] = useState(false);
6
8
return (
7
9
<>
8
10
<title>Posts</title>
9
11
<div className="w-screen p-5 flex flex-col items-center gap-10">
12
+
<nav className="flex justify-between items-center w-full sticky top-0">
13
+
<h1 className="text-3xl font-bold text-left">Blog App</h1>
14
+
{searchBarDisplay ? (
15
+
<>
16
+
<input
17
+
type="text"
18
+
placeholder="Search..."
19
+
className="border border-gray-300 rounded px-2 py-1"
20
+
onChange={(e) => {
21
+
// Implement search functionality here
22
+
}}
23
+
/>
24
+
<button
25
+
className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center"
26
+
onClick={() => displaySearchBar(false)}
27
+
>
28
+
Close
29
+
</button>
30
+
</>
31
+
) : (
32
+
<button
33
+
className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center"
34
+
onClick={() => displaySearchBar(true)}
35
+
>
36
+
Search
37
+
</button>
38
+
)}
39
+
<div className="flex w-full justify-end items-center">
40
+
<Link to="/post" className="w-1/3">
41
+
<div className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center">
42
+
New Post
43
+
</div>
44
+
</Link>
45
+
</div>
46
+
</nav>
10
47
<div className="flex flex-col gap-4 md:grid md:grid-cols-3 items-center justify-between w-full">
11
-
<h1 className="text-5xl font-bold md:col-start-2 text-center w-full">
12
-
Posts
13
-
</h1>
14
48
<div className="flex w-full justify-end items-center">
49
+
<h1 className="text-5xl font-bold text-center">Posts</h1>
15
50
<Link to="/post" className="w-1/3">
16
51
<div className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center">
17
52
New Post
+137
-10
server/index.ts
+137
-10
server/index.ts
···
1
1
import express from "express";
2
+
import { readFile, writeFile, exists } from "fs/promises";
2
3
3
4
const app = express();
4
5
const port = process.env["NODE_PORT"] ?? 5173;
5
6
6
-
app.get("/fast", (req, res) => {
7
-
res.send("Fast response!");
7
+
interface Task {
8
+
id: string;
9
+
title: string;
10
+
completed: boolean;
11
+
}
12
+
13
+
const getTasks: () => Promise<Task[]> = async () => {
14
+
if (!(await exists("tasks.json"))) {
15
+
await writeFile("tasks.json", JSON.stringify([]));
16
+
}
17
+
const file = await readFile("tasks.json", "utf-8");
18
+
if (file.length === 0) {
19
+
return [];
20
+
}
21
+
return JSON.parse(file);
22
+
};
23
+
24
+
const updateTask = async (task: Task): Promise<void> => {
25
+
const tasks = await getTasks();
26
+
const index = tasks.findIndex((t) => t.id === task.id);
27
+
if (index !== -1) {
28
+
tasks[index] = task;
29
+
} else {
30
+
tasks.push(task);
31
+
}
32
+
await writeFile("tasks.json", JSON.stringify(tasks));
33
+
};
34
+
35
+
const removeTask = async (id: string): Promise<void> => {
36
+
const tasks = await getTasks();
37
+
const index = tasks.findIndex((t) => t.id === id);
38
+
if (index !== -1) {
39
+
tasks.splice(index, 1);
40
+
await writeFile("tasks.json", JSON.stringify(tasks));
41
+
}
42
+
};
43
+
44
+
app.use(express.json());
45
+
46
+
app.get("/tasks", async (_req, res) => {
47
+
res.json(await getTasks());
48
+
});
49
+
50
+
app.post("/tasks", async (req, res) => {
51
+
if (!(typeof req.body.title === "string")) {
52
+
res.status(400).send("Invalid title");
53
+
return;
54
+
}
55
+
const newTask = {
56
+
id: Math.random().toString(16).substring(2, 8),
57
+
title: req.body.title,
58
+
completed: false,
59
+
};
60
+
await updateTask(newTask);
61
+
res.json(newTask).status(201);
62
+
});
63
+
64
+
app.get("/tasks/:id", async (req, res) => {
65
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
66
+
if (!task) {
67
+
res.status(404).send("Task not found");
68
+
} else {
69
+
res.json(task);
70
+
}
71
+
});
72
+
73
+
app.put("/tasks/:id", async (req, res) => {
74
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
75
+
if (!task) {
76
+
res.status(404).send("Task not found");
77
+
} else {
78
+
const missing = [];
79
+
if (req.body.title === undefined) missing.push("title");
80
+
if (req.body.completed === undefined) missing.push("completed");
81
+
if (missing.length > 0) {
82
+
res
83
+
.status(400)
84
+
.send(
85
+
`Missing field${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`,
86
+
);
87
+
}
88
+
const badTypes = [];
89
+
if (!(typeof req.body.title === "string")) badTypes.push("title");
90
+
if (!(typeof req.body.completed === "boolean")) badTypes.push("completed");
91
+
if (badTypes.length > 0) {
92
+
res
93
+
.status(400)
94
+
.send(
95
+
`Invalid type${badTypes.length > 1 ? "s" : ""}: ${badTypes.join(", ")}`,
96
+
);
97
+
return;
98
+
}
99
+
task.title = req.body.title ?? task.title;
100
+
task.completed = req.body.completed ?? task.completed;
101
+
await updateTask(task);
102
+
res.json(task);
103
+
}
104
+
});
105
+
106
+
app.patch("/tasks/:id", async (req, res) => {
107
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
108
+
if (!task) {
109
+
res.status(404).send("Task not found");
110
+
} else {
111
+
const badTypes = [];
112
+
if (
113
+
Object.keys(req.body).includes("title") &&
114
+
!(typeof req.body.title === "string")
115
+
)
116
+
badTypes.push("title");
117
+
if (
118
+
Object.keys(req.body).includes("completed") &&
119
+
!(typeof req.body.completed === "boolean")
120
+
)
121
+
badTypes.push("completed");
122
+
if (badTypes.length > 0) {
123
+
res
124
+
.status(400)
125
+
.send(
126
+
`Invalid type${badTypes.length > 1 ? "s" : ""}: ${badTypes.join(", ")}`,
127
+
);
128
+
return;
129
+
}
130
+
task.title = req.body.title ?? task.title;
131
+
task.completed = req.body.completed ?? task.completed;
132
+
await updateTask(task);
133
+
res.json(task);
134
+
}
8
135
});
9
136
10
-
app.get("/slow", (req, res) => {
11
-
console.log("Slow task start");
12
-
const t = Date.now();
13
-
setTimeout(() => {
14
-
let f = Date.now();
15
-
console.log(`Slow task finished in ${f - t}`);
16
-
res.send(`Slow response after ${f - t} ms`);
17
-
}, 5000);
137
+
app.delete("/tasks/:id", async (req, res) => {
138
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
139
+
if (!task) {
140
+
res.status(404).send("Task not found");
141
+
} else {
142
+
await removeTask(task.id);
143
+
res.status(204).send();
144
+
}
18
145
});
19
146
20
147
app.listen(port, () => {
+1
server/tasks.json
+1
server/tasks.json
···
1
+
[{"id":"2d516f","title":"","completed":4},{"id":"a4ec84","title":"","completed":4},{"id":"a667ea","title":"","completed":4},{"id":"b4d420","title":"4e4tta475aj","completed":false}]