package main import ( "database/sql" "encoding/json" "fmt" "html/template" "log" "net/http" "os" "path/filepath" "threadr/handlers" "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 _, 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, type VARCHAR(50) 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) } log.Println("Database tables created or already exist") return nil } func main() { 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() // Create tables if they don't exist err = createTablesIfNotExist(db) if err != nil { log.Fatal("Error creating database tables:", err) } 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)) }