Skip to content
Open
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"start": "http-server dist"
Comment thread
chriscalo marked this conversation as resolved.
"start": "node server.js"
},
"dependencies": {
"client-sessions": "^0.8.0",
"express": "^4.18.2",
"vue": "^3.0.5"
},
"devDependencies": {
Expand Down
50 changes: 50 additions & 0 deletions server.js

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not following the pattern:

https://chriscalo.github.io/dev-skills/skills/nodejs/express/server-startup

Please fix. Use the listen() function verbatim.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createServer } from "node:http";
import { execSync } from "node:child_process";
import express from "express";
import { app } from "./src/api/index.js";
import notFound from "./src/api/404.js";

const DEVELOPMENT = process.env.NODE_ENV !== "production";

function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

function clearPort(port) {
try {
execSync(`fuser -k ${port}/tcp`, { stdio: "ignore" });
} catch {}
}

app.use(express.static("dist"));
app.use(notFound);

export async function listen(app, port) {
const listener = createServer(app);
return startServer();

async function startServer() {
try {
await new Promise((resolve, reject) => {
listener.listen(port);
listener.once("listening", resolve);
listener.once("error", reject);
});

const url = `http://localhost:${port}`;
return { url, port, listener };
} catch (error) {
if (DEVELOPMENT && error.code === "EADDRINUSE") {
console.warn(`Port ${port} in use, retrying...`);
await clearPort(port);
await delay(1000);
return startServer();
}
throw error;
}
}
}

const port = Number(process.env.PORT) || 3000;
const { url } = await listen(app, port);
console.log(`Listening on ${url}`);
11 changes: 11 additions & 0 deletions src/api/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
Comment thread
chriscalo marked this conversation as resolved.
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
<p>The page you requested could not be found.</p>
</body>
</html>
9 changes: 9 additions & 0 deletions src/api/404.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { fileURLToPath } from "url";
import { dirname, join } from "path";

const __dirname = dirname(fileURLToPath(import.meta.url));
const notFoundPath = join(__dirname, "404.html");

export default function notFound(req, res) {
Comment thread
chriscalo marked this conversation as resolved.
res.status(404).sendFile(notFoundPath);
}
44 changes: 44 additions & 0 deletions src/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import express from "express";
import sessions from "client-sessions";
import { fileURLToPath } from "url";
import { dirname, join } from "path";

const __dirname = dirname(fileURLToPath(import.meta.url));

const ONE_DAY_MS = 24 * 60 * 60 * 1000;
const FIVE_MINUTES_MS = 5 * 60 * 1000;

export const app = express();

app.use(sessions({
cookieName: "session",
secret: process.env.SESSION_SECRET,
duration: ONE_DAY_MS,
activeDuration: FIVE_MINUTES_MS,
cookie: { httpOnly: true, secure: true, sameSite: "lax" },
}));

app.get("/login", (req, res) => {
res.sendFile(join(__dirname, "login.html"));
});

app.post("/api/login", express.urlencoded({ extended: false }), (req, res) => {
req.session.user = { name: req.body.name };
res.json({ ok: true });
});

app.get("/logout", (req, res) => {
res.sendFile(join(__dirname, "logout.html"));
});

app.post("/api/logout", (req, res) => {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There needs to be a UI for logging out

req.session.reset();
res.json({ ok: true });
});

export function requireLogin(req, res, next) {
Comment thread
chriscalo marked this conversation as resolved.
if (!req.session?.user) {
return res.status(401).json({ error: "unauthorized" });
}
next();
}
29 changes: 29 additions & 0 deletions src/api/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Log In</title>
</head>
<body>
<h1>Log In</h1>
<form>
<label>
Name
<input type="text" name="name" required />
</label>
<button type="submit">Log In</button>
</form>
<script>
document.querySelector("form").addEventListener("submit", async (e) => {
e.preventDefault();
const res = await fetch("/api/login", {
method: "POST",
body: new URLSearchParams(new FormData(e.target)),
});
if (res.ok) {
window.location.href = "/";
}
});
</script>
</body>
</html>
22 changes: 22 additions & 0 deletions src/api/logout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Log Out</title>
</head>
<body>
<h1>Log Out</h1>
<form>
<button type="submit">Log Out</button>
</form>
<script>
document.querySelector("form").addEventListener("submit", async (e) => {
e.preventDefault();
const res = await fetch("/api/logout", { method: "POST" });
if (res.ok) {
window.location.href = "/login";
}
});
</script>
</body>
</html>