# 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=`). - 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=`):** - 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=`):** - 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=`). - 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=`). - 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.