404 lines
20 KiB
Markdown
404 lines
20 KiB
Markdown
# 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.
|