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, file storage locations, and session security. 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", "session_secret": "change-me-to-32-byte-random", "session_secure": false } Notes:
session_secretshould be a 32+ byte random value. At runtime, it is overridden by theTHREADR_SESSION_SECRETenvironment variable if present (recommended for production).session_securecontrols whether cookies are markedSecure; set totruein HTTPS environments.
-
config/config.json: The active configuration file, copied from
config.json.sampleand 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.sampleand modified as needed.
Core Application Files
- main.go:
The entry point of the ThreadR application.
- Parses command-line flags (e.g.,
--initializefor database setup). - Loads the
config/config.jsonfile. - Establishes a connection to the MariaDB database.
- If
--initializeis set:- Calls
createTablesIfNotExistto set up all necessary database tables. - Calls
ensureAdminUserto guide the creation of an initial admin user.
- Calls
- 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.
- Parses command-line flags (e.g.,
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 fromconfig.json(and env overrides). Fields include DB settings, domain, file storage dir,session_secret, andsession_secure.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, applying secure cookie options (HttpOnly, SameSite=Lax, Secure configurable) and attaching the session to 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 theconfig/about_page.htmlbodyfile and renders it within theabout.htmltemplate. -
handlers/accept_cookie.go: Handles the cookie banner acceptance. Sets a
threadr_cookie_bannercookie 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.htmltemplate 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.htmltemplate with lists of public and accessible private boards.
- Checks if the logged-in user has admin privileges (
-
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.htmltemplate.
- 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.
Clientstruct: Represents an individual WebSocket connection with user and board ID.ChatHubstruct: 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"] }
- HTTP Request (
-
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.
- Retrieves file metadata from the database using
-
handlers/home.go: Handles requests for the root path (
/). Renders thehome.htmltemplate, 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.Likefor database operations.
-
handlers/login.go: Handles user login (
/login/).- On GET: Renders the
login.htmltemplate. - 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.
- On GET: Renders the
-
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.htmltemplate.
-
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.htmltemplate, 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.Filerecord, saves the file to disk, and updates the user'spfp_file_id. - Updates the user's display name and bio in the database.
- Redirects to the profile page after successful update.
- For profile pictures, it hashes the file, creates a
-
handlers/signup.go: Handles new user registration (
/signup/).- On GET: Renders the
signup.htmltemplate. - On POST: Creates a new user in the database after hashing the password. Redirects to the login page on success.
- On GET: Renders the
-
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.htmltemplate 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.htmltemplate.
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:
-
Boardstruct: Represents a forum board. ExampleBoardstruct: 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 byprivatestatus.
-
-
models/board_permission.go:
BoardPermissionstruct: 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:
ChatMessagestruct: Represents a single chat message in a chat board. Includes fields for user, content, reply, timestamp, and mentions. ExampleChatMessagestruct: type ChatMessage struct { ID intjson:"id"BoardID intjson:"boardId"UserID intjson:"userId"Content stringjson:"content"ReplyTo intjson:"replyTo"Timestamp time.Timejson:"timestamp"Username stringjson:"username"PfpFileID sql.NullInt64json:"pfpFileId"Mentions []stringjson:"mentions"} Example JSON output (as seen inhandlers/chat.gobroadcast): { "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:
Filestruct: Represents metadata for an uploaded file (e.g., profile pictures). ExampleFilestruct: 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:
Likestruct: 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:
Newsstruct: 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:
Notificationstruct: 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:
Poststruct: 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:
Reactionstruct: 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:
Repoststruct: 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:
Threadstruct: 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:
Userstruct: Represents a user in the system, including authentication details, profile info, and global permissions. ExampleUserstruct: 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
navbarandcookie_bannerpartials. It also defines acontentblock 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
ShowCookieBanneris true inPageData. -
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
LoggedInstatus (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
AboutContentfrom the handler, allowing for fully custom HTML content without needing an additionalcontentblock. -
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
divfor chat messages. - An input area with a
textareafor 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.pngimage. -
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.