Skip to content

Repository files navigation

Forum Project

A full-stack forum application built with Go (backend) and vanilla JavaScript (frontend). Users register, log in, create posts, comment, react, and receive notifications. A four-tier moderation system (guest → user → moderator → admin) is enforced entirely server-side.


Table of Contents

  1. Features
  2. Technology Stack
  3. Architecture
  4. Prerequisites
  5. Installation
  6. Environment Variables
  7. Running the Application
  8. Testing
  9. Database
  10. Authentication
  11. Moderation
  12. API Overview
  13. Deployment
  14. Troubleshooting
  15. Project Documentation
  16. Contributors

Features

Authentication

  • Email / password registration and login
  • Secure password hashing with bcrypt
  • UUID session tokens stored in HttpOnly cookies
  • Google and GitHub OAuth login
  • Password reset via email link (falls back to stderr log when SMTP is unset)

Posts & Comments

  • Create, edit, and delete posts
  • Draft / published / archived states
  • Image uploads on posts and comments
  • Category tagging (many-to-many)
  • Like / dislike reactions (mutually exclusive per user per entity)

Notifications

  • Triggered by: post reactions, comment reactions, new comments on your posts
  • Polling-based (no WebSocket required)
  • Badge counter, dropdown panel, sound feedback
  • Mark as read individually or in bulk

User Profiles

  • Public profile: avatar, username, role, bio (≤ 280 chars), join date, stats, published posts
  • Owner view: additionally shows email, inline avatar/bio editing, and full activity
  • My Activity (posts, comments, liked/disliked posts) merged into the owner's profile page
  • /activity redirects to the owner's profile

Moderation & RBAC

Four roles enforced server-side (lowest → highest):

Role Capabilities
Guest View posts, comments, and reactions (read-only)
User Create/edit/delete own posts and comments, react, request moderator role
Moderator Delete any post/comment, report posts to admins
Admin Manage roles, approve/reject moderator requests, receive/reply to reports, manage categories

Key invariants:

  • First registered user is automatically made admin.
  • An admin cannot demote themselves.
  • The system refuses to remove the last admin (enforced in the persistence layer).

Technology Stack

Component Technology
Backend Go (standard library)
Frontend Go (template server) + Vanilla JavaScript
Database SQLite (via github.com/mattn/go-sqlite3)
Auth bcrypt (golang.org/x/crypto), UUID sessions (github.com/google/uuid)
OAuth Google, GitHub (standard net/http, no OAuth library)
Container Docker + Docker Compose

Architecture

Browser
  ↓ HTTP (port 3000)
Frontend Server  (cmd/frontend)
  ├── Renders HTML templates  (web/templates/)
  ├── Serves static assets    (web/static/)
  └── Proxies /api/ →
        Backend Server  (cmd/backend, port 8080)
          ├── Middleware: Recoverer → CORS → (optional/required) Auth
          ├── Router     (internal/router/ — all routes under /api/v1)
          ├── Handlers   (internal/handlers/ — request validation, JSON)
          ├── RBAC       (internal/auth/rbac.go — server-side enforcement)
          └── DB Layer   (internal/db/ — SQL, transactions, migrations)
                └── SQLite  (data/forum.db or /data/forum.db in Docker)

Layer rules:

  • internal/db/ — no net/http or encoding/json imports
  • internal/handlers/ — no new inline SQL (use internal/db/ functions)
  • internal/auth/ — centralised RBAC, never bypassed

For detailed architecture documentation see architecture.md.


Prerequisites

  • Go 1.24 or later
  • make
  • GCC / CGO toolchain (required for go-sqlite3)
  • Docker and Docker Compose (optional — backend only)

Installation

git clone <repository-url>
cd forum-moderation

# Download Go module dependencies
make deps          # runs: go mod tidy

# (Optional) create a .env file for local configuration
cp .env.example .env

Environment Variables

Copy .env.example to .env and adjust values. All variables have safe defaults; none are required for local development.

Application

Variable Default Required Description
APP_ENV production No development enables DEBUG logs. Any other value → production (INFO/WARN/ERROR only)
PORT 8080 No TCP port the backend HTTP server listens on
TZ UTC No IANA timezone (e.g. Europe/Athens). Invalid values fall back to UTC

Database

Variable Default Required Description
DB_PATH /data/forum.db (Docker) or ./data/forum.db (local) No Path to the SQLite database file. Local default is auto-detected from the presence of ./data/

Frontend / Cookies

Variable Default Required Description
COOKIE_SECURE false No Set true to add the Secure flag to session cookies. Must be true in production (HTTPS)
FRONTEND_URL http://localhost:3000 No Public URL of the frontend — used in password-reset email links and OAuth redirect URIs
FRONTEND_ORIGIN (FRONTEND_URL value) No CORS allowed origin. Defaults to FRONTEND_URL when unset

OAuth (optional — features disabled when unset)

Variable Default Description
GOOGLE_CLIENT_ID Google OAuth App client ID
GOOGLE_CLIENT_SECRET Google OAuth App client secret
GOOGLE_REDIRECT_URL Google OAuth callback (e.g. http://localhost:8080/api/v1/auth/google/callback)
GITHUB_CLIENT_ID GitHub OAuth App client ID
GITHUB_CLIENT_SECRET GitHub OAuth App client secret
GITHUB_REDIRECT_URL GitHub OAuth callback (e.g. http://localhost:8080/api/v1/auth/github/callback)

Email / SMTP (optional — falls back to stderr log when unset)

Variable Default Description
SMTP_HOST SMTP server hostname
SMTP_PORT 587 SMTP port (465 for implicit TLS, 587 for STARTTLS)
SMTP_USER SMTP username / sender address
SMTP_PASS SMTP password
SMTP_FROM (SMTP_USER) From address in outgoing emails

Startup log

At startup the backend logs its resolved configuration (no secrets are logged):

[INFO ] starting env=production port=8080 db_path=/data/forum.db tz=UTC log_level=info
[INFO ] database ready path=/data/forum.db
[INFO ] server listening addr=http://localhost:8080

Running the Application

The project uses two separate servers that must both be running:

Server Port Command
Backend API 8080 make run-backend
Frontend UI 3000 make run-frontend

Development (recommended)

make run-all       # starts backend (8080) + frontend (3000)
                   # frontend auto-opens http://localhost:3000 in the browser

Stop both servers:

make stop-all

Development .env

APP_ENV=development
PORT=8080
TZ=UTC
DB_PATH=./data/forum.db
COOKIE_SECURE=false
FRONTEND_URL=http://localhost:3000

Production (Docker)

make docker-build  # build the backend image
make docker-run    # run the backend container (detached)
make docker-stop   # stop and remove the container

In production the frontend still needs to run separately (it is a Go binary, not served by the same container as the backend).

Production environment variables:

APP_ENV=production
PORT=8080
TZ=UTC
DB_PATH=/data/forum.db
COOKIE_SECURE=true
FRONTEND_URL=https://forum.example.com
FRONTEND_ORIGIN=https://forum.example.com

Logging differences

Mode Levels logged Format
development DEBUG, INFO, WARN, ERROR [LEVEL] message key=value … to stderr
production INFO, WARN, ERROR [LEVEL] message key=value … to stderr

Testing

make test          # go test ./... (all packages)
make vet           # go vet ./...
make fmt           # gofmt check
make ci            # full gate: build → fmt → vet → policy → test

What is covered

  • Integration tests (internal/tests/) — 31 test files exercising every API endpoint through httptest with an in-memory SQLite database.
  • Unit tests — co-located with source files (_test.go), covering handlers, DB helpers, RBAC, and image workflow logic.
  • Config tests (internal/config/) — default values, env overrides, invalid port, timezone fallback, APP_ENV validation.
  • Logger tests (internal/logger/) — level suppression in production, field formatting, output format.

All tests use an in-memory SQLite database; no external services are required.

Database reset

# Remove the database file to start fresh (the schema is re-applied on next startup)
rm -f data/forum.db

Database

  • Engine: SQLite (single file, no server required)
  • Driver: github.com/mattn/go-sqlite3 (CGO)
  • Location: ./data/forum.db (local) or /data/forum.db (Docker / DB_PATH)
  • Initialization: the schema is embedded in the binary (internal/db/forum_schema.sql). On first run the file is created and the schema applied automatically.
  • Migrations: idempotent ALTER TABLE / CREATE TABLE IF NOT EXISTS statements run at every startup via db.InitDB — safe to run against existing databases.
  • Backup: copy the SQLite file while the application is stopped, or use .backup via the SQLite CLI.
  • Pragmas: WAL journal mode, foreign keys enabled, 5 s busy timeout, NORMAL synchronous mode.

Core tables

Table Purpose
users Accounts, roles, avatar, bio
oauth_users OAuth provider links per user
sessions Active session tokens
posts Forum threads (draft / published / archived)
comments Replies to posts
categories Post categories
post_categories Post ↔ category many-to-many
reactions Like / dislike on posts and comments
notifications In-app notifications
moderator_requests User → admin role-upgrade requests
reports Moderator → admin post reports
post_edits Post edit history
comment_edits Comment edit history
password_resets Password-reset tokens

Authentication

Local authentication

  1. POST /api/v1/users/register — create account (bcrypt password hash stored)
  2. POST /api/v1/users/login — validates credentials, creates session, sets HttpOnly SameSite=Lax cookie
  3. POST /api/v1/users/logout — invalidates session

Sessions are UUID tokens stored server-side. The cookie never contains user data.

Password reset

  1. POST /api/v1/users/forgot-password — generates a time-limited token and emails a reset link
  2. POST /api/v1/users/reset-password — validates token and updates password

When SMTP_HOST is not set (development), the reset link is printed to stderr instead of sent by email.

OAuth (Google / GitHub)

  1. Redirect user to GET /api/v1/auth/{google|github}
  2. Provider redirects back to GET /api/v1/auth/{google|github}/callback
  3. Backend creates or links the account and sets a session cookie

GitHub OAuth requires the user's primary email to be verified and public (or accessible via the GitHub emails API).

Cookie security

  • HttpOnly — JavaScript cannot read the session cookie
  • SameSite=Lax — protects against cross-site request forgery
  • Secure — set only when COOKIE_SECURE=true (enable in production with HTTPS)

Moderation

Role promotion

  1. A logged-in user submits a moderator-role request via the UI (or POST /api/v1/moderator-request).
  2. Admins review open requests in the Admin Panel.
  3. Admin approves or rejects. The user is notified and their role changes accordingly.

Reporting content

  1. A moderator selects "Report" on a post.
  2. A report is created (POST /api/v1/reports) with a reason.
  3. Admins see all open reports in the Admin Panel.
  4. Admin replies to or resolves the report.

Admin Panel capabilities

  • View and search all users
  • Change any user's role (except last-admin demotion or self-demotion)
  • Activate / deactivate accounts
  • Manage categories (create, rename, delete)
  • Review and action moderator requests
  • Review, reply to, and resolve reports

API Overview

All endpoints are versioned under /api/v1. The backend returns JSON with a consistent envelope:

{ "data": { ... } }
{ "error": { "code": "NOT_FOUND", "message": "..." } }

Selected endpoints

Method Path Auth Description
GET /api/v1/health Health check
POST /api/v1/users/register Register
POST /api/v1/users/login Login
POST /api/v1/users/logout Logout
GET /api/v1/users/me Current user
PATCH /api/v1/users/me/profile Update bio
POST /api/v1/users/me/avatar Upload avatar
GET /api/v1/users/{id}/profile optional Public profile
GET /api/v1/posts optional List published posts
POST /api/v1/posts Create post
GET /api/v1/posts/{id} optional Get post
PATCH /api/v1/posts/{id} Edit post
DELETE /api/v1/posts/{id} Delete post
GET/POST /api/v1/posts/draft List / create draft
GET /api/v1/categories List categories
GET /api/v1/notifications List notifications
PATCH /api/v1/notifications/{id} Mark notification read
DELETE /api/v1/notifications/{id} Delete notification
POST /api/v1/moderator-request Request moderator role
POST /api/v1/reports ✓ (mod+) Report a post
GET /api/v1/admin/users ✓ (admin) List all users
PATCH /api/v1/admin/users/{id}/role ✓ (admin) Change role
GET /api/v1/admin/reports ✓ (admin) List reports

For the full route list see internal/router/router.go.


Deployment

Docker (recommended for backend)

# Build
make docker-build

# Run with a persistent database volume
docker run -d \
  -p 8080:8080 \
  -v forum_data:/data \
  -e APP_ENV=production \
  -e DB_PATH=/data/forum.db \
  -e COOKIE_SECURE=true \
  -e FRONTEND_URL=https://forum.example.com \
  forum-backend

# Using the Makefile shorthand (env vars in Makefile docker-run target):
make docker-run

Persistent storage

  • Database: mount /data as a Docker volume so forum.db survives container restarts.
  • Uploaded images: the frontend binary serves uploads from web/static/uploads/. If you run the frontend in a container or behind a proxy, ensure this path is writable and persistent.

Reverse proxy (nginx / Caddy)

Route all traffic to the frontend (:3000); the frontend proxies /api/ requests to the backend (:8080) automatically. You do not need to expose the backend port publicly.

Common deployment checklist

  • COOKIE_SECURE=true (HTTPS required)
  • APP_ENV=production
  • DB_PATH points to a persistent volume
  • FRONTEND_URL and FRONTEND_ORIGIN set to the public domain
  • OAuth redirect URLs registered with the provider to match the public domain
  • SMTP_* variables set if email is needed
  • /data volume mounted before first run

Common pitfalls

Port already in use:

# Linux — free the port
fuser -k 8080/tcp

# Or change the port:
PORT=9090 make run-backend

CGO not available (SQLite build error):

# CGO is required for go-sqlite3. Ensure GCC is installed:
sudo apt install gcc
# Then build normally — CGO_ENABLED=1 is the default

Database file not found / permission error:

# Ensure the data directory exists before the first local run:
mkdir -p data

OAuth login fails:

Verify that GOOGLE_REDIRECT_URL / GITHUB_REDIRECT_URL exactly match the callback URL registered in the OAuth app settings, including scheme and port.


Troubleshooting

Symptom Likely cause Fix
listen tcp :8080: bind: address already in use Another process on port 8080 fuser -k 8080/tcp or set PORT=...
cgo: C compiler "gcc" not found GCC missing sudo apt install gcc
no such file or directory: ./data/forum.db data/ dir missing mkdir -p data
OAuth redirect mismatch Redirect URL not registered Register callback URL in OAuth app
Password reset link logged to stderr SMTP_HOST not set Expected in dev; set SMTP vars in prod
DEBUG logs in production APP_ENV not set Set APP_ENV=production

Project Documentation

Document Description
architecture.md Layered architecture, layer rules, component map
AGENTS.md Conventions for AI coding agents working on this project
docs/PRD.md Product requirements — what the product does and why
docs/SDS.md Software design spec — data model, API surface, layering rules
docs/roadmap.md Issue-driven roadmap, grouped by theme
.env.example Annotated environment variable reference

Contributors / Authors

  • Chris Baikas (chbaikas)
  • Alex Smyroglou (asmyrogl)

License

This project is licensed under the MIT License.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages