title: "God's Witness",
};
+export const METRICS_KEY = ["metrics", "visits"] as const;
+
await Deno.mkdir(STORAGE_DIR, { recursive: true });
const kv = await Deno.openKv(`${STORAGE_DIR}/db`);
export function __closeKvForTests() {
return `${slug}-${id}.md`;
}
+async function incrementVisitCount(): Promise<number> {
+ const result = await kv.get<number>(METRICS_KEY);
+ const next = (result.value ?? 0) + 1;
+ await kv.set(METRICS_KEY, next);
+ return next;
+}
+
+export async function getVisitCount(): Promise<number> {
+ const result = await kv.get<number>(METRICS_KEY);
+ return result.value ?? 0;
+}
+
// Session management
function generateSessionId(): string {
return crypto.randomUUID();
color: #666;
margin-bottom: 15px;
}
+ .metrics-section {
+ background: white;
+ padding: 25px;
+ border-radius: 4px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+ margin-bottom: 30px;
+ }
+ .metrics-section h3 {
+ margin-bottom: 15px;
+ font-size: 1.2em;
+ color: #333;
+ }
+ .metrics-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ }
+ .metrics-card {
+ flex: 1;
+ min-width: 200px;
+ padding: 15px;
+ border-radius: 4px;
+ border: 1px solid #eee;
+ background: #fafafa;
+ }
+ .metrics-label {
+ font-size: 0.9em;
+ color: #777;
+ margin-bottom: 5px;
+ }
+ .metrics-value {
+ font-size: 1.8em;
+ font-weight: 600;
+ color: #222;
+ }
.error {
background: #f8d7da;
color: #721c24;
async function renderAdminPage(
username: string,
csrfToken: string,
+ visitCount: number,
): Promise<string> {
const posts = await getAllPosts();
const sortedPosts = posts.sort((a, b) => b.timestamp - a.timestamp);
}).join("")
: '<div class="empty">No posts yet. Create your first post below!</div>';
+ const metricsHTML = `
+ <section class="metrics-section">
+ <h3>Metrics</h3>
+ <div class="metrics-grid">
+ <div class="metrics-card">
+ <div class="metrics-label">Total visits</div>
+ <div class="metrics-value">${visitCount.toLocaleString()}</div>
+ </div>
+ </div>
+ </section>
+ `;
+
return renderHTML(`
${renderHeader(username)}
<button type="submit">Publish</button>
</form>
+ ${metricsHTML}
+
<section>
${postsHTML}
</section>
});
}
-async function adminPageResponse(username: string): Promise<Response> {
+async function adminPageResponse(username: string, visitCount: number): Promise<Response> {
const csrfToken = generateCsrfToken();
- const html = await renderAdminPage(username, csrfToken);
+ const html = await renderAdminPage(username, csrfToken, visitCount);
return new Response(html, {
headers: {
"Content-Type": "text/html",
export async function handler(req: Request): Promise<Response> {
const url = new URL(req.url);
+ const visitCount = await incrementVisitCount();
// Check authentication
const sessionId = getSessionIdFromCookie(req);
});
}
- return await adminPageResponse(session.username);
+ return await adminPageResponse(session.username, visitCount);
}
// Login page
}
});
});
+
+Deno.test("visit counter increments per request", async () => {
+ await withTempDir(async () => {
+ const mod = await import(
+ new URL("../main.ts", import.meta.url).href +
+ `?t=${crypto.randomUUID()}`
+ );
+ const { handler, METRICS_KEY, __closeKvForTests } = mod;
+
+ for (let i = 0; i < 3; i++) {
+ await handler(new Request("http://localhost/"));
+ }
+
+ const kv = await Deno.openKv("./storage/db");
+ const result = await kv.get<number>(METRICS_KEY);
+ kv.close();
+
+ if (result.value !== 3) {
+ throw new Error(
+ `Expected 3 visits recorded, got ${result.value ?? 0}`,
+ );
+ }
+
+ __closeKvForTests?.();
+ });
+});