gosora/main.go
Azareal 14a14b7e80 More work on Cosora, we have a screenshot of it up now, although it's super experimental at the moment.
This commit might be a little broken, another is coming to fix things up!

The topics list is now paginated.
Refactored the error handling system.
Added the Trumboyg WYSIWYG editor for Cosora.
Moved Delete() out of the TopicStore and into *Topic
You can now bulk delete and bulk lock topics on Cosora.
h1s are now formatted properly on Firefox.
Added more ARIA Labels.
SuperModOnly is now a piece of middleware for the Control Panel routes.
Refactored and extended the router generator.
Improved the SEO for the paginators.
Added bits of Microdata to improve SEO further.
Wrote benchmarks for users.Get() and users.BypassGet()
More errors are caught now.
You can now attach pcss files to posts.
Improved the error logging for JavaScript.
Topic list avatars now link to the associated profiles.
Added last poster avatars to the forum list.
2017-10-30 09:57:08 +00:00

336 lines
11 KiB
Go

/*
*
* Gosora Main File
* Copyright Azareal 2016 - 2018
*
*/
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"time"
//"runtime/pprof"
)
var version = Version{Major: 0, Minor: 1, Patch: 0, Tag: "dev"}
const hour int = 60 * 60
const day int = hour * 24
const week int = day * 7
const month int = day * 30
const year int = day * 365
const kilobyte int = 1024
const megabyte int = kilobyte * 1024
const gigabyte int = megabyte * 1024
const terabyte int = gigabyte * 1024
const petabyte int = terabyte * 1024
const saltLength int = 32
const sessionLength int = 80
var router *GenRouter
var startTime time.Time
// ? - Make this more customisable?
var externalSites = map[string]string{
"YT": "https://www.youtube.com/",
}
type StringList []string
// ? - Should we allow users to upload .php or .go files? It could cause security issues. We could store them with a mangled extension to render them inert
// TODO: Let admins manage this from the Control Panel
var allowedFileExts = StringList{
"png", "jpg", "jpeg", "svg", "bmp", "gif", "tif", "webp", "apng", // images
"txt", "xml", "json", "yaml", "toml", "ini", "md", "html", "rtf", "js", "py", "rb", "css", "scss", "less", "eqcss", "pcss", "java", "ts", "cs", "c", "cc", "cpp", "cxx", "C", "c++", "h", "hh", "hpp", "hxx", "h++", "rs", "rlib", "htaccess", "gitignore", // text
"mp3", "mp4", "avi", "wmv", "webm", // video
"otf", "woff2", "woff", "ttf", "eot", // fonts
}
var imageFileExts = StringList{
"png", "jpg", "jpeg", "svg", "bmp", "gif", "tif", "webp", "apng",
}
var archiveFileExts = StringList{
"bz2", "zip", "gz", "7z", "tar", "cab",
}
var executableFileExts = StringList{
"exe", "jar", "phar", "shar", "iso",
}
// TODO: Write a test for this
func (slice StringList) Contains(needle string) bool {
for _, item := range slice {
if item == needle {
return true
}
}
return false
}
var staticFiles = make(map[string]SFile)
var logWriter = io.MultiWriter(os.Stderr)
func main() {
// TODO: Recover from panics
/*defer func() {
r := recover()
if r != nil {
log.Print(r)
debug.PrintStack()
return
}
}()*/
// TODO: Have a file for each run with the time/date the server started as the file name?
// TODO: Log panics with recover()
f, err := os.OpenFile("./operations.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil {
log.Fatal(err)
}
logWriter = io.MultiWriter(os.Stderr, f)
log.SetOutput(logWriter)
//if profiling {
// f, err := os.Create("startup_cpu.prof")
// if err != nil {
// log.Fatal(err)
// }
// pprof.StartCPUProfile(f)
//}
log.Print("Running Gosora v" + version.String())
fmt.Println("")
startTime = time.Now()
log.Print("Processing configuration data")
err = processConfig()
if err != nil {
log.Fatal(err)
}
err = initThemes()
if err != nil {
log.Fatal(err)
}
err = initDatabase()
if err != nil {
log.Fatal(err)
}
rstore = NewSQLReplyStore()
prstore = NewSQLProfileReplyStore()
initTemplates()
err = initPhrases()
if err != nil {
log.Fatal(err)
}
log.Print("Loading the static files.")
err = initStaticFiles()
if err != nil {
log.Fatal(err)
}
log.Print("Initialising the widgets")
err = initWidgets()
if err != nil {
log.Fatal(err)
}
log.Print("Initialising the authentication system")
auth = NewDefaultAuth()
err = LoadWordFilters()
if err != nil {
log.Fatal(err)
}
err = verifyConfig()
if err != nil {
log.Fatal(err)
}
// Run this goroutine once a second
secondTicker := time.NewTicker(1 * time.Second)
fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
//hour_ticker := time.NewTicker(1 * time.Hour)
go func() {
for {
select {
case <-secondTicker.C:
//log.Print("Running the second ticker")
// TODO: Add a plugin hook here
err := handleExpiredScheduledGroups()
if err != nil {
LogError(err)
}
// TODO: Handle delayed moderation tasks
// TODO: Handle the daily clean-up. Move this to a 24 hour task?
// Sync with the database, if there are any changes
err = handleServerSync()
if err != nil {
LogError(err)
}
// TODO: Manage the TopicStore, UserStore, and ForumStore
// TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
// TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
// TODO: Rescan the static files for changes
// TODO: Add a plugin hook here
case <-fifteenMinuteTicker.C:
// TODO: Add a plugin hook here
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TODO: Publish scheduled posts.
// TODO: Add a plugin hook here
}
}
}()
log.Print("Initialising the router")
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
////router.HandleFunc("/static/", routeStatic)
////router.HandleFunc("/overview/", routeOverview)
////router.HandleFunc("/topics/create/", routeTopicCreate)
////router.HandleFunc("/topics/", routeTopics)
////router.HandleFunc("/forums/", routeForums)
////router.HandleFunc("/forum/", routeForum)
router.HandleFunc("/topic/create/submit/", routeTopicCreateSubmit)
router.HandleFunc("/topic/", routeTopicID)
router.HandleFunc("/reply/create/", routeCreateReply)
//router.HandleFunc("/reply/edit/", routeReplyEdit)
//router.HandleFunc("/reply/delete/", routeReplyDelete)
router.HandleFunc("/reply/edit/submit/", routeReplyEditSubmit)
router.HandleFunc("/reply/delete/submit/", routeReplyDeleteSubmit)
router.HandleFunc("/reply/like/submit/", routeReplyLikeSubmit)
///router.HandleFunc("/report/submit/", route_report_submit)
router.HandleFunc("/topic/edit/submit/", routeEditTopic)
router.HandleFunc("/topic/delete/submit/", routeDeleteTopic)
router.HandleFunc("/topic/stick/submit/", routeStickTopic)
router.HandleFunc("/topic/unstick/submit/", routeUnstickTopic)
router.HandleFunc("/topic/lock/submit/", routeLockTopic)
router.HandleFunc("/topic/unlock/submit/", routeUnlockTopic)
router.HandleFunc("/topic/like/submit/", routeLikeTopic)
// Custom Pages
router.HandleFunc("/pages/", routeCustomPage)
// Accounts
router.HandleFunc("/accounts/login/", routeLogin)
router.HandleFunc("/accounts/create/", routeRegister)
router.HandleFunc("/accounts/logout/", routeLogout)
router.HandleFunc("/accounts/login/submit/", routeLoginSubmit)
router.HandleFunc("/accounts/create/submit/", routeRegisterSubmit)
//router.HandleFunc("/accounts/list/", routeLogin) // Redirect /accounts/ and /user/ to here.. // Get a list of all of the accounts on the forum
//router.HandleFunc("/accounts/create/full/", routeLogout) // Advanced account creator for admins?
//router.HandleFunc("/user/edit/", routeLogout)
router.HandleFunc("/user/edit/critical/", routeAccountOwnEditCritical) // Password & Email
router.HandleFunc("/user/edit/critical/submit/", routeAccountOwnEditCriticalSubmit)
router.HandleFunc("/user/edit/avatar/", routeAccountOwnEditAvatar)
router.HandleFunc("/user/edit/avatar/submit/", routeAccountOwnEditAvatarSubmit)
router.HandleFunc("/user/edit/username/", routeAccountOwnEditUsername)
router.HandleFunc("/user/edit/username/submit/", routeAccountOwnEditUsernameSubmit)
router.HandleFunc("/user/edit/email/", routeAccountOwnEditEmail)
router.HandleFunc("/user/edit/token/", routeAccountOwnEditEmailTokenSubmit)
router.HandleFunc("/user/", routeProfile)
router.HandleFunc("/profile/reply/create/", routeProfileReplyCreate)
router.HandleFunc("/profile/reply/edit/submit/", routeProfileReplyEditSubmit)
router.HandleFunc("/profile/reply/delete/submit/", routeProfileReplyDeleteSubmit)
//router.HandleFunc("/user/edit/submit/", routeLogout) // routeLogout? what on earth? o.o
//router.HandleFunc("/users/ban/", routeBan)
router.HandleFunc("/users/ban/submit/", routeBanSubmit)
router.HandleFunc("/users/unban/", routeUnban)
router.HandleFunc("/users/activate/", routeActivate)
router.HandleFunc("/users/ips/", routeIps)
// The Control Panel
// TODO: Rename the commented route handlers to the new camelCase format :'(
////router.HandleFunc("/panel/", routePanel)
////router.HandleFunc("/panel/forums/", routePanelForums)
////router.HandleFunc("/panel/forums/create/", routePanelForumsCreateSubmit)
////router.HandleFunc("/panel/forums/delete/", routePanelForumsDelete)
////router.HandleFunc("/panel/forums/delete/submit/", routePanelForumsDeleteSubmit)
////router.HandleFunc("/panel/forums/edit/", routePanelForumsEdit)
////router.HandleFunc("/panel/forums/edit/submit/", routePanelForumsEditSubmit)
////router.HandleFunc("/panel/forums/edit/perms/submit/", routePanelForumsEditPermsSubmit)
////router.HandleFunc("/panel/settings/", routePanelSettings)
////router.HandleFunc("/panel/settings/edit/", routePanelSetting)
////router.HandleFunc("/panel/settings/edit/submit/", routePanelSettingEdit)
///router.HandleFunc("/panel/themes/", route_panel_themes)
///router.HandleFunc("/panel/themes/default/", route_panel_themes_default)
///router.HandleFunc("/panel/plugins/", route_panel_plugins)
///router.HandleFunc("/panel/plugins/activate/", route_panel_plugins_activate)
///router.HandleFunc("/panel/plugins/deactivate/", route_panel_plugins_deactivate)
///router.HandleFunc("/panel/users/", route_panel_users)
///router.HandleFunc("/panel/users/edit/", route_panel_users_edit)
///router.HandleFunc("/panel/users/edit/submit/", route_panel_users_edit_submit)
///router.HandleFunc("/panel/groups/", route_panel_groups)
///router.HandleFunc("/panel/groups/edit/", route_panel_groups_edit)
///router.HandleFunc("/panel/groups/edit/perms/", route_panel_groups_edit_perms)
///router.HandleFunc("/panel/groups/edit/submit/", route_panel_groups_edit_submit)
///router.HandleFunc("/panel/groups/edit/perms/submit/", route_panel_groups_edit_perms_submit)
///router.HandleFunc("/panel/groups/create/", route_panel_groups_create_submit)
///router.HandleFunc("/panel/logs/mod/", route_panel_logs_mod)
///router.HandleFunc("/panel/debug/", route_panel_debug)
////router.HandleFunc("/api/", routeAPI)
//router.HandleFunc("/exit/", routeExit)
////router.HandleFunc("/", config.DefaultRoute)
router.HandleFunc("/ws/", routeWebsockets)
log.Print("Initialising the plugins")
initPlugins()
defer db.Close()
//if profiling {
// pprof.StopCPUProfile()
//}
// TODO: Let users run *both* HTTP and HTTPS
log.Print("Initialising the HTTP server")
if !site.EnableSsl {
if site.Port == "" {
site.Port = "80"
}
log.Print("Listening on port " + site.Port)
err = http.ListenAndServe(":"+site.Port, router)
} else {
if site.Port == "" {
site.Port = "443"
}
if site.Port == "80" || site.Port == "443" {
// We should also run the server on port 80
// TODO: Redirect to port 443
go func() {
log.Print("Listening on port 80")
err = http.ListenAndServe(":80", &HTTPSRedirect{})
if err != nil {
log.Fatal(err)
}
}()
}
log.Print("Listening on port " + site.Port)
err = http.ListenAndServeTLS(":"+site.Port, config.SslFullchain, config.SslPrivkey, router)
}
// Why did the server stop?
if err != nil {
log.Fatal(err)
}
}