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.
- 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
. Example JSON forConfig
: { "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 theconfig/about_page.htmlbody
file and renders it within theabout.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.
- 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.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"] }
- 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.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.
- 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.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'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.html
template. - 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.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. ExampleBoard
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 byprivate
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. ExampleChatMessage
struct: 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.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). ExampleFile
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. ExampleUser
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
andcookie_banner
partials. It also defines acontent
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 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
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 additionalcontent
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.