318 lines
11 KiB
Go
318 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"threadr/handlers"
|
|
"threadr/models"
|
|
|
|
"github.com/gorilla/sessions"
|
|
_ "github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
func loadConfig(filename string) (*handlers.Config, error) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
var config handlers.Config
|
|
err = json.NewDecoder(file).Decode(&config)
|
|
return &config, err
|
|
}
|
|
|
|
func createTablesIfNotExist(db *sql.DB) error {
|
|
// Create boards table
|
|
_, err := db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS boards (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
private BOOLEAN DEFAULT FALSE,
|
|
public_visible BOOLEAN DEFAULT TRUE,
|
|
pinned_threads TEXT,
|
|
custom_landing_page TEXT,
|
|
color_scheme VARCHAR(255)
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating boards table: %v", err)
|
|
}
|
|
|
|
// Create users table
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
username VARCHAR(255) NOT NULL UNIQUE,
|
|
display_name VARCHAR(255),
|
|
pfp_url VARCHAR(255),
|
|
bio TEXT,
|
|
authentication_string VARCHAR(128) NOT NULL,
|
|
authentication_salt VARCHAR(255) NOT NULL,
|
|
authentication_algorithm VARCHAR(50) NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
verified BOOLEAN DEFAULT FALSE,
|
|
permissions BIGINT DEFAULT 0
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating users table: %v", err)
|
|
}
|
|
|
|
// Create threads table (without type field)
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS threads (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
board_id INT NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
created_by_user_id INT NOT NULL,
|
|
accepted_answer_post_id INT,
|
|
FOREIGN KEY (board_id) REFERENCES boards(id)
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating threads table: %v", err)
|
|
}
|
|
|
|
// Create posts table
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS posts (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
thread_id INT NOT NULL,
|
|
user_id INT NOT NULL,
|
|
post_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
edit_time TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
content TEXT,
|
|
attachment_hash BIGINT,
|
|
attachment_name VARCHAR(255),
|
|
title VARCHAR(255),
|
|
reply_to INT DEFAULT -1,
|
|
FOREIGN KEY (thread_id) REFERENCES threads(id)
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating posts table: %v", err)
|
|
}
|
|
|
|
// Create likes table
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS likes (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
post_id INT NOT NULL,
|
|
user_id INT NOT NULL,
|
|
type VARCHAR(20) NOT NULL,
|
|
UNIQUE KEY unique_like (post_id, user_id),
|
|
FOREIGN KEY (post_id) REFERENCES posts(id)
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating likes table: %v", err)
|
|
}
|
|
|
|
// Create board_permissions table
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS board_permissions (
|
|
user_id INT NOT NULL,
|
|
board_id INT NOT NULL,
|
|
permissions BIGINT DEFAULT 0,
|
|
PRIMARY KEY (user_id, board_id),
|
|
FOREIGN KEY (board_id) REFERENCES boards(id)
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating board_permissions table: %v", err)
|
|
}
|
|
|
|
// Create notifications table
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
user_id INT NOT NULL,
|
|
type VARCHAR(50) NOT NULL,
|
|
related_id INT NOT NULL,
|
|
is_read BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating notifications table: %v", err)
|
|
}
|
|
|
|
// Create reactions table
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS reactions (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
post_id INT NOT NULL,
|
|
user_id INT NOT NULL,
|
|
emoji VARCHAR(10) NOT NULL,
|
|
FOREIGN KEY (post_id) REFERENCES posts(id)
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating reactions table: %v", err)
|
|
}
|
|
|
|
// Create reposts table
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS reposts (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
thread_id INT NOT NULL,
|
|
board_id INT NOT NULL,
|
|
user_id INT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (thread_id) REFERENCES threads(id),
|
|
FOREIGN KEY (board_id) REFERENCES boards(id)
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating reposts table: %v", err)
|
|
}
|
|
|
|
// Create news table
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS news (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
title VARCHAR(255) NOT NULL,
|
|
content TEXT NOT NULL,
|
|
posted_by INT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)`)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating news table: %v", err)
|
|
}
|
|
|
|
log.Println("Database tables created or already exist")
|
|
return nil
|
|
}
|
|
|
|
func ensureAdminUser(db *sql.DB, config *handlers.Config) error {
|
|
adminUser, err := models.GetUserByUsername(db, config.AdminUsername)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return fmt.Errorf("error checking for admin user: %v", err)
|
|
}
|
|
|
|
// If admin user doesn't exist, create one
|
|
if adminUser == nil {
|
|
log.Printf("Creating admin user: %s", config.AdminUsername)
|
|
err = models.CreateUser(db, config.AdminUsername, config.AdminPassword)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating admin user: %v", err)
|
|
}
|
|
|
|
// Get the newly created admin user to update permissions
|
|
adminUser, err = models.GetUserByUsername(db, config.AdminUsername)
|
|
if err != nil {
|
|
return fmt.Errorf("error fetching new admin user: %v", err)
|
|
}
|
|
|
|
// Set admin permissions (all permissions)
|
|
_, err = db.Exec("UPDATE users SET permissions = ? WHERE id = ?",
|
|
models.PermCreateBoard|models.PermManageUsers, adminUser.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("error setting admin permissions: %v", err)
|
|
}
|
|
log.Println("Admin user created successfully with full permissions")
|
|
} else {
|
|
log.Println("Admin user already exists")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
// Define command-line flag for initialization
|
|
initialize := flag.Bool("initialize", false, "Initialize database tables and admin user")
|
|
flag.BoolVar(initialize, "i", false, "Short for --initialize")
|
|
flag.Parse()
|
|
|
|
config, err := loadConfig("config/config.json")
|
|
if err != nil {
|
|
log.Fatal("Error loading config:", err)
|
|
}
|
|
|
|
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", config.DBUsername, config.DBPassword, config.DBServerHost, config.DBDatabase)
|
|
db, err := sql.Open("mysql", dsn)
|
|
if err != nil {
|
|
log.Fatal("Error connecting to database:", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Perform initialization if the flag is set
|
|
if *initialize {
|
|
log.Println("Initializing database...")
|
|
err = createTablesIfNotExist(db)
|
|
if err != nil {
|
|
log.Fatal("Error creating database tables:", err)
|
|
}
|
|
|
|
err = ensureAdminUser(db, config)
|
|
if err != nil {
|
|
log.Fatal("Error ensuring admin user:", err)
|
|
}
|
|
|
|
log.Println("Initialization completed successfully. Exiting.")
|
|
return
|
|
}
|
|
|
|
// Normal startup (without automatic table creation)
|
|
log.Println("Starting ThreadR server...")
|
|
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
log.Fatal("Error getting working directory:", err)
|
|
}
|
|
|
|
// Parse partial templates
|
|
tmpl := template.Must(template.ParseFiles(
|
|
filepath.Join(dir, "templates/partials/navbar.html"),
|
|
filepath.Join(dir, "templates/partials/cookie_banner.html"),
|
|
))
|
|
|
|
// Parse page-specific templates with unique names
|
|
tmpl, err = tmpl.ParseFiles(
|
|
filepath.Join(dir, "templates/pages/about.html"),
|
|
filepath.Join(dir, "templates/pages/board.html"),
|
|
filepath.Join(dir, "templates/pages/boards.html"),
|
|
filepath.Join(dir, "templates/pages/home.html"),
|
|
filepath.Join(dir, "templates/pages/login.html"),
|
|
filepath.Join(dir, "templates/pages/news.html"),
|
|
filepath.Join(dir, "templates/pages/profile.html"),
|
|
filepath.Join(dir, "templates/pages/profile_edit.html"),
|
|
filepath.Join(dir, "templates/pages/signup.html"),
|
|
filepath.Join(dir, "templates/pages/thread.html"),
|
|
filepath.Join(dir, "templates/pages/userhome.html"),
|
|
)
|
|
if err != nil {
|
|
log.Fatal("Error parsing page templates:", err)
|
|
}
|
|
|
|
store := sessions.NewCookieStore([]byte("secret-key")) // Replace with secure key in production
|
|
|
|
app := &handlers.App{
|
|
DB: db,
|
|
Store: store,
|
|
Config: config,
|
|
Tmpl: tmpl,
|
|
}
|
|
|
|
fs := http.FileServer(http.Dir("static"))
|
|
http.Handle(config.ThreadrDir+"/static/", http.StripPrefix(config.ThreadrDir+"/static/", fs))
|
|
|
|
http.HandleFunc(config.ThreadrDir+"/", app.SessionMW(handlers.HomeHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/login/", app.SessionMW(handlers.LoginHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/logout/", app.SessionMW(handlers.LogoutHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/userhome/", app.SessionMW(app.RequireLoginMW(handlers.UserHomeHandler(app))))
|
|
http.HandleFunc(config.ThreadrDir+"/boards/", app.SessionMW(handlers.BoardsHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/board/", app.SessionMW(handlers.BoardHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/thread/", app.SessionMW(handlers.ThreadHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/about/", app.SessionMW(handlers.AboutHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/profile/", app.SessionMW(app.RequireLoginMW(handlers.ProfileHandler(app))))
|
|
http.HandleFunc(config.ThreadrDir+"/profile/edit/", app.SessionMW(app.RequireLoginMW(handlers.ProfileEditHandler(app))))
|
|
http.HandleFunc(config.ThreadrDir+"/like/", app.SessionMW(app.RequireLoginMW(handlers.LikeHandler(app))))
|
|
http.HandleFunc(config.ThreadrDir+"/news/", app.SessionMW(handlers.NewsHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/signup/", app.SessionMW(handlers.SignupHandler(app)))
|
|
http.HandleFunc(config.ThreadrDir+"/accept_cookie/", app.SessionMW(handlers.AcceptCookieHandler(app)))
|
|
|
|
log.Println("Server starting on :8080")
|
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
} |