Revamped Tempra Conflux.

Updated the readme.
Added more comments.
The forumView cache for MemoryForumStore now excludes Social Groups.
Moved the word filter logic into it's own file.
You can now change the language via the configuration file.
Moved the inline CSS into the CSS files.
This commit is contained in:
Azareal 2017-09-13 16:09:13 +01:00
parent 01bef7a320
commit ce9195c841
30 changed files with 443 additions and 204 deletions

View File

@ -152,15 +152,11 @@ We're looking for ways to clean-up the plugin system so that all of them (except
![Tempra Conflux Mobile](https://github.com/Azareal/Gosora/blob/master/images/tempra-conflux-control-panel.png) ![Tempra Conflux Mobile](https://github.com/Azareal/Gosora/blob/master/images/tempra-conflux-control-panel.png)
![Cosmo Conflux Theme](https://github.com/Azareal/Gosora/blob/master/images/cosmo-conflux.png)
![Cosmo Theme](https://github.com/Azareal/Gosora/blob/master/images/cosmo.png)
More images in the /images/ folder. Beware though, some of them are *really* outdated. More images in the /images/ folder. Beware though, some of them are *really* outdated.
# Dependencies # Dependencies
* Go 1.8 * Go 1.9
* MariaDB (or any other MySQL compatible database engine). We'll allow other database engines in the future. * MariaDB (or any other MySQL compatible database engine). We'll allow other database engines in the future.

View File

@ -1,4 +1,9 @@
/* Work in progress. Check back later! */ /*
*
* Gosora Forum Store
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import ( import (
@ -15,6 +20,7 @@ var forumCreateMutex sync.Mutex
var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms
var fstore ForumStore var fstore ForumStore
// ForumStore is an interface for accessing the forums and the metadata stored on them
type ForumStore interface { type ForumStore interface {
LoadForums() error LoadForums() error
DirtyGet(id int) *Forum DirtyGet(id int) *Forum
@ -43,9 +49,9 @@ type ForumStore interface {
GetGlobalCount() int GetGlobalCount() int
} }
// MemoryForumStore is a struct which holds an arbitrary number of forums in memory, usually all of them, although we might introduce functionality to hold a smaller subset in memory for sites with an extremely large number of forums
type MemoryForumStore struct { type MemoryForumStore struct {
//forums map[int]*Forum forums sync.Map // map[int]*Forum
forums sync.Map
forumView atomic.Value // []*Forum forumView atomic.Value // []*Forum
//fids []int //fids []int
forumCount int forumCount int
@ -56,6 +62,7 @@ type MemoryForumStore struct {
getForumCount *sql.Stmt getForumCount *sql.Stmt
} }
// NewMemoryForumStore gives you a new instance of MemoryForumStore
func NewMemoryForumStore() *MemoryForumStore { func NewMemoryForumStore() *MemoryForumStore {
getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "fid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "fid = ?", "", "")
if err != nil { if err != nil {
@ -81,6 +88,7 @@ func NewMemoryForumStore() *MemoryForumStore {
} }
} }
// TODO: Add support for subforums
func (mfs *MemoryForumStore) LoadForums() error { func (mfs *MemoryForumStore) LoadForums() error {
log.Print("Adding the uncategorised forum") log.Print("Adding the uncategorised forum")
forumUpdateMutex.Lock() forumUpdateMutex.Lock()
@ -89,7 +97,7 @@ func (mfs *MemoryForumStore) LoadForums() error {
var forumView []*Forum var forumView []*Forum
addForum := func(forum *Forum) { addForum := func(forum *Forum) {
mfs.forums.Store(forum.ID, forum) mfs.forums.Store(forum.ID, forum)
if forum.Active && forum.Name != "" { if forum.Active && forum.Name != "" && forum.ParentType == "" {
forumView = append(forumView, forum) forumView = append(forumView, forum)
} }
} }
@ -132,7 +140,8 @@ func (mfs *MemoryForumStore) rebuildView() {
var forumView []*Forum var forumView []*Forum
mfs.forums.Range(func(_ interface{}, value interface{}) bool { mfs.forums.Range(func(_ interface{}, value interface{}) bool {
forum := value.(*Forum) forum := value.(*Forum)
if forum.Active && forum.Name != "" { // ? - ParentType blank means that it doesn't have a parent
if forum.Active && forum.Name != "" && forum.ParentType == "" {
forumView = append(forumView, forum) forumView = append(forumView, forum)
} }
return true return true

View File

@ -124,7 +124,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
for pb.Next() { for pb.Next() {
topicW.Body.Reset() topicW.Body.Reset()
route_topic_id(topicW, topicReqAdmin, user) routeTopicID(topicW, topicReqAdmin, user)
} }
}) })
} }
@ -143,7 +143,7 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) {
topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil)) topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil))
for pb.Next() { for pb.Next() {
topicW.Body.Reset() topicW.Body.Reset()
route_topic_id(topicW, topicReq, guestUser) routeTopicID(topicW, topicReq, guestUser)
} }
}) })
} }

3
group_store.go Normal file
View File

@ -0,0 +1,3 @@
package main
// TODO: Coming Soon. Probably in the next commit or two

View File

@ -146,22 +146,23 @@ func main() {
func init() { func init() {
// Site Info // Site Info
site.Name = "` + siteName + `" // Should be a setting in the database site.Name = "` + siteName + `"
site.Email = "" // Should be a setting in the database site.Email = ""
site.Url = "` + siteURL + `" site.URL = "` + siteURL + `"
site.Port = "` + serverPort + `" site.Port = "` + serverPort + `"
site.EnableSsl = false site.EnableSsl = false
site.EnableEmails = false site.EnableEmails = false
site.HasProxy = false // Cloudflare counts as this, if it's sitting in the middle site.HasProxy = false // Cloudflare counts as this, if it's sitting in the middle
config.SslPrivkey = "" config.SslPrivkey = ""
config.SslFullchain = "" config.SslFullchain = ""
site.Language = "english"
// Database details // Database details
db_config.Host = "` + dbHost + `" dbConfig.Host = "` + dbHost + `"
db_config.Username = "` + dbUsername + `" dbConfig.Username = "` + dbUsername + `"
db_config.Password = "` + dbPassword + `" dbConfig.Password = "` + dbPassword + `"
db_config.Dbname = "` + dbName + `" dbConfig.Dbname = "` + dbName + `"
db_config.Port = "` + dbPort + `" // You probably won't need to change this dbConfig.Port = "` + dbPort + `" // You probably won't need to change this
// Limiters // Limiters
config.MaxRequestSize = 5 * megabyte config.MaxRequestSize = 5 * megabyte

View File

@ -53,7 +53,7 @@ func _initMysql() (err error) {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
fmt.Println("Unable to find the database. Attempting to create it") fmt.Println("Unable to find the database. Attempting to create it")
_, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + dbName + "") _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + dbName)
if err != nil { if err != nil {
return err return err
} }

View File

@ -39,5 +39,9 @@
}, },
"SettingLabels": { "SettingLabels": {
"activation_type": "Activate All,Email Activation,Admin Approval" "activation_type": "Activate All,Email Activation,Admin Approval"
},
"Accounts": {
"VerifyEmailSubject": "Validate Your Email @ {{name}}",
"VerifyEmailBody": "Dear {{username}}, following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. {{schema}}://{{url}}/user/edit/token/{{token}}\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
} }
} }

62
main.go
View File

@ -1,4 +1,9 @@
/* Copyright Azareal 2016 - 2018 */ /*
*
* Gosora Main File
* Copyright Azareal 2016 - 2018
*
*/
package main package main
import ( import (
@ -8,7 +13,6 @@ import (
"net/http" "net/http"
"os" "os"
"strings" "strings"
"sync/atomic"
"time" "time"
//"runtime/pprof" //"runtime/pprof"
) )
@ -27,8 +31,6 @@ const terabyte int = gigabyte * 1024
const saltLength int = 32 const saltLength int = 32
const sessionLength int = 80 const sessionLength int = 80
var enableWebsockets = false // Don't change this, the value is overwritten by an initialiser
var router *GenRouter var router *GenRouter
var startTime time.Time var startTime time.Time
@ -41,55 +43,13 @@ var groupCapCount int
var staticFiles = make(map[string]SFile) var staticFiles = make(map[string]SFile)
var logWriter = io.MultiWriter(os.Stderr) var logWriter = io.MultiWriter(os.Stderr)
type WordFilter struct {
ID int
Find string
Replacement string
}
type WordFilterBox map[int]WordFilter
var wordFilterBox atomic.Value // An atomic value holding a WordFilterBox
func init() {
wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter)))
}
func LoadWordFilters() error {
rows, err := get_word_filters_stmt.Query()
if err != nil {
return err
}
defer rows.Close()
var wordFilters = WordFilterBox(make(map[int]WordFilter))
var wfid int
var find string
var replacement string
for rows.Next() {
err := rows.Scan(&wfid, &find, &replacement)
if err != nil {
return err
}
wordFilters[wfid] = WordFilter{ID: wfid, Find: find, Replacement: replacement}
}
wordFilterBox.Store(wordFilters)
return rows.Err()
}
func addWordFilter(id int, find string, replacement string) {
wordFilters := wordFilterBox.Load().(WordFilterBox)
wordFilters[id] = WordFilter{ID: id, Find: find, Replacement: replacement}
wordFilterBox.Store(wordFilters)
}
func processConfig() { func processConfig() {
config.Noavatar = strings.Replace(config.Noavatar, "{site_url}", site.Url, -1) config.Noavatar = strings.Replace(config.Noavatar, "{site_url}", site.URL, -1)
if site.Port != "80" && site.Port != "443" { if site.Port != "80" && site.Port != "443" {
site.Url = strings.TrimSuffix(site.Url, "/") site.URL = strings.TrimSuffix(site.URL, "/")
site.Url = strings.TrimSuffix(site.Url, "\\") site.URL = strings.TrimSuffix(site.URL, "\\")
site.Url = strings.TrimSuffix(site.Url, ":") site.URL = strings.TrimSuffix(site.URL, ":")
site.Url = site.Url + ":" + site.Port site.URL = site.URL + ":" + site.Port
} }
} }

View File

@ -137,7 +137,7 @@ func TestForumStore(t *testing.T) {
func TestSlugs(t *testing.T) { func TestSlugs(t *testing.T) {
var res string var res string
var msgList []ME_Pair var msgList []MEPair
msgList = addMEPair(msgList, "Unknown", "unknown") msgList = addMEPair(msgList, "Unknown", "unknown")
msgList = addMEPair(msgList, "Unknown2", "unknown2") msgList = addMEPair(msgList, "Unknown2", "unknown2")

View File

@ -27,12 +27,12 @@ func init() {
func _initDatabase() (err error) { func _initDatabase() (err error) {
var _dbpassword string var _dbpassword string
if db_config.Password != "" { if dbConfig.Password != "" {
_dbpassword = ":" + db_config.Password _dbpassword = ":" + dbConfig.Password
} }
// Open the database connection // Open the database connection
db, err = sql.Open("mysql", db_config.Username+_dbpassword+"@tcp("+db_config.Host+":"+db_config.Port+")/"+db_config.Dbname+"?collation="+dbCollation) db, err = sql.Open("mysql", dbConfig.Username+_dbpassword+"@tcp("+dbConfig.Host+":"+dbConfig.Port+")/"+dbConfig.Dbname+"?collation="+dbCollation)
if err != nil { if err != nil {
return err return err
} }

View File

@ -5,33 +5,36 @@ package main
import "errors" import "errors"
import "net/http" import "net/http"
var wsHub WS_Hub // TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it?
var enableWebsockets = false // Put this in caps for consistency with the other constants?
var wsHub WSHub
var errWsNouser = errors.New("This user isn't connected via WebSockets") var errWsNouser = errors.New("This user isn't connected via WebSockets")
type WS_Hub struct { type WSHub struct {
} }
func (_ *WS_Hub) guestCount() int { func (_ *WSHub) guestCount() int {
return 0 return 0
} }
func (_ *WS_Hub) userCount() int { func (_ *WSHub) userCount() int {
return 0 return 0
} }
func (hub *WS_Hub) broadcastMessage(_ string) error { func (hub *WSHub) broadcastMessage(_ string) error {
return nil return nil
} }
func (hub *WS_Hub) pushMessage(_ int, _ string) error { func (hub *WSHub) pushMessage(_ int, _ string) error {
return errWsNouser return errWsNouser
} }
func (hub *WS_Hub) pushAlert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error { func (hub *WSHub) pushAlert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return errWsNouser return errWsNouser
} }
func (hub *WS_Hub) pushAlerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error { func (hub *WSHub) pushAlerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return errWsNouser return errWsNouser
} }

View File

@ -207,10 +207,10 @@ type PanelEditForumPage struct {
Groups []GroupForumPermPreset Groups []GroupForumPermPreset
} }
type NameLangPair struct { /*type NameLangPair struct {
Name string Name string
LangStr string LangStr string
} }*/
type NameLangToggle struct { type NameLangToggle struct {
Name string Name string

View File

@ -19,7 +19,6 @@ import (
// TODO: Let the admin edit phrases from inside the Control Panel? How should we persist these? Should we create a copy of the langpack or edit the primaries? Use the changeLangpack mutex for this? // TODO: Let the admin edit phrases from inside the Control Panel? How should we persist these? Should we create a copy of the langpack or edit the primaries? Use the changeLangpack mutex for this?
// nolint Be quiet megacheck, this *is* used // nolint Be quiet megacheck, this *is* used
var currentLanguage = "english"
var currentLangPack atomic.Value var currentLangPack atomic.Value
var langpackCount int // TODO: Use atomics for this var langpackCount int // TODO: Use atomics for this
@ -40,6 +39,7 @@ type LanguagePack struct {
GlobalPerms map[string]string GlobalPerms map[string]string
LocalPerms map[string]string LocalPerms map[string]string
SettingLabels map[string]string SettingLabels map[string]string
Accounts map[string]string // TODO: Apply these phrases in the software proper
} }
// TODO: Add the ability to edit language JSON files from the Control Panel and automatically scan the files for changes // TODO: Add the ability to edit language JSON files from the Control Panel and automatically scan the files for changes
@ -86,9 +86,9 @@ func initPhrases() error {
return errors.New("You don't have any language packs") return errors.New("You don't have any language packs")
} }
langPack, ok := langpacks.Load(currentLanguage) langPack, ok := langpacks.Load(site.Language)
if !ok { if !ok {
return errors.New("Couldn't find the " + currentLanguage + " language pack") return errors.New("Couldn't find the " + site.Language + " language pack")
} }
currentLangPack.Store(langPack) currentLangPack.Store(langPack)
return nil return nil
@ -139,6 +139,14 @@ func GetAllSettingLabels() map[string]string {
return currentLangPack.Load().(*LanguagePack).SettingLabels return currentLangPack.Load().(*LanguagePack).SettingLabels
} }
func GetAccountPhrase(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).Accounts[name]
if !ok {
return "{name}"
}
return res
}
// ? - Use runtime reflection for updating phrases? // ? - Use runtime reflection for updating phrases?
// TODO: Implement these // TODO: Implement these
func AddPhrase() { func AddPhrase() {

View File

@ -3,22 +3,21 @@ package main
import "strconv" import "strconv"
import "testing" import "testing"
// TODO: Replace the soft tabs with hard ones
// go test -v // go test -v
type ME_Pair struct { type MEPair struct {
Msg string Msg string
Expects string Expects string
} }
func addMEPair(msgList []ME_Pair, msg string, expects string) []ME_Pair { func addMEPair(msgList []MEPair, msg string, expects string) []MEPair {
return append(msgList, ME_Pair{msg, expects}) return append(msgList, MEPair{msg, expects})
} }
func TestBBCodeRender(t *testing.T) { func TestBBCodeRender(t *testing.T) {
//t.Skip() //t.Skip()
var res string var res string
var msgList []ME_Pair var msgList []MEPair
msgList = addMEPair(msgList, "hi", "hi") msgList = addMEPair(msgList, "hi", "hi")
msgList = addMEPair(msgList, "😀", "😀") msgList = addMEPair(msgList, "😀", "😀")
msgList = addMEPair(msgList, "[b]😀[/b]", "<b>😀</b>") msgList = addMEPair(msgList, "[b]😀[/b]", "<b>😀</b>")
@ -190,7 +189,7 @@ func TestBBCodeRender(t *testing.T) {
func TestMarkdownRender(t *testing.T) { func TestMarkdownRender(t *testing.T) {
//t.Skip() //t.Skip()
var res string var res string
var msgList []ME_Pair var msgList []MEPair
msgList = addMEPair(msgList, "hi", "hi") msgList = addMEPair(msgList, "hi", "hi")
msgList = addMEPair(msgList, "**hi**", "<b>hi</b>") msgList = addMEPair(msgList, "**hi**", "<b>hi</b>")
msgList = addMEPair(msgList, "_hi_", "<u>hi</u>") msgList = addMEPair(msgList, "_hi_", "<u>hi</u>")

View File

@ -1,7 +1,7 @@
/* /*
* *
* Gosora Route Handlers * Gosora Route Handlers
* Copyright Azareal 2016 - 2018 * Copyright Azareal 2016 - 2018
* *
*/ */
package main package main

21
site.go
View File

@ -2,24 +2,23 @@ package main
import "net/http" import "net/http"
// TODO: Add a langPack configuration item or setting var site = &Site{Name: "Magical Fairy Land", Language: "english"}
var dbConfig = DBConfig{Host: "localhost"}
var site = &Site{Name: "Magical Fairy Land"}
var db_config = DB_Config{Host: "localhost"}
var config Config var config Config
var dev DevConfig var dev DevConfig
type Site struct { type Site struct {
Name string Name string // ? - Move this into the settings table?
Email string Email string // ? - Move this into the settings table?
Url string URL string
Port string Port string
EnableSsl bool EnableSsl bool
EnableEmails bool EnableEmails bool
HasProxy bool HasProxy bool
Language string // ? - Move this into the settings table?
} }
type DB_Config struct { type DBConfig struct {
Host string Host string
Username string Username string
Password string Password string
@ -44,13 +43,13 @@ type Config struct {
DefaultRoute func(http.ResponseWriter, *http.Request, User) DefaultRoute func(http.ResponseWriter, *http.Request, User)
DefaultGroup int DefaultGroup int
ActivationGroup int ActivationGroup int
StaffCss string StaffCss string // ? - Move this into the settings table? Might be better to implement this as Group CSS
UncategorisedForumVisible bool UncategorisedForumVisible bool
MinifyTemplates bool MinifyTemplates bool
MultiServer bool MultiServer bool
Noavatar string Noavatar string // ? - Move this into the settings table?
ItemsPerPage int ItemsPerPage int // ? - Move this into the settings table?
} }
type DevConfig struct { type DevConfig struct {

View File

@ -1,3 +1,9 @@
/*
*
* Gosora Task System
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import "time" import "time"

View File

@ -246,7 +246,7 @@ var topic_91 = []byte(`
`) `)
var footer_0 = []byte(`<div class="footer"> var footer_0 = []byte(`<div class="footer">
<div id="poweredBy" style="float: left;margin-top: 4px;">Powered by Gosora - <span style="font-size: 12px;">Made with love by Azareal</span></div> <div id="poweredBy">Powered by Gosora - <span>Made with love by Azareal</span></div>
<form action="/theme/" method="post"> <form action="/theme/" method="post">
<div id="themeSelector" style="float: right;"> <div id="themeSelector" style="float: right;">
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance"> <select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
@ -312,10 +312,8 @@ var topic_alt_19 = []byte(`
</form> </form>
</div> </div>
<!-- Stop inling this x.x --> <div class="rowblock post_container">
<style type="text/css">.rowitem:last-child .content_container { margin-bottom: 5px !important; }</style> <article class="rowitem passive deletable_block editable_parent post_item top_post">
<div class="rowblock post_container" style="border-top: none;">
<article class="rowitem passive deletable_block editable_parent post_item top_post" style="background-color: #eaeaea;padding-top: 4px;padding-left: 5px;clear: both;border-bottom: none;padding-right: 4px;padding-bottom: 2px;">
<div class="userinfo"> <div class="userinfo">
<div class="avatar_item" style="background-image: url(`) <div class="avatar_item" style="background-image: url(`)
var topic_alt_20 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div> var topic_alt_20 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
@ -426,7 +424,7 @@ var topic_alt_84 = []byte(`
var topic_alt_85 = []byte(`</div> var topic_alt_85 = []byte(`</div>
`) `)
var topic_alt_86 = []byte(` var topic_alt_86 = []byte(`
<div class="rowblock topic_reply_form" style="border-top: none;"> <div class="rowblock topic_reply_form">
<form action="/reply/create/" method="post"> <form action="/reply/create/" method="post">
<input name="tid" value='`) <input name="tid" value='`)
var topic_alt_87 = []byte(`' type="hidden" /> var topic_alt_87 = []byte(`' type="hidden" />

View File

@ -1,5 +1,5 @@
<div class="footer"> <div class="footer">
<div id="poweredBy" style="float: left;margin-top: 4px;">Powered by Gosora - <span style="font-size: 12px;">Made with love by Azareal</span></div> <div id="poweredBy">Powered by Gosora - <span>Made with love by Azareal</span></div>
<form action="/theme/" method="post"> <form action="/theme/" method="post">
<div id="themeSelector" style="float: right;"> <div id="themeSelector" style="float: right;">
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance"> <select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">

View File

@ -22,10 +22,8 @@
</form> </form>
</div> </div>
<!-- Stop inling this x.x --> <div class="rowblock post_container">
<style type="text/css">.rowitem:last-child .content_container { margin-bottom: 5px !important; }</style> <article class="rowitem passive deletable_block editable_parent post_item top_post">
<div class="rowblock post_container" style="border-top: none;">
<article class="rowitem passive deletable_block editable_parent post_item top_post" style="background-color: #eaeaea;padding-top: 4px;padding-left: 5px;clear: both;border-bottom: none;padding-right: 4px;padding-bottom: 2px;">
<div class="userinfo"> <div class="userinfo">
<div class="avatar_item" style="background-image: url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div> <div class="avatar_item" style="background-image: url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<a href="{{.Topic.UserLink}}" class="the_name">{{.Topic.CreatedByName}}</a> <a href="{{.Topic.UserLink}}" class="the_name">{{.Topic.CreatedByName}}</a>
@ -79,7 +77,7 @@
</article> </article>
{{end}}</div> {{end}}</div>
{{if .CurrentUser.Perms.CreateReply}} {{if .CurrentUser.Perms.CreateReply}}
<div class="rowblock topic_reply_form" style="border-top: none;"> <div class="rowblock topic_reply_form">
<form action="/reply/create/" method="post"> <form action="/reply/create/" method="post">
<input name="tid" value='{{.Topic.ID}}' type="hidden" /> <input name="tid" value='{{.Topic.ID}}' type="hidden" />
<div class="formrow"> <div class="formrow">

View File

@ -460,6 +460,13 @@ input, select, textarea {
font-size: 13px; font-size: 13px;
padding: 4px; padding: 4px;
} }
#poweredBy {
float: left;
margin-top: 4px;
}
#poweredBy span {
font-size: 12px;
}
/* Forum View */ /* Forum View */
.rowhead { .rowhead {

View File

@ -7,21 +7,29 @@
body { body {
font-family: arial; font-family: arial;
padding-bottom: 8px; padding-bottom: 8px;
/* TODO: Redo the background */
background-image: url('/static/test_bg2.svg'); background-image: url('/static/test_bg2.svg');
background-size: cover; background-size: cover;
background: white;
} }
/* Patch for Edge, until they fix emojis in arial x.x */ /* Patch for Edge, until they fix emojis in arial x.x */
@supports (-ms-ime-align:auto) { .user_content { font-family: Segoe UI Emoji, arial; } } @supports (-ms-ime-align:auto) {
.user_content { font-family: Segoe UI Emoji, arial; }
}
ul { ul {
padding-left: 0px; padding-left: 0px;
padding-right: 0px; padding-right: 0px;
height: 36px; height: 36px;
list-style-type: none; list-style-type: none;
border: 1px solid #ccc; border: 1px solid hsl(0,0%,80%);
background-color: white; background: hsl(0, 0%, 97%);
margin-bottom: 12px; margin-bottom: 12px;
margin-top: 0px;
border-bottom: 1.5px inset hsl(0,0%,80%);
margin-left: -8px;
margin-right: -8px;
} }
li { li {
height: 35px; height: 35px;
@ -29,7 +37,9 @@ li {
padding-top: 8px; padding-top: 8px;
padding-bottom: 8px; padding-bottom: 8px;
} }
li:hover { background: rgb(250,250,250); } li:hover {
background: rgb(250,250,250);
}
li a { li a {
text-decoration: none; text-decoration: none;
/*color: #515151;*/ /*color: #515151;*/
@ -38,12 +48,14 @@ li a {
} }
.menu_left { .menu_left {
float: left; float: left;
border-right: 1px solid #ccc; border-right: 1px solid hsl(0,0%,80%);
border-bottom: 1.5px inset hsl(0,0%,80%);
padding-right: 10px; padding-right: 10px;
background: hsl(0, 0%, 98%);
} }
.menu_right { .menu_right {
float: right; float: right;
border-left: 1px solid #ccc; border-left: 1px solid hsl(0,0%,80%);
padding-right: 10px; padding-right: 10px;
} }
@ -85,8 +97,9 @@ li a {
color: black; color: black;
font-weight: bold; font-weight: bold;
} }
.selectedAlert .alert_counter { display: none; } .selectedAlert .alert_counter, .menu_alerts .alertList {
.menu_alerts .alertList { display: none; } display: none;
}
.selectedAlert .alertList { .selectedAlert .alertList {
position: absolute; position: absolute;
top: 51px; top: 51px;
@ -96,10 +109,10 @@ li a {
line-height: 16px; line-height: 16px;
width: 300px; width: 300px;
right: calc(5% + 7px); right: calc(5% + 7px);
border-top: 1px solid #ccc; border-top: 1px solid hsl(0,0%,80%);
border-left: 1px solid #ccc; border-left: 1px solid hsl(0,0%,80%);
border-right: 1px solid #ccc; border-right: 1px solid hsl(0,0%,80%);
border-bottom: 1px solid #ccc; border-bottom: 1px solid hsl(0,0%,80%);
margin-bottom: 10px; margin-bottom: 10px;
} }
.alertItem { .alertItem {
@ -136,19 +149,47 @@ li a {
padding: 0px; padding: 0px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
background: hsl(0, 0%, 98%);
border: 1px solid hsl(0,0%,80%);
border-top: none;
} }
#back {
padding: 12px;
padding-top: 0px;
}
/* Explict declaring each border direction to fix a bug in Chrome where an override to .rowhead was also applying to .rowblock in some cases */
.rowblock { .rowblock {
border: 1px solid #ccc; border: 1px solid hsl(0,0%,80%);
width: 100%; width: 100%;
padding: 0px; padding: 0px;
padding-top: 0px; padding-top: 0px;
border-top: 1px solid hsl(0,0%,80%);
border-left: 1px solid hsl(0,0%,80%);
border-right: 1px solid hsl(0,0%,80%);
border-bottom: 1.5px inset hsl(0,0%,80%);
}
.rowblock:empty {
display: none;
} }
.rowblock:empty { display: none; }
.rowsmall { .rowsmall {
font-size:12px; font-size:12px;
} }
/* Firefox specific CSS */
@supports (-moz-appearance: none) {
ul, .menu_left, .rowblock {
border-bottom: 2px inset hsl(0,0%,40%);
}
}
/* Edge... We can't get the exact shade here, because of how they implemented it x.x */
@supports (-ms-ime-align:auto) {
ul, .menu_left, .rowblock {
border-bottom: 1.5px inset hsl(0,0%,100%);
}
}
.rowlist { .rowlist {
font-size: 14px; font-size: 14px;
} }
@ -162,25 +203,6 @@ li a {
padding-left: 48px; padding-left: 48px;
} }
.colblock_left {
border: 1px solid #ccc;
padding: 0px;
padding-top: 0px;
width: 30%;
float: left;
margin-right: 8px;
}
.colblock_right {
border: 1px solid #ccc;
padding: 0px;
padding-top: 0px;
width: 65%;
overflow: hidden;
word-wrap: break-word;
}
.colblock_left:empty, .colblock_right:empty { display: none; }
/* The new method of doing columns layouts, colblock is now deprecated :( */
.colstack_left { .colstack_left {
float: left; float: left;
width: 30%; width: 30%;
@ -192,7 +214,7 @@ li a {
width: calc(70% - 15px); width: calc(70% - 15px);
} }
.colstack_item { .colstack_item {
border: 1px solid #ccc; border: 1px solid hsl(0,0%,80%);
padding: 0px; padding: 0px;
padding-top: 0px; padding-top: 0px;
width: 100%; width: 100%;
@ -211,7 +233,7 @@ li a {
margin-top: 2px; margin-top: 2px;
} }
.grid_item { .grid_item {
border: 1px solid #ccc; border: 1px solid hsl(0,0%,80%);
word-wrap: break-word; word-wrap: break-word;
background-color: white; background-color: white;
width: 100%; width: 100%;
@ -223,21 +245,33 @@ li a {
padding-bottom: 12px; padding-bottom: 12px;
font-size: 16px; font-size: 16px;
} }
.grid_istat { margin-bottom: 5px; } .grid_istat {
margin-bottom: 5px;
}
.stat_green { background-color: lightgreen; border-color: green; } .stat_green {
.stat_orange { background-color: #ffe4b3; border-color: orange; } background-color: lightgreen;
.stat_red { background-color: #ffb2b2; border-color: red; } border-color: green;
.stat_disabled { background-color: lightgray; border-color: gray; } }
.stat_orange {
background-color: #ffe4b3;
border-color: orange;
}
.stat_red {
background-color: #ffb2b2;
border-color: red;
}
.stat_disabled {
background-color: lightgray;
border-color: gray;
}
.rowitem { .rowitem {
width: 100%; width: 100%;
/*padding-left: 8px; /*padding-left: 8px;
padding-right: 8px; padding-right: 8px;
padding-top: 17px; padding-top: 17px;
padding-bottom: 12px; padding-bottom: 12px;*/
font-weight: bold;
text-transform: uppercase;*/
padding-left: 10px; padding-left: 10px;
padding-top: 14px; padding-top: 14px;
padding-bottom: 12px; padding-bottom: 12px;
@ -249,13 +283,16 @@ li a {
text-transform: none; text-transform: none;
} }
.rowitem:not(:last-child) { .rowitem:not(:last-child) {
border-bottom: 1px dotted #ccc; border-bottom: 1px solid hsl(0,0%,85%);
} }
.rowitem a { .rowitem a {
text-decoration: none; text-decoration: none;
color: black; color: black;
} }
.rowitem a:hover { color: silver; } .rowitem a:hover {
color: hsl(0,0%,40%);
}
.opthead { display: none; } .opthead { display: none; }
.datarow { .datarow {
padding-top: 10px; padding-top: 10px;
@ -272,8 +309,12 @@ li a {
content: " "; content: " ";
display: table; display: table;
} }
.formrow:after { clear: both; } .formrow:after {
.formrow:not(:last-child) { border-bottom: 1px dotted #ccc; } clear: both;
}
.formrow:not(:last-child) {
border-bottom: 1px dotted hsl(0,0%,80%);
}
.formitem { .formitem {
float: left; float: left;
@ -282,8 +323,12 @@ li a {
/*font-size: 17px;*/ /*font-size: 17px;*/
font-weight: normal; font-weight: normal;
} }
.formitem:not(:last-child) { border-right: 1px dotted #ccc; } .formitem:not(:last-child) {
.formitem.invisible_border { border: none; } border-right: 1px dotted hsl(0,0%,80%);
}
.formitem.invisible_border {
border: none;
}
/* Mostly for textareas */ /* Mostly for textareas */
.formitem:only-child { width: 100%; } .formitem:only-child { width: 100%; }
@ -296,7 +341,9 @@ li a {
margin: 0 auto; margin: 0 auto;
float: none; float: none;
} }
.formitem:not(:only-child) input, .formitem:not(:only-child) select { padding: 3px;/*5px;*/ } .formitem:not(:only-child) input, .formitem:not(:only-child) select {
padding: 3px;/*5px;*/
}
.formitem:not(:only-child).formlabel { .formitem:not(:only-child).formlabel {
padding-top: 15px;/*18px;*/ padding-top: 15px;/*18px;*/
padding-bottom: 12px;/*16px;*/ padding-bottom: 12px;/*16px;*/
@ -308,7 +355,7 @@ li a {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
font-size: 15px; font-size: 15px;
border-color: #ccc; border-color: hsl(0,0%,80%);
} }
button { button {
@ -318,6 +365,33 @@ button {
/* Topics */ /* Topics */
.topic_list {
display: grid;
grid-template-columns: calc(100% - 204px) 204px;
}
.topic_list .topic_inner_right {
display: none;
}
.topic_list .lastReplyAt {
white-space: nowrap;
}
@supports not (display: grid) {
.topic_list .rowitem {
float: left;
overflow: hidden;
}
.topic_list .topic_left {
width: calc(100% - 204px);
}
.topic_list .topic_right {
width: 204px;
}
}
.topic_status_sticky {
display: none;
}
.topic_sticky { .topic_sticky {
background-color: rgb(255,255,234); background-color: rgb(255,255,234);
} }
@ -338,8 +412,22 @@ button {
} }
.topic_status:empty { display: none; } .topic_status:empty { display: none; }
.rowhead, .colstack_head {
border-bottom: none;
}
.rowhead .rowitem, .opthead .rowitem, .colstack_head .rowitem { .rowhead .rowitem, .opthead .rowitem, .colstack_head .rowitem {
background: linear-gradient(to bottom, white, hsl(0, 0%, 93%)); /*background: linear-gradient(to bottom, white, hsl(0, 0%, 93%));*/
background: hsl(0, 0%, 98%);
}
.rowhead h1, .colstack_head h1 {
-webkit-margin-before: 0; /* Opera / Chrome Implicit padding */
-webkit-margin-after: 0;
font-weight: normal;
font-size: 16px;
margin-block-start: 0; /* Firefox Implicit padding */
margin-block-end: 0;
margin-top: 0;
margin-bottom: 0; /* Edge Implicit padding */
} }
.topic_sticky_head { .topic_sticky_head {
background-color: #FFFFEA; background-color: #FFFFEA;
@ -360,7 +448,7 @@ button {
color: #505050; /* 80,80,80 */ color: #505050; /* 80,80,80 */
background-color: #FFFFFF; background-color: #FFFFFF;
border-style: solid; border-style: solid;
border-color: #ccc; border-color: hsl(0,0%,80%);
border-width: 1px; border-width: 1px;
font-size: 15px; font-size: 15px;
} }
@ -396,7 +484,7 @@ button.username {
display: block; display: block;
padding: 5px; padding: 5px;
margin-bottom: 10px; margin-bottom: 10px;
border: 1px solid #ccc; border: 1px solid hsl(0,0%,80%);
background-color: white; background-color: white;
} }
.alert_success { .alert_success {
@ -415,16 +503,19 @@ button.username {
} }
/* Tempra Conflux */ /* Tempra Conflux */
.user_content { .user_content {
padding: 5px; padding: 5px;
margin-top: 3px; margin-top: 2px;
margin-bottom: 0; margin-bottom: 0;
background: white; background: white;
min-height: 145px; min-height: 145px;
padding-bottom: 0; padding-bottom: 0;
width: 100%; width: 100%;
} }
.user_content.nobuttons { min-height: 168px; } .user_content.nobuttons {
min-height: 168px;
}
.button_container { .button_container {
border-top: solid 1px #eaeaea; border-top: solid 1px #eaeaea;
@ -515,7 +606,8 @@ button.username {
float: left; float: left;
position: sticky; position: sticky;
top: 4px; top: 4px;
box-shadow:0 1px 2px rgba(0,0,0,.1); /*box-shadow: 0 1px 2px rgba(0,0,0,.1);*/
border-bottom: 1px inset hsl(0,0%,80%);
} }
.userinfo .avatar_item { .userinfo .avatar_item {
background-repeat: no-repeat, repeat-y; background-repeat: no-repeat, repeat-y;
@ -534,7 +626,8 @@ button.username {
min-height: 128px; min-height: 128px;
margin-bottom: 0; margin-bottom: 0;
margin-right: 3px; margin-right: 3px;
box-shadow:0 1px 2px rgba(0,0,0,.1); /*box-shadow: 0 1px 2px rgba(0,0,0,.1);*/
border-bottom: 1px inset hsl(0,0%,80%);
} }
.action_item .userinfo { display: none; } .action_item .userinfo { display: none; }
@ -551,7 +644,7 @@ button.username {
border-width: 1px; border-width: 1px;
background-color: #FFFFFF; background-color: #FFFFFF;
border-style: solid; border-style: solid;
border-color: #ccc; border-color: hsl(0,0%,80%);
padding: 0px; padding: 0px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
@ -574,4 +667,62 @@ button.username {
top: -2px; top: -2px;
} }
.post_container {
border-bottom: none;
}
.topic_reply_form {
border-top: 1px solid hsl(0,0%,80%);
}
.post_container .post_item {
background-color: #eaeaea;
padding-top: 4px;
padding-left: 5px;
clear: both;
border-bottom: none;
padding-right: 4px;
padding-bottom: 2px;
}
.post_container .post_item:first-child {
padding-top: 6px;
}
.post_container .post_item:last-child .content_container {
margin-bottom: 6px !important;
}
.footer {
border: 1px solid hsl(0,0%,80%);
margin-top: 12px;
clear: both;
height: 40px;
padding: 6px;
padding-left: 10px;
padding-right: 10px;
background-color: white;
border-bottom: 1.5px inset hsl(0,0%,80%);
}
.footer select {
padding: 2px;
margin-top: 1px;
}
#poweredBy {
float: left;
margin-top: 4px;
}
#poweredBy span {
font-size: 12px;
}
/* Firefox specific CSS */
@supports (-moz-appearance: none) {
.footer {
border-bottom: 2px inset hsl(0,0%,40%);
}
}
/* Edge... We can't get the exact shade here, because of how they implemented it x.x */
@supports (-ms-ime-align:auto) {
.footer {
border-bottom: 1.5px inset hsl(0,0%,100%);
}
}
{{template "media.partial.css" }} {{template "media.partial.css" }}

View File

@ -373,9 +373,6 @@ button {
.topic_list .topic_inner_right { .topic_list .topic_inner_right {
display: none; display: none;
} }
.topic_list .rowitem:last-child {
margin-bottom: 10px;
}
.topic_list .lastReplyAt { .topic_list .lastReplyAt {
white-space: nowrap; white-space: nowrap;
} }
@ -598,6 +595,27 @@ button.username {
top: -2px; top: -2px;
} }
.footer {
border: 1px solid #ccc;
margin-top: 12px;
clear: both;
height: 40px;
padding: 6px;
padding-left: 10px;
padding-right: 10px;
}
.footer select {
padding: 2px;
margin-top: 1px;
}
#poweredBy {
float: left;
margin-top: 4px;
}
#poweredBy span {
font-size: 12px;
}
#profile_comments .rowitem { #profile_comments .rowitem {
background-repeat: no-repeat, repeat-y; background-repeat: no-repeat, repeat-y;
background-size: 128px; background-size: 128px;

View File

@ -1,3 +1,9 @@
/*
*
* Gosora Topic File
* Copyright Azareal 2017 - 2018
*
*/
package main package main
//import "fmt" //import "fmt"

View File

@ -1,3 +1,9 @@
/*
*
* Gosora Topic Store
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import "log" import "log"
@ -32,6 +38,7 @@ type MemoryTopicStore struct {
sync.RWMutex sync.RWMutex
} }
// NewMemoryTopicStore gives you a new instance of MemoryTopicStore
func NewMemoryTopicStore(capacity int) *MemoryTopicStore { func NewMemoryTopicStore(capacity int) *MemoryTopicStore {
stmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "") stmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
if err != nil { if err != nil {

View File

@ -1,3 +1,9 @@
/*
*
* Gosora User File
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import ( import (
@ -141,7 +147,7 @@ func SendValidationEmail(username string, email string, token string) bool {
// TODO: Move these to the phrase system // TODO: Move these to the phrase system
subject := "Validate Your Email @ " + site.Name subject := "Validate Your Email @ " + site.Name
msg := "Dear " + username + ", following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. " + schema + "://" + site.Url + "/user/edit/token/" + token + "\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused." msg := "Dear " + username + ", following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. " + schema + "://" + site.URL + "/user/edit/token/" + token + "\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
return SendEmail(email, subject, msg) return SendEmail(email, subject, msg)
} }

View File

@ -46,6 +46,7 @@ type MemoryUserStore struct {
sync.RWMutex sync.RWMutex
} }
// NewMemoryUserStore gives you a new instance of MemoryUserStore
func NewMemoryUserStore(capacity int) *MemoryUserStore { func NewMemoryUserStore(capacity int) *MemoryUserStore {
getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "")
if err != nil { if err != nil {

View File

@ -1,3 +1,9 @@
/*
*
* Utility Functions And Stuff
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import ( import (

View File

@ -1,5 +1,11 @@
// +build !no_ws // +build !no_ws
/*
*
* Gosora WebSocket Subsystem
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import ( import (
@ -17,44 +23,46 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
type WS_User struct { type WSUser struct {
conn *websocket.Conn conn *websocket.Conn
User *User User *User
} }
type WS_Hub struct { type WSHub struct {
onlineUsers map[int]*WS_User onlineUsers map[int]*WSUser
onlineGuests map[*WS_User]bool onlineGuests map[*WSUser]bool
guests sync.RWMutex guests sync.RWMutex
users sync.RWMutex users sync.RWMutex
} }
var wsHub WS_Hub // TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it?
var enableWebsockets = true // Put this in caps for consistency with the other constants?
var wsHub WSHub
var wsUpgrader = websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024} var wsUpgrader = websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}
var errWsNouser = errors.New("This user isn't connected via WebSockets") var errWsNouser = errors.New("This user isn't connected via WebSockets")
func init() { func init() {
enableWebsockets = true adminStatsWatchers = make(map[*WSUser]bool)
adminStatsWatchers = make(map[*WS_User]bool) wsHub = WSHub{
wsHub = WS_Hub{ onlineUsers: make(map[int]*WSUser),
onlineUsers: make(map[int]*WS_User), onlineGuests: make(map[*WSUser]bool),
onlineGuests: make(map[*WS_User]bool),
} }
} }
func (hub *WS_Hub) guestCount() int { func (hub *WSHub) guestCount() int {
defer hub.guests.RUnlock() defer hub.guests.RUnlock()
hub.guests.RLock() hub.guests.RLock()
return len(hub.onlineGuests) return len(hub.onlineGuests)
} }
func (hub *WS_Hub) userCount() int { func (hub *WSHub) userCount() int {
defer hub.users.RUnlock() defer hub.users.RUnlock()
hub.users.RLock() hub.users.RLock()
return len(hub.onlineUsers) return len(hub.onlineUsers)
} }
func (hub *WS_Hub) broadcastMessage(msg string) error { func (hub *WSHub) broadcastMessage(msg string) error {
hub.users.RLock() hub.users.RLock()
for _, wsUser := range hub.onlineUsers { for _, wsUser := range hub.onlineUsers {
w, err := wsUser.conn.NextWriter(websocket.TextMessage) w, err := wsUser.conn.NextWriter(websocket.TextMessage)
@ -67,7 +75,7 @@ func (hub *WS_Hub) broadcastMessage(msg string) error {
return nil return nil
} }
func (hub *WS_Hub) pushMessage(targetUser int, msg string) error { func (hub *WSHub) pushMessage(targetUser int, msg string) error {
hub.users.RLock() hub.users.RLock()
wsUser, ok := hub.onlineUsers[targetUser] wsUser, ok := hub.onlineUsers[targetUser]
hub.users.RUnlock() hub.users.RUnlock()
@ -85,7 +93,7 @@ func (hub *WS_Hub) pushMessage(targetUser int, msg string) error {
return nil return nil
} }
func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error { func (hub *WSHub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
//log.Print("In pushAlert") //log.Print("In pushAlert")
hub.users.RLock() hub.users.RLock()
wsUser, ok := hub.onlineUsers[targetUser] wsUser, ok := hub.onlineUsers[targetUser]
@ -111,9 +119,9 @@ func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType
return nil return nil
} }
func (hub *WS_Hub) pushAlerts(users []int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error { func (hub *WSHub) pushAlerts(users []int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
//log.Print("In pushAlerts") //log.Print("In pushAlerts")
var wsUsers []*WS_User var wsUsers []*WSUser
hub.users.RLock() hub.users.RLock()
// We don't want to keep a lock on this for too long, so we'll accept some nil pointers // We don't want to keep a lock on this for too long, so we'll accept some nil pointers
for _, uid := range users { for _, uid := range users {
@ -165,7 +173,7 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
wsUser := &WS_User{conn, userptr} wsUser := &WSUser{conn, userptr}
if user.ID == 0 { if user.ID == 0 {
wsHub.guests.Lock() wsHub.guests.Lock()
wsHub.onlineGuests[wsUser] = true wsHub.onlineGuests[wsUser] = true
@ -224,7 +232,7 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user User) {
conn.Close() conn.Close()
} }
func wsPageResponses(wsUser *WS_User, page []byte) { func wsPageResponses(wsUser *WSUser, page []byte) {
switch string(page) { switch string(page) {
case "/panel/": case "/panel/":
//log.Print("/panel/ WS Route") //log.Print("/panel/ WS Route")
@ -255,7 +263,7 @@ func wsPageResponses(wsUser *WS_User, page []byte) {
} }
} }
func wsLeavePage(wsUser *WS_User, page []byte) { func wsLeavePage(wsUser *WSUser, page []byte) {
switch string(page) { switch string(page) {
case "/panel/": case "/panel/":
adminStatsMutex.Lock() adminStatsMutex.Lock()
@ -264,7 +272,7 @@ func wsLeavePage(wsUser *WS_User, page []byte) {
} }
} }
var adminStatsWatchers map[*WS_User]bool var adminStatsWatchers map[*WSUser]bool
var adminStatsMutex sync.RWMutex var adminStatsMutex sync.RWMutex
func adminStatsTicker() { func adminStatsTicker() {

45
word_filters.go Normal file
View File

@ -0,0 +1,45 @@
package main
import "sync/atomic"
type WordFilter struct {
ID int
Find string
Replacement string
}
type WordFilterBox map[int]WordFilter
var wordFilterBox atomic.Value // An atomic value holding a WordFilterBox
func init() {
wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter)))
}
func LoadWordFilters() error {
rows, err := get_word_filters_stmt.Query()
if err != nil {
return err
}
defer rows.Close()
var wordFilters = WordFilterBox(make(map[int]WordFilter))
var wfid int
var find string
var replacement string
for rows.Next() {
err := rows.Scan(&wfid, &find, &replacement)
if err != nil {
return err
}
wordFilters[wfid] = WordFilter{ID: wfid, Find: find, Replacement: replacement}
}
wordFilterBox.Store(wordFilters)
return rows.Err()
}
func addWordFilter(id int, find string, replacement string) {
wordFilters := wordFilterBox.Load().(WordFilterBox)
wordFilters[id] = WordFilter{ID: id, Find: find, Replacement: replacement}
wordFilterBox.Store(wordFilters)
}