threadr.lostcave.ddnss.de/DOCUMENTATION.md

20 KiB

ThreadR Rewritten - Technical Specification

Project Overview

ThreadR Rewritten is a free and open-source forum engine, re-implemented in Go. It aims to provide a robust and extensible platform for users to host their own forum instances. The project, initially a PHP/MySQL school project, has been completely rewritten to leverage Go's performance and concurrency features. It supports traditional forum boards, real-time chat boards, user profiles, news announcements, and file uploads (specifically for profile pictures).

File-by-File Explanation

This section details the purpose and functionality of each significant file and directory within the ThreadR project. This, of course, assumes you have a decent understanding of Go.

Configuration Files

  • config/config.json.sample: This file provides a template for the main application configuration. It defines critical parameters for the application to run, such as database credentials, domain, and file storage locations. Example content: { "domain_name": "localhost", "threadr_dir": "/threadr", "db_username": "threadr_user", "db_password": "threadr_password", "db_database": "threadr_db", "db_svr_host": "localhost:3306", "file_storage_dir": "files" }

  • config/config.json: The active configuration file, copied from config.json.sample and modified for the specific deployment. Contains sensitive information like database passwords.

  • config/about_page.htmlbody.sample: A template HTML snippet for the "About" page content. This allows administrators to customize the about page without modifying Go templates.

  • config/about_page.htmlbody: The active HTML content for the "About" page, copied from about_page.htmlbody.sample and modified as needed.

Core Application Files

  • main.go: The entry point of the ThreadR application.
    • Parses command-line flags (e.g., --initialize for database setup).
    • Loads the config/config.json file.
    • Establishes a connection to the MariaDB database.
    • If --initialize is set:
      • Calls createTablesIfNotExist to set up all necessary database tables.
      • Calls ensureAdminUser to guide the creation of an initial admin user.
    • Initializes Gorilla Sessions for session management.
    • Loads HTML templates for rendering pages.
    • Sets up all HTTP routes and maps them to their respective handler functions, wrapped with session and login middleware as needed.
    • Starts the HTTP server on port 8080.

Handlers Directory (handlers/)

This directory contains the HTTP handler functions that process incoming requests, interact with models, and render responses.

  • handlers/app.go: Defines common application-wide structures and middleware:

    • PageData: A struct holding data passed to HTML templates for rendering common elements (title, navbar state, login status, cookie banner, base paths, current URL).
    • Config: A struct to unmarshal application configuration from config.json. Example JSON for Config: { "domain_name": "localhost", "threadr_dir": "/threadr", "db_username": "threadr_user", "db_password": "threadr_password", "db_database": "threadr_db", "db_svr_host": "localhost:3306", "file_storage_dir": "files" }
    • App: The main application context struct, holding pointers to the database connection, session store, configuration, and templates.
    • SessionMW: Middleware to retrieve or create a new Gorilla session for each request, making the session available in the request context.
    • RequireLoginMW: Middleware to enforce user authentication for specific routes, redirecting unauthenticated users to the login page.
  • handlers/about.go: Handles requests for the /about/ page. Reads the config/about_page.htmlbody file and renders it within the about.html template.

  • handlers/accept_cookie.go: Handles the cookie banner acceptance. Sets a threadr_cookie_banner cookie in the user's browser for 30 days and redirects them back to their previous page or the home page.

  • handlers/board.go: Handles requests for individual forum boards (/board/?id=<id>).

    • Fetches board details and threads within it.
    • Enforces permissions for private boards (redirects if not logged in or unauthorized).
    • Handles POST requests to create new threads within the board if the user is logged in and has permission.
    • Renders the board.html template with board and thread data.
  • handlers/boards.go: Handles requests for the /boards/ page, listing all available public and private boards.

    • Checks if the logged-in user has admin privileges (PermCreateBoard).
    • Handles POST requests to create new boards (classic or chat type) if the user is an admin.
    • Filters private boards based on user permissions.
    • Renders the boards.html template with lists of public and accessible private boards.
  • handlers/chat.go: Handles both rendering the chat interface and managing WebSocket connections for real-time chat.

    • HTTP Request (/chat/?id=<id>):
      • Authenticates the user and fetches board details.
      • Enforces permissions for private chat boards.
      • Fetches recent chat messages for the specified board.
      • Renders the chat.html template.
    • WebSocket Request (/chat/?ws=true&id=<id>):
      • Upgrades the HTTP connection to a WebSocket.
      • Manages client connections via a ChatHub.
      • Receives JSON messages from clients, creates models.ChatMessage, saves them to the DB, and broadcasts them to all clients in the same board.
    • Client struct: Represents an individual WebSocket connection with user and board ID.
    • ChatHub struct: Manages active WebSocket clients, message broadcasting, and client registration/unregistration. Example JSON message for broadcast: { "id": 123, "boardId": 1, "userId": 456, "content": "Hello, world! @username", "replyTo": -1, "timestamp": "2024-07-30T10:30:00Z", "username": "chatter1", "pfpFileId": { "Int64": 789, "Valid": true }, "mentions": ["username"] }
  • handlers/file.go: Handles requests for serving uploaded files, primarily profile pictures (/file?id=<id>).

    • Retrieves file metadata from the database using models.GetFileByID.
    • Constructs the file path and serves the file using http.ServeFile.
  • handlers/home.go: Handles requests for the root path (/). Renders the home.html template, displaying a welcome message and the ThreadR logo.

  • handlers/like.go: Handles POST requests for liking or disliking posts (/like/).

    • Requires login.
    • Checks for existing likes/dislikes and updates or deletes them based on user action.
    • Interacts with models.Like for database operations.
  • handlers/login.go: Handles user login (/login/).

    • On GET: Renders the login.html template.
    • On POST: Authenticates the user against the database, sets session values upon successful login, and redirects to user home. Shows an error if login fails.
  • handlers/logout.go: Handles user logout (/logout/). Clears the user's session and redirects to the home page.

  • handlers/news.go: Handles requests for the /news/ page.

    • Fetches and displays all news items from the database.
    • If the user is an admin, it allows posting new news items via POST requests and deleting existing ones.
    • Renders the news.html template.
  • handlers/profile.go: Handles requests for the user's profile page (/profile/).

    • Requires login.
    • Fetches user details from the database using models.GetUserByID.
    • Renders the profile.html template, displaying user information.
  • handlers/profile_edit.go: Handles editing of user profiles, including display name, bio, and profile picture upload (/profile/edit/).

    • Requires login.
    • On GET: Fetches current user data and renders profile_edit.html.
    • On POST: Processes form data, including file uploads.
      • For profile pictures, it hashes the file, creates a models.File record, saves the file to disk, and updates the user's pfp_file_id.
      • Updates the user's display name and bio in the database.
      • Redirects to the profile page after successful update.
  • handlers/signup.go: Handles new user registration (/signup/).

    • On GET: Renders the signup.html template.
    • On POST: Creates a new user in the database after hashing the password. Redirects to the login page on success.
  • handlers/thread.go: Handles requests for individual discussion threads (/thread/?id=<id>).

    • Fetches thread and associated posts.
    • Enforces permissions for private boards (if the thread belongs to one).
    • Handles POST requests to create new posts within the thread if the user is logged in and has permission.
    • Renders the thread.html template with thread and post data.
  • handlers/userhome.go: Handles requests for the user's personal home page (/userhome/).

    • Requires login.
    • Fetches current user details.
    • Renders the userhome.html template.

Models Directory (models/)

This directory contains data structures and functions for interacting with the database. Each file typically corresponds to a database table or a logical data entity.

  • models/board.go:

    • Board struct: Represents a forum board. Example Board struct: type Board struct { ID int Name string Description string Private bool PublicVisible bool PinnedThreads []int // Stored as JSON array in DB CustomLandingPage string ColorScheme string Type string // "classic" or "chat" }

    • GetBoardByID: Fetches a single board by its ID.

    • GetAllBoards: Fetches all boards, optionally filtered by private status.

  • models/board_permission.go:

    • BoardPermission struct: Represents user permissions for a specific board.
    • Defines bitmask constants for different permissions (PermPostInBoard, PermModerateBoard, PermViewBoard).
    • GetBoardPermission: Retrieves a user's permissions for a given board.
    • SetBoardPermission: Inserts or updates a user's permissions for a board.
    • HasBoardPermission: Checks if a user has a specific permission for a board.
  • models/chat.go:

    • ChatMessage struct: Represents a single chat message in a chat board. Includes fields for user, content, reply, timestamp, and mentions. Example ChatMessage struct: type ChatMessage struct { ID int json:"id" BoardID int json:"boardId" UserID int json:"userId" Content string json:"content" ReplyTo int json:"replyTo" Timestamp time.Time json:"timestamp" Username string json:"username" PfpFileID sql.NullInt64 json:"pfpFileId" Mentions []string json:"mentions" } Example JSON output (as seen in handlers/chat.go broadcast): { "id": 123, "boardId": 1, "userId": 456, "content": "Hello, world! @username", "replyTo": -1, "timestamp": "2024-07-30T10:30:00Z", "username": "chatter1", "pfpFileId": { "Int64": 789, "Valid": true }, "mentions": ["username"] }
    • CreateChatMessage: Inserts a new chat message into the database.
    • GetRecentChatMessages: Retrieves a limited number of the most recent messages for a board.
    • GetChatMessageByID: Fetches a single chat message by its ID.
    • extractMentions: Utility function to parse usernames mentioned in a message.
  • models/file.go:

    • File struct: Represents metadata for an uploaded file (e.g., profile pictures). Example File struct: type File struct { ID int OriginalName string Hash string HashAlgorithm string } Hypothetical JSON representation: { "id": 789, "originalName": "my_pfp.png", "hash": "a1b2c3d4...", "hashAlgorithm": "sha256" }
    • GetFileByID: Fetches file metadata by its ID.
    • CreateFile: Creates a new file record in the database and returns its ID.
  • models/like.go:

    • Like struct: Represents a user's "like" or "dislike" on a post.
    • GetLikesByPostID: Retrieves all likes/dislikes for a specific post.
    • GetLikeByPostAndUser: Retrieves a specific like/dislike by post and user.
    • CreateLike: Adds a new like/dislike.
    • UpdateLikeType: Changes an existing like to a dislike or vice-versa.
    • DeleteLike: Removes a like/dislike.
  • models/news.go:

    • News struct: Represents a news announcement.
    • GetAllNews: Retrieves all news items, ordered by creation time.
    • CreateNews: Adds a new news item.
    • DeleteNews: Removes a news item.
  • models/notification.go:

    • Notification struct: Represents a user notification (stubbed for future expansion).
    • GetNotificationsByUserID: Retrieves notifications for a specific user.
    • CreateNotification: Stub for creating a notification.
    • MarkNotificationAsRead: Stub for marking a notification as read.
  • models/post.go:

    • Post struct: Represents a forum post within a thread.
    • GetPostsByThreadID: Retrieves all posts for a given thread.
    • CreatePost: Adds a new post to a thread.
  • models/reaction.go:

    • Reaction struct: Represents a user's emoji reaction to a post (stubbed for future expansion).
    • GetReactionsByPostID: Retrieves all reactions for a post.
    • CreateReaction: Stub for creating a reaction.
    • DeleteReaction: Stub for deleting a reaction.
  • models/repost.go:

    • Repost struct: Represents a re-post of a thread to another board (stubbed for future expansion).
    • GetRepostsByThreadID: Retrieves all reposts for a thread.
    • CreateRepost: Stub for creating a repost.
  • models/thread.go:

    • Thread struct: Represents a discussion thread within a board.
    • GetThreadByID: Fetches a single thread by its ID.
    • GetThreadsByBoardID: Fetches all threads for a given board.
    • CreateThread: Adds a new thread to a board.
  • models/user.go:

    • User struct: Represents a user in the system, including authentication details, profile info, and global permissions. Example User struct: type User struct { ID int Username string DisplayName string PfpFileID sql.NullInt64 // Nullable foreign key to files.id Bio string AuthenticationString string AuthenticationSalt string AuthenticationAlgorithm string CreatedAt time.Time UpdatedAt time.Time Verified bool Permissions int64 // Bitmask for global permissions } Hypothetical JSON representation (sensitive fields omitted): { "id": 456, "username": "testuser", "displayName": "Test User", "pfpFileId": { "Int64": 789, "Valid": true }, "bio": "Just a test user.", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-07-30T10:00:00Z", "verified": false, "permissions": 3 // Assuming PermCreateBoard | PermManageUsers }
    • Defines bitmask constants for global permissions (PermCreateBoard, PermManageUsers).
    • GetUserByID: Fetches a user by their ID.
    • GetUserByUsername: Fetches a user by their username.
    • CheckPassword: Verifies a given password against the stored hash, salt, and algorithm.
    • HashPassword: Hashes a password using a salt and specified algorithm (currently SHA256).
    • CreateUser: Creates a new user with hashed password.
    • UpdateUserProfile: Updates a user's display name and bio.
    • UpdateUserPfp: Updates a user's profile picture file ID.
    • HasGlobalPermission: Checks if a user has a specific global permission.

Static Directory (static/)

  • static/style.css: The main stylesheet for the ThreadR application, defining the visual theme, layout, and responsive design elements. It includes light and dark mode support.

  • static/img/ThreadR.png: The main logo or banner image for the ThreadR application, displayed on the home page.

Templates Directory (templates/)

This directory holds HTML templates for rendering the user interface. It follows a structure of a base template, partials (reusable components), and page-specific templates.

  • templates/base.html: The foundational HTML template. It defines the basic HTML structure, includes the stylesheet, and incorporates the navbar and cookie_banner partials. It also defines a content block where page-specific content will be inserted.

  • templates/partials/cookie_banner.html: A reusable template snippet that renders a cookie consent banner at the bottom of the page if ShowCookieBanner is true in PageData.

  • templates/partials/navbar.html: A reusable template snippet that renders the navigation bar at the top of the page. It dynamically highlights the active page and shows different links based on LoggedIn status (e.g., Login/Signup vs. User Home/Profile/Logout).

  • templates/pages/about.html: Page-specific template for the "About" section. It's unique in that it directly renders AboutContent from the handler, allowing for fully custom HTML content without needing an additional content block.

  • templates/pages/board.html: Page-specific template for displaying an individual forum board, listing its threads and providing a form to create new threads.

  • templates/pages/boards.html: Page-specific template for listing all public and accessible private forum boards, and an admin form for creating new boards.

  • templates/pages/chat.html: Page-specific template for a real-time chat board. It includes:

    • A header with board name and description.
    • A scrollable div for chat messages.
    • An input area with a textarea for messages and a "Send" button.
    • Client-side JavaScript for WebSocket communication, message appending, replying to messages, and username autocomplete.
    • Extensive inline CSS for chat-specific styling.
  • templates/pages/home.html: Page-specific template for the home page, displaying a welcome message and the ThreadR.png image.

  • templates/pages/login.html: Page-specific template for the user login form. Displays error messages if authentication fails.

  • templates/pages/news.html: Page-specific template for displaying news announcements. Includes forms for admins to post and delete news items.

  • templates/pages/profile.html: Page-specific template for displaying a user's profile information. Shows username, display name, profile picture (if uploaded), bio, and account creation/update times.

  • templates/pages/profile_edit.html: Page-specific template for editing a user's profile. Provides forms to update display name, bio, and upload a new profile picture.

  • templates/pages/signup.html: Page-specific template for the new user registration form.

  • templates/pages/thread.html: Page-specific template for displaying an individual discussion thread, listing its posts, and providing forms for users to post new messages or reply to existing ones.