From 560e9e877c1f48b754e3bc1f99414c6930584faf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 05:44:32 +0000 Subject: [PATCH 1/4] Initial plan From 4dbe1008ea32554b66a04a529e96589693e3846a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 05:46:28 +0000 Subject: [PATCH 2/4] Initial exploration of repository structure Co-authored-by: duckeydev <171411578+duckeydev@users.noreply.github.com> --- bun.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/bun.lock b/bun.lock index 4a88db6..59648e2 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "opendiscuss", From 4e5d5858fad1d2be5dcc638fccafc6795d89c148 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 05:51:12 +0000 Subject: [PATCH 3/4] Create forum with Bun and modern UI Co-authored-by: duckeydev <171411578+duckeydev@users.noreply.github.com> --- .gitignore | 5 + README.md | 44 ++++++- database.ts | 91 ++++++++++++++ index.ts | 86 +++++++++++++ templates.ts | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 560 insertions(+), 3 deletions(-) create mode 100644 database.ts create mode 100644 templates.ts diff --git a/.gitignore b/.gitignore index a14702c..b3adcef 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,8 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store + +# SQLite databases +*.db +*.db-shm +*.db-wal diff --git a/README.md b/README.md index 7a5f062..4a5ea12 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,53 @@ -# opendiscuss +# OpenDiscuss 💬 -To install dependencies: +A modern, fast forum built with [Bun](https://bun.com) featuring a beautiful gradient UI. + +## Features + +- ✨ Create and read discussion posts +- 💬 Add comments to posts +- 🎨 Modern, responsive UI with gradient design +- ⚡ Lightning-fast performance with Bun +- 💾 SQLite database for data persistence +- 🚀 No external dependencies for the UI (pure CSS) + +## Installation + +Install dependencies: ```bash bun install ``` -To run: +## Running the Forum + +Start the server: ```bash bun run index.ts ``` +The forum will be available at `http://localhost:3000` + +## Usage + +1. **Home Page**: View all posts in reverse chronological order +2. **Create Post**: Click "Create New Post" to start a new discussion +3. **View Post**: Click "Read More" on any post to view the full content and comments +4. **Add Comment**: On a post detail page, fill in the comment form to join the discussion + +## Project Structure + +- `index.ts` - Main server application with routes +- `database.ts` - SQLite database functions for posts and comments +- `templates.ts` - HTML templates with embedded CSS +- `utils/sqliteKV.ts` - Key-value store utility (optional) + +## Technology Stack + +- **Runtime**: Bun +- **Database**: SQLite (built into Bun) +- **Frontend**: Server-side rendered HTML with CSS +- **Backend**: Bun's native HTTP server + This project was created using `bun init` in bun v1.2.21. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/database.ts b/database.ts new file mode 100644 index 0000000..9c80a58 --- /dev/null +++ b/database.ts @@ -0,0 +1,91 @@ +import { Database } from "bun:sqlite"; + +export interface Post { + id: number; + title: string; + content: string; + author: string; + createdAt: string; +} + +export interface Comment { + id: number; + postId: number; + content: string; + author: string; + createdAt: string; +} + +export function initDatabase(path = "forum.db") { + const db = new Database(path); + + // Create posts table + db.run(` + CREATE TABLE IF NOT EXISTS posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + author TEXT NOT NULL, + createdAt TEXT NOT NULL + ) + `); + + // Create comments table + db.run(` + CREATE TABLE IF NOT EXISTS comments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + postId INTEGER NOT NULL, + content TEXT NOT NULL, + author TEXT NOT NULL, + createdAt TEXT NOT NULL, + FOREIGN KEY (postId) REFERENCES posts(id) + ) + `); + + return db; +} + +export function getAllPosts(db: Database): Post[] { + const posts = db.query("SELECT * FROM posts ORDER BY createdAt DESC").all() as Post[]; + return posts; +} + +export function getPost(db: Database, id: number): Post | null { + const post = db.query("SELECT * FROM posts WHERE id = ?").get(id) as Post | null; + return post; +} + +export function createPost(db: Database, title: string, content: string, author: string): Post { + const createdAt = new Date().toISOString(); + const result = db.run( + "INSERT INTO posts (title, content, author, createdAt) VALUES (?, ?, ?, ?)", + [title, content, author, createdAt] + ); + return { + id: Number(result.lastInsertRowid), + title, + content, + author, + createdAt + }; +} + +export function getComments(db: Database, postId: number): Comment[] { + const comments = db.query("SELECT * FROM comments WHERE postId = ? ORDER BY createdAt ASC").all(postId) as Comment[]; + return comments; +} + +export function createComment(db: Database, postId: number, content: string, author: string): Comment { + const createdAt = new Date().toISOString(); + const result = db.run( + "INSERT INTO comments (postId, content, author, createdAt) VALUES (?, ?, ?, ?)", + [postId, content, author, createdAt] + ); + return { + id: Number(result.lastInsertRowid), + postId, + content, + author, + createdAt + }; +} diff --git a/index.ts b/index.ts index e69de29..47b4a7a 100644 --- a/index.ts +++ b/index.ts @@ -0,0 +1,86 @@ +import { initDatabase, getAllPosts, getPost, createPost, getComments, createComment } from "./database"; +import { baseHTML, homePageContent, newPostFormContent, postDetailContent } from "./templates"; + +const db = initDatabase(); +const PORT = 3000; + +const server = Bun.serve({ + port: PORT, + async fetch(req) { + const url = new URL(req.url); + const path = url.pathname; + + // Home page - list all posts + if (path === "/" && req.method === "GET") { + const posts = getAllPosts(db); + return new Response(baseHTML("Home", homePageContent(posts)), { + headers: { "Content-Type": "text/html" }, + }); + } + + // New post form + if (path === "/new" && req.method === "GET") { + return new Response(baseHTML("Create New Post", newPostFormContent()), { + headers: { "Content-Type": "text/html" }, + }); + } + + // Create new post + if (path === "/new" && req.method === "POST") { + const formData = await req.formData(); + const title = formData.get("title") as string; + const content = formData.get("content") as string; + const author = formData.get("author") as string; + + if (title && content && author) { + createPost(db, title, content, author); + return Response.redirect(url.origin + "/", 303); + } + + return new Response("Missing required fields", { status: 400 }); + } + + // View single post + const postMatch = path.match(/^\/post\/(\d+)$/); + if (postMatch && req.method === "GET") { + const postId = parseInt(postMatch[1] || "0"); + const post = getPost(db, postId); + + if (!post) { + return new Response(baseHTML("Not Found", "
Post not found
"), { + status: 404, + headers: { "Content-Type": "text/html" }, + }); + } + + const comments = getComments(db, postId); + return new Response(baseHTML(post.title, postDetailContent(post, comments)), { + headers: { "Content-Type": "text/html" }, + }); + } + + // Add comment to post + const commentMatch = path.match(/^\/post\/(\d+)\/comment$/); + if (commentMatch && req.method === "POST") { + const postId = parseInt(commentMatch[1] || "0"); + const formData = await req.formData(); + const content = formData.get("content") as string; + const author = formData.get("author") as string; + + if (content && author) { + createComment(db, postId, content, author); + return Response.redirect(url.origin + `/post/${postId}`, 303); + } + + return new Response("Missing required fields", { status: 400 }); + } + + // 404 for everything else + return new Response(baseHTML("404", "The page you're looking for doesn't exist.
A modern forum built with Bun
+Be the first to start a discussion!
+No comments yet. Be the first to comment!
'; + + return ` +Invalid post ID
"), { + status: 400, + headers: { "Content-Type": "text/html" }, + }); + } + const post = getPost(db, postId); if (!post) { @@ -62,12 +70,17 @@ const server = Bun.serve({ // Add comment to post const commentMatch = path.match(/^\/post\/(\d+)\/comment$/); if (commentMatch && req.method === "POST") { - const postId = parseInt(commentMatch[1] || "0"); + const postId = parseInt(commentMatch[1]!); + + if (isNaN(postId)) { + return new Response("Invalid post ID", { status: 400 }); + } + const formData = await req.formData(); - const content = formData.get("content") as string; - const author = formData.get("author") as string; + const content = formData.get("content"); + const author = formData.get("author"); - if (content && author) { + if (typeof content === "string" && typeof author === "string" && content && author) { createComment(db, postId, content, author); return Response.redirect(url.origin + `/post/${postId}`, 303); }