Refactored the code to follow best standards better.

Added Basic IP Search.
Added more items to .gitignore
Add a VSCode settings file.
Refactored the theme system to allow me to add a per-user theme switcher in the following commit.
Fixed h1s, rowmenus, profiles and the Control Panel Dashboard on Tempra Simple.
We now catch more extreme edge case errors.
Renamed route_panel_themes_default to route_panel_themes_set_default.
Centralised some of the per-route ExtData fields.
Added an ExtData field to headerLite.
Moved SettingLabels into the Phrase System.
This commit is contained in:
Azareal 2017-09-03 05:50:31 +01:00
parent 99da1fcbaa
commit ba36814d8d
77 changed files with 7264 additions and 6992 deletions

4
.gitignore vendored
View File

@ -1,6 +1,9 @@
tmp/* tmp/*
tmp2/* tmp2/*
cert_test/* cert_test/*
tmp.txt
run_notemplategen.bat
brun.bat
uploads/avatar_* uploads/avatar_*
uploads/socialgroup_* uploads/socialgroup_*
@ -10,3 +13,4 @@ bin/*
*.prof *.prof
*.log *.log
.DS_Store .DS_Store
.vscode/launch.json

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
// Place your settings in this file to overwrite default and user settings.
{
"editor.insertSpaces": false
}

145
alerts.go
View File

@ -1,3 +1,9 @@
/*
*
* Gosora Alerts System
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import "log" import "log"
@ -20,10 +26,10 @@ import "errors"
"{x}{created a new topic}{topic}" "{x}{created a new topic}{topic}"
*/ */
func build_alert(asid int, event string, elementType string, actor_id int, targetUser_id int, elementID int, user User /* The current user */) (string, error) { func buildAlert(asid int, event string, elementType string, actorID int, targetUserID int, elementID int, user User /* The current user */) (string, error) {
var targetUser *User var targetUser *User
actor, err := users.CascadeGet(actor_id) actor, err := users.CascadeGet(actorID)
if err != nil { if err != nil {
return "", errors.New("Unable to find the actor") return "", errors.New("Unable to find the actor")
} }
@ -37,84 +43,85 @@ func build_alert(asid int, event string, elementType string, actor_id int, targe
}*/ }*/
if event == "friend_invite" { if event == "friend_invite" {
return `{"msg":"You received a friend invite from {0}","sub":["` + actor.Name + `"],"path":"`+actor.Link+`","avatar":"`+strings.Replace(actor.Avatar,"/","\\/",-1)+`","asid":"`+strconv.Itoa(asid)+`"}`, nil return `{"msg":"You received a friend invite from {0}","sub":["` + actor.Name + `"],"path":"` + actor.Link + `","avatar":"` + strings.Replace(actor.Avatar, "/", "\\/", -1) + `","asid":"` + strconv.Itoa(asid) + `"}`, nil
} }
var act, post_act, url, area string var act, postAct, url, area string
var start_frag, end_frag string var startFrag, endFrag string
switch(elementType) { switch elementType {
case "forum": case "forum":
if event == "reply" { if event == "reply" {
act = "created a new topic" act = "created a new topic"
topic, err := topics.CascadeGet(elementID)
if err != nil {
return "", errors.New("Unable to find the linked topic")
}
url = topic.Link
area = topic.Title
// Store the forum ID in the targetUser column instead of making a new one? o.O
// Add an additional column for extra information later on when we add the ability to link directly to posts. We don't need the forum data for now...
} else {
act = "did something in a forum"
}
case "topic":
topic, err := topics.CascadeGet(elementID) topic, err := topics.CascadeGet(elementID)
if err != nil { if err != nil {
return "", errors.New("Unable to find the linked topic") return "", errors.New("Unable to find the linked topic")
} }
url = topic.Link url = topic.Link
area = topic.Title area = topic.Title
// Store the forum ID in the targetUser column instead of making a new one? o.O
// Add an additional column for extra information later on when we add the ability to link directly to posts. We don't need the forum data for now...
} else {
act = "did something in a forum"
}
case "topic":
topic, err := topics.CascadeGet(elementID)
if err != nil {
return "", errors.New("Unable to find the linked topic")
}
url = topic.Link
area = topic.Title
if targetUser_id == user.ID { if targetUserID == user.ID {
post_act = " your topic" postAct = " your topic"
} }
case "user": case "user":
targetUser, err = users.CascadeGet(elementID) targetUser, err = users.CascadeGet(elementID)
if err != nil { if err != nil {
return "", errors.New("Unable to find the target user") return "", errors.New("Unable to find the target user")
} }
area = targetUser.Name area = targetUser.Name
end_frag = "'s profile" endFrag = "'s profile"
url = targetUser.Link url = targetUser.Link
case "post": case "post":
topic, err := get_topic_by_reply(elementID) topic, err := getTopicByReply(elementID)
if err != nil { if err != nil {
return "", errors.New("Unable to find the linked reply or parent topic") return "", errors.New("Unable to find the linked reply or parent topic")
} }
url = topic.Link url = topic.Link
area = topic.Title area = topic.Title
if targetUser_id == user.ID { if targetUserID == user.ID {
post_act = " your post in" postAct = " your post in"
} }
default: default:
return "", errors.New("Invalid elementType") return "", errors.New("Invalid elementType")
} }
switch(event) { switch event {
case "like": case "like":
if elementType == "user" { if elementType == "user" {
act = "likes" act = "likes"
end_frag = "" endFrag = ""
if targetUser.ID == user.ID { if targetUser.ID == user.ID {
area = "you" area = "you"
}
} else {
act = "liked"
} }
case "mention": } else {
if elementType == "user" { act = "liked"
act = "mentioned you on" }
} else { case "mention":
act = "mentioned you in" if elementType == "user" {
post_act = "" act = "mentioned you on"
} } else {
case "reply": act = "replied to" act = "mentioned you in"
postAct = ""
}
case "reply":
act = "replied to"
} }
return `{"msg":"{0} ` + start_frag + act + post_act + ` {1}` + end_frag + `","sub":["` + actor.Name + `","` + area + `"],"path":"` + url + `","avatar":"` + actor.Avatar + `","asid":"`+strconv.Itoa(asid)+`"}`, nil return `{"msg":"{0} ` + startFrag + act + postAct + ` {1}` + endFrag + `","sub":["` + actor.Name + `","` + area + `"],"path":"` + url + `","avatar":"` + actor.Avatar + `","asid":"` + strconv.Itoa(asid) + `"}`, nil
} }
func notify_watchers(asid int64) { func notifyWatchers(asid int64) {
rows, err := get_watchers_stmt.Query(asid) rows, err := get_watchers_stmt.Query(asid)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
log.Fatal(err.Error()) log.Fatal(err.Error())
@ -129,22 +136,22 @@ func notify_watchers(asid int64) {
log.Fatal(err.Error()) log.Fatal(err.Error())
return return
} }
uids = append(uids,uid) uids = append(uids, uid)
} }
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
return return
} }
rows.Close() _ = rows.Close()
var actor_id, targetUser_id, elementID int var actorID, targetUserID, elementID int
var event, elementType string var event, elementType string
err = get_activity_entry_stmt.QueryRow(asid).Scan(&actor_id, &targetUser_id, &event, &elementType, &elementID) err = get_activity_entry_stmt.QueryRow(asid).Scan(&actorID, &targetUserID, &event, &elementType, &elementID)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
log.Fatal(err.Error()) log.Fatal(err.Error())
return return
} }
_ = ws_hub.push_alerts(uids, int(asid), event, elementType, actor_id, targetUser_id, elementID) _ = wsHub.pushAlerts(uids, int(asid), event, elementType, actorID, targetUserID, elementID)
} }

83
auth.go
View File

@ -1,4 +1,9 @@
/* Work in progress */ /*
*
* Gosora Authentication Interface
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import "log" import "log"
@ -11,11 +16,15 @@ import "./query_gen/lib"
import "golang.org/x/crypto/bcrypt" import "golang.org/x/crypto/bcrypt"
var auth Auth var auth Auth
var ErrMismatchedHashAndPassword = bcrypt.ErrMismatchedHashAndPassword
var ErrPasswordTooLong = errors.New("The password you selected is too long") // Silly, but we don't want bcrypt to bork on us
type Auth interface // ErrMismatchedHashAndPassword is thrown whenever a hash doesn't match it's unhashed password
{ var ErrMismatchedHashAndPassword = bcrypt.ErrMismatchedHashAndPassword
// ErrPasswordTooLong is silly, but we don't want bcrypt to bork on us
var ErrPasswordTooLong = errors.New("The password you selected is too long")
// Auth is the main authentication interface.
type Auth interface {
Authenticate(username string, password string) (uid int, err error) Authenticate(username string, password string) (uid int, err error)
Logout(w http.ResponseWriter, uid int) Logout(w http.ResponseWriter, uid int)
ForceLogout(uid int) error ForceLogout(uid int) error
@ -25,82 +34,86 @@ type Auth interface
CreateSession(uid int) (session string, err error) CreateSession(uid int) (session string, err error)
} }
type DefaultAuth struct // DefaultAuth is the default authenticator used by Gosora, may be swapped with an alternate authenticator in some situations. E.g. To support LDAP.
{ type DefaultAuth struct {
login *sql.Stmt login *sql.Stmt
logout *sql.Stmt logout *sql.Stmt
} }
// NewDefaultAuth is a factory for spitting out DefaultAuths
func NewDefaultAuth() *DefaultAuth { func NewDefaultAuth() *DefaultAuth {
login_stmt, err := qgen.Builder.SimpleSelect("users","uid, password, salt","name = ?","","") loginStmt, err := qgen.Builder.SimpleSelect("users", "uid, password, salt", "name = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
logout_stmt, err := qgen.Builder.SimpleUpdate("users","session = ''","uid = ?") logoutStmt, err := qgen.Builder.SimpleUpdate("users", "session = ''", "uid = ?")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &DefaultAuth{ return &DefaultAuth{
login: login_stmt, login: loginStmt,
logout: logout_stmt, logout: logoutStmt,
} }
} }
// Authenticate checks if a specific username and password is valid and returns the UID for the corresponding user, if so. Otherwise, a user safe error.
func (auth *DefaultAuth) Authenticate(username string, password string) (uid int, err error) { func (auth *DefaultAuth) Authenticate(username string, password string) (uid int, err error) {
var real_password, salt string var realPassword, salt string
err = auth.login.QueryRow(username).Scan(&uid, &real_password, &salt) err = auth.login.QueryRow(username).Scan(&uid, &realPassword, &salt)
if err == ErrNoRows { if err == ErrNoRows {
return 0, errors.New("We couldn't find an account with that username.") return 0, errors.New("We couldn't find an account with that username.") // nolint
} else if err != nil { } else if err != nil {
LogError(err) LogError(err)
return 0, errors.New("There was a glitch in the system. Please contact the system administrator.") return 0, errors.New("There was a glitch in the system. Please contact your local administrator.") // nolint
} }
if salt == "" { if salt == "" {
// Send an email to admin for this? // Send an email to admin for this?
LogError(errors.New("Missing salt for user #" + strconv.Itoa(uid) + ". Potential security breach.")) LogError(errors.New("Missing salt for user #" + strconv.Itoa(uid) + ". Potential security breach."))
return 0, errors.New("There was a glitch in the system. Please contact the system administrator.") return 0, errors.New("There was a glitch in the system. Please contact your local administrator")
} }
err = CheckPassword(real_password,password,salt) err = CheckPassword(realPassword, password, salt)
if err == ErrMismatchedHashAndPassword { if err == ErrMismatchedHashAndPassword {
return 0, errors.New("That's not the correct password.") return 0, errors.New("That's not the correct password.")
} else if err != nil { } else if err != nil {
LogError(err) LogError(err)
return 0, errors.New("There was a glitch in the system. Please contact the system administrator.") return 0, errors.New("There was a glitch in the system. Please contact your local administrator.")
} }
return uid, nil return uid, nil
} }
// ForceLogout logs the user out of every computer, not just the one they logged out of
func (auth *DefaultAuth) ForceLogout(uid int) error { func (auth *DefaultAuth) ForceLogout(uid int) error {
_, err := auth.logout.Exec(uid) _, err := auth.logout.Exec(uid)
if err != nil { if err != nil {
LogError(err) LogError(err)
return errors.New("There was a glitch in the system. Please contact the system administrator.") return errors.New("There was a glitch in the system. Please contact your local administrator.")
} }
// Flush the user out of the cache and reload // Flush the user out of the cache and reload
err = users.Load(uid) err = users.Load(uid)
if err != nil { if err != nil {
return errors.New("Your account no longer exists!") return errors.New("Your account no longer exists.")
} }
return nil return nil
} }
// Logout logs you out of the computer you requested the logout for, but not the other computers you're logged in with
func (auth *DefaultAuth) Logout(w http.ResponseWriter, _ int) { func (auth *DefaultAuth) Logout(w http.ResponseWriter, _ int) {
cookie := http.Cookie{Name:"uid",Value:"",Path:"/",MaxAge: year} cookie := http.Cookie{Name: "uid", Value: "", Path: "/", MaxAge: year}
http.SetCookie(w,&cookie) http.SetCookie(w, &cookie)
cookie = http.Cookie{Name:"session",Value:"",Path:"/",MaxAge: year} cookie = http.Cookie{Name: "session", Value: "", Path: "/", MaxAge: year}
http.SetCookie(w,&cookie) http.SetCookie(w, &cookie)
} }
func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session string) { func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session string) {
cookie := http.Cookie{Name: "uid",Value: strconv.Itoa(uid),Path: "/",MaxAge: year} cookie := http.Cookie{Name: "uid", Value: strconv.Itoa(uid), Path: "/", MaxAge: year}
http.SetCookie(w,&cookie) http.SetCookie(w, &cookie)
cookie = http.Cookie{Name: "session",Value: session,Path: "/",MaxAge: year} cookie = http.Cookie{Name: "session", Value: session, Path: "/", MaxAge: year}
http.SetCookie(w,&cookie) http.SetCookie(w, &cookie)
} }
func (auth *DefaultAuth) GetCookies(r *http.Request) (uid int, session string, err error) { func (auth *DefaultAuth) GetCookies(r *http.Request) (uid int, session string, err error) {
@ -123,26 +136,26 @@ func (auth *DefaultAuth) GetCookies(r *http.Request) (uid int, session string, e
func (auth *DefaultAuth) SessionCheck(w http.ResponseWriter, r *http.Request) (user *User, halt bool) { func (auth *DefaultAuth) SessionCheck(w http.ResponseWriter, r *http.Request) (user *User, halt bool) {
uid, session, err := auth.GetCookies(r) uid, session, err := auth.GetCookies(r)
if err != nil { if err != nil {
return &guest_user, false return &guestUser, false
} }
// Is this session valid..? // Is this session valid..?
user, err = users.CascadeGet(uid) user, err = users.CascadeGet(uid)
if err == ErrNoRows { if err == ErrNoRows {
return &guest_user, false return &guestUser, false
} else if err != nil { } else if err != nil {
InternalError(err,w) InternalError(err, w)
return &guest_user, true return &guestUser, true
} }
if user.Session == "" || session != user.Session { if user.Session == "" || session != user.Session {
return &guest_user, false return &guestUser, false
} }
return user, false return user, false
} }
func(auth *DefaultAuth) CreateSession(uid int) (session string, err error) { func (auth *DefaultAuth) CreateSession(uid int) (session string, err error) {
session, err = GenerateSafeString(sessionLength) session, err = GenerateSafeString(sessionLength)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -1,13 +1,20 @@
package main package main
import "errors" import "errors"
// Go away, linter. We need to differentiate constants from variables somehow ;)
// nolint
const CACHE_STATIC int = 0 const CACHE_STATIC int = 0
const CACHE_DYNAMIC int = 1 const CACHE_DYNAMIC int = 1
const CACHE_SQL int = 2 const CACHE_SQL int = 2
var ErrCacheDesync = errors.New("The cache is out of synchronisation with the database.") // TO-DO: A cross-server synchronisation mechanism // ErrCacheDesync is thrown whenever a piece of data, for instance, a user is out of sync with the database. Currently unused.
var ErrStoreCapacityOverflow = errors.New("This datastore has already reached it's max capacity") var ErrCacheDesync = errors.New("The cache is out of sync with the database.") // TO-DO: A cross-server synchronisation mechanism
// ErrStoreCapacityOverflow is thrown whenever a datastore reaches it's maximum hard capacity. I'm not sure *if* this one is used, at the moment. Probably.
var ErrStoreCapacityOverflow = errors.New("This datastore has reached it's maximum capacity.")
// nolint
type DataStore interface { type DataStore interface {
Load(id int) error Load(id int) error
Get(id int) (interface{}, error) Get(id int) (interface{}, error)

View File

@ -5,20 +5,21 @@ import "encoding/json"
import "database/sql" import "database/sql"
var db *sql.DB var db *sql.DB
var db_version string var dbVersion string
var db_adapter string var dbAdapter string
// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores
var ErrNoRows = sql.ErrNoRows var ErrNoRows = sql.ErrNoRows
func init_database() (err error) { func initDatabase() (err error) {
// Engine specific code // Engine specific code
err = _init_database() err = _initDatabase()
if err != nil { if err != nil {
return err return err
} }
log.Print("Loading the usergroups.") log.Print("Loading the usergroups.")
groups = append(groups, Group{ID:0,Name:"System"}) groups = append(groups, Group{ID: 0, Name: "System"})
rows, err := get_groups_stmt.Query() rows, err := get_groups_stmt.Query()
if err != nil { if err != nil {
@ -27,9 +28,9 @@ func init_database() (err error) {
defer rows.Close() defer rows.Close()
i := 1 i := 1
for ;rows.Next();i++ { for ; rows.Next(); i++ {
group := Group{ID: 0,} group := Group{ID: 0}
err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.PluginPermsText, &group.Is_Mod, &group.Is_Admin, &group.Is_Banned, &group.Tag) err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.PluginPermsText, &group.IsMod, &group.IsAdmin, &group.IsBanned, &group.Tag)
if err != nil { if err != nil {
return err return err
} }
@ -37,7 +38,7 @@ func init_database() (err error) {
// Ugh, you really shouldn't physically delete these items, it makes a big mess of things // Ugh, you really shouldn't physically delete these items, it makes a big mess of things
if group.ID != i { if group.ID != i {
log.Print("Stop physically deleting groups. You are messing up the IDs. Use the Group Manager or delete_group() instead x.x") log.Print("Stop physically deleting groups. You are messing up the IDs. Use the Group Manager or delete_group() instead x.x")
fill_group_id_gap(i, group.ID) fillGroupIDGap(i, group.ID)
} }
err = json.Unmarshal(group.PermissionsText, &group.Perms) err = json.Unmarshal(group.PermissionsText, &group.Perms)
@ -49,7 +50,7 @@ func init_database() (err error) {
log.Printf("%+v\n", group.Perms) log.Printf("%+v\n", group.Perms)
} }
err = json.Unmarshal(group.PluginPermsText, &group.PluginPerms) err = json.Unmarshal(group.PluginPermsText, &group.PluginPerms)
if err != nil { if err != nil {
return err return err
} }
@ -71,19 +72,18 @@ func init_database() (err error) {
GuestPerms = groups[6].Perms GuestPerms = groups[6].Perms
log.Print("Loading the forums.") log.Print("Loading the forums.")
fstore = NewStaticForumStore() fstore = NewStaticForumStore()
err = fstore.LoadForums() err = fstore.LoadForums()
if err != nil { if err != nil {
return err return err
} }
log.Print("Loading the forum permissions.") log.Print("Loading the forum permissions.")
err = build_forum_permissions() err = buildForumPermissions()
if err != nil { if err != nil {
return err return err
} }
log.Print("Loading the settings.") log.Print("Loading the settings.")
err = LoadSettings() err = LoadSettings()
if err != nil { if err != nil {
@ -97,10 +97,5 @@ func init_database() (err error) {
} }
log.Print("Loading the themes.") log.Print("Loading the themes.")
err = LoadThemes() return LoadThemes()
if err != nil {
return err
}
return nil
} }

284
errors.go
View File

@ -8,283 +8,331 @@ import "net/http"
import "runtime/debug" import "runtime/debug"
// TO-DO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page? // TO-DO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page?
var error_buffer_mutex sync.RWMutex var errorBufferMutex sync.RWMutex
var error_buffer []error var errorBuffer []error
//var notfound_count_per_second int //var notfound_count_per_second int
//var noperms_count_per_second int //var noperms_count_per_second int
var error_internal []byte var errorInternal []byte
var error_notfound []byte var errorNotfound []byte
func init_errors() error { func initErrors() error {
var b bytes.Buffer var b bytes.Buffer
user := User{0,"guest","Guest","",0,false,false,false,false,false,false,GuestPerms,nil,"",false,"","","","","",0,0,"0.0.0.0.0",0} user := User{0, "guest", "Guest", "", 0, false, false, false, false, false, false, GuestPerms, nil, "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0}
pi := Page{"Internal Server Error",user,hvars,tList,"A problem has occurred in the system."} pi := Page{"Internal Server Error", user, hvars, tList, "A problem has occurred in the system."}
err := templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil { if err != nil {
return err return err
} }
error_internal = b.Bytes() errorInternal = b.Bytes()
b.Reset() b.Reset()
pi = Page{"Not Found",user,hvars,tList,"The requested page doesn't exist."} pi = Page{"Not Found", user, hvars, tList, "The requested page doesn't exist."}
err = templates.ExecuteTemplate(&b,"error.html", pi) err = templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil { if err != nil {
return err return err
} }
error_notfound = b.Bytes() errorNotfound = b.Bytes()
return nil return nil
} }
// LogError logs internal handler errors which can't be handled with InternalError() as a wrapper for log.Fatal(), we might do more with it in the future
func LogError(err error) { func LogError(err error) {
log.Print(err) log.Print(err)
debug.PrintStack() debug.PrintStack()
error_buffer_mutex.Lock() errorBufferMutex.Lock()
defer error_buffer_mutex.Unlock() defer errorBufferMutex.Unlock()
error_buffer = append(error_buffer,err) errorBuffer = append(errorBuffer, err)
log.Fatal("") log.Fatal("")
} }
// InternalError is the main function for handling internal errors, while simultaneously printing out a page for the end-user to let them know that *something* has gone wrong
func InternalError(err error, w http.ResponseWriter) { func InternalError(err error, w http.ResponseWriter) {
w.Write(error_internal) _, _ = w.Write(errorInternal)
log.Print(err) log.Print(err)
debug.PrintStack() debug.PrintStack()
error_buffer_mutex.Lock() errorBufferMutex.Lock()
defer error_buffer_mutex.Unlock() defer errorBufferMutex.Unlock()
error_buffer = append(error_buffer,err) errorBuffer = append(errorBuffer, err)
log.Fatal("") log.Fatal("")
} }
func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, is_js string) { // InternalErrorJSQ is the JSON "maybe" version of InternalError which can handle both JSON and normal requests
func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, isJs bool) {
w.WriteHeader(500) w.WriteHeader(500)
if is_js == "0" { if !isJs {
w.Write(error_internal) _, _ = w.Write(errorInternal)
} else { } else {
w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`)) _, _ = w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`))
} }
log.Print(err) log.Print(err)
debug.PrintStack() debug.PrintStack()
error_buffer_mutex.Lock() errorBufferMutex.Lock()
defer error_buffer_mutex.Unlock() defer errorBufferMutex.Unlock()
error_buffer = append(error_buffer,err) errorBuffer = append(errorBuffer, err)
log.Fatal("") log.Fatal("")
} }
// InternalErrorJS is the JSON version of InternalError on routes we know will only be requested via JSON. E.g. An API.
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) { func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`)) _, _ = w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`))
error_buffer_mutex.Lock() errorBufferMutex.Lock()
defer error_buffer_mutex.Unlock() defer errorBufferMutex.Unlock()
error_buffer = append(error_buffer,err) errorBuffer = append(errorBuffer, err)
log.Fatal(err) log.Fatal(err)
} }
func PreError(errmsg string, w http.ResponseWriter, r *http.Request) { func PreError(errmsg string, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
user := User{ID:0,Group:6,Perms:GuestPerms,} user := User{ID: 0, Group: 6, Perms: GuestPerms}
pi := Page{"Error",user,hvars,tList,errmsg} pi := Page{"Error", user, hvars, tList, errmsg}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html",pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} }
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) { func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(500) w.WriteHeader(500)
pi := Page{"Local Error",user,hvars,tList,errmsg} pi := Page{"Local Error", user, hvars, tList, errmsg}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html",pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} }
func LoginRequired(w http.ResponseWriter, r *http.Request, user User) { func LoginRequired(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(401) w.WriteHeader(401)
pi := Page{"Local Error",user,hvars,tList,"You need to login to do that."} pi := Page{"Local Error", user, hvars, tList, "You need to login to do that."}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html",pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} }
func PreErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) { func PreErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
w.Write([]byte(`{"errmsg":"` + errmsg + `"}`)) _, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
} }
func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, is_js string) { func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs bool) {
w.WriteHeader(500) w.WriteHeader(500)
if is_js == "0" { if !isJs {
user := User{ID:0,Group:6,Perms:GuestPerms,} user := User{ID: 0, Group: 6, Perms: GuestPerms}
pi := Page{"Local Error",user,hvars,tList,errmsg} pi := Page{"Local Error", user, hvars, tList, errmsg}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} else { } else {
w.Write([]byte(`{"errmsg":"` + errmsg + `"}`)) _, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
} }
} }
func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user User, is_js string) { func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(500) w.WriteHeader(500)
if is_js == "0" { if !isJs {
pi := Page{"Local Error",user,hvars,tList,errmsg} pi := Page{"Local Error", user, hvars, tList, errmsg}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} else { } else {
w.Write([]byte(`{"errmsg":"` + errmsg + `"}`)) _, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
} }
} }
func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) { func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
w.Write([]byte(`{'errmsg': '` + errmsg + `'}`)) _, _ = w.Write([]byte(`{'errmsg': '` + errmsg + `'}`))
} }
func NoPermissions(w http.ResponseWriter, r *http.Request, user User) { func NoPermissions(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403) w.WriteHeader(403)
pi := Page{"Local Error",user,hvars,tList,"You don't have permission to do that."} pi := Page{"Local Error", user, hvars, tList, "You don't have permission to do that."}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
if err != nil {
LogError(err)
}
errpage := b.String() errpage := b.String()
fmt.Fprintln(w,errpage) fmt.Fprintln(w, errpage)
} }
func NoPermissionsJSQ(w http.ResponseWriter, r *http.Request, user User, is_js string) { func NoPermissionsJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(403) w.WriteHeader(403)
if is_js == "0" { if !isJs {
pi := Page{"Local Error",user,hvars,tList,"You don't have permission to do that."} pi := Page{"Local Error", user, hvars, tList, "You don't have permission to do that."}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} else { } else {
w.Write([]byte(`{"errmsg":"You don't have permission to do that."}`)) _, _ = w.Write([]byte(`{"errmsg":"You don't have permission to do that."}`))
} }
} }
func Banned(w http.ResponseWriter, r *http.Request, user User) { func Banned(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403) w.WriteHeader(403)
pi := Page{"Banned",user,hvars,tList,"You have been banned from this site."} pi := Page{"Banned", user, hvars, tList, "You have been banned from this site."}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} }
func BannedJSQ(w http.ResponseWriter, r *http.Request, user User, is_js string) { // nolint
func BannedJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(403) w.WriteHeader(403)
if is_js == "0" { if !isJs {
pi := Page{"Banned",user,hvars,tList,"You have been banned from this site."} pi := Page{"Banned", user, hvars, tList, "You have been banned from this site."}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} else { } else {
w.Write([]byte(`{"errmsg":"You have been banned from this site."}`)) _, _ = w.Write([]byte(`{"errmsg":"You have been banned from this site."}`))
} }
} }
func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, is_js string) { // nolint
func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(401) w.WriteHeader(401)
if is_js == "0" { if !isJs {
pi := Page{"Local Error",user,hvars,tList,"You need to login to do that."} pi := Page{"Local Error", user, hvars, tList, "You need to login to do that."}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} else { } else {
w.Write([]byte(`{"errmsg":"You need to login to do that."}`)) _, _ = w.Write([]byte(`{"errmsg":"You need to login to do that."}`))
} }
} }
func SecurityError(w http.ResponseWriter, r *http.Request, user User) { func SecurityError(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403) w.WriteHeader(403)
pi := Page{"Security Error",user,hvars,tList,"There was a security issue with your request."} pi := Page{"Security Error", user, hvars, tList, "There was a security issue with your request."}
if pre_render_hooks["pre_render_security_error"] != nil { if preRenderHooks["pre_render_security_error"] != nil {
if run_pre_render_hook("pre_render_security_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_security_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} }
func NotFound(w http.ResponseWriter, r *http.Request) { func NotFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404) w.WriteHeader(404)
w.Write(error_notfound) _, _ = w.Write(errorNotfound)
} }
// nolint
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) { func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(errcode) w.WriteHeader(errcode)
pi := Page{errtitle,user,hvars,tList,errmsg} pi := Page{errtitle, user, hvars, tList, errmsg}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} }
func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, is_js string) { // nolint
func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, isJs bool) {
w.WriteHeader(errcode) w.WriteHeader(errcode)
if is_js == "0" { if !isJs {
pi := Page{errtitle,user,hvars,tList,errmsg} pi := Page{errtitle, user, hvars, tList, errmsg}
if pre_render_hooks["pre_render_error"] != nil { if preRenderHooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) { if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return return
} }
} }
var b bytes.Buffer var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi) err := templates.ExecuteTemplate(&b, "error.html", pi)
fmt.Fprintln(w,b.String()) if err != nil {
LogError(err)
}
fmt.Fprintln(w, b.String())
} else { } else {
w.Write([]byte(`{"errmsg":"` + errmsg + `"}`)) _, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
} }
} }

View File

@ -0,0 +1,19 @@
package main
import "github.com/oschwald/geoip2-golang"
var geoip_db *geoip.DB
var geoip_db_location string = "geoip_db.mmdb"
func init() {
plugins["geoip"] = NewPlugin("geoip","Geoip","Azareal","http://github.com/Azareal","","","",init_geoip,nil,deactivate_geoip,nil,nil)
}
func init_geoip() (err error) {
geoip_db, err = geoip2.Open(geoip_db_location)
return err
}
func deactivate_geoip() {
geoip_db.Close()
}

309
extend.go
View File

@ -1,95 +1,101 @@
/* Copyright Azareal 2016 - 2018 */ /*
*
* Gosora Plugin System
* Copyright Azareal 2016 - 2018
*
*/
package main package main
import "log" import "log"
import "net/http" import "net/http"
var plugins map[string]*Plugin = make(map[string]*Plugin) var plugins = make(map[string]*Plugin)
// Hooks with a single argument. Is this redundant? Might be useful for inlining, as variadics aren't inlined? Are closures even inlined to begin with? // Hooks with a single argument. Is this redundant? Might be useful for inlining, as variadics aren't inlined? Are closures even inlined to begin with?
var hooks map[string][]func(interface{})interface{} = map[string][]func(interface{})interface{}{ var hooks = map[string][]func(interface{}) interface{}{
"forums_frow_assign": nil, "forums_frow_assign": nil,
"topic_create_frow_assign": nil, "topic_create_frow_assign": nil,
"rrow_assign": nil, // TO-DO: Rename this hook to topic_rrow_assign "rrow_assign": nil, // TO-DO: Rename this hook to topic_rrow_assign
} }
// Hooks with a variable number of arguments // Hooks with a variable number of arguments
var vhooks map[string]func(...interface{})interface{} = map[string]func(...interface{})interface{}{ var vhooks = map[string]func(...interface{}) interface{}{
"simple_forum_check_pre_perms": nil, "simple_forum_check_pre_perms": nil,
"forum_check_pre_perms": nil, "forum_check_pre_perms": nil,
"intercept_build_widgets": nil, "intercept_build_widgets": nil,
"forum_trow_assign": nil, "forum_trow_assign": nil,
"topics_topic_row_assign": nil, "topics_topic_row_assign": nil,
//"topics_user_row_assign": nil, //"topics_user_row_assign": nil,
"create_group_preappend": nil, // What is this? Investigate! "create_group_preappend": nil, // What is this? Investigate!
"topic_create_pre_loop": nil, "topic_create_pre_loop": nil,
} }
// Hooks which take in and spit out a string. This is usually used for parser components // Hooks which take in and spit out a string. This is usually used for parser components
var sshooks map[string][]func(string)string = map[string][]func(string)string{ var sshooks = map[string][]func(string) string{
"preparse_preassign": nil, "preparse_preassign": nil,
"parse_assign": nil, "parse_assign": nil,
} }
// The hooks which run before the template is rendered for a route // The hooks which run before the template is rendered for a route
var pre_render_hooks map[string][]func(http.ResponseWriter, *http.Request, *User, interface{})bool = map[string][]func(http.ResponseWriter, *http.Request, *User, interface{})bool{ var preRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User, interface{}) bool{
"pre_render": nil, "pre_render": nil,
"pre_render_forum_list": nil, "pre_render_forum_list": nil,
"pre_render_view_forum": nil, "pre_render_view_forum": nil,
"pre_render_topic_list": nil, "pre_render_topic_list": nil,
"pre_render_view_topic": nil, "pre_render_view_topic": nil,
"pre_render_profile": nil, "pre_render_profile": nil,
"pre_render_custom_page": nil, "pre_render_custom_page": nil,
"pre_render_overview": nil, "pre_render_overview": nil,
"pre_render_create_topic": nil, "pre_render_create_topic": nil,
"pre_render_account_own_edit_critical": nil, "pre_render_account_own_edit_critical": nil,
"pre_render_account_own_edit_avatar": nil, "pre_render_account_own_edit_avatar": nil,
"pre_render_account_own_edit_username": nil, "pre_render_account_own_edit_username": nil,
"pre_render_account_own_edit_email": nil, "pre_render_account_own_edit_email": nil,
"pre_render_login": nil, "pre_render_login": nil,
"pre_render_register": nil, "pre_render_register": nil,
"pre_render_ban": nil, "pre_render_ban": nil,
"pre_render_ips": nil,
"pre_render_panel_dashboard": nil, "pre_render_panel_dashboard": nil,
"pre_render_panel_forums": nil, "pre_render_panel_forums": nil,
"pre_render_panel_delete_forum": nil, "pre_render_panel_delete_forum": nil,
"pre_render_panel_edit_forum": nil, "pre_render_panel_edit_forum": nil,
"pre_render_panel_settings": nil, "pre_render_panel_settings": nil,
"pre_render_panel_setting": nil, "pre_render_panel_setting": nil,
"pre_render_panel_word_filters": nil, "pre_render_panel_word_filters": nil,
"pre_render_panel_word_filters_edit": nil, "pre_render_panel_word_filters_edit": nil,
"pre_render_panel_plugins": nil, "pre_render_panel_plugins": nil,
"pre_render_panel_users": nil, "pre_render_panel_users": nil,
"pre_render_panel_edit_user": nil, "pre_render_panel_edit_user": nil,
"pre_render_panel_groups": nil, "pre_render_panel_groups": nil,
"pre_render_panel_edit_group": nil, "pre_render_panel_edit_group": nil,
"pre_render_panel_edit_group_perms": nil, "pre_render_panel_edit_group_perms": nil,
"pre_render_panel_themes": nil, "pre_render_panel_themes": nil,
"pre_render_panel_mod_log": nil, "pre_render_panel_mod_log": nil,
"pre_render_error": nil, // Note: This hook isn't run for a few errors whose templates are computed at startup and reused, such as InternalError. This hook is also not available in JS mode. "pre_render_error": nil, // Note: This hook isn't run for a few errors whose templates are computed at startup and reused, such as InternalError. This hook is also not available in JS mode.
"pre_render_security_error": nil, "pre_render_security_error": nil,
} }
type Plugin struct type Plugin struct {
{ UName string
UName string Name string
Name string Author string
Author string URL string
URL string Settings string
Settings string Active bool
Active bool Tag string
Tag string Type string
Type string
Installable bool Installable bool
Installed bool Installed bool
Init func()error Init func() error
Activate func()error Activate func() error
Deactivate func() Deactivate func()
Install func()error Install func() error
Uninstall func()error Uninstall func() error
Hooks map[string]int Hooks map[string]int
} }
@ -119,114 +125,111 @@ func LoadPlugins() error {
plugin.Installed = installed plugin.Installed = installed
plugins[uname] = plugin plugins[uname] = plugin
} }
err = rows.Err() return rows.Err()
if err != nil {
return err
}
return nil
} }
func NewPlugin(uname string, name string, author string, url string, settings string, tag string, ptype string, init func()error, activate func()error, deactivate func(), install func()error, uninstall func()error) *Plugin { func NewPlugin(uname string, name string, author string, url string, settings string, tag string, ptype string, init func() error, activate func() error, deactivate func(), install func() error, uninstall func() error) *Plugin {
return &Plugin{ return &Plugin{
UName: uname, UName: uname,
Name: name, Name: name,
Author: author, Author: author,
URL: url, URL: url,
Settings: settings, Settings: settings,
Tag: tag, Tag: tag,
Type: ptype, Type: ptype,
Installable: (install != nil), Installable: (install != nil),
Init: init, Init: init,
Activate: activate, Activate: activate,
Deactivate: deactivate, Deactivate: deactivate,
Install: install, Install: install,
//Uninstall: uninstall, //Uninstall: uninstall,
/* /*
The Active field should never be altered by a plugin. It's used internally by the software to determine whether an admin has enabled a plugin or not and whether to run it. This will be overwritten by the user's preference. The Active field should never be altered by a plugin. It's used internally by the software to determine whether an admin has enabled a plugin or not and whether to run it. This will be overwritten by the user's preference.
*/ */
Active: false, Active: false,
Installed: false, Installed: false,
Hooks: make(map[string]int), Hooks: make(map[string]int),
} }
} }
func (plugin *Plugin) AddHook(name string, handler interface{}) { func (plugin *Plugin) AddHook(name string, handler interface{}) {
switch h := handler.(type) { switch h := handler.(type) {
case func(interface{})interface{}: case func(interface{}) interface{}:
if len(hooks[name]) == 0 { if len(hooks[name]) == 0 {
var hookSlice []func(interface{})interface{} var hookSlice []func(interface{}) interface{}
hookSlice = append(hookSlice, h) hookSlice = append(hookSlice, h)
hooks[name] = hookSlice hooks[name] = hookSlice
} else { } else {
hooks[name] = append(hooks[name], h) hooks[name] = append(hooks[name], h)
} }
plugin.Hooks[name] = len(hooks[name]) plugin.Hooks[name] = len(hooks[name])
case func(string)string: case func(string) string:
if len(sshooks[name]) == 0 { if len(sshooks[name]) == 0 {
var hookSlice []func(string)string var hookSlice []func(string) string
hookSlice = append(hookSlice, h) hookSlice = append(hookSlice, h)
sshooks[name] = hookSlice sshooks[name] = hookSlice
} else { } else {
sshooks[name] = append(sshooks[name], h) sshooks[name] = append(sshooks[name], h)
} }
plugin.Hooks[name] = len(sshooks[name]) plugin.Hooks[name] = len(sshooks[name])
case func(http.ResponseWriter, *http.Request, *User, interface{})bool: case func(http.ResponseWriter, *http.Request, *User, interface{}) bool:
if len(pre_render_hooks[name]) == 0 { if len(preRenderHooks[name]) == 0 {
var hookSlice []func(http.ResponseWriter, *http.Request, *User, interface{})bool var hookSlice []func(http.ResponseWriter, *http.Request, *User, interface{}) bool
hookSlice = append(hookSlice, h) hookSlice = append(hookSlice, h)
pre_render_hooks[name] = hookSlice preRenderHooks[name] = hookSlice
} else { } else {
pre_render_hooks[name] = append(pre_render_hooks[name], h) preRenderHooks[name] = append(preRenderHooks[name], h)
} }
plugin.Hooks[name] = len(pre_render_hooks[name]) plugin.Hooks[name] = len(preRenderHooks[name])
case func(...interface{}) interface{}: case func(...interface{}) interface{}:
vhooks[name] = h vhooks[name] = h
plugin.Hooks[name] = 0 plugin.Hooks[name] = 0
default: default:
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()? panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
} }
} }
func (plugin *Plugin) RemoveHook(name string, handler interface{}) { func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
switch handler.(type) { switch handler.(type) {
case func(interface{})interface{}: case func(interface{}) interface{}:
key := plugin.Hooks[name] key := plugin.Hooks[name]
hook := hooks[name] hook := hooks[name]
if len(hook) == 1 { if len(hook) == 1 {
hook = []func(interface{})interface{}{} hook = []func(interface{}) interface{}{}
} else { } else {
hook = append(hook[:key], hook[key + 1:]...) hook = append(hook[:key], hook[key+1:]...)
} }
hooks[name] = hook hooks[name] = hook
case func(string)string: case func(string) string:
key := plugin.Hooks[name] key := plugin.Hooks[name]
hook := sshooks[name] hook := sshooks[name]
if len(hook) == 1 { if len(hook) == 1 {
hook = []func(string)string{} hook = []func(string) string{}
} else { } else {
hook = append(hook[:key], hook[key + 1:]...) hook = append(hook[:key], hook[key+1:]...)
} }
sshooks[name] = hook sshooks[name] = hook
case func(http.ResponseWriter, *http.Request, *User, interface{})bool: case func(http.ResponseWriter, *http.Request, *User, interface{}) bool:
key := plugin.Hooks[name] key := plugin.Hooks[name]
hook := pre_render_hooks[name] hook := preRenderHooks[name]
if len(hook) == 1 { if len(hook) == 1 {
hook = []func(http.ResponseWriter, *http.Request, *User, interface{})bool{} hook = []func(http.ResponseWriter, *http.Request, *User, interface{}) bool{}
} else { } else {
hook = append(hook[:key], hook[key + 1:]...) hook = append(hook[:key], hook[key+1:]...)
} }
pre_render_hooks[name] = hook preRenderHooks[name] = hook
case func(...interface{}) interface{}: case func(...interface{}) interface{}:
delete(vhooks, name) delete(vhooks, name)
default: default:
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()? panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
} }
delete(plugin.Hooks, name) delete(plugin.Hooks, name)
} }
var plugins_inited bool = false var pluginsInited = false
func init_plugins() {
func initPlugins() {
for name, body := range plugins { for name, body := range plugins {
log.Print("Added plugin " + name) log.Print("Added plugin " + name)
if body.Active { if body.Active {
@ -241,49 +244,49 @@ func init_plugins() {
} }
} }
} }
plugins_inited = true pluginsInited = true
} }
func run_hook(name string, data interface{}) interface{} { func runHook(name string, data interface{}) interface{} {
for _, hook := range hooks[name] { for _, hook := range hooks[name] {
data = hook(data) data = hook(data)
} }
return data return data
} }
func run_hook_noreturn(name string, data interface{}) { func runHookNoreturn(name string, data interface{}) {
for _, hook := range hooks[name] { for _, hook := range hooks[name] {
_ = hook(data) _ = hook(data)
} }
} }
func run_vhook(name string, data ...interface{}) interface{} { func runVhook(name string, data ...interface{}) interface{} {
return vhooks[name](data...) return vhooks[name](data...)
} }
func run_vhook_noreturn(name string, data ...interface{}) { func runVhookNoreturn(name string, data ...interface{}) {
_ = vhooks[name](data...) _ = vhooks[name](data...)
} }
// Trying to get a teeny bit of type-safety where-ever possible, especially for such a critical set of hooks // Trying to get a teeny bit of type-safety where-ever possible, especially for such a critical set of hooks
func run_sshook(name string, data string) string { func runSshook(name string, data string) string {
for _, hook := range sshooks[name] { for _, hook := range sshooks[name] {
data = hook(data) data = hook(data)
} }
return data return data
} }
func run_pre_render_hook(name string, w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) { func runPreRenderHook(name string, w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
// This hook runs on ALL pre_render hooks // This hook runs on ALL pre_render hooks
for _, hook := range pre_render_hooks["pre_render"] { for _, hook := range preRenderHooks["pre_render"] {
if hook(w,r,user,data) { if hook(w, r, user, data) {
return true return true
} }
} }
// The actual pre_render hook // The actual pre_render hook
for _, hook := range pre_render_hooks[name] { for _, hook := range preRenderHooks[name] {
if hook(w,r,user,data) { if hook(w, r, user, data) {
return true return true
} }
} }

View File

@ -1,65 +1,59 @@
package main package main
import ( import (
"log"
"bytes" "bytes"
"strings" "log"
"mime" "mime"
"strings"
//"errors" //"errors"
"os"
"io/ioutil"
"path/filepath"
"net/http"
"compress/gzip" "compress/gzip"
"io/ioutil"
"net/http"
"os"
"path/filepath"
) )
type SFile struct type SFile struct {
{ Data []byte
Data []byte GzipData []byte
GzipData []byte Pos int64
Pos int64 Length int64
Length int64 GzipLength int64
GzipLength int64 Mimetype string
Mimetype string Info os.FileInfo
Info os.FileInfo
FormattedModTime string FormattedModTime string
} }
type CssData struct type CssData struct {
{
ComingSoon string ComingSoon string
} }
func init_static_files() { func initStaticFiles() error {
log.Print("Loading the static files.") return filepath.Walk("./public", func(path string, f os.FileInfo, err error) error {
err := filepath.Walk("./public", func(path string, f os.FileInfo, err error) error {
if f.IsDir() { if f.IsDir() {
return nil return nil
} }
path = strings.Replace(path,"\\","/",-1) path = strings.Replace(path, "\\", "/", -1)
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return err return err
} }
path = strings.TrimPrefix(path,"public/") path = strings.TrimPrefix(path, "public/")
var ext string = filepath.Ext("/public/" + path) var ext = filepath.Ext("/public/" + path)
gzip_data := compress_bytes_gzip(data) gzipData := compressBytesGzip(data)
static_files["/static/" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)} staticFiles["/static/"+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}
if dev.DebugMode { if dev.DebugMode {
log.Print("Added the '" + path + "' static file.") log.Print("Added the '" + path + "' static file.")
} }
return nil return nil
}) })
if err != nil {
log.Fatal(err)
}
} }
func add_static_file(path string, prefix string) error { func addStaticFile(path string, prefix string) error {
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return err return err
@ -73,11 +67,11 @@ func add_static_file(path string, prefix string) error {
return err return err
} }
var ext string = filepath.Ext(path) var ext = filepath.Ext(path)
path = strings.TrimPrefix(path, prefix) path = strings.TrimPrefix(path, prefix)
gzip_data := compress_bytes_gzip(data) gzipData := compressBytesGzip(data)
static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)} staticFiles["/static"+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}
if dev.DebugMode { if dev.DebugMode {
log.Print("Added the '" + path + "' static file") log.Print("Added the '" + path + "' static file")
@ -85,10 +79,10 @@ func add_static_file(path string, prefix string) error {
return nil return nil
} }
func compress_bytes_gzip(in []byte) []byte { func compressBytesGzip(in []byte) []byte {
var buff bytes.Buffer var buff bytes.Buffer
gz := gzip.NewWriter(&buff) gz := gzip.NewWriter(&buff)
gz.Write(in) _, _ = gz.Write(in) // TO-DO: What if this errors? What circumstances could it error under? Should we add a second return value?
gz.Close() _ = gz.Close()
return buff.Bytes() return buff.Bytes()
} }

View File

@ -4,51 +4,48 @@ package main
import "strconv" import "strconv"
import _ "github.com/go-sql-driver/mysql" import _ "github.com/go-sql-driver/mysql"
type ForumAdmin struct type ForumAdmin struct {
{ ID int
ID int Name string
Name string Desc string
Desc string Active bool
Active bool Preset string
Preset string
TopicCount int TopicCount int
PresetLang string PresetLang string
} }
type Forum struct type Forum struct {
{ ID int
ID int Link string
Link string Name string
Name string Desc string
Desc string Active bool
Active bool Preset string
Preset string ParentID int
ParentID int ParentType string
ParentType string TopicCount int
TopicCount int
LastTopicLink string LastTopicLink string
LastTopic string LastTopic string
LastTopicID int LastTopicID int
LastReplyer string LastReplyer string
LastReplyerID int LastReplyerID int
LastTopicTime string LastTopicTime string
} }
type ForumSimple struct type ForumSimple struct {
{ ID int
ID int Name string
Name string
Active bool Active bool
Preset string Preset string
} }
func build_forum_url(slug string, fid int) string { func buildForumUrl(slug string, fid int) string {
if slug == "" { if slug == "" {
return "/forum/" + strconv.Itoa(fid) return "/forum/" + strconv.Itoa(fid)
} }
return "/forum/" + slug + "." + strconv.Itoa(fid) return "/forum/" + slug + "." + strconv.Itoa(fid)
} }
func get_forum_url_prefix() string { func getForumURLPrefix() string {
return "/forum/" return "/forum/"
} }

View File

@ -3,17 +3,17 @@ package main
import "log" import "log"
import "sync" import "sync"
//import "sync/atomic" //import "sync/atomic"
import "database/sql" import "database/sql"
import "./query_gen/lib" import "./query_gen/lib"
var forum_update_mutex sync.Mutex var forumUpdateMutex sync.Mutex
var forum_create_mutex sync.Mutex var forumCreateMutex sync.Mutex
var forum_perms map[int]map[int]ForumPerms // [gid][fid]Perms var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms
var fstore ForumStore var fstore ForumStore
type ForumStore interface type ForumStore interface {
{
LoadForums() error LoadForums() error
DirtyGet(id int) *Forum DirtyGet(id int) *Forum
Get(id int) (*Forum, error) Get(id int) (*Forum, error)
@ -28,63 +28,62 @@ type ForumStore interface
CascadeDelete(id int) error CascadeDelete(id int) error
IncrementTopicCount(id int) error IncrementTopicCount(id int) error
DecrementTopicCount(id int) error DecrementTopicCount(id int) error
UpdateLastTopic(topic_name string, tid int, username string, uid int, time string, fid int) error UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error
Exists(id int) bool Exists(id int) bool
GetAll() ([]*Forum,error) GetAll() ([]*Forum, error)
GetAllIDs() ([]int,error) GetAllIDs() ([]int, error)
//GetChildren(parentID int, parentType string) ([]*Forum,error) //GetChildren(parentID int, parentType string) ([]*Forum,error)
//GetFirstChild(parentID int, parentType string) (*Forum,error) //GetFirstChild(parentID int, parentType string) (*Forum,error)
CreateForum(forum_name string, forum_desc string, active bool, preset string) (int, error) CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error)
GetGlobalCount() int GetGlobalCount() int
} }
type StaticForumStore struct type StaticForumStore struct {
{
forums []*Forum // The IDs for a forum tend to be low and sequential for the most part, so we can get more performance out of using a slice instead of a map AND it has better concurrency forums []*Forum // The IDs for a forum tend to be low and sequential for the most part, so we can get more performance out of using a slice instead of a map AND it has better concurrency
//fids []int //fids []int
forumCapCount int forumCapCount int
get *sql.Stmt get *sql.Stmt
get_all *sql.Stmt getAll *sql.Stmt
forum_count *sql.Stmt forumCount *sql.Stmt
} }
func NewStaticForumStore() *StaticForumStore { func NewStaticForumStore() *StaticForumStore {
get_stmt, 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 {
log.Fatal(err) log.Fatal(err)
} }
get_all_stmt, err := qgen.Builder.SimpleSelect("forums","fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime","","fid ASC","") getAllStmt, err := qgen.Builder.SimpleSelect("forums", "fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "", "fid ASC", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
forum_count_stmt, err := qgen.Builder.SimpleCount("forums","name != ''","") forumCountStmt, err := qgen.Builder.SimpleCount("forums", "name != ''", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &StaticForumStore{ return &StaticForumStore{
get: get_stmt, get: getStmt,
get_all: get_all_stmt, getAll: getAllStmt,
forum_count: forum_count_stmt, forumCount: forumCountStmt,
} }
} }
func (sfs *StaticForumStore) LoadForums() error { func (sfs *StaticForumStore) LoadForums() error {
log.Print("Adding the uncategorised forum") log.Print("Adding the uncategorised forum")
var forums []*Forum = []*Forum{ var forums = []*Forum{
&Forum{0,build_forum_url(name_to_slug("Uncategorised"),0),"Uncategorised","",config.UncategorisedForumVisible,"all",0,"",0,"","",0,"",0,""}, &Forum{0, buildForumUrl(nameToSlug("Uncategorised"), 0), "Uncategorised", "", config.UncategorisedForumVisible, "all", 0, "", 0, "", "", 0, "", 0, ""},
} }
rows, err := get_forums_stmt.Query() rows, err := get_forums_stmt.Query()
if err != nil { if err != nil {
return err return err
} }
defer rows.Close() defer rows.Close()
var i int = 1 var i = 1
for ;rows.Next();i++ { for ; rows.Next(); i++ {
forum := Forum{ID:0,Active:true,Preset:"all"} forum := Forum{ID: 0, Active: true, Preset: "all"}
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
if err != nil { if err != nil {
return err return err
@ -93,7 +92,7 @@ func (sfs *StaticForumStore) LoadForums() error {
// Ugh, you really shouldn't physically delete these items, it makes a big mess of things // Ugh, you really shouldn't physically delete these items, it makes a big mess of things
if forum.ID != i { if forum.ID != i {
log.Print("Stop physically deleting forums. You are messing up the IDs. Use the Forum Manager or delete_forum() instead x.x") log.Print("Stop physically deleting forums. You are messing up the IDs. Use the Forum Manager or delete_forum() instead x.x")
sfs.fill_forum_id_gap(i, forum.ID) sfs.fillForumIDGap(i, forum.ID)
} }
if forum.Name == "" { if forum.Name == "" {
@ -104,9 +103,9 @@ func (sfs *StaticForumStore) LoadForums() error {
log.Print("Adding the " + forum.Name + " forum") log.Print("Adding the " + forum.Name + " forum")
} }
forum.Link = build_forum_url(name_to_slug(forum.Name),forum.ID) forum.Link = buildForumUrl(nameToSlug(forum.Name), forum.ID)
forum.LastTopicLink = build_topic_url(name_to_slug(forum.LastTopic),forum.LastTopicID) forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
forums = append(forums,&forum) forums = append(forums, &forum)
} }
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
@ -120,7 +119,7 @@ func (sfs *StaticForumStore) LoadForums() error {
func (sfs *StaticForumStore) DirtyGet(id int) *Forum { func (sfs *StaticForumStore) DirtyGet(id int) *Forum {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") { if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
return &Forum{ID:-1,Name:""} return &Forum{ID: -1, Name: ""}
} }
return sfs.forums[id] return sfs.forums[id]
} }
@ -147,16 +146,13 @@ func (sfs *StaticForumStore) CascadeGetCopy(id int) (forum Forum, err error) {
} }
func (sfs *StaticForumStore) BypassGet(id int) (*Forum, error) { func (sfs *StaticForumStore) BypassGet(id int) (*Forum, error) {
var forum Forum = Forum{ID:id} var forum = Forum{ID: id}
err := sfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) err := sfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
if err != nil { return &forum, err
return nil, err
}
return &forum, nil
} }
func (sfs *StaticForumStore) Load(id int) error { func (sfs *StaticForumStore) Load(id int) error {
var forum Forum = Forum{ID:id} var forum = Forum{ID: id}
err := sfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) err := sfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
if err != nil { if err != nil {
return err return err
@ -167,17 +163,17 @@ func (sfs *StaticForumStore) Load(id int) error {
// TO-DO: Set should be able to add new indices not just replace existing ones for consistency with UserStore and TopicStore // TO-DO: Set should be able to add new indices not just replace existing ones for consistency with UserStore and TopicStore
func (sfs *StaticForumStore) Set(forum *Forum) error { func (sfs *StaticForumStore) Set(forum *Forum) error {
forum_update_mutex.Lock() forumUpdateMutex.Lock()
if !sfs.Exists(forum.ID) { if !sfs.Exists(forum.ID) {
forum_update_mutex.Unlock() forumUpdateMutex.Unlock()
return ErrNoRows return ErrNoRows
} }
sfs.forums[forum.ID] = forum sfs.forums[forum.ID] = forum
forum_update_mutex.Unlock() forumUpdateMutex.Unlock()
return nil return nil
} }
func (sfs *StaticForumStore) GetAll() ([]*Forum,error) { func (sfs *StaticForumStore) GetAll() ([]*Forum, error) {
return sfs.forums, nil return sfs.forums, nil
} }
@ -191,9 +187,9 @@ func (sfs *StaticForumStore) GetFirstChild(parentID int, parentType string) (*Fo
// We can cheat slightly, as the StaticForumStore has all the IDs under the cap ;) // We can cheat slightly, as the StaticForumStore has all the IDs under the cap ;)
// Should we cache this? Well, it's only really used for superadmins right now. // Should we cache this? Well, it's only really used for superadmins right now.
func (sfs *StaticForumStore) GetAllIDs() ([]int,error) { func (sfs *StaticForumStore) GetAllIDs() ([]int, error) {
var max int = sfs.forumCapCount var max = sfs.forumCapCount
var ids []int = make([]int,max) var ids = make([]int, max)
for i := 0; i < max; i++ { for i := 0; i < max; i++ {
ids[i] = i ids[i] = i
} }
@ -205,13 +201,13 @@ func (sfs *StaticForumStore) Exists(id int) bool {
} }
func (sfs *StaticForumStore) Delete(id int) error { func (sfs *StaticForumStore) Delete(id int) error {
forum_update_mutex.Lock() forumUpdateMutex.Lock()
if !sfs.Exists(id) { if !sfs.Exists(id) {
forum_update_mutex.Unlock() forumUpdateMutex.Unlock()
return nil return nil
} }
sfs.forums[id].Name = "" sfs.forums[id].Name = ""
forum_update_mutex.Unlock() forumUpdateMutex.Unlock()
return nil return nil
} }
@ -221,13 +217,13 @@ func (sfs *StaticForumStore) CascadeDelete(id int) error {
return err return err
} }
forum_update_mutex.Lock() forumUpdateMutex.Lock()
_, err = delete_forum_stmt.Exec(id) _, err = delete_forum_stmt.Exec(id)
if err != nil { if err != nil {
return err return err
} }
forum.Name = "" forum.Name = ""
forum_update_mutex.Unlock() forumUpdateMutex.Unlock()
return nil return nil
} }
@ -236,11 +232,11 @@ func (sfs *StaticForumStore) IncrementTopicCount(id int) error {
if err != nil { if err != nil {
return err return err
} }
_, err = add_topics_to_forum_stmt.Exec(1,id) _, err = add_topics_to_forum_stmt.Exec(1, id)
if err != nil { if err != nil {
return err return err
} }
forum.TopicCount += 1 forum.TopicCount++
return nil return nil
} }
@ -253,7 +249,7 @@ func (sfs *StaticForumStore) DecrementTopicCount(id int) error {
if err != nil { if err != nil {
return err return err
} }
forum.TopicCount -= 1 forum.TopicCount--
return nil return nil
} }
@ -264,7 +260,7 @@ func (sfs *StaticForumStore) UpdateLastTopic(topic_name string, tid int, usernam
return err return err
} }
_, err = update_forum_cache_stmt.Exec(topic_name,tid,username,uid,fid) _, err = update_forum_cache_stmt.Exec(topic_name, tid, username, uid, fid)
if err != nil { if err != nil {
return err return err
} }
@ -278,15 +274,15 @@ func (sfs *StaticForumStore) UpdateLastTopic(topic_name string, tid int, usernam
return nil return nil
} }
func (sfs *StaticForumStore) CreateForum(forum_name string, forum_desc string, active bool, preset string) (int, error) { func (sfs *StaticForumStore) CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error) {
var fid int var fid int
err := forum_entry_exists_stmt.QueryRow().Scan(&fid) err := forum_entry_exists_stmt.QueryRow().Scan(&fid)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return 0, err return 0, err
} }
if err != ErrNoRows { if err != ErrNoRows {
forum_update_mutex.Lock() forumUpdateMutex.Lock()
_, err = update_forum_stmt.Exec(forum_name, forum_desc, active, preset, fid) _, err = update_forum_stmt.Exec(forumName, forumDesc, active, preset, fid)
if err != nil { if err != nil {
return fid, err return fid, err
} }
@ -294,16 +290,16 @@ func (sfs *StaticForumStore) CreateForum(forum_name string, forum_desc string, a
if err != nil { if err != nil {
return 0, ErrCacheDesync return 0, ErrCacheDesync
} }
forum.Name = forum_name forum.Name = forumName
forum.Desc = forum_desc forum.Desc = forumDesc
forum.Active = active forum.Active = active
forum.Preset = preset forum.Preset = preset
forum_update_mutex.Unlock() forumUpdateMutex.Unlock()
return fid, nil return fid, nil
} }
forum_create_mutex.Lock() forumCreateMutex.Lock()
res, err := create_forum_stmt.Exec(forum_name, forum_desc, active, preset) res, err := create_forum_stmt.Exec(forumName, forumDesc, active, preset)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -314,27 +310,26 @@ func (sfs *StaticForumStore) CreateForum(forum_name string, forum_desc string, a
} }
fid = int(fid64) fid = int(fid64)
sfs.forums = append(sfs.forums, &Forum{fid,build_forum_url(name_to_slug(forum_name),fid),forum_name,forum_desc,active,preset,0,"",0,"","",0,"",0,""}) sfs.forums = append(sfs.forums, &Forum{fid, buildForumUrl(nameToSlug(forumName), fid), forumName, forumDesc, active, preset, 0, "", 0, "", "", 0, "", 0, ""})
sfs.forumCapCount++ sfs.forumCapCount++
// TO-DO: Add a GroupStore. How would it interact with the ForumStore? // TO-DO: Add a GroupStore. How would it interact with the ForumStore?
permmap_to_query(preset_to_permmap(preset),fid) permmapToQuery(presetToPermmap(preset), fid)
forum_create_mutex.Unlock() forumCreateMutex.Unlock()
return fid, nil return fid, nil
} }
func (sfs *StaticForumStore) fill_forum_id_gap(biggerID int, smallerID int) { func (sfs *StaticForumStore) fillForumIDGap(biggerID int, smallerID int) {
dummy := Forum{ID:0,Name:"",Active:false,Preset:"all"} dummy := Forum{ID: 0, Name: "", Active: false, Preset: "all"}
for i := smallerID; i > biggerID; i++ { for i := smallerID; i > biggerID; i++ {
sfs.forums = append(sfs.forums, &dummy) sfs.forums = append(sfs.forums, &dummy)
} }
} }
// Return the total number of forums
// TO-DO: Get the total count of forums in the forum store minus the blanked forums rather than doing a heavy query for this? // TO-DO: Get the total count of forums in the forum store minus the blanked forums rather than doing a heavy query for this?
func (sfs *StaticForumStore) GetGlobalCount() int { // GetGlobalCount returns the total number of forums
var fcount int func (sfs *StaticForumStore) GetGlobalCount() (fcount int) {
err := sfs.forum_count.QueryRow().Scan(&fcount) err := sfs.forumCount.QueryRow().Scan(&fcount)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }

View File

@ -7,6 +7,7 @@ package main
import "log" import "log"
import "database/sql" import "database/sql"
// nolint
var get_user_stmt *sql.Stmt var get_user_stmt *sql.Stmt
var get_reply_stmt *sql.Stmt var get_reply_stmt *sql.Stmt
var get_user_reply_stmt *sql.Stmt var get_user_reply_stmt *sql.Stmt
@ -123,6 +124,7 @@ var add_forum_perms_to_forum_staff_stmt *sql.Stmt
var add_forum_perms_to_forum_members_stmt *sql.Stmt var add_forum_perms_to_forum_members_stmt *sql.Stmt
var notify_watchers_stmt *sql.Stmt var notify_watchers_stmt *sql.Stmt
// nolint
func _gen_mysql() (err error) { func _gen_mysql() (err error) {
if dev.DebugMode { if dev.DebugMode {
log.Print("Building the generated statements") log.Print("Building the generated statements")

View File

@ -6,6 +6,7 @@ package main
import "log" import "log"
import "database/sql" import "database/sql"
// nolint
var add_replies_to_topic_stmt *sql.Stmt var add_replies_to_topic_stmt *sql.Stmt
var remove_replies_from_topic_stmt *sql.Stmt var remove_replies_from_topic_stmt *sql.Stmt
var add_topics_to_forum_stmt *sql.Stmt var add_topics_to_forum_stmt *sql.Stmt
@ -46,6 +47,7 @@ var verify_email_stmt *sql.Stmt
var set_temp_group_stmt *sql.Stmt var set_temp_group_stmt *sql.Stmt
var update_word_filter_stmt *sql.Stmt var update_word_filter_stmt *sql.Stmt
// nolint
func _gen_pgsql() (err error) { func _gen_pgsql() (err error) {
if dev.DebugMode { if dev.DebugMode {
log.Print("Building the generated statements") log.Print("Building the generated statements")

View File

@ -169,7 +169,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
route_panel_themes(w,req,user) route_panel_themes(w,req,user)
return return
case "/panel/themes/default/": case "/panel/themes/default/":
route_panel_themes_default(w,req,user,extra_data) route_panel_themes_set_default(w,req,user,extra_data)
return return
case "/panel/plugins/": case "/panel/plugins/":
route_panel_plugins(w,req,user) route_panel_plugins(w,req,user)

View File

@ -2,56 +2,52 @@ package main
import ( import (
//"os" //"os"
"log"
"bytes" "bytes"
"strings" "log"
"strconv" "strconv"
"strings"
//"math/rand" //"math/rand"
"testing" "database/sql"
"time"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"html/template" "testing"
"io/ioutil" "time"
"database/sql"
//"runtime/pprof" //"runtime/pprof"
//_ "github.com/go-sql-driver/mysql" //_ "github.com/go-sql-driver/mysql"
//"github.com/erikstmartin/go-testdb" //"github.com/erikstmartin/go-testdb"
//"github.com/husobee/vestigo" //"github.com/husobee/vestigo"
) )
var db_test *sql.DB var dbTest *sql.DB
var db_prod *sql.DB var dbProd *sql.DB
var gloinited bool var gloinited bool
func gloinit() error { func gloinit() error {
dev.DebugMode = false dev.DebugMode = false
//nogrouplog = true //nogrouplog = true
// init_database is a little noisy for a benchmark
//discard := ioutil.Discard
//log.SetOutput(discard)
startTime = time.Now() startTime = time.Now()
//timeLocation = startTime.Location() processConfig()
process_config()
init_themes() err := initThemes()
err := init_database()
if err != nil { if err != nil {
return err return err
} }
db_prod = db err = initDatabase()
if err != nil {
return err
}
dbProd = db
//db_test, err = sql.Open("testdb","") //db_test, err = sql.Open("testdb","")
//if err != nil { //if err != nil {
// return err // return err
//} //}
init_templates() initTemplates()
db_prod.SetMaxOpenConns(64) dbProd.SetMaxOpenConns(64)
err = init_errors() err = initErrors()
if err != nil { if err != nil {
return err return err
} }
@ -64,11 +60,14 @@ func gloinit() error {
topics = NewSqlTopicStore() topics = NewSqlTopicStore()
} }
init_static_files() log.Print("Loading the static files.")
//log.SetOutput(os.Stdout) err = initStaticFiles()
if err != nil {
return err
}
auth = NewDefaultAuth() auth = NewDefaultAuth()
err = init_word_filters() err = initWordFilters()
if err != nil { if err != nil {
return err return err
} }
@ -79,182 +78,20 @@ func gloinit() error {
} }
func init() { func init() {
gloinit() err := gloinit()
}
func BenchmarkTopicTemplateSerial(b *testing.B) {
b.ReportAllocs()
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,make(map[string]bool),"",false,"","","","","",-1,58,"127.0.0.1"}
topic := TopicUser{Title: "Lol",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",ClassName: "",Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"}
var replyList []Reply
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry","Jerry",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry2","Jerry2",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry3","Jerry3",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry4","Jerry4",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry5","Jerry5",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry6","Jerry6",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry7","Jerry7",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry8","Jerry8",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry9","Jerry9",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry10","Jerry10",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
headerVars := HeaderVars{
NoticeList:[]string{"test"},
Stylesheets:[]string{"panel"},
Scripts:[]string{"whatever"},
Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
Site:site,
}
tpage := TopicPage{"Topic Blah",user,headerVars,replyList,topic,1,1,extData}
tpage2 := TopicPage{"Topic Blah",admin,headerVars,replyList,topic,1,1,extData}
w := ioutil.Discard
b.Run("compiled_useradmin", func(b *testing.B) {
for i := 0; i < b.N; i++ {
template_topic(tpage2,w)
}
})
b.Run("interpreted_useradmin", func(b *testing.B) {
for i := 0; i < b.N; i++ {
templates.ExecuteTemplate(w,"topic.html", tpage2)
}
})
b.Run("compiled_userguest", func(b *testing.B) {
for i := 0; i < b.N; i++ {
template_topic(tpage,w)
}
})
b.Run("interpreted_userguest", func(b *testing.B) {
for i := 0; i < b.N; i++ {
templates.ExecuteTemplate(w,"topic.html", tpage)
}
})
w2 := httptest.NewRecorder()
b.Run("compiled_useradmin_recorder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
w2.Body.Reset()
template_topic(tpage2,w2)
}
})
b.Run("interpreted_useradmin_recorder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
w2.Body.Reset()
templates.ExecuteTemplate(w2,"topic.html", tpage2)
}
})
b.Run("compiled_userguest_recorder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
w2.Body.Reset()
template_topic(tpage,w2)
}
})
b.Run("interpreted_userguest_recorder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
w2.Body.Reset()
templates.ExecuteTemplate(w2,"topic.html", tpage)
}
})
/*f, err := os.Create("topic_bench.prof")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()*/
}
func BenchmarkTopicsTemplateSerial(b *testing.B) {
b.ReportAllocs()
user := User{0,build_profile_url("bob",0),"Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,build_profile_url("admin-alice",1),"Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,make(map[string]bool),"",false,"","","","","Admin",58,580,"127.0.0.1"}
var topicList []*TopicsRow
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
headerVars := HeaderVars{
NoticeList:[]string{"test"},
Stylesheets:[]string{"panel"},
Scripts:[]string{"whatever"},
Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
Site:site,
}
w := ioutil.Discard
tpage := TopicsPage{"Topic Blah",user,headerVars,topicList,extData}
tpage2 := TopicsPage{"Topic Blah",admin,headerVars,topicList,extData}
b.Run("compiled_useradmin", func(b *testing.B) {
for i := 0; i < b.N; i++ {
template_topics(tpage2,w)
}
})
b.Run("interpreted_useradmin",func(b *testing.B) {
for i := 0; i < b.N; i++ {
templates.ExecuteTemplate(w,"topics.html", tpage2)
}
})
b.Run("compiled_userguest",func(b *testing.B) {
for i := 0; i < b.N; i++ {
template_topics(tpage,w)
}
})
b.Run("interpreted_userguest",func(b *testing.B) {
for i := 0; i < b.N; i++ {
templates.ExecuteTemplate(w,"topics.html", tpage)
}
})
}
func BenchmarkStaticRouteParallel(b *testing.B) {
b.ReportAllocs()
if !gloinited {
gloinit()
}
if !plugins_inited {
init_plugins()
}
b.RunParallel(func(pb *testing.PB) {
static_w := httptest.NewRecorder()
static_req := httptest.NewRequest("get","/static/global.js",bytes.NewReader(nil))
static_handler := http.HandlerFunc(route_static)
for pb.Next() {
//static_w.Code = 200
static_w.Body.Reset()
static_handler.ServeHTTP(static_w,static_req)
//if static_w.Code != 200 {
// pb.Print(static_w.Body)
// panic("HTTP Error!")
//}
}
})
} }
// TO-DO: Swap out LocalError for a panic for this? // TO-DO: Swap out LocalError for a panic for this?
func BenchmarkTopicAdminRouteParallel(b *testing.B) { func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
if !gloinited { if !gloinited {
gloinit() err := gloinit()
if err != nil {
b.Fatal(err)
}
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
@ -262,27 +99,27 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
if !admin.Is_Admin { if !admin.IsAdmin {
b.Fatal("UID1 is not an admin") b.Fatal("UID1 is not an admin")
} }
admin_uid_cookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year} adminUIDCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: year}
admin_session_cookie := http.Cookie{Name:"session",Value: admin.Session,Path:"/",MaxAge: year} adminSessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: year}
topic_w := httptest.NewRecorder() topicW := httptest.NewRecorder()
topic_req := httptest.NewRequest("get","/topic/1",bytes.NewReader(nil)) topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil))
topic_req_admin := topic_req topicReqAdmin := topicReq
topic_req_admin.AddCookie(&admin_uid_cookie) topicReqAdmin.AddCookie(&adminUIDCookie)
topic_req_admin.AddCookie(&admin_session_cookie) topicReqAdmin.AddCookie(&adminSessionCookie)
// Deal with the session stuff, etc. // Deal with the session stuff, etc.
user, ok := PreRoute(topic_w,topic_req_admin) user, ok := PreRoute(topicW, topicReqAdmin)
if !ok { if !ok {
b.Fatal("Mysterious error!") b.Fatal("Mysterious error!")
} }
for pb.Next() { for pb.Next() {
topic_w.Body.Reset() topicW.Body.Reset()
route_topic_id(topic_w,topic_req_admin,user) route_topic_id(topicW, topicReqAdmin, user)
} }
}) })
} }
@ -290,15 +127,18 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
func BenchmarkTopicGuestRouteParallel(b *testing.B) { func BenchmarkTopicGuestRouteParallel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
if !gloinited { if !gloinited {
gloinit() err := gloinit()
if err != nil {
b.Fatal(err)
}
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
topic_w := httptest.NewRecorder() topicW := httptest.NewRecorder()
topic_req := httptest.NewRequest("get","/topic/1",bytes.NewReader(nil)) topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil))
for pb.Next() { for pb.Next() {
topic_w.Body.Reset() topicW.Body.Reset()
route_topic_id(topic_w,topic_req,guest_user) route_topic_id(topicW, topicReq, guestUser)
} }
}) })
} }
@ -628,13 +468,16 @@ func BenchmarkForumsGuestRouteParallel(b *testing.B) {
func BenchmarkQueryTopicParallel(b *testing.B) { func BenchmarkQueryTopicParallel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
if !gloinited { if !gloinited {
gloinit() err := gloinit()
if err != nil {
b.Fatal(err)
}
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var tu TopicUser var tu TopicUser
for pb.Next() { for pb.Next() {
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.Is_Closed, &tu.Sticky, &tu.ParentID, &tu.IpAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level) err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
if err == ErrNoRows { if err == ErrNoRows {
log.Fatal("No rows found!") log.Fatal("No rows found!")
return return
@ -649,13 +492,16 @@ func BenchmarkQueryTopicParallel(b *testing.B) {
func BenchmarkQueryPreparedTopicParallel(b *testing.B) { func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
if !gloinited { if !gloinited {
gloinit() err := gloinit()
if err != nil {
b.Fatal(err)
}
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var tu TopicUser var tu TopicUser
for pb.Next() { for pb.Next() {
err := get_topic_user_stmt.QueryRow(1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.Is_Closed, &tu.Sticky, &tu.ParentID, &tu.IpAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level) err := get_topic_user_stmt.QueryRow(1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
if err == ErrNoRows { if err == ErrNoRows {
b.Fatal("No rows found!") b.Fatal("No rows found!")
return return
@ -672,7 +518,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
var tu TopicUser var tu TopicUser
b.Run("topic", func(b *testing.B) { b.Run("topic", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.Is_Closed, &tu.Sticky, &tu.ParentID, &tu.IpAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level) err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
if err == ErrNoRows { if err == ErrNoRows {
b.Fatal("No rows found!") b.Fatal("No rows found!")
return return
@ -691,7 +537,8 @@ func BenchmarkQueriesSerial(b *testing.B) {
} }
defer rows.Close() defer rows.Close()
for rows.Next() {} for rows.Next() {
}
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -701,7 +548,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
}) })
var replyItem Reply var replyItem Reply
var is_super_admin bool var isSuperAdmin bool
var group int var group int
b.Run("topic_replies_scan", func(b *testing.B) { b.Run("topic_replies_scan", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -711,7 +558,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
return return
} }
for rows.Next() { for rows.Next() {
err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &is_super_admin, &group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IpAddress) err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &isSuperAdmin, &group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IPAddress)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
return return
@ -1059,32 +906,32 @@ func BenchmarkParserSerial(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.Run("empty_post", func(b *testing.B) { b.Run("empty_post", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = parse_message("") _ = parseMessage("")
} }
}) })
b.Run("short_post", func(b *testing.B) { b.Run("short_post", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = parse_message("Hey everyone, how's it going?") _ = parseMessage("Hey everyone, how's it going?")
} }
}) })
b.Run("one_smily", func(b *testing.B) { b.Run("one_smily", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = parse_message("Hey everyone, how's it going? :)") _ = parseMessage("Hey everyone, how's it going? :)")
} }
}) })
b.Run("five_smilies", func(b *testing.B) { b.Run("five_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = parse_message("Hey everyone, how's it going? :):):):):)") _ = parseMessage("Hey everyone, how's it going? :):):):):)")
} }
}) })
b.Run("ten_smilies", func(b *testing.B) { b.Run("ten_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = parse_message("Hey everyone, how's it going? :):):):):):):):):):)") _ = parseMessage("Hey everyone, how's it going? :):):):):):):):):):)")
} }
}) })
b.Run("twenty_smilies", func(b *testing.B) { b.Run("twenty_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = parse_message("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)") _ = parseMessage("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)")
} }
}) })
} }
@ -1093,47 +940,47 @@ func BenchmarkBBCodePluginWithRegexpSerial(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.Run("empty_post", func(b *testing.B) { b.Run("empty_post", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("") _ = bbcodeRegexParse("")
} }
}) })
b.Run("short_post", func(b *testing.B) { b.Run("short_post", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("Hey everyone, how's it going?") _ = bbcodeRegexParse("Hey everyone, how's it going?")
} }
}) })
b.Run("one_smily", func(b *testing.B) { b.Run("one_smily", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("Hey everyone, how's it going? :)") _ = bbcodeRegexParse("Hey everyone, how's it going? :)")
} }
}) })
b.Run("five_smilies", func(b *testing.B) { b.Run("five_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("Hey everyone, how's it going? :):):):):)") _ = bbcodeRegexParse("Hey everyone, how's it going? :):):):):)")
} }
}) })
b.Run("ten_smilies", func(b *testing.B) { b.Run("ten_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("Hey everyone, how's it going? :):):):):):):):):):)") _ = bbcodeRegexParse("Hey everyone, how's it going? :):):):):):):):):):)")
} }
}) })
b.Run("twenty_smilies", func(b *testing.B) { b.Run("twenty_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)") _ = bbcodeRegexParse("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)")
} }
}) })
b.Run("one_bold", func(b *testing.B) { b.Run("one_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("[b]H[/b]ey everyone, how's it going?") _ = bbcodeRegexParse("[b]H[/b]ey everyone, how's it going?")
} }
}) })
b.Run("five_bold", func(b *testing.B) { b.Run("five_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b]eryone, how's it going?") _ = bbcodeRegexParse("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b]eryone, how's it going?")
} }
}) })
b.Run("ten_bold", func(b *testing.B) { b.Run("ten_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_regex_parse("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b][b]e[/b][b]r[/b][b]y[/b][b]o[/b][b]n[/b]e, how's it going?") _ = bbcodeRegexParse("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b][b]e[/b][b]r[/b][b]y[/b][b]o[/b][b]n[/b]e, how's it going?")
} }
}) })
} }
@ -1142,47 +989,47 @@ func BenchmarkBBCodePluginWithoutCodeTagSerial(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.Run("empty_post", func(b *testing.B) { b.Run("empty_post", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("") _ = bbcodeParseWithoutCode("")
} }
}) })
b.Run("short_post", func(b *testing.B) { b.Run("short_post", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("Hey everyone, how's it going?") _ = bbcodeParseWithoutCode("Hey everyone, how's it going?")
} }
}) })
b.Run("one_smily", func(b *testing.B) { b.Run("one_smily", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("Hey everyone, how's it going? :)") _ = bbcodeParseWithoutCode("Hey everyone, how's it going? :)")
} }
}) })
b.Run("five_smilies", func(b *testing.B) { b.Run("five_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("Hey everyone, how's it going? :):):):):)") _ = bbcodeParseWithoutCode("Hey everyone, how's it going? :):):):):)")
} }
}) })
b.Run("ten_smilies", func(b *testing.B) { b.Run("ten_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("Hey everyone, how's it going? :):):):):):):):):):)") _ = bbcodeParseWithoutCode("Hey everyone, how's it going? :):):):):):):):):):)")
} }
}) })
b.Run("twenty_smilies", func(b *testing.B) { b.Run("twenty_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)") _ = bbcodeParseWithoutCode("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)")
} }
}) })
b.Run("one_bold", func(b *testing.B) { b.Run("one_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("[b]H[/b]ey everyone, how's it going?") _ = bbcodeParseWithoutCode("[b]H[/b]ey everyone, how's it going?")
} }
}) })
b.Run("five_bold", func(b *testing.B) { b.Run("five_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b]eryone, how's it going?") _ = bbcodeParseWithoutCode("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b]eryone, how's it going?")
} }
}) })
b.Run("ten_bold", func(b *testing.B) { b.Run("ten_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_parse_without_code("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b][b]e[/b][b]r[/b][b]y[/b][b]o[/b][b]n[/b]e, how's it going?") _ = bbcodeParseWithoutCode("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b][b]e[/b][b]r[/b][b]y[/b][b]o[/b][b]n[/b]e, how's it going?")
} }
}) })
} }
@ -1191,47 +1038,47 @@ func BenchmarkBBCodePluginWithFullParserSerial(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.Run("empty_post", func(b *testing.B) { b.Run("empty_post", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("") _ = bbcodeFullParse("")
} }
}) })
b.Run("short_post", func(b *testing.B) { b.Run("short_post", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("Hey everyone, how's it going?") _ = bbcodeFullParse("Hey everyone, how's it going?")
} }
}) })
b.Run("one_smily", func(b *testing.B) { b.Run("one_smily", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("Hey everyone, how's it going? :)") _ = bbcodeFullParse("Hey everyone, how's it going? :)")
} }
}) })
b.Run("five_smilies", func(b *testing.B) { b.Run("five_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("Hey everyone, how's it going? :):):):):)") _ = bbcodeFullParse("Hey everyone, how's it going? :):):):):)")
} }
}) })
b.Run("ten_smilies", func(b *testing.B) { b.Run("ten_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("Hey everyone, how's it going? :):):):):):):):):):)") _ = bbcodeFullParse("Hey everyone, how's it going? :):):):):):):):):):)")
} }
}) })
b.Run("twenty_smilies", func(b *testing.B) { b.Run("twenty_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)") _ = bbcodeFullParse("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)")
} }
}) })
b.Run("one_bold", func(b *testing.B) { b.Run("one_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("[b]H[/b]ey everyone, how's it going?") _ = bbcodeFullParse("[b]H[/b]ey everyone, how's it going?")
} }
}) })
b.Run("five_bold", func(b *testing.B) { b.Run("five_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b]eryone, how's it going?") _ = bbcodeFullParse("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b]eryone, how's it going?")
} }
}) })
b.Run("ten_bold", func(b *testing.B) { b.Run("ten_bold", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = bbcode_full_parse("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b][b]e[/b][b]r[/b][b]y[/b][b]o[/b][b]n[/b]e, how's it going?") _ = bbcodeFullParse("[b]H[/b][b]e[/b][b]y[/b] [b]e[/b][b]v[/b][b]e[/b][b]r[/b][b]y[/b][b]o[/b][b]n[/b]e, how's it going?")
} }
}) })
} }
@ -1450,28 +1297,28 @@ func TestForumsGuestRoute(t *testing.T) {
}*/ }*/
func TestSplittyThing(t *testing.T) { func TestSplittyThing(t *testing.T) {
var extra_data string var extraData string
var path string = "/pages/hohoho" var path = "/pages/hohoho"
t.Log("Raw Path:",path) t.Log("Raw Path:", path)
if path[len(path) - 1] != '/' { if path[len(path)-1] != '/' {
extra_data = path[strings.LastIndexByte(path,'/') + 1:] extraData = path[strings.LastIndexByte(path, '/')+1:]
path = path[:strings.LastIndexByte(path,'/') + 1] path = path[:strings.LastIndexByte(path, '/')+1]
} }
t.Log("Path:", path) t.Log("Path:", path)
t.Log("Extra Data:", extra_data) t.Log("Extra Data:", extraData)
t.Log("Path Bytes:", []byte(path)) t.Log("Path Bytes:", []byte(path))
t.Log("Extra Data Bytes:", []byte(extra_data)) t.Log("Extra Data Bytes:", []byte(extraData))
t.Log("Splitty thing test") t.Log("Splitty thing test")
path = "/topics/" path = "/topics/"
extra_data = "" extraData = ""
t.Log("Raw Path:",path) t.Log("Raw Path:", path)
if path[len(path) - 1] != '/' { if path[len(path)-1] != '/' {
extra_data = path[strings.LastIndexByte(path,'/') + 1:] extraData = path[strings.LastIndexByte(path, '/')+1:]
path = path[:strings.LastIndexByte(path,'/') + 1] path = path[:strings.LastIndexByte(path, '/')+1]
} }
t.Log("Path:", path) t.Log("Path:", path)
t.Log("Extra Data:", extra_data) t.Log("Extra Data:", extraData)
t.Log("Path Bytes:", []byte(path)) t.Log("Path Bytes:", []byte(path))
t.Log("Extra Data Bytes:", []byte(extra_data)) t.Log("Extra Data Bytes:", []byte(extraData))
} }

View File

@ -3,65 +3,64 @@ package main
import "sync" import "sync"
import "encoding/json" import "encoding/json"
var group_update_mutex sync.Mutex var groupUpdateMutex sync.Mutex
type GroupAdmin struct type GroupAdmin struct {
{ ID int
ID int Name string
Name string Rank string
Rank string
RankClass string RankClass string
CanEdit bool CanEdit bool
CanDelete bool CanDelete bool
} }
type Group struct type Group struct {
{ ID int
ID int Name string
Name string IsMod bool
Is_Mod bool IsAdmin bool
Is_Admin bool IsBanned bool
Is_Banned bool Tag string
Tag string Perms Perms
Perms Perms
PermissionsText []byte PermissionsText []byte
PluginPerms map[string]bool // Custom permissions defined by plugins. What if two plugins declare the same permission, but they handle them in incompatible ways? Very unlikely, we probably don't need to worry about this, the plugin authors should be aware of each other to some extent PluginPerms map[string]bool // Custom permissions defined by plugins. What if two plugins declare the same permission, but they handle them in incompatible ways? Very unlikely, we probably don't need to worry about this, the plugin authors should be aware of each other to some extent
PluginPermsText []byte PluginPermsText []byte
Forums []ForumPerms Forums []ForumPerms
CanSee []int // The IDs of the forums this group can see CanSee []int // The IDs of the forums this group can see
} }
var group_create_mutex sync.Mutex var groupCreateMutex sync.Mutex
func create_group(group_name string, tag string, is_admin bool, is_mod bool, is_banned bool) (int, error) {
func createGroup(groupName string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) {
var gid int var gid int
err := group_entry_exists_stmt.QueryRow().Scan(&gid) err := group_entry_exists_stmt.QueryRow().Scan(&gid)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return 0, err return 0, err
} }
if err != ErrNoRows { if err != ErrNoRows {
group_update_mutex.Lock() groupUpdateMutex.Lock()
_, err = update_group_rank_stmt.Exec(is_admin, is_mod, is_banned, gid) _, err = update_group_rank_stmt.Exec(isAdmin, isMod, isBanned, gid)
if err != nil { if err != nil {
return gid, err return gid, err
} }
_, err = update_group_stmt.Exec(group_name, tag, gid) _, err = update_group_stmt.Exec(groupName, tag, gid)
if err != nil { if err != nil {
return gid, err return gid, err
} }
groups[gid].Name = group_name groups[gid].Name = groupName
groups[gid].Tag = tag groups[gid].Tag = tag
groups[gid].Is_Banned = is_banned groups[gid].IsBanned = isBanned
groups[gid].Is_Mod = is_mod groups[gid].IsMod = isMod
groups[gid].Is_Admin = is_admin groups[gid].IsAdmin = isAdmin
group_update_mutex.Unlock() groupUpdateMutex.Unlock()
return gid, nil return gid, nil
} }
group_create_mutex.Lock() groupCreateMutex.Lock()
var permstr string = "{}" var permstr = "{}"
res, err := create_group_stmt.Exec(group_name, tag, is_admin, is_mod, is_banned, permstr) res, err := create_group_stmt.Exec(groupName, tag, isAdmin, isMod, isBanned, permstr)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -72,17 +71,17 @@ func create_group(group_name string, tag string, is_admin bool, is_mod bool, is_
} }
gid = int(gid64) gid = int(gid64)
var perms Perms = BlankPerms var perms = BlankPerms
var blankForums []ForumPerms var blankForums []ForumPerms
var blankIntList []int var blankIntList []int
var plugin_perms map[string]bool = make(map[string]bool) var pluginPerms = make(map[string]bool)
var plugin_perms_bytes []byte = []byte("{}") var pluginPermsBytes = []byte("{}")
if vhooks["create_group_preappend"] != nil { if vhooks["create_group_preappend"] != nil {
run_vhook("create_group_preappend", &plugin_perms, &plugin_perms_bytes) runVhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
} }
groups = append(groups, Group{gid,group_name,is_mod,is_admin,is_banned,tag,perms,[]byte(permstr),plugin_perms,plugin_perms_bytes,blankForums,blankIntList}) groups = append(groups, Group{gid, groupName, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList})
group_create_mutex.Unlock() groupCreateMutex.Unlock()
// Generate the forum permissions based on the presets... // Generate the forum permissions based on the presets...
fdata, err := fstore.GetAll() fdata, err := fstore.GetAll()
@ -90,20 +89,20 @@ func create_group(group_name string, tag string, is_admin bool, is_mod bool, is_
return 0, err return 0, err
} }
permupdate_mutex.Lock() permUpdateMutex.Lock()
for _, forum := range fdata { for _, forum := range fdata {
var thePreset string var thePreset string
if is_admin { if isAdmin {
thePreset = "admins" thePreset = "admins"
} else if is_mod { } else if isMod {
thePreset = "staff" thePreset = "staff"
} else if is_banned { } else if isBanned {
thePreset = "banned" thePreset = "banned"
} else { } else {
thePreset = "members" thePreset = "members"
} }
permmap := preset_to_permmap(forum.Preset) permmap := presetToPermmap(forum.Preset)
permitem := permmap[thePreset] permitem := permmap[thePreset]
permitem.Overrides = true permitem.Overrides = true
permstr, err := json.Marshal(permitem) permstr, err := json.Marshal(permitem)
@ -111,20 +110,20 @@ func create_group(group_name string, tag string, is_admin bool, is_mod bool, is_
return gid, err return gid, err
} }
perms := string(permstr) perms := string(permstr)
_, err = add_forum_perms_to_group_stmt.Exec(gid,forum.ID,forum.Preset,perms) _, err = add_forum_perms_to_group_stmt.Exec(gid, forum.ID, forum.Preset, perms)
if err != nil { if err != nil {
return gid, err return gid, err
} }
err = rebuild_forum_permissions(forum.ID) err = rebuildForumPermissions(forum.ID)
if err != nil { if err != nil {
return gid, err return gid, err
} }
} }
permupdate_mutex.Unlock() permUpdateMutex.Unlock()
return gid, nil return gid, nil
} }
func group_exists(gid int) bool { func groupExists(gid int) bool {
return (gid <= groupCapCount) && (gid > 0) && groups[gid].Name != "" return (gid <= groupCapCount) && (gid > 0) && groups[gid].Name != ""
} }

View File

@ -1,40 +1,47 @@
/* Copyright Azareal 2017 - 2018 */ /*
*
* Gosora Installer
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import ( import (
"bufio"
"database/sql"
"fmt" "fmt"
"os" "os"
"bufio"
"strconv"
"database/sql"
"runtime/debug" "runtime/debug"
"strconv"
"../query_gen/lib" "../query_gen/lib"
) )
const saltLength int = 32 const saltLength int = 32
var db *sql.DB var db *sql.DB
var scanner *bufio.Scanner var scanner *bufio.Scanner
var db_adapter string = "mysql" var dbAdapter = "mysql"
var db_host string var dbHost string
var db_username string var dbUsername string
var db_password string var dbPassword string
var db_name string var dbName string
var db_port string var dbPort string
var site_name, site_url, server_port string var siteName, siteURL, serverPort string
var default_adapter string = "mysql" var defaultAdapter = "mysql"
var default_host string = "localhost" var defaultHost = "localhost"
var default_username string = "root" var defaultUsername = "root"
var default_dbname string = "gosora" var defaultDbname = "gosora"
var default_site_name string = "Site Name" var defaultSiteName = "Site Name"
var default_site_url string = "localhost" var defaultsiteURL = "localhost"
var default_server_port string = "80" // 8080's a good one, if you're testing and don't want it to clash with port 80 var defaultServerPort = "80" // 8080's a good one, if you're testing and don't want it to clash with port 80
var init_database func()error = _init_mysql // func() error, removing type to satisfy lint
var table_defs func()error = _table_defs_mysql var initDatabase = _initMysql
var initial_data func()error = _initial_data_mysql var tableDefs = _tableDefsMysql
var initialData = _initialDataMysql
func main() { func main() {
// Capture panics rather than immediately closing the window on Windows // Capture panics rather than immediately closing the window on Windows
@ -43,7 +50,7 @@ func main() {
if r != nil { if r != nil {
fmt.Println(r) fmt.Println(r)
debug.PrintStack() debug.PrintStack()
press_any_key() pressAnyKey()
return return
} }
}() }()
@ -51,7 +58,7 @@ func main() {
scanner = bufio.NewScanner(os.Stdin) scanner = bufio.NewScanner(os.Stdin)
fmt.Println("Welcome to Gosora's Installer") fmt.Println("Welcome to Gosora's Installer")
fmt.Println("We're going to take you through a few steps to help you get started :)") fmt.Println("We're going to take you through a few steps to help you get started :)")
if !get_database_details() { if !getDatabaseDetails() {
err := scanner.Err() err := scanner.Err()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -59,11 +66,11 @@ func main() {
fmt.Println("Something went wrong!") fmt.Println("Something went wrong!")
} }
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
if !get_site_details() { if !getSiteDetails() {
err := scanner.Err() err := scanner.Err()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -71,66 +78,66 @@ func main() {
fmt.Println("Something went wrong!") fmt.Println("Something went wrong!")
} }
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
err := init_database() err := initDatabase()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
err = table_defs() err = tableDefs()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
hashed_password, salt, err := BcryptGeneratePassword("password") hashedPassword, salt, err := BcryptGeneratePassword("password")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
// Build the admin user query // Build the admin user query
admin_user_stmt, err := qgen.Builder.SimpleInsert("users","name, password, salt, email, group, is_super_admin, active, createdAt, lastActiveAt, message, last_ip","'Admin',?,?,'admin@localhost',1,1,1,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'','127.0.0.1'") adminUserStmt, err := qgen.Builder.SimpleInsert("users", "name, password, salt, email, group, is_super_admin, active, createdAt, lastActiveAt, message, last_ip", "'Admin',?,?,'admin@localhost',1,1,1,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'','127.0.0.1'")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
// Run the admin user query // Run the admin user query
_, err = admin_user_stmt.Exec(hashed_password,salt) _, err = adminUserStmt.Exec(hashedPassword, salt)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
err = initial_data() err = initialData()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
if db_adapter == "mysql" { if dbAdapter == "mysql" {
err = _mysql_seed_database() err = _mysqlSeedDatabase()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
} }
@ -139,10 +146,10 @@ func main() {
func init() { func init() {
// Site Info // Site Info
site.Name = "` + site_name + `" // Should be a setting in the database site.Name = "` + siteName + `" // Should be a setting in the database
site.Email = "" // Should be a setting in the database site.Email = "" // Should be a setting in the database
site.Url = "` + site_url + `" site.Url = "` + siteURL + `"
site.Port = "` + server_port + `" 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
@ -150,11 +157,11 @@ config.SslPrivkey = ""
config.SslFullchain = "" config.SslFullchain = ""
// Database details // Database details
db_config.Host = "` + db_host + `" db_config.Host = "` + dbHost + `"
db_config.Username = "` + db_username + `" db_config.Username = "` + dbUsername + `"
db_config.Password = "` + db_password + `" db_config.Password = "` + dbPassword + `"
db_config.Dbname = "` + db_name + `" db_config.Dbname = "` + dbName + `"
db_config.Port = "` + db_port + `" // You probably won't need to change this db_config.Port = "` + dbPort + `" // You probably won't need to change this
// Limiters // Limiters
config.MaxRequestSize = 5 * megabyte config.MaxRequestSize = 5 * megabyte
@ -195,7 +202,7 @@ dev.DebugMode = true
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
@ -204,7 +211,7 @@ dev.DebugMode = true
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Aborting installation...") fmt.Println("Aborting installation...")
press_any_key() pressAnyKey()
return return
} }
@ -214,121 +221,122 @@ dev.DebugMode = true
fmt.Println("Yay, you have successfully installed Gosora!") fmt.Println("Yay, you have successfully installed Gosora!")
fmt.Println("Your name is Admin and you can login with the password 'password'. Don't forget to change it! Seriously. It's really insecure.") fmt.Println("Your name is Admin and you can login with the password 'password'. Don't forget to change it! Seriously. It's really insecure.")
press_any_key() pressAnyKey()
} }
func get_database_details() bool { func getDatabaseDetails() bool {
fmt.Println("Which database driver do you wish to use? mysql, mysql, or mysql? Default: mysql") fmt.Println("Which database driver do you wish to use? mysql, mysql, or mysql? Default: mysql")
if !scanner.Scan() { if !scanner.Scan() {
return false return false
} }
db_adapter = scanner.Text() dbAdapter = scanner.Text()
if db_adapter == "" { if dbAdapter == "" {
db_adapter = default_adapter dbAdapter = defaultAdapter
} }
db_adapter = set_db_adapter(db_adapter) dbAdapter = setDBAdapter(dbAdapter)
fmt.Println("Set database adapter to " + db_adapter) fmt.Println("Set database adapter to " + dbAdapter)
fmt.Println("Database Host? Default: " + default_host) fmt.Println("Database Host? Default: " + defaultHost)
if !scanner.Scan() { if !scanner.Scan() {
return false return false
} }
db_host = scanner.Text() dbHost = scanner.Text()
if db_host == "" { if dbHost == "" {
db_host = default_host dbHost = defaultHost
} }
fmt.Println("Set database host to " + db_host) fmt.Println("Set database host to " + dbHost)
fmt.Println("Database Username? Default: " + default_username) fmt.Println("Database Username? Default: " + defaultUsername)
if !scanner.Scan() { if !scanner.Scan() {
return false return false
} }
db_username = scanner.Text() dbUsername = scanner.Text()
if db_username == "" { if dbUsername == "" {
db_username = default_username dbUsername = defaultUsername
} }
fmt.Println("Set database username to " + db_username) fmt.Println("Set database username to " + dbUsername)
fmt.Println("Database Password? Default: ''") fmt.Println("Database Password? Default: ''")
if !scanner.Scan() { if !scanner.Scan() {
return false return false
} }
db_password = scanner.Text() dbPassword = scanner.Text()
if len(db_password) == 0 { if len(dbPassword) == 0 {
fmt.Println("You didn't set a password for this user. This won't block the installation process, but it might create security issues in the future.\n") fmt.Println("You didn't set a password for this user. This won't block the installation process, but it might create security issues in the future.")
fmt.Println("")
} else { } else {
fmt.Println("Set password to " + obfuscate_password(db_password)) fmt.Println("Set password to " + obfuscatePassword(dbPassword))
} }
fmt.Println("Database Name? Pick a name you like or one provided to you. Default: " + default_dbname) fmt.Println("Database Name? Pick a name you like or one provided to you. Default: " + defaultDbname)
if !scanner.Scan() { if !scanner.Scan() {
return false return false
} }
db_name = scanner.Text() dbName = scanner.Text()
if db_name == "" { if dbName == "" {
db_name = default_dbname dbName = defaultDbname
} }
fmt.Println("Set database name to " + db_name) fmt.Println("Set database name to " + dbName)
return true return true
} }
func get_site_details() bool { func getSiteDetails() bool {
fmt.Println("Okay. We also need to know some actual information about your site!") fmt.Println("Okay. We also need to know some actual information about your site!")
fmt.Println("What's your site's name? Default: " + default_site_name) fmt.Println("What's your site's name? Default: " + defaultSiteName)
if !scanner.Scan() { if !scanner.Scan() {
return false return false
} }
site_name = scanner.Text() siteName = scanner.Text()
if site_name == "" { if siteName == "" {
site_name = default_site_name siteName = defaultSiteName
} }
fmt.Println("Set the site name to " + site_name) fmt.Println("Set the site name to " + siteName)
fmt.Println("What's your site's url? Default: " + default_site_url) fmt.Println("What's your site's url? Default: " + defaultsiteURL)
if !scanner.Scan() { if !scanner.Scan() {
return false return false
} }
site_url = scanner.Text() siteURL = scanner.Text()
if site_url == "" { if siteURL == "" {
site_url = default_site_url siteURL = defaultsiteURL
} }
fmt.Println("Set the site url to " + site_url) fmt.Println("Set the site url to " + siteURL)
fmt.Println("What port do you want the server to listen on? If you don't know what this means, you should probably leave it on the default. Default: " + default_server_port) fmt.Println("What port do you want the server to listen on? If you don't know what this means, you should probably leave it on the default. Default: " + defaultServerPort)
if !scanner.Scan() { if !scanner.Scan() {
return false return false
} }
server_port = scanner.Text() serverPort = scanner.Text()
if server_port == "" { if serverPort == "" {
server_port = default_server_port serverPort = defaultServerPort
} }
_, err := strconv.Atoi(server_port) _, err := strconv.Atoi(serverPort)
if err != nil { if err != nil {
fmt.Println("That's not a valid number!") fmt.Println("That's not a valid number!")
return false return false
} }
fmt.Println("Set the server port to " + server_port) fmt.Println("Set the server port to " + serverPort)
return true return true
} }
func set_db_adapter(name string) string { func setDBAdapter(name string) string {
switch(name) { switch name {
//case "wip-pgsql": //case "wip-pgsql":
// set_pgsql_adapter() // set_pgsql_adapter()
// return "wip-pgsql" // return "wip-pgsql"
} }
_set_mysql_adapter() _setMysqlAdapter()
return "mysql" return "mysql"
} }
func obfuscate_password(password string) (out string) { func obfuscatePassword(password string) (out string) {
for i := 0; i < len(password); i++ { for i := 0; i < len(password); i++ {
out += "*" out += "*"
} }
return out return out
} }
func press_any_key() { func pressAnyKey() {
//fmt.Println("Press any key to exit...") //fmt.Println("Press any key to exit...")
fmt.Println("Please press enter to exit...") fmt.Println("Please press enter to exit...")
for scanner.Scan() { for scanner.Scan() {

View File

@ -1,34 +1,39 @@
/* Copyright Azareal 2017 - 2018 */ /*
*
* Gosora MySQL Interface
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import ( import (
"fmt"
"bytes" "bytes"
"strings" "database/sql"
"strconv" "fmt"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"database/sql" "strconv"
"strings"
"../query_gen/lib" "../query_gen/lib"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
) )
//var db_collation string = "utf8mb4_general_ci" //var dbCollation string = "utf8mb4_general_ci"
func _set_mysql_adapter() { func _setMysqlAdapter() {
db_port = "3306" dbPort = "3306"
init_database = _init_mysql initDatabase = _initMysql
table_defs = _table_defs_mysql tableDefs = _tableDefsMysql
initial_data = _initial_data_mysql initialData = _initialDataMysql
} }
func _init_mysql() (err error) { func _initMysql() (err error) {
_db_password := db_password _dbPassword := dbPassword
if _db_password != "" { if _dbPassword != "" {
_db_password = ":" + _db_password _dbPassword = ":" + _dbPassword
} }
db, err = sql.Open("mysql",db_username + _db_password + "@tcp(" + db_host + ":" + db_port + ")/") db, err = sql.Open("mysql", dbUsername+_dbPassword+"@tcp("+dbHost+":"+dbPort+")/")
if err != nil { if err != nil {
return err return err
} }
@ -41,22 +46,22 @@ func _init_mysql() (err error) {
fmt.Println("Successfully connected to the database") fmt.Println("Successfully connected to the database")
var waste string var waste string
err = db.QueryRow("SHOW DATABASES LIKE '" + db_name + "'").Scan(&waste) err = db.QueryRow("SHOW DATABASES LIKE '" + dbName + "'").Scan(&waste)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return err return err
} }
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 " + db_name + "") _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + dbName + "")
if err != nil { if err != nil {
return err return err
} }
fmt.Println("The database was successfully created") fmt.Println("The database was successfully created")
} }
fmt.Println("Switching to database " + db_name) fmt.Println("Switching to database " + dbName)
_, err = db.Exec("USE " + db_name) _, err = db.Exec("USE " + dbName)
if err != nil { if err != nil {
return err return err
} }
@ -71,22 +76,22 @@ func _init_mysql() (err error) {
return nil return nil
} }
func _table_defs_mysql() error { func _tableDefsMysql() error {
//fmt.Println("Creating the tables") //fmt.Println("Creating the tables")
files, _ := ioutil.ReadDir("./schema/mysql/") files, _ := ioutil.ReadDir("./schema/mysql/")
for _, f := range files { for _, f := range files {
if !strings.HasPrefix(f.Name(),"query_") { if !strings.HasPrefix(f.Name(), "query_") {
continue continue
} }
var table string var table string
var ext string var ext string
table = strings.TrimPrefix(f.Name(),"query_") table = strings.TrimPrefix(f.Name(), "query_")
ext = filepath.Ext(table) ext = filepath.Ext(table)
if ext != ".sql" { if ext != ".sql" {
continue continue
} }
table = strings.TrimSuffix(table,ext) table = strings.TrimSuffix(table, ext)
fmt.Println("Creating table '" + table + "'") fmt.Println("Creating table '" + table + "'")
data, err := ioutil.ReadFile("./schema/mysql/" + f.Name()) data, err := ioutil.ReadFile("./schema/mysql/" + f.Name())
@ -97,7 +102,7 @@ func _table_defs_mysql() error {
_, err = db.Exec(string(data)) _, err = db.Exec(string(data))
if err != nil { if err != nil {
fmt.Println("Failed query:",string(data)) fmt.Println("Failed query:", string(data))
return err return err
} }
} }
@ -105,10 +110,10 @@ func _table_defs_mysql() error {
return nil return nil
} }
func _initial_data_mysql() error { func _initialDataMysql() error {
return nil // Coming Soon return nil // Coming Soon
fmt.Println("Seeding the tables") /*fmt.Println("Seeding the tables")
data, err := ioutil.ReadFile("./schema/mysql/inserts.sql") data, err := ioutil.ReadFile("./schema/mysql/inserts.sql")
if err != nil { if err != nil {
return err return err
@ -122,10 +127,10 @@ func _initial_data_mysql() error {
} }
//fmt.Println("Finished inserting the database data") //fmt.Println("Finished inserting the database data")
return nil return nil*/
} }
func _mysql_seed_database() error { func _mysqlSeedDatabase() error {
fmt.Println("Opening the database seed file") fmt.Println("Opening the database seed file")
sqlContents, err := ioutil.ReadFile("./mysql.sql") sqlContents, err := ioutil.ReadFile("./mysql.sql")
if err != nil { if err != nil {

View File

@ -1,5 +1,10 @@
/* Under heavy development */ /*
/* Copyright Azareal 2017 - 2018 */ *
* Gosora PostgreSQL Interface
* Under heavy development
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import "fmt" import "fmt"
@ -8,19 +13,19 @@ import "database/sql"
import _ "github.com/go-sql-driver/mysql" import _ "github.com/go-sql-driver/mysql"
// We don't need SSL to run an installer... Do we? // We don't need SSL to run an installer... Do we?
var db_sslmode = "disable" var dbSslmode = "disable"
func _set_pgsql_adapter() { func _setPgsqlAdapter() {
db_port = "5432" dbPort = "5432"
init_database = _init_pgsql initDatabase = _initPgsql
} }
func _init_pgsql() (err error) { func _initPgsql() (err error) {
_db_password := db_password _dbPassword := dbPassword
if _db_password != "" { if _dbPassword != "" {
_db_password = " password=" + _pg_escape_bit(_db_password) _dbPassword = " password=" + _pgEscapeBit(_dbPassword)
} }
db, err = sql.Open("postgres", "host='" + _pg_escape_bit(db_host) + "' port='" + _pg_escape_bit(db_port) + "' user='" + _pg_escape_bit(db_username) + "' dbname='" + _pg_escape_bit(db_name) + "'" + _db_password + " sslmode='" + db_sslmode + "'") db, err = sql.Open("postgres", "host='"+_pgEscapeBit(dbHost)+"' port='"+_pgEscapeBit(dbPort)+"' user='"+_pgEscapeBit(dbUsername)+"' dbname='"+_pgEscapeBit(dbName)+"'"+_dbPassword+" sslmode='"+dbSslmode+"'")
if err != nil { if err != nil {
return err return err
} }
@ -31,7 +36,7 @@ func _init_pgsql() (err error) {
return nil return nil
} }
func _pg_escape_bit(bit string) string { func _pgEscapeBit(bit string) string {
// TO-DO: Write a custom parser, so that backslashes work properly in the sql.Open string. Do something similar for the database driver, if possible? // TO-DO: Write a custom parser, so that backslashes work properly in the sql.Open string. Do something similar for the database driver, if possible?
return strings.Replace(bit,"'","\\'",-1) return strings.Replace(bit, "'", "\\'", -1)
} }

View File

@ -6,7 +6,7 @@ import "golang.org/x/crypto/bcrypt"
// Generate a cryptographically secure set of random bytes.. // Generate a cryptographically secure set of random bytes..
func GenerateSafeString(length int) (string, error) { func GenerateSafeString(length int) (string, error) {
rb := make([]byte,length) rb := make([]byte, length)
_, err := rand.Read(rb) _, err := rand.Read(rb)
if err != nil { if err != nil {
return "", err return "", err
@ -14,24 +14,25 @@ func GenerateSafeString(length int) (string, error) {
return base64.URLEncoding.EncodeToString(rb), nil return base64.URLEncoding.EncodeToString(rb), nil
} }
func BcryptGeneratePassword(password string) (hashed_password string, salt string, err error) { // Generate a bcrypt hash from a password and a salt
func BcryptGeneratePassword(password string) (hashedPassword string, salt string, err error) {
salt, err = GenerateSafeString(saltLength) salt, err = GenerateSafeString(saltLength)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
password = password + salt password = password + salt
hashed_password, err = BcryptGeneratePasswordNoSalt(password) hashedPassword, err = bcryptGeneratePasswordNoSalt(password)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return hashed_password, salt, nil return hashedPassword, salt, nil
} }
func BcryptGeneratePasswordNoSalt(password string) (hash string, err error) { func bcryptGeneratePasswordNoSalt(password string) (hash string, err error) {
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return "", err return "", err
} }
return string(hashed_password), nil return string(hashedPassword), nil
} }

121
main.go
View File

@ -3,17 +3,17 @@ package main
import ( import (
"fmt" "fmt"
"log"
"strings"
"time"
"io" "io"
"os" "log"
"sync/atomic"
"net/http" "net/http"
"os"
"strings"
"sync/atomic"
"time"
//"runtime/pprof" //"runtime/pprof"
) )
var version Version = Version{Major:0,Minor:1,Patch:0,Tag:"dev"} var version = Version{Major: 0, Minor: 1, Patch: 0, Tag: "dev"}
const hour int = 60 * 60 const hour int = 60 * 60
const day int = hour * 24 const day int = hour * 24
@ -26,38 +26,40 @@ const gigabyte int = megabyte * 1024
const terabyte int = gigabyte * 1024 const terabyte int = gigabyte * 1024
const saltLength int = 32 const saltLength int = 32
const sessionLength int = 80 const sessionLength int = 80
var enable_websockets bool = false // Don't change this, the value is overwritten by an initialiser
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
var external_sites map[string]string = map[string]string{ var externalSites = map[string]string{
"YT":"https://www.youtube.com/", "YT": "https://www.youtube.com/",
} }
var groups []Group var groups []Group
var groupCapCount int var groupCapCount int
var static_files map[string]SFile = make(map[string]SFile) var staticFiles = make(map[string]SFile)
var logWriter io.Writer = io.MultiWriter(os.Stderr) var logWriter = io.MultiWriter(os.Stderr)
type WordFilter struct { type WordFilter struct {
ID int ID int
Find string Find string
Replacement string Replacement string
} }
type WordFilterBox map[int]WordFilter type WordFilterBox map[int]WordFilter
var wordFilterBox atomic.Value // An atomic value holding a WordFilterBox var wordFilterBox atomic.Value // An atomic value holding a WordFilterBox
func init() { func init() {
wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter))) wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter)))
} }
func init_word_filters() error { func initWordFilters() error {
rows, err := get_word_filters_stmt.Query() rows, err := get_word_filters_stmt.Query()
if err != nil { if err != nil {
return err return err
} }
defer rows.Close() defer rows.Close()
var wordFilters WordFilterBox = wordFilterBox.Load().(WordFilterBox) var wordFilters = wordFilterBox.Load().(WordFilterBox)
var wfid int var wfid int
var find string var find string
var replacement string var replacement string
@ -67,36 +69,36 @@ func init_word_filters() error {
if err != nil { if err != nil {
return err return err
} }
wordFilters[wfid] = WordFilter{ID:wfid,Find:find,Replacement:replacement} wordFilters[wfid] = WordFilter{ID: wfid, Find: find, Replacement: replacement}
} }
wordFilterBox.Store(wordFilters) wordFilterBox.Store(wordFilters)
return rows.Err() return rows.Err()
} }
func add_word_filter(id int, find string, replacement string) { func addWordFilter(id int, find string, replacement string) {
wordFilters := wordFilterBox.Load().(WordFilterBox) wordFilters := wordFilterBox.Load().(WordFilterBox)
wordFilters[id] = WordFilter{ID:id,Find:find,Replacement:replacement} wordFilters[id] = WordFilter{ID: id, Find: find, Replacement: replacement}
wordFilterBox.Store(wordFilters) wordFilterBox.Store(wordFilters)
} }
func process_config() { 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
} }
} }
func main(){ func main() {
// TO-DO: Have a file for each run with the time/date the server started as the file name? // TO-DO: Have a file for each run with the time/date the server started as the file name?
// TO-DO: Log panics with recover() // TO-DO: Log panics with recover()
f, err := os.OpenFile("./operations.log",os.O_WRONLY|os.O_APPEND|os.O_CREATE,0755) f, err := os.OpenFile("./operations.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
logWriter = io.MultiWriter(os.Stderr,f) logWriter = io.MultiWriter(os.Stderr, f)
log.SetOutput(logWriter) log.SetOutput(logWriter)
//if profiling { //if profiling {
@ -113,16 +115,20 @@ func main(){
//timeLocation = startTime.Location() //timeLocation = startTime.Location()
log.Print("Processing configuration data") log.Print("Processing configuration data")
process_config() processConfig()
init_themes() err = initThemes()
err = init_database()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
init_templates() err = initDatabase()
err = init_errors() if err != nil {
log.Fatal(err)
}
initTemplates()
err = initErrors()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -135,10 +141,14 @@ func main(){
topics = NewSqlTopicStore() topics = NewSqlTopicStore()
} }
init_static_files() log.Print("Loading the static files.")
err = initStaticFiles()
if err != nil {
log.Fatal(err)
}
log.Print("Initialising the widgets") log.Print("Initialising the widgets")
err = init_widgets() err = initWidgets()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -146,34 +156,34 @@ func main(){
log.Print("Initialising the authentication system") log.Print("Initialising the authentication system")
auth = NewDefaultAuth() auth = NewDefaultAuth()
err = init_word_filters() err = initWordFilters()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// Run this goroutine once a second // Run this goroutine once a second
second_ticker := time.NewTicker(1 * time.Second) secondTicker := time.NewTicker(1 * time.Second)
fifteen_minute_ticker := time.NewTicker(15 * time.Minute) fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
//hour_ticker := time.NewTicker(1 * time.Hour) //hour_ticker := time.NewTicker(1 * time.Hour)
go func() { go func() {
for { for {
select { select {
case <- second_ticker.C: case <-secondTicker.C:
//log.Print("Running the second ticker") //log.Print("Running the second ticker")
err := handle_expired_scheduled_groups() err := handleExpiredScheduledGroups()
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
// TO-DO: Handle delayed moderation tasks // TO-DO: Handle delayed moderation tasks
// TO-DO: Handle the daily clean-up. Move this to a 24 hour task? // TO-DO: Handle the daily clean-up. Move this to a 24 hour task?
// TO-DO: Sync with the database, if there are any changes // TO-DO: Sync with the database, if there are any changes
// TO-DO: Manage the TopicStore, UserStore, and ForumStore // TO-DO: Manage the TopicStore, UserStore, and ForumStore
// TO-DO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high // TO-DO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
// TO-DO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task? // TO-DO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
case <- fifteen_minute_ticker.C: case <-fifteenMinuteTicker.C:
// TO-DO: Automatically lock topics, if they're really old, and the associated setting is enabled. // TO-DO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TO-DO: Publish scheduled posts. // TO-DO: Publish scheduled posts.
// TO-DO: Delete the empty users_groups_scheduler entries // TO-DO: Delete the empty users_groups_scheduler entries
} }
} }
}() }()
@ -231,6 +241,7 @@ func main(){
router.HandleFunc("/users/ban/submit/", route_ban_submit) router.HandleFunc("/users/ban/submit/", route_ban_submit)
router.HandleFunc("/users/unban/", route_unban) router.HandleFunc("/users/unban/", route_unban)
router.HandleFunc("/users/activate/", route_activate) router.HandleFunc("/users/activate/", route_activate)
router.HandleFunc("/users/ips/", route_ips)
// The Control Panel // The Control Panel
///router.HandleFunc("/panel/", route_panel) ///router.HandleFunc("/panel/", route_panel)
@ -267,7 +278,7 @@ func main(){
router.HandleFunc("/ws/", route_websockets) router.HandleFunc("/ws/", route_websockets)
log.Print("Initialising the plugins") log.Print("Initialising the plugins")
init_plugins() initPlugins()
defer db.Close() defer db.Close()
@ -281,7 +292,7 @@ func main(){
if site.Port == "" { if site.Port == "" {
site.Port = "80" site.Port = "80"
} }
err = http.ListenAndServe(":" + site.Port, router) err = http.ListenAndServe(":"+site.Port, router)
} else { } else {
if site.Port == "" { if site.Port == "" {
site.Port = "443" site.Port = "443"
@ -296,7 +307,7 @@ func main(){
} }
}() }()
} }
err = http.ListenAndServeTLS(":" + site.Port, config.SslFullchain, config.SslPrivkey, router) err = http.ListenAndServeTLS(":"+site.Port, config.SslFullchain, config.SslPrivkey, router)
} }
// Why did the server stop? // Why did the server stop?

View File

@ -5,210 +5,213 @@ import "testing"
// TO-DO: Generate a test database to work with rather than a live one // TO-DO: Generate a test database to work with rather than a live one
func TestUserStore(t *testing.T) { func TestUserStore(t *testing.T) {
if !gloinited { if !gloinited {
gloinit() err := gloinit()
if err != nil {
t.Fatal(err)
}
} }
if !plugins_inited { if !pluginsInited {
init_plugins() initPlugins()
} }
var user *User var user *User
var err error var err error
user, err = users.CascadeGet(-1) user, err = users.CascadeGet(-1)
if err == nil { if err == nil {
t.Error("UID #-1 shouldn't exist") t.Error("UID #-1 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
t.Fatal(err) t.Fatal(err)
} }
user, err = users.CascadeGet(0) user, err = users.CascadeGet(0)
if err == nil { if err == nil {
t.Error("UID #0 shouldn't exist") t.Error("UID #0 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
t.Fatal(err) t.Fatal(err)
} }
user, err = users.CascadeGet(1) user, err = users.CascadeGet(1)
if err == ErrNoRows { if err == ErrNoRows {
t.Error("Couldn't find UID #1") t.Error("Couldn't find UID #1")
} else if err != nil { } else if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if user.ID != 1 { if user.ID != 1 {
t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.") t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
} }
// TO-DO: Lock onto the specific error type. Is this even possible without sacrificing the detailed information in the error message? // TO-DO: Lock onto the specific error type. Is this even possible without sacrificing the detailed information in the error message?
var userList map[int]*User var userList map[int]*User
userList, err = users.BulkCascadeGetMap([]int{-1}) _, err = users.BulkCascadeGetMap([]int{-1})
if err == nil { if err == nil {
t.Error("UID #-1 shouldn't exist") t.Error("UID #-1 shouldn't exist")
} }
userList, err = users.BulkCascadeGetMap([]int{0}) _, err = users.BulkCascadeGetMap([]int{0})
if err == nil { if err == nil {
t.Error("UID #0 shouldn't exist") t.Error("UID #0 shouldn't exist")
} }
userList, err = users.BulkCascadeGetMap([]int{1}) userList, err = users.BulkCascadeGetMap([]int{1})
if err == ErrNoRows { if err == ErrNoRows {
t.Error("Couldn't find UID #1") t.Error("Couldn't find UID #1")
} else if err != nil { } else if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(userList) == 0 { if len(userList) == 0 {
t.Error("The returned map is empty for UID #0") t.Error("The returned map is empty for UID #0")
} else if len(userList) > 1 { } else if len(userList) > 1 {
t.Error("Too many results were returned for UID #0") t.Error("Too many results were returned for UID #0")
} }
user, ok := userList[1] user, ok := userList[1]
if !ok { if !ok {
t.Error("We couldn't find UID #0 in the returned map") t.Error("We couldn't find UID #0 in the returned map")
t.Error("userList",userList) t.Error("userList", userList)
} }
if user.ID != 1 { if user.ID != 1 {
t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.") t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
} }
} }
func TestForumStore(t *testing.T) { func TestForumStore(t *testing.T) {
if !gloinited { if !gloinited {
gloinit() gloinit()
} }
if !plugins_inited { if !pluginsInited {
init_plugins() initPlugins()
} }
var forum *Forum var forum *Forum
var err error var err error
forum, err = fstore.CascadeGet(-1) forum, err = fstore.CascadeGet(-1)
if err == nil { if err == nil {
t.Error("FID #-1 shouldn't exist") t.Error("FID #-1 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
t.Fatal(err) t.Fatal(err)
} }
forum, err = fstore.CascadeGet(0) forum, err = fstore.CascadeGet(0)
if err == ErrNoRows { if err == ErrNoRows {
t.Error("Couldn't find FID #0") t.Error("Couldn't find FID #0")
} else if err != nil { } else if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if forum.ID != 0 { if forum.ID != 0 {
t.Error("forum.ID doesn't not match the requested UID. Got '" + strconv.Itoa(forum.ID) + "' instead.") t.Error("forum.ID doesn't not match the requested UID. Got '" + strconv.Itoa(forum.ID) + "' instead.")
} }
if forum.Name != "Uncategorised" { if forum.Name != "Uncategorised" {
t.Error("FID #0 is named '" + forum.Name + "' and not 'Uncategorised'") t.Error("FID #0 is named '" + forum.Name + "' and not 'Uncategorised'")
} }
forum, err = fstore.CascadeGet(1) forum, err = fstore.CascadeGet(1)
if err == ErrNoRows { if err == ErrNoRows {
t.Error("Couldn't find FID #1") t.Error("Couldn't find FID #1")
} else if err != nil { } else if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if forum.ID != 1 { if forum.ID != 1 {
t.Error("forum.ID doesn't not match the requested UID. Got '" + strconv.Itoa(forum.ID) + "' instead.'") t.Error("forum.ID doesn't not match the requested UID. Got '" + strconv.Itoa(forum.ID) + "' instead.'")
} }
if forum.Name != "Reports" { if forum.Name != "Reports" {
t.Error("FID #0 is named '" + forum.Name + "' and not 'Reports'") t.Error("FID #0 is named '" + forum.Name + "' and not 'Reports'")
} }
forum, err = fstore.CascadeGet(2) forum, err = fstore.CascadeGet(2)
if err == ErrNoRows { if err == ErrNoRows {
t.Error("Couldn't find FID #2") t.Error("Couldn't find FID #2")
} else if err != nil { } else if err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestSlugs(t *testing.T) { func TestSlugs(t *testing.T) {
var res string var res string
var msgList []ME_Pair var msgList []ME_Pair
msgList = addMEPair(msgList,"Unknown","unknown") msgList = addMEPair(msgList, "Unknown", "unknown")
msgList = addMEPair(msgList,"Unknown2","unknown2") msgList = addMEPair(msgList, "Unknown2", "unknown2")
msgList = addMEPair(msgList,"Unknown ","unknown") msgList = addMEPair(msgList, "Unknown ", "unknown")
msgList = addMEPair(msgList,"Unknown 2","unknown-2") msgList = addMEPair(msgList, "Unknown 2", "unknown-2")
msgList = addMEPair(msgList,"Unknown 2","unknown-2") msgList = addMEPair(msgList, "Unknown 2", "unknown-2")
msgList = addMEPair(msgList,"Admin Alice","admin-alice") msgList = addMEPair(msgList, "Admin Alice", "admin-alice")
msgList = addMEPair(msgList,"Admin_Alice","adminalice") msgList = addMEPair(msgList, "Admin_Alice", "adminalice")
msgList = addMEPair(msgList,"Admin_Alice-","adminalice") msgList = addMEPair(msgList, "Admin_Alice-", "adminalice")
msgList = addMEPair(msgList,"-Admin_Alice-","adminalice") msgList = addMEPair(msgList, "-Admin_Alice-", "adminalice")
msgList = addMEPair(msgList,"-Admin@Alice-","adminalice") msgList = addMEPair(msgList, "-Admin@Alice-", "adminalice")
msgList = addMEPair(msgList,"-Admin😀Alice-","adminalice") msgList = addMEPair(msgList, "-Admin😀Alice-", "adminalice")
msgList = addMEPair(msgList,"u","u") msgList = addMEPair(msgList, "u", "u")
msgList = addMEPair(msgList,"","untitled") msgList = addMEPair(msgList, "", "untitled")
msgList = addMEPair(msgList," ","untitled") msgList = addMEPair(msgList, " ", "untitled")
msgList = addMEPair(msgList,"-","untitled") msgList = addMEPair(msgList, "-", "untitled")
msgList = addMEPair(msgList,"--","untitled") msgList = addMEPair(msgList, "--", "untitled")
msgList = addMEPair(msgList,"é","é") msgList = addMEPair(msgList, "é", "é")
msgList = addMEPair(msgList,"-é-","é") msgList = addMEPair(msgList, "-é-", "é")
for _, item := range msgList { for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'") t.Log("Testing string '" + item.Msg + "'")
res = name_to_slug(item.Msg) res = nameToSlug(item.Msg)
if res != item.Expects { if res != item.Expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",item.Expects) t.Error("Expected:", item.Expects)
} }
} }
} }
func TestAuth(t *testing.T) { func TestAuth(t *testing.T) {
// bcrypt likes doing stupid things, so this test will probably fail // bcrypt likes doing stupid things, so this test will probably fail
var real_password string var realPassword string
var hashed_password string var hashedPassword string
var password string var password string
var salt string var salt string
var err error var err error
/* No extra salt tests, we might not need this extra salt, as bcrypt has it's own? */ /* No extra salt tests, we might not need this extra salt, as bcrypt has it's own? */
real_password = "Madame Cassandra's Mystic Orb" realPassword = "Madame Cassandra's Mystic Orb"
t.Log("Set real_password to '" + real_password + "'") t.Log("Set real_password to '" + realPassword + "'")
t.Log("Hashing the real password") t.Log("Hashing the real password")
hashed_password, err = BcryptGeneratePasswordNoSalt(real_password) hashedPassword, err = BcryptGeneratePasswordNoSalt(realPassword)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
password = real_password password = realPassword
t.Log("Testing password '" + password + "'") t.Log("Testing password '" + password + "'")
t.Log("Testing salt '" + salt + "'") t.Log("Testing salt '" + salt + "'")
err = CheckPassword(hashed_password,password,salt) err = CheckPassword(hashedPassword, password, salt)
if err == ErrMismatchedHashAndPassword { if err == ErrMismatchedHashAndPassword {
t.Error("The two don't match") t.Error("The two don't match")
} else if err == ErrPasswordTooLong { } else if err == ErrPasswordTooLong {
t.Error("CheckPassword thinks the password is too long") t.Error("CheckPassword thinks the password is too long")
} else if err != nil { } else if err != nil {
t.Error(err) t.Error(err)
} }
password = "hahaha" password = "hahaha"
t.Log("Testing password '" + password + "'") t.Log("Testing password '" + password + "'")
t.Log("Testing salt '" + salt + "'") t.Log("Testing salt '" + salt + "'")
err = CheckPassword(hashed_password,password,salt) err = CheckPassword(hashedPassword, password, salt)
if err == ErrPasswordTooLong { if err == ErrPasswordTooLong {
t.Error("CheckPassword thinks the password is too long") t.Error("CheckPassword thinks the password is too long")
} else if err == nil { } else if err == nil {
t.Error("The two shouldn't match!") t.Error("The two shouldn't match!")
} }
password = "Madame Cassandra's Mystic" password = "Madame Cassandra's Mystic"
t.Log("Testing password '" + password + "'") t.Log("Testing password '" + password + "'")
t.Log("Testing salt '" + salt + "'") t.Log("Testing salt '" + salt + "'")
err = CheckPassword(hashed_password,password,salt) err = CheckPassword(hashedPassword, password, salt)
if err == ErrPasswordTooLong { if err == ErrPasswordTooLong {
t.Error("CheckPassword thinks the password is too long") t.Error("CheckPassword thinks the password is too long")
} else if err == nil { } else if err == nil {
t.Error("The two shouldn't match!") t.Error("The two shouldn't match!")
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -4,31 +4,35 @@
package main package main
import "log" import "log"
//import "time" //import "time"
import "database/sql" import "database/sql"
import _ "github.com/go-sql-driver/mysql" import _ "github.com/go-sql-driver/mysql"
import "./query_gen/lib" import "./query_gen/lib"
var db_collation string = "utf8mb4_general_ci" var dbCollation = "utf8mb4_general_ci"
var get_activity_feed_by_watcher_stmt *sql.Stmt var get_activity_feed_by_watcher_stmt *sql.Stmt
var get_activity_count_by_watcher_stmt *sql.Stmt var get_activity_count_by_watcher_stmt *sql.Stmt
var todays_post_count_stmt *sql.Stmt var todays_post_count_stmt *sql.Stmt
var todays_topic_count_stmt *sql.Stmt var todays_topic_count_stmt *sql.Stmt
var todays_report_count_stmt *sql.Stmt var todays_report_count_stmt *sql.Stmt
var todays_newuser_count_stmt *sql.Stmt var todays_newuser_count_stmt *sql.Stmt
var find_users_by_ip_users_stmt *sql.Stmt
var find_users_by_ip_topics_stmt *sql.Stmt
var find_users_by_ip_replies_stmt *sql.Stmt
func init() { func init() {
db_adapter = "mysql" dbAdapter = "mysql"
} }
func _init_database() (err error) { func _initDatabase() (err error) {
var _dbpassword string var _dbpassword string
if(db_config.Password != ""){ if db_config.Password != "" {
_dbpassword = ":" + db_config.Password _dbpassword = ":" + db_config.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=" + db_collation) db, err = sql.Open("mysql", db_config.Username+_dbpassword+"@tcp("+db_config.Host+":"+db_config.Port+")/"+db_config.Dbname+"?collation="+dbCollation)
if err != nil { if err != nil {
return err return err
} }
@ -40,7 +44,7 @@ func _init_database() (err error) {
} }
// Fetch the database version // Fetch the database version
db.QueryRow("SELECT VERSION()").Scan(&db_version) db.QueryRow("SELECT VERSION()").Scan(&dbVersion)
// Set the number of max open connections // Set the number of max open connections
db.SetMaxOpenConns(64) db.SetMaxOpenConns(64)
@ -62,6 +66,7 @@ func _init_database() (err error) {
return err return err
} }
// TO-DO: Is there a less noisy way of doing this for tests?
log.Print("Preparing get_activity_feed_by_watcher statement.") log.Print("Preparing get_activity_feed_by_watcher statement.")
get_activity_feed_by_watcher_stmt, err = db.Prepare("SELECT activity_stream_matches.asid, activity_stream.actor, activity_stream.targetUser, activity_stream.event, activity_stream.elementType, activity_stream.elementID FROM `activity_stream_matches` INNER JOIN `activity_stream` ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE `watcher` = ? ORDER BY activity_stream.asid ASC LIMIT 8") get_activity_feed_by_watcher_stmt, err = db.Prepare("SELECT activity_stream_matches.asid, activity_stream.actor, activity_stream.targetUser, activity_stream.event, activity_stream.elementType, activity_stream.elementID FROM `activity_stream_matches` INNER JOIN `activity_stream` ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE `watcher` = ? ORDER BY activity_stream.asid ASC LIMIT 8")
if err != nil { if err != nil {
@ -98,5 +103,19 @@ func _init_database() (err error) {
return err return err
} }
return nil log.Print("Preparing find_users_by_ip_users statement.")
find_users_by_ip_users_stmt, err = db.Prepare("select uid from users where last_ip = ?")
if err != nil {
return err
}
log.Print("Preparing find_users_by_ip_topics statement.")
find_users_by_ip_topics_stmt, err = db.Prepare("select uid from users where uid in(select createdBy from topics where ipaddress = ?)")
if err != nil {
return err
}
log.Print("Preparing find_users_by_ip_replies statement.")
find_users_by_ip_replies_stmt, err = db.Prepare("select uid from users where uid in(select createdBy from replies where ipaddress = ?)")
return err
} }

View File

@ -5,11 +5,10 @@ package main
import "errors" import "errors"
import "net/http" import "net/http"
var ws_hub WS_Hub var wsHub WS_Hub
var ws_nouser error = errors.New("This user isn't connected via WebSockets") var wsNouser error = errors.New("This user isn't connected via WebSockets")
type WS_Hub struct type WS_Hub struct {
{
} }
func (_ *WS_Hub) guest_count() int { func (_ *WS_Hub) guest_count() int {
@ -25,15 +24,15 @@ func (hub *WS_Hub) broadcast_message(_ string) error {
} }
func (hub *WS_Hub) push_message(_ int, _ string) error { func (hub *WS_Hub) push_message(_ int, _ string) error {
return ws_nouser return wsNouser
} }
func(hub *WS_Hub) push_alert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error { func (hub *WS_Hub) push_alert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return ws_nouser return wsNouser
} }
func(hub *WS_Hub) push_alerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error { func (hub *WS_Hub) push_alerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return ws_nouser return wsNouser
} }
func route_websockets(_ http.ResponseWriter, _ *http.Request, _ User) { func route_websockets(_ http.ResponseWriter, _ *http.Request, _ User) {

885
pages.go

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import "sync"
import "strconv" import "strconv"
import "encoding/json" import "encoding/json"
var permupdate_mutex sync.Mutex var permUpdateMutex sync.Mutex
var BlankPerms Perms var BlankPerms Perms
var BlankForumPerms ForumPerms var BlankForumPerms ForumPerms
var GuestPerms Perms var GuestPerms Perms
@ -14,9 +14,9 @@ var ReadReplyForumPerms ForumPerms
var ReadWriteForumPerms ForumPerms var ReadWriteForumPerms ForumPerms
var AllPerms Perms var AllPerms Perms
var AllForumPerms ForumPerms var AllForumPerms ForumPerms
var AllPluginPerms map[string]bool = make(map[string]bool) var AllPluginPerms = make(map[string]bool)
var LocalPermList []string = []string{ var LocalPermList = []string{
"ViewTopic", "ViewTopic",
"LikeItem", "LikeItem",
"CreateTopic", "CreateTopic",
@ -29,7 +29,7 @@ var LocalPermList []string = []string{
"CloseTopic", "CloseTopic",
} }
var GlobalPermList []string = []string{ var GlobalPermList = []string{
"BanUsers", "BanUsers",
"ActivateUsers", "ActivateUsers",
"EditUser", "EditUser",
@ -52,71 +52,69 @@ var GlobalPermList []string = []string{
} }
// Permission Structure: ActionComponent[Subcomponent]Flag // Permission Structure: ActionComponent[Subcomponent]Flag
type Perms struct type Perms struct {
{
// Global Permissions // Global Permissions
BanUsers bool BanUsers bool
ActivateUsers bool ActivateUsers bool
EditUser bool EditUser bool
EditUserEmail bool EditUserEmail bool
EditUserPassword bool EditUserPassword bool
EditUserGroup bool EditUserGroup bool
EditUserGroupSuperMod bool EditUserGroupSuperMod bool
EditUserGroupAdmin bool EditUserGroupAdmin bool
EditGroup bool EditGroup bool
EditGroupLocalPerms bool EditGroupLocalPerms bool
EditGroupGlobalPerms bool EditGroupGlobalPerms bool
EditGroupSuperMod bool EditGroupSuperMod bool
EditGroupAdmin bool EditGroupAdmin bool
ManageForums bool // This could be local, albeit limited for per-forum managers? ManageForums bool // This could be local, albeit limited for per-forum managers?
EditSettings bool EditSettings bool
ManageThemes bool ManageThemes bool
ManagePlugins bool ManagePlugins bool
ViewAdminLogs bool ViewAdminLogs bool
ViewIPs bool ViewIPs bool
// Forum permissions // Forum permissions
ViewTopic bool ViewTopic bool
LikeItem bool LikeItem bool
CreateTopic bool CreateTopic bool
EditTopic bool EditTopic bool
DeleteTopic bool DeleteTopic bool
CreateReply bool CreateReply bool
//CreateReplyToOwn bool //CreateReplyToOwn bool
EditReply bool EditReply bool
//EditOwnReply bool //EditOwnReply bool
DeleteReply bool DeleteReply bool
PinTopic bool PinTopic bool
CloseTopic bool CloseTopic bool
//CloseOwnTopic bool //CloseOwnTopic bool
//ExtData map[string]bool //ExtData map[string]bool
} }
/* Inherit from group permissions for ones we don't have */ /* Inherit from group permissions for ones we don't have */
type ForumPerms struct type ForumPerms struct {
{ ViewTopic bool
ViewTopic bool LikeItem bool
LikeItem bool
CreateTopic bool CreateTopic bool
EditTopic bool EditTopic bool
DeleteTopic bool DeleteTopic bool
CreateReply bool CreateReply bool
//CreateReplyToOwn bool //CreateReplyToOwn bool
EditReply bool EditReply bool
//EditOwnReply bool //EditOwnReply bool
DeleteReply bool DeleteReply bool
PinTopic bool PinTopic bool
CloseTopic bool CloseTopic bool
//CloseOwnTopic bool //CloseOwnTopic bool
Overrides bool Overrides bool
ExtData map[string]bool ExtData map[string]bool
} }
func init() { func init() {
BlankPerms = Perms{ BlankPerms = Perms{
//ExtData: make(map[string]bool), //ExtData: make(map[string]bool),
} }
BlankForumPerms = ForumPerms{ BlankForumPerms = ForumPerms{
@ -129,80 +127,80 @@ func init() {
} }
AllPerms = Perms{ AllPerms = Perms{
BanUsers: true, BanUsers: true,
ActivateUsers: true, ActivateUsers: true,
EditUser: true, EditUser: true,
EditUserEmail: true, EditUserEmail: true,
EditUserPassword: true, EditUserPassword: true,
EditUserGroup: true, EditUserGroup: true,
EditUserGroupSuperMod: true, EditUserGroupSuperMod: true,
EditUserGroupAdmin: true, EditUserGroupAdmin: true,
EditGroup: true, EditGroup: true,
EditGroupLocalPerms: true, EditGroupLocalPerms: true,
EditGroupGlobalPerms: true, EditGroupGlobalPerms: true,
EditGroupSuperMod: true, EditGroupSuperMod: true,
EditGroupAdmin: true, EditGroupAdmin: true,
ManageForums: true, ManageForums: true,
EditSettings: true, EditSettings: true,
ManageThemes: true, ManageThemes: true,
ManagePlugins: true, ManagePlugins: true,
ViewAdminLogs: true, ViewAdminLogs: true,
ViewIPs: true, ViewIPs: true,
ViewTopic: true, ViewTopic: true,
LikeItem: true, LikeItem: true,
CreateTopic: true, CreateTopic: true,
EditTopic: true, EditTopic: true,
DeleteTopic: true, DeleteTopic: true,
CreateReply: true, CreateReply: true,
EditReply: true, EditReply: true,
DeleteReply: true, DeleteReply: true,
PinTopic: true, PinTopic: true,
CloseTopic: true, CloseTopic: true,
//ExtData: make(map[string]bool), //ExtData: make(map[string]bool),
} }
AllForumPerms = ForumPerms{ AllForumPerms = ForumPerms{
ViewTopic: true, ViewTopic: true,
LikeItem: true, LikeItem: true,
CreateTopic: true, CreateTopic: true,
EditTopic: true, EditTopic: true,
DeleteTopic: true, DeleteTopic: true,
CreateReply: true, CreateReply: true,
EditReply: true, EditReply: true,
DeleteReply: true, DeleteReply: true,
PinTopic: true, PinTopic: true,
CloseTopic: true, CloseTopic: true,
Overrides: true, Overrides: true,
ExtData: make(map[string]bool), ExtData: make(map[string]bool),
} }
ReadWriteForumPerms = ForumPerms{ ReadWriteForumPerms = ForumPerms{
ViewTopic: true, ViewTopic: true,
LikeItem: true, LikeItem: true,
CreateTopic: true, CreateTopic: true,
CreateReply: true, CreateReply: true,
Overrides: true, Overrides: true,
ExtData: make(map[string]bool), ExtData: make(map[string]bool),
} }
ReadReplyForumPerms = ForumPerms{ ReadReplyForumPerms = ForumPerms{
ViewTopic: true, ViewTopic: true,
LikeItem: true, LikeItem: true,
CreateReply: true, CreateReply: true,
Overrides: true, Overrides: true,
ExtData: make(map[string]bool), ExtData: make(map[string]bool),
} }
ReadForumPerms = ForumPerms{ ReadForumPerms = ForumPerms{
ViewTopic: true, ViewTopic: true,
Overrides: true, Overrides: true,
ExtData: make(map[string]bool), ExtData: make(map[string]bool),
} }
guest_user.Perms = GuestPerms guestUser.Perms = GuestPerms
if dev.DebugMode { if dev.DebugMode {
log.Printf("Guest Perms: %+v\n", GuestPerms) log.Printf("Guest Perms: %+v\n", GuestPerms)
@ -210,51 +208,51 @@ func init() {
} }
} }
func preset_to_permmap(preset string) (out map[string]ForumPerms) { func presetToPermmap(preset string) (out map[string]ForumPerms) {
out = make(map[string]ForumPerms) out = make(map[string]ForumPerms)
switch(preset) { switch preset {
case "all": case "all":
out["guests"] = ReadForumPerms out["guests"] = ReadForumPerms
out["members"] = ReadWriteForumPerms out["members"] = ReadWriteForumPerms
out["staff"] = AllForumPerms out["staff"] = AllForumPerms
out["admins"] = AllForumPerms out["admins"] = AllForumPerms
case "announce": case "announce":
out["guests"] = ReadForumPerms out["guests"] = ReadForumPerms
out["members"] = ReadReplyForumPerms out["members"] = ReadReplyForumPerms
out["staff"] = AllForumPerms out["staff"] = AllForumPerms
out["admins"] = AllForumPerms out["admins"] = AllForumPerms
case "members": case "members":
out["guests"] = BlankForumPerms out["guests"] = BlankForumPerms
out["members"] = ReadWriteForumPerms out["members"] = ReadWriteForumPerms
out["staff"] = AllForumPerms out["staff"] = AllForumPerms
out["admins"] = AllForumPerms out["admins"] = AllForumPerms
case "staff": case "staff":
out["guests"] = BlankForumPerms out["guests"] = BlankForumPerms
out["members"] = BlankForumPerms out["members"] = BlankForumPerms
out["staff"] = ReadWriteForumPerms out["staff"] = ReadWriteForumPerms
out["admins"] = AllForumPerms out["admins"] = AllForumPerms
case "admins": case "admins":
out["guests"] = BlankForumPerms out["guests"] = BlankForumPerms
out["members"] = BlankForumPerms out["members"] = BlankForumPerms
out["staff"] = BlankForumPerms out["staff"] = BlankForumPerms
out["admins"] = AllForumPerms out["admins"] = AllForumPerms
case "archive": case "archive":
out["guests"] = ReadForumPerms out["guests"] = ReadForumPerms
out["members"] = ReadForumPerms out["members"] = ReadForumPerms
out["staff"] = ReadForumPerms out["staff"] = ReadForumPerms
out["admins"] = ReadForumPerms //CurateForumPerms. Delete / Edit but no create? out["admins"] = ReadForumPerms //CurateForumPerms. Delete / Edit but no create?
default: default:
out["guests"] = BlankForumPerms out["guests"] = BlankForumPerms
out["members"] = BlankForumPerms out["members"] = BlankForumPerms
out["staff"] = BlankForumPerms out["staff"] = BlankForumPerms
out["admins"] = BlankForumPerms out["admins"] = BlankForumPerms
} }
return out return out
} }
func permmap_to_query(permmap map[string]ForumPerms, fid int) error { func permmapToQuery(permmap map[string]ForumPerms, fid int) error {
permupdate_mutex.Lock() permUpdateMutex.Lock()
defer permupdate_mutex.Unlock() defer permUpdateMutex.Unlock()
_, err := delete_forum_perms_by_forum_stmt.Exec(fid) _, err := delete_forum_perms_by_forum_stmt.Exec(fid)
if err != nil { if err != nil {
@ -265,7 +263,7 @@ func permmap_to_query(permmap map[string]ForumPerms, fid int) error {
if err != nil { if err != nil {
return err return err
} }
_, err = add_forum_perms_to_forum_admins_stmt.Exec(fid,"",perms) _, err = add_forum_perms_to_forum_admins_stmt.Exec(fid, "", perms)
if err != nil { if err != nil {
return err return err
} }
@ -274,7 +272,7 @@ func permmap_to_query(permmap map[string]ForumPerms, fid int) error {
if err != nil { if err != nil {
return err return err
} }
_, err = add_forum_perms_to_forum_staff_stmt.Exec(fid,"",perms) _, err = add_forum_perms_to_forum_staff_stmt.Exec(fid, "", perms)
if err != nil { if err != nil {
return err return err
} }
@ -283,7 +281,7 @@ func permmap_to_query(permmap map[string]ForumPerms, fid int) error {
if err != nil { if err != nil {
return err return err
} }
_, err = add_forum_perms_to_forum_members_stmt.Exec(fid,"",perms) _, err = add_forum_perms_to_forum_members_stmt.Exec(fid, "", perms)
if err != nil { if err != nil {
return err return err
} }
@ -292,15 +290,15 @@ func permmap_to_query(permmap map[string]ForumPerms, fid int) error {
if err != nil { if err != nil {
return err return err
} }
_, err = add_forum_perms_to_group_stmt.Exec(6,fid,"",perms) _, err = add_forum_perms_to_group_stmt.Exec(6, fid, "", perms)
if err != nil { if err != nil {
return err return err
} }
return rebuild_forum_permissions(fid) return rebuildForumPermissions(fid)
} }
func rebuild_forum_permissions(fid int) error { func rebuildForumPermissions(fid int) error {
if dev.DebugMode { if dev.DebugMode {
log.Print("Loading the forum permissions") log.Print("Loading the forum permissions")
} }
@ -332,34 +330,34 @@ func rebuild_forum_permissions(fid int) error {
} }
pperms.ExtData = make(map[string]bool) pperms.ExtData = make(map[string]bool)
pperms.Overrides = true pperms.Overrides = true
_, ok := forum_perms[gid] _, ok := forumPerms[gid]
if !ok { if !ok {
forum_perms[gid] = make(map[int]ForumPerms) forumPerms[gid] = make(map[int]ForumPerms)
} }
forum_perms[gid][fid] = pperms forumPerms[gid][fid] = pperms
} }
for gid, _ := range groups { for gid := range groups {
if dev.DebugMode { if dev.DebugMode {
log.Print("Updating the forum permissions for Group #" + strconv.Itoa(gid)) log.Print("Updating the forum permissions for Group #" + strconv.Itoa(gid))
} }
var blank_list []ForumPerms var blankList []ForumPerms
var blank_int_list []int var blankIntList []int
groups[gid].Forums = blank_list groups[gid].Forums = blankList
groups[gid].CanSee = blank_int_list groups[gid].CanSee = blankIntList
for ffid, _ := range forums { for ffid := range forums {
forum_perm, ok := forum_perms[gid][ffid] forumPerm, ok := forumPerms[gid][ffid]
if ok { if ok {
//log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid))
groups[gid].Forums = append(groups[gid].Forums,forum_perm) groups[gid].Forums = append(groups[gid].Forums, forumPerm)
} else { } else {
//log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid))
forum_perm = BlankForumPerms forumPerm = BlankForumPerms
groups[gid].Forums = append(groups[gid].Forums,forum_perm) groups[gid].Forums = append(groups[gid].Forums, forumPerm)
} }
if forum_perm.Overrides { if forumPerm.Overrides {
if forum_perm.ViewTopic { if forumPerm.ViewTopic {
groups[gid].CanSee = append(groups[gid].CanSee, ffid) groups[gid].CanSee = append(groups[gid].CanSee, ffid)
} }
} else if groups[gid].Perms.ViewTopic { } else if groups[gid].Perms.ViewTopic {
@ -369,13 +367,13 @@ func rebuild_forum_permissions(fid int) error {
if dev.SuperDebug { if dev.SuperDebug {
log.Printf("groups[gid].CanSee %+v\n", groups[gid].CanSee) log.Printf("groups[gid].CanSee %+v\n", groups[gid].CanSee)
log.Printf("groups[gid].Forums %+v\n", groups[gid].Forums) log.Printf("groups[gid].Forums %+v\n", groups[gid].Forums)
log.Print("len(groups[gid].Forums)",len(groups[gid].Forums)) log.Print("len(groups[gid].Forums)", len(groups[gid].Forums))
} }
} }
return nil return nil
} }
func build_forum_permissions() error { func buildForumPermissions() error {
forums, err := fstore.GetAll() forums, err := fstore.GetAll()
if err != nil { if err != nil {
return err return err
@ -391,7 +389,7 @@ func build_forum_permissions() error {
log.Print("Adding the forum permissions") log.Print("Adding the forum permissions")
} }
// Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice // Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice
forum_perms = make(map[int]map[int]ForumPerms) forumPerms = make(map[int]map[int]ForumPerms)
for rows.Next() { for rows.Next() {
var gid, fid int var gid, fid int
var perms []byte var perms []byte
@ -406,32 +404,32 @@ func build_forum_permissions() error {
} }
pperms.ExtData = make(map[string]bool) pperms.ExtData = make(map[string]bool)
pperms.Overrides = true pperms.Overrides = true
_, ok := forum_perms[gid] _, ok := forumPerms[gid]
if !ok { if !ok {
forum_perms[gid] = make(map[int]ForumPerms) forumPerms[gid] = make(map[int]ForumPerms)
} }
forum_perms[gid][fid] = pperms forumPerms[gid][fid] = pperms
} }
for gid, _ := range groups { for gid := range groups {
if dev.DebugMode { if dev.DebugMode {
log.Print("Adding the forum permissions for Group #" + strconv.Itoa(gid) + " - " + groups[gid].Name) log.Print("Adding the forum permissions for Group #" + strconv.Itoa(gid) + " - " + groups[gid].Name)
} }
//groups[gid].Forums = append(groups[gid].Forums,BlankForumPerms) // GID 0. No longer needed now that Uncategorised occupies that slot //groups[gid].Forums = append(groups[gid].Forums,BlankForumPerms) // GID 0. No longer needed now that Uncategorised occupies that slot
for fid, _ := range forums { for fid := range forums {
forum_perm, ok := forum_perms[gid][fid] forumPerm, ok := forumPerms[gid][fid]
if ok { if ok {
// Override group perms // Override group perms
//log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid))
groups[gid].Forums = append(groups[gid].Forums,forum_perm) groups[gid].Forums = append(groups[gid].Forums, forumPerm)
} else { } else {
// Inherit from Group // Inherit from Group
//log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid))
forum_perm = BlankForumPerms forumPerm = BlankForumPerms
groups[gid].Forums = append(groups[gid].Forums,forum_perm) groups[gid].Forums = append(groups[gid].Forums, forumPerm)
} }
if forum_perm.Overrides { if forumPerm.Overrides {
if forum_perm.ViewTopic { if forumPerm.ViewTopic {
groups[gid].CanSee = append(groups[gid].CanSee, fid) groups[gid].CanSee = append(groups[gid].CanSee, fid)
} }
} else if groups[gid].Perms.ViewTopic { } else if groups[gid].Perms.ViewTopic {
@ -448,26 +446,26 @@ func build_forum_permissions() error {
return nil return nil
} }
func forum_perms_to_group_forum_preset(fperms ForumPerms) string { func forumPermsToGroupForumPreset(fperms ForumPerms) string {
if !fperms.Overrides { if !fperms.Overrides {
return "default" return "default"
} }
if !fperms.ViewTopic { if !fperms.ViewTopic {
return "no_access" return "no_access"
} }
var can_post bool = (fperms.LikeItem && fperms.CreateTopic && fperms.CreateReply) var canPost = (fperms.LikeItem && fperms.CreateTopic && fperms.CreateReply)
var can_moderate bool = (can_post && fperms.EditTopic && fperms.DeleteTopic && fperms.EditReply && fperms.DeleteReply && fperms.PinTopic && fperms.CloseTopic) var canModerate = (canPost && fperms.EditTopic && fperms.DeleteTopic && fperms.EditReply && fperms.DeleteReply && fperms.PinTopic && fperms.CloseTopic)
if can_moderate { if canModerate {
return "can_moderate" return "can_moderate"
} }
if (fperms.EditTopic || fperms.DeleteTopic || fperms.EditReply || fperms.DeleteReply || fperms.PinTopic || fperms.CloseTopic) { if fperms.EditTopic || fperms.DeleteTopic || fperms.EditReply || fperms.DeleteReply || fperms.PinTopic || fperms.CloseTopic {
if !can_post { if !canPost {
return "custom" return "custom"
} }
return "quasi_mod" return "quasi_mod"
} }
if can_post { if canPost {
return "can_post" return "can_post"
} }
if fperms.ViewTopic && !fperms.LikeItem && !fperms.CreateTopic && !fperms.CreateReply { if fperms.ViewTopic && !fperms.LikeItem && !fperms.CreateTopic && !fperms.CreateReply {
@ -476,63 +474,72 @@ func forum_perms_to_group_forum_preset(fperms ForumPerms) string {
return "custom" return "custom"
} }
func group_forum_preset_to_forum_perms(preset string) (fperms ForumPerms, changed bool) { func groupForumPresetToForumPerms(preset string) (fperms ForumPerms, changed bool) {
switch(preset) { switch preset {
case "read_only": case "read_only":
return ReadForumPerms, true return ReadForumPerms, true
case "can_post": case "can_post":
return ReadWriteForumPerms, true return ReadWriteForumPerms, true
case "can_moderate": case "can_moderate":
return AllForumPerms, true return AllForumPerms, true
case "no_access": case "no_access":
return ForumPerms{Overrides: true,ExtData: make(map[string]bool)}, true return ForumPerms{Overrides: true, ExtData: make(map[string]bool)}, true
case "default": case "default":
return BlankForumPerms, true return BlankForumPerms, true
//case "custom": return fperms, false //case "custom": return fperms, false
} }
return fperms, false return fperms, false
} }
func strip_invalid_group_forum_preset(preset string) string { func stripInvalidGroupForumPreset(preset string) string {
switch(preset) { switch preset {
case "read_only","can_post","can_moderate","no_access","default","custom": case "read_only", "can_post", "can_moderate", "no_access", "default", "custom":
return preset return preset
} }
return "" return ""
} }
func strip_invalid_preset(preset string) string { func stripInvalidPreset(preset string) string {
switch(preset) { switch preset {
case "all","announce","members","staff","admins","archive","custom": case "all", "announce", "members", "staff", "admins", "archive", "custom":
break return preset
default: return "" default:
return ""
} }
return preset
} }
func preset_to_lang(preset string) string { // TO-DO: Move this into the phrase system?
switch(preset) { func presetToLang(preset string) string {
case "all": return "Public" switch preset {
case "announce": return "Announcements" case "all":
case "members": return "Member Only" return "Public"
case "staff": return "Staff Only" case "announce":
case "admins": return "Admin Only" return "Announcements"
case "archive": return "Archive" case "members":
case "custom": return "Custom" return "Member Only"
case "staff":
return "Staff Only"
case "admins":
return "Admin Only"
case "archive":
return "Archive"
case "custom":
return "Custom"
default:
return ""
} }
return ""
} }
func rebuild_group_permissions(gid int) error { func rebuildGroupPermissions(gid int) error {
var permstr []byte var permstr []byte
log.Print("Reloading a group") log.Print("Reloading a group")
err := db.QueryRow("select permissions from users_groups where gid = ?",gid).Scan(&permstr) err := db.QueryRow("select permissions from users_groups where gid = ?", gid).Scan(&permstr)
if err != nil { if err != nil {
return err return err
} }
tmp_perms := Perms{ tmp_perms := Perms{
//ExtData: make(map[string]bool), //ExtData: make(map[string]bool),
} }
err = json.Unmarshal(permstr, &tmp_perms) err = json.Unmarshal(permstr, &tmp_perms)
if err != nil { if err != nil {
@ -543,7 +550,7 @@ func rebuild_group_permissions(gid int) error {
return nil return nil
} }
func override_perms(perms *Perms, status bool) { func overridePerms(perms *Perms, status bool) {
if status { if status {
*perms = AllPerms *perms = AllPerms
} else { } else {
@ -552,7 +559,7 @@ func override_perms(perms *Perms, status bool) {
} }
// TO-DO: We need a better way of overriding forum perms rather than setting them one by one // TO-DO: We need a better way of overriding forum perms rather than setting them one by one
func override_forum_perms(perms *Perms, status bool) { func overrideForumPerms(perms *Perms, status bool) {
perms.ViewTopic = status perms.ViewTopic = status
perms.LikeItem = status perms.LikeItem = status
perms.CreateTopic = status perms.CreateTopic = status
@ -565,10 +572,10 @@ func override_forum_perms(perms *Perms, status bool) {
perms.CloseTopic = status perms.CloseTopic = status
} }
func register_plugin_perm(name string) { func registerPluginPerm(name string) {
AllPluginPerms[name] = true AllPluginPerms[name] = true
} }
func deregister_plugin_perm(name string) { func deregisterPluginPerm(name string) {
delete(AllPluginPerms,name) delete(AllPluginPerms, name)
} }

View File

@ -1,101 +1,102 @@
/*
*
* Gosora Phrase System
* Copyright Azareal 2017 - 2018
*
*/
package main package main
import "sync" import (
"sync"
"sync/atomic"
)
// I wish we had constant maps x.x // TO-DO: 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?
var phrase_mutex sync.RWMutex var changeLangpackMutex sync.Mutex
var perm_phrase_mutex sync.RWMutex var currentLanguage = "english"
var change_langpack_mutex sync.Mutex var currentLangPack atomic.Value
var currentLanguage string = "english"
var currentLangPack *LanguagePack
type LevelPhrases struct type LevelPhrases struct {
{ Level string
Level string LevelMax string
LevelMax string
// Override the phrase for individual levels, if the phrases exist // Override the phrase for individual levels, if the phrases exist
Levels []string // index = level Levels []string // index = level
} }
type LanguagePack struct type LanguagePack struct {
{ Name string
Name string Phrases map[string]string // Should we use a sync map or a struct for these? It would be nice, if we could keep all the phrases consistent.
Phrases map[string]string // Should we use a sync map or a struct for these? It would be nice, if we could keep all the phrases consistent. LevelPhrases LevelPhrases
LevelPhrases LevelPhrases
GlobalPermPhrases map[string]string GlobalPermPhrases map[string]string
LocalPermPhrases map[string]string LocalPermPhrases map[string]string
SettingLabels map[string]string
} }
// TO-DO: Move the english language pack into it's own file and just keep the common logic here // TO-DO: Move the english language pack into it's own file and just keep the common logic here
var langpacks map[string]*LanguagePack = map[string]*LanguagePack{ var langpacks = map[string]*LanguagePack{
"english": &LanguagePack{ "english": &LanguagePack{
Name: "english", Name: "english",
// We'll be implementing the level phrases in the software proper very very soon! // We'll be implementing the level phrases in the software proper very very soon!
LevelPhrases: LevelPhrases{ LevelPhrases: LevelPhrases{
Level: "Level {0}", Level: "Level {0}",
LevelMax: "", // Add a max level setting? LevelMax: "", // Add a max level setting?
}, },
GlobalPermPhrases: map[string]string{ GlobalPermPhrases: map[string]string{
"BanUsers": "Can ban users", "BanUsers": "Can ban users",
"ActivateUsers": "Can activate users", "ActivateUsers": "Can activate users",
"EditUser": "Can edit users", "EditUser": "Can edit users",
"EditUserEmail": "Can change a user's email", "EditUserEmail": "Can change a user's email",
"EditUserPassword": "Can change a user's password", "EditUserPassword": "Can change a user's password",
"EditUserGroup": "Can change a user's group", "EditUserGroup": "Can change a user's group",
"EditUserGroupSuperMod": "Can edit super-mods", "EditUserGroupSuperMod": "Can edit super-mods",
"EditUserGroupAdmin": "Can edit admins", "EditUserGroupAdmin": "Can edit admins",
"EditGroup": "Can edit groups", "EditGroup": "Can edit groups",
"EditGroupLocalPerms": "Can edit a group's minor perms", "EditGroupLocalPerms": "Can edit a group's minor perms",
"EditGroupGlobalPerms": "Can edit a group's global perms", "EditGroupGlobalPerms": "Can edit a group's global perms",
"EditGroupSuperMod": "Can edit super-mod groups", "EditGroupSuperMod": "Can edit super-mod groups",
"EditGroupAdmin": "Can edit admin groups", "EditGroupAdmin": "Can edit admin groups",
"ManageForums": "Can manage forums", "ManageForums": "Can manage forums",
"EditSettings": "Can edit settings", "EditSettings": "Can edit settings",
"ManageThemes": "Can manage themes", "ManageThemes": "Can manage themes",
"ManagePlugins": "Can manage plugins", "ManagePlugins": "Can manage plugins",
"ViewAdminLogs": "Can view the administrator action logs", "ViewAdminLogs": "Can view the administrator action logs",
"ViewIPs": "Can view IP addresses", "ViewIPs": "Can view IP addresses",
}, },
LocalPermPhrases: map[string]string{ LocalPermPhrases: map[string]string{
"ViewTopic": "Can view topics", "ViewTopic": "Can view topics",
"LikeItem": "Can like items", "LikeItem": "Can like items",
"CreateTopic": "Can create topics", "CreateTopic": "Can create topics",
"EditTopic": "Can edit topics", "EditTopic": "Can edit topics",
"DeleteTopic": "Can delete topics", "DeleteTopic": "Can delete topics",
"CreateReply": "Can create replies", "CreateReply": "Can create replies",
"EditReply": "Can edit replies", "EditReply": "Can edit replies",
"DeleteReply": "Can delete replies", "DeleteReply": "Can delete replies",
"PinTopic": "Can pin topics", "PinTopic": "Can pin topics",
"CloseTopic": "Can lock topics", "CloseTopic": "Can lock topics",
},
SettingLabels: map[string]string{
"activation_type": "Activate All,Email Activation,Admin Approval",
}, },
}, },
} }
func init() { func init() {
currentLangPack = langpacks[currentLanguage] currentLangPack.Store(langpacks[currentLanguage])
} }
// We might not need to use a mutex for this, we shouldn't need to change the phrases after start-up, and when we do we could overwrite the entire map // We might not need to use a mutex for this, we shouldn't need to change the phrases after start-up, and when we do we could overwrite the entire map
func GetPhrase(name string) (string,bool) { func GetPhrase(name string) (string, bool) {
phrase_mutex.RLock() res, ok := currentLangPack.Load().(*LanguagePack).Phrases[name]
defer perm_phrase_mutex.RUnlock()
res, ok := currentLangPack.Phrases[name]
return res, ok
}
func GetPhraseUnsafe(name string) (string,bool) {
res, ok := currentLangPack.Phrases[name]
return res, ok return res, ok
} }
func GetGlobalPermPhrase(name string) string { func GetGlobalPermPhrase(name string) string {
perm_phrase_mutex.RLock() res, ok := currentLangPack.Load().(*LanguagePack).GlobalPermPhrases[name]
defer perm_phrase_mutex.RUnlock()
res, ok := currentLangPack.GlobalPermPhrases[name]
if !ok { if !ok {
return "{name}" return "{name}"
} }
@ -103,31 +104,46 @@ func GetGlobalPermPhrase(name string) string {
} }
func GetLocalPermPhrase(name string) string { func GetLocalPermPhrase(name string) string {
perm_phrase_mutex.RLock() res, ok := currentLangPack.Load().(*LanguagePack).LocalPermPhrases[name]
defer perm_phrase_mutex.RUnlock()
res, ok := currentLangPack.LocalPermPhrases[name]
if !ok { if !ok {
return "{name}" return "{name}"
} }
return res return res
} }
func GetSettingLabel(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).SettingLabels[name]
if !ok {
return "{name}"
}
return res
}
// Is this a copy of the map or a pointer to it? We don't want to accidentally create a race condition
func GetAllSettingLabels() map[string]string {
return currentLangPack.Load().(*LanguagePack).SettingLabels
}
// Use runtime reflection for updating phrases?
func AddPhrase() { func AddPhrase() {
} }
func UpdatePhrase() {
}
func DeletePhrase() { func DeletePhrase() {
} }
// TO-DO: Use atomics to store the pointer of the current active langpack?
func ChangeLanguagePack(name string) (exists bool) { func ChangeLanguagePack(name string) (exists bool) {
change_langpack_mutex.Lock() changeLangpackMutex.Lock()
pack, ok := langpacks[name] pack, ok := langpacks[name]
if !ok { if !ok {
change_langpack_mutex.Unlock() changeLangpackMutex.Unlock()
return false return false
} }
currentLangPack = pack currentLangPack.Store(pack)
change_langpack_mutex.Unlock() changeLangpackMutex.Unlock()
return true return true
} }

View File

@ -1,172 +1,175 @@
package main package main
//import "log" import (
//import "fmt" //"log"
import "bytes" //"fmt"
//import "strings" "bytes"
import "strconv"
import "regexp" //"strings"
import "time" "math/rand"
import "math/rand" "regexp"
"strconv"
"time"
)
var random *rand.Rand var random *rand.Rand
var bbcode_invalid_number []byte var bbcodeInvalidNumber []byte
var bbcode_no_negative []byte var bbcodeNoNegative []byte
var bbcode_missing_tag []byte var bbcodeMissingTag []byte
var bbcode_bold *regexp.Regexp var bbcodeBold *regexp.Regexp
var bbcode_italic *regexp.Regexp var bbcodeItalic *regexp.Regexp
var bbcode_underline *regexp.Regexp var bbcodeUnderline *regexp.Regexp
var bbcode_strikethrough *regexp.Regexp var bbcodeStrikethrough *regexp.Regexp
var bbcode_url *regexp.Regexp var bbcodeURL *regexp.Regexp
var bbcode_url_label *regexp.Regexp var bbcodeURLLabel *regexp.Regexp
var bbcode_quotes *regexp.Regexp var bbcodeQuotes *regexp.Regexp
var bbcode_code *regexp.Regexp var bbcodeCode *regexp.Regexp
func init() { func init() {
plugins["bbcode"] = NewPlugin("bbcode","BBCode","Azareal","http://github.com/Azareal","","","",init_bbcode,nil,deactivate_bbcode,nil,nil) plugins["bbcode"] = NewPlugin("bbcode", "BBCode", "Azareal", "http://github.com/Azareal", "", "", "", initBbcode, nil, deactivateBbcode, nil, nil)
} }
func init_bbcode() error { func initBbcode() error {
//plugins["bbcode"].AddHook("parse_assign", bbcode_parse_without_code) //plugins["bbcode"].AddHook("parse_assign", bbcode_parse_without_code)
plugins["bbcode"].AddHook("parse_assign", bbcode_full_parse) plugins["bbcode"].AddHook("parse_assign", bbcodeFullParse)
bbcode_invalid_number = []byte("<span style='color: red;'>[Invalid Number]</span>") bbcodeInvalidNumber = []byte("<span style='color: red;'>[Invalid Number]</span>")
bbcode_no_negative = []byte("<span style='color: red;'>[No Negative Numbers]</span>") bbcodeNoNegative = []byte("<span style='color: red;'>[No Negative Numbers]</span>")
bbcode_missing_tag = []byte("<span style='color: red;'>[Missing Tag]</span>") bbcodeMissingTag = []byte("<span style='color: red;'>[Missing Tag]</span>")
bbcode_bold = regexp.MustCompile(`(?s)\[b\](.*)\[/b\]`) bbcodeBold = regexp.MustCompile(`(?s)\[b\](.*)\[/b\]`)
bbcode_italic = regexp.MustCompile(`(?s)\[i\](.*)\[/i\]`) bbcodeItalic = regexp.MustCompile(`(?s)\[i\](.*)\[/i\]`)
bbcode_underline = regexp.MustCompile(`(?s)\[u\](.*)\[/u\]`) bbcodeUnderline = regexp.MustCompile(`(?s)\[u\](.*)\[/u\]`)
bbcode_strikethrough = regexp.MustCompile(`(?s)\[s\](.*)\[/s\]`) bbcodeStrikethrough = regexp.MustCompile(`(?s)\[s\](.*)\[/s\]`)
urlpattern := `(http|https|ftp|mailto*)(:??)\/\/([\.a-zA-Z\/]+)` urlpattern := `(http|https|ftp|mailto*)(:??)\/\/([\.a-zA-Z\/]+)`
bbcode_url = regexp.MustCompile(`\[url\]` + urlpattern + `\[/url\]`) bbcodeURL = regexp.MustCompile(`\[url\]` + urlpattern + `\[/url\]`)
bbcode_url_label = regexp.MustCompile(`(?s)\[url=` + urlpattern + `\](.*)\[/url\]`) bbcodeURLLabel = regexp.MustCompile(`(?s)\[url=` + urlpattern + `\](.*)\[/url\]`)
bbcode_quotes = regexp.MustCompile(`\[quote\](.*)\[/quote\]`) bbcodeQuotes = regexp.MustCompile(`\[quote\](.*)\[/quote\]`)
bbcode_code = regexp.MustCompile(`\[code\](.*)\[/code\]`) bbcodeCode = regexp.MustCompile(`\[code\](.*)\[/code\]`)
random = rand.New(rand.NewSource(time.Now().UnixNano())) random = rand.New(rand.NewSource(time.Now().UnixNano()))
return nil return nil
} }
func deactivate_bbcode() { func deactivateBbcode() {
//plugins["bbcode"].RemoveHook("parse_assign", bbcode_parse_without_code) //plugins["bbcode"].RemoveHook("parse_assign", bbcode_parse_without_code)
plugins["bbcode"].RemoveHook("parse_assign", bbcode_full_parse) plugins["bbcode"].RemoveHook("parse_assign", bbcodeFullParse)
} }
func bbcode_regex_parse(msg string) string { func bbcodeRegexParse(msg string) string {
msg = bbcode_bold.ReplaceAllString(msg,"<b>$1</b>") msg = bbcodeBold.ReplaceAllString(msg, "<b>$1</b>")
msg = bbcode_italic.ReplaceAllString(msg,"<i>$1</i>") msg = bbcodeItalic.ReplaceAllString(msg, "<i>$1</i>")
msg = bbcode_underline.ReplaceAllString(msg,"<u>$1</u>") msg = bbcodeUnderline.ReplaceAllString(msg, "<u>$1</u>")
msg = bbcode_strikethrough.ReplaceAllString(msg,"<s>$1</s>") msg = bbcodeStrikethrough.ReplaceAllString(msg, "<s>$1</s>")
msg = bbcode_url.ReplaceAllString(msg,"<a href=''$1$2//$3' rel='nofollow'>$1$2//$3</i>") msg = bbcodeURL.ReplaceAllString(msg, "<a href=''$1$2//$3' rel='nofollow'>$1$2//$3</i>")
msg = bbcode_url_label.ReplaceAllString(msg,"<a href=''$1$2//$3' rel='nofollow'>$4</i>") msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href=''$1$2//$3' rel='nofollow'>$4</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class='postQuote'>$1</span>") msg = bbcodeQuotes.ReplaceAllString(msg, "<span class='postQuote'>$1</span>")
//msg = bbcode_code.ReplaceAllString(msg,"<span class='codequotes'>$1</span>") //msg = bbcodeCode.ReplaceAllString(msg,"<span class='codequotes'>$1</span>")
return msg return msg
} }
// Only does the simple BBCode like [u], [b], [i] and [s] // Only does the simple BBCode like [u], [b], [i] and [s]
func bbcode_simple_parse(msg string) string { func bbcodeSimpleParse(msg string) string {
var has_u, has_b, has_i, has_s bool var hasU, hasB, hasI, hasS bool
msgbytes := []byte(msg) msgbytes := []byte(msg)
for i := 0; (i + 2) < len(msgbytes); i++ { for i := 0; (i + 2) < len(msgbytes); i++ {
if msgbytes[i] == '[' && msgbytes[i + 2] == ']' { if msgbytes[i] == '[' && msgbytes[i+2] == ']' {
if msgbytes[i + 1] == 'b' && !has_b { if msgbytes[i+1] == 'b' && !hasB {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_b = true hasB = true
} else if msgbytes[i + 1] == 'i' && !has_i { } else if msgbytes[i+1] == 'i' && !hasI {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_i = true hasI = true
} else if msgbytes[i + 1] == 'u' && !has_u { } else if msgbytes[i+1] == 'u' && !hasU {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_u = true hasU = true
} else if msgbytes[i + 1] == 's' && !has_s { } else if msgbytes[i+1] == 's' && !hasS {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_s = true hasS = true
} }
i += 2 i += 2
} }
} }
// There's an unclosed tag in there x.x // There's an unclosed tag in there x.x
if has_i || has_u || has_b || has_s { if hasI || hasU || hasB || hasS {
close_under := []byte("</u>") closeUnder := []byte("</u>")
close_italic := []byte("</i>") closeItalic := []byte("</i>")
close_bold := []byte("</b>") closeBold := []byte("</b>")
close_strike := []byte("</s>") closeStrike := []byte("</s>")
if has_i { if hasI {
msgbytes = append(msgbytes, close_italic...) msgbytes = append(msgbytes, closeItalic...)
} }
if has_u { if hasU {
msgbytes = append(msgbytes, close_under...) msgbytes = append(msgbytes, closeUnder...)
} }
if has_b { if hasB {
msgbytes = append(msgbytes, close_bold...) msgbytes = append(msgbytes, closeBold...)
} }
if has_s { if hasS {
msgbytes = append(msgbytes, close_strike...) msgbytes = append(msgbytes, closeStrike...)
} }
} }
return string(msgbytes) return string(msgbytes)
} }
// Here for benchmarking purposes. Might add a plugin setting for disabling [code] as it has it's paws everywhere // Here for benchmarking purposes. Might add a plugin setting for disabling [code] as it has it's paws everywhere
func bbcode_parse_without_code(msg string) string { func bbcodeParseWithoutCode(msg string) string {
var has_u, has_b, has_i, has_s bool var hasU, hasB, hasI, hasS bool
var complex_bbc bool var complexBbc bool
msgbytes := []byte(msg) msgbytes := []byte(msg)
for i := 0; (i + 3) < len(msgbytes); i++ { for i := 0; (i + 3) < len(msgbytes); i++ {
if msgbytes[i] == '[' { if msgbytes[i] == '[' {
if msgbytes[i + 2] != ']' { if msgbytes[i+2] != ']' {
if msgbytes[i + 1] == '/' { if msgbytes[i+1] == '/' {
if msgbytes[i + 3] == ']' { if msgbytes[i+3] == ']' {
if msgbytes[i + 2] == 'b' { if msgbytes[i+2] == 'b' {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 3] = '>' msgbytes[i+3] = '>'
has_b = false hasB = false
} else if msgbytes[i + 2] == 'i' { } else if msgbytes[i+2] == 'i' {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 3] = '>' msgbytes[i+3] = '>'
has_i = false hasI = false
} else if msgbytes[i + 2] == 'u' { } else if msgbytes[i+2] == 'u' {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 3] = '>' msgbytes[i+3] = '>'
has_u = false hasU = false
} else if msgbytes[i + 2] == 's' { } else if msgbytes[i+2] == 's' {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 3] = '>' msgbytes[i+3] = '>'
has_s = false hasS = false
} }
i += 3 i += 3
} else { } else {
complex_bbc = true complexBbc = true
} }
} else { } else {
complex_bbc = true complexBbc = true
} }
} else { } else {
if msgbytes[i + 1] == 'b' && !has_b { if msgbytes[i+1] == 'b' && !hasB {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_b = true hasB = true
} else if msgbytes[i + 1] == 'i' && !has_i { } else if msgbytes[i+1] == 'i' && !hasI {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_i = true hasI = true
} else if msgbytes[i + 1] == 'u' && !has_u { } else if msgbytes[i+1] == 'u' && !hasU {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_u = true hasU = true
} else if msgbytes[i + 1] == 's' && !has_s { } else if msgbytes[i+1] == 's' && !hasS {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_s = true hasS = true
} }
i += 2 i += 2
} }
@ -174,74 +177,75 @@ func bbcode_parse_without_code(msg string) string {
} }
// There's an unclosed tag in there x.x // There's an unclosed tag in there x.x
if has_i || has_u || has_b || has_s { if hasI || hasU || hasB || hasS {
close_under := []byte("</u>") closeUnder := []byte("</u>")
close_italic := []byte("</i>") closeItalic := []byte("</i>")
close_bold := []byte("</b>") closeBold := []byte("</b>")
close_strike := []byte("</s>") closeStrike := []byte("</s>")
if has_i { if hasI {
msgbytes = append(bytes.TrimSpace(msgbytes), close_italic...) msgbytes = append(bytes.TrimSpace(msgbytes), closeItalic...)
} }
if has_u { if hasU {
msgbytes = append(bytes.TrimSpace(msgbytes), close_under...) msgbytes = append(bytes.TrimSpace(msgbytes), closeUnder...)
} }
if has_b { if hasB {
msgbytes = append(bytes.TrimSpace(msgbytes), close_bold...) msgbytes = append(bytes.TrimSpace(msgbytes), closeBold...)
} }
if has_s { if hasS {
msgbytes = append(bytes.TrimSpace(msgbytes), close_strike...) msgbytes = append(bytes.TrimSpace(msgbytes), closeStrike...)
} }
} }
// Copy the new complex parser over once the rough edges have been smoothed over // Copy the new complex parser over once the rough edges have been smoothed over
if complex_bbc { if complexBbc {
msg = bbcode_url.ReplaceAllString(msg,"<a href='$1$2//$3' rel='nofollow'>$1$2//$3</i>") msg = string(msgbytes)
msg = bbcode_url_label.ReplaceAllString(msg,"<a href='$1$2//$3' rel='nofollow'>$4</i>") msg = bbcodeURL.ReplaceAllString(msg, "<a href='$1$2//$3' rel='nofollow'>$1$2//$3</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class='postQuote'>$1</span>") msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href='$1$2//$3' rel='nofollow'>$4</i>")
msg = bbcode_code.ReplaceAllString(msg,"<span class='codequotes'>$1</span>") msg = bbcodeQuotes.ReplaceAllString(msg, "<span class='postQuote'>$1</span>")
return bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>")
} else {
return string(msgbytes)
} }
return string(msgbytes)
} }
// Does every type of BBCode // Does every type of BBCode
func bbcode_full_parse(msg string) string { func bbcodeFullParse(msg string) string {
var has_u, has_b, has_i, has_s, has_c bool var hasU, hasB, hasI, hasS, hasC bool
var complex_bbc bool var complexBbc bool
msgbytes := []byte(msg) msgbytes := []byte(msg)
msgbytes = append(msgbytes,space_gap...) msgbytes = append(msgbytes, spaceGap...)
//log.Print("BBCode Simple Pre:","`"+string(msgbytes)+"`") //log.Print("BBCode Simple Pre:","`"+string(msgbytes)+"`")
//log.Print("----") //log.Print("----")
for i := 0; i < len(msgbytes); i++ { for i := 0; i < len(msgbytes); i++ {
if msgbytes[i] == '[' { if msgbytes[i] == '[' {
if msgbytes[i + 2] != ']' { if msgbytes[i+2] != ']' {
if msgbytes[i + 1] == '/' { if msgbytes[i+1] == '/' {
if msgbytes[i + 3] == ']' { if msgbytes[i+3] == ']' {
if !has_c { if !hasC {
if msgbytes[i + 2] == 'b' { if msgbytes[i+2] == 'b' {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 3] = '>' msgbytes[i+3] = '>'
has_b = false hasB = false
} else if msgbytes[i + 2] == 'i' { } else if msgbytes[i+2] == 'i' {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 3] = '>' msgbytes[i+3] = '>'
has_i = false hasI = false
} else if msgbytes[i + 2] == 'u' { } else if msgbytes[i+2] == 'u' {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 3] = '>' msgbytes[i+3] = '>'
has_u = false hasU = false
} else if msgbytes[i + 2] == 's' { } else if msgbytes[i+2] == 's' {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 3] = '>' msgbytes[i+3] = '>'
has_s = false hasS = false
} }
i += 3 i += 3
} }
} else { } else {
if msgbytes[i+2] == 'c' && msgbytes[i+3] == 'o' && msgbytes[i+4] == 'd' && msgbytes[i+5] == 'e' && msgbytes[i+6] == ']' { if msgbytes[i+2] == 'c' && msgbytes[i+3] == 'o' && msgbytes[i+4] == 'd' && msgbytes[i+5] == 'e' && msgbytes[i+6] == ']' {
has_c = false hasC = false
i += 7 i += 7
} }
//if msglen >= (i+6) { //if msglen >= (i+6) {
@ -250,36 +254,36 @@ func bbcode_full_parse(msg string) string {
// log.Print(i+6) // log.Print(i+6)
// log.Print(string(msgbytes[i:i+6])) // log.Print(string(msgbytes[i:i+6]))
//} //}
complex_bbc = true complexBbc = true
} }
} else { } else {
if msgbytes[i+1] == 'c' && msgbytes[i+2] == 'o' && msgbytes[i+3] == 'd' && msgbytes[i+4] == 'e' && msgbytes[i+5] == ']' { if msgbytes[i+1] == 'c' && msgbytes[i+2] == 'o' && msgbytes[i+3] == 'd' && msgbytes[i+4] == 'e' && msgbytes[i+5] == ']' {
has_c = true hasC = true
i += 6 i += 6
} }
//if msglen >= (i+5) { //if msglen >= (i+5) {
// log.Print("boo2") // log.Print("boo2")
// log.Print(string(msgbytes[i:i+5])) // log.Print(string(msgbytes[i:i+5]))
//} //}
complex_bbc = true complexBbc = true
} }
} else if !has_c { } else if !hasC {
if msgbytes[i + 1] == 'b' && !has_b { if msgbytes[i+1] == 'b' && !hasB {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_b = true hasB = true
} else if msgbytes[i + 1] == 'i' && !has_i { } else if msgbytes[i+1] == 'i' && !hasI {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_i = true hasI = true
} else if msgbytes[i + 1] == 'u' && !has_u { } else if msgbytes[i+1] == 'u' && !hasU {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_u = true hasU = true
} else if msgbytes[i + 1] == 's' && !has_s { } else if msgbytes[i+1] == 's' && !hasS {
msgbytes[i] = '<' msgbytes[i] = '<'
msgbytes[i + 2] = '>' msgbytes[i+2] = '>'
has_s = true hasS = true
} }
i += 2 i += 2
} }
@ -287,86 +291,86 @@ func bbcode_full_parse(msg string) string {
} }
// There's an unclosed tag in there somewhere x.x // There's an unclosed tag in there somewhere x.x
if has_i || has_u || has_b || has_s { if hasI || hasU || hasB || hasS {
close_under := []byte("</u>") closeUnder := []byte("</u>")
close_italic := []byte("</i>") closeItalic := []byte("</i>")
close_bold := []byte("</b>") closeBold := []byte("</b>")
close_strike := []byte("</s>") closeStrike := []byte("</s>")
if has_i { if hasI {
msgbytes = append(bytes.TrimSpace(msgbytes), close_italic...) msgbytes = append(bytes.TrimSpace(msgbytes), closeItalic...)
} }
if has_u { if hasU {
msgbytes = append(bytes.TrimSpace(msgbytes), close_under...) msgbytes = append(bytes.TrimSpace(msgbytes), closeUnder...)
} }
if has_b { if hasB {
msgbytes = append(bytes.TrimSpace(msgbytes), close_bold...) msgbytes = append(bytes.TrimSpace(msgbytes), closeBold...)
} }
if has_s { if hasS {
msgbytes = append(bytes.TrimSpace(msgbytes), close_strike...) msgbytes = append(bytes.TrimSpace(msgbytes), closeStrike...)
} }
msgbytes = append(msgbytes,space_gap...) msgbytes = append(msgbytes, spaceGap...)
} }
if complex_bbc { if complexBbc {
i := 0 i := 0
var start, lastTag int var start, lastTag int
var outbytes []byte var outbytes []byte
//log.Print("BBCode Pre:","`"+string(msgbytes)+"`") //log.Print("BBCode Pre:","`"+string(msgbytes)+"`")
//log.Print("----") //log.Print("----")
for ; i < len(msgbytes); i++ { for ; i < len(msgbytes); i++ {
MainLoop: MainLoop:
if msgbytes[i] == '[' { if msgbytes[i] == '[' {
OuterComplex: OuterComplex:
if msgbytes[i + 1] == 'u' { if msgbytes[i+1] == 'u' {
if msgbytes[i+2] == 'r' && msgbytes[i+3] == 'l' && msgbytes[i+4] == ']' { if msgbytes[i+2] == 'r' && msgbytes[i+3] == 'l' && msgbytes[i+4] == ']' {
start = i + 5 start = i + 5
outbytes = append(outbytes, msgbytes[lastTag:i]...) outbytes = append(outbytes, msgbytes[lastTag:i]...)
i = start i = start
i += partial_url_bytes_len(msgbytes[start:]) i += partialURLBytesLen(msgbytes[start:])
//log.Print("Partial Bytes:",string(msgbytes[start:])) //log.Print("Partial Bytes:",string(msgbytes[start:]))
//log.Print("-----") //log.Print("-----")
if !bytes.Equal(msgbytes[i:i+6],[]byte("[/url]")) { if !bytes.Equal(msgbytes[i:i+6], []byte("[/url]")) {
//log.Print("Invalid Bytes:",string(msgbytes[i:i+6])) //log.Print("Invalid Bytes:",string(msgbytes[i:i+6]))
//log.Print("-----") //log.Print("-----")
outbytes = append(outbytes, invalid_url...) outbytes = append(outbytes, invalidURL...)
goto MainLoop goto MainLoop
} }
outbytes = append(outbytes, url_open...) outbytes = append(outbytes, urlOpen...)
outbytes = append(outbytes, msgbytes[start:i]...) outbytes = append(outbytes, msgbytes[start:i]...)
outbytes = append(outbytes, url_open2...) outbytes = append(outbytes, urlOpen2...)
outbytes = append(outbytes, msgbytes[start:i]...) outbytes = append(outbytes, msgbytes[start:i]...)
outbytes = append(outbytes, url_close...) outbytes = append(outbytes, urlClose...)
i += 6 i += 6
lastTag = i lastTag = i
} }
} else if msgbytes[i + 1] == 'r' { } else if msgbytes[i+1] == 'r' {
if bytes.Equal(msgbytes[i+2:i+6],[]byte("and]")) { if bytes.Equal(msgbytes[i+2:i+6], []byte("and]")) {
outbytes = append(outbytes, msgbytes[lastTag:i]...) outbytes = append(outbytes, msgbytes[lastTag:i]...)
start = i + 6 start = i + 6
i = start i = start
for ;; i++ { for ; ; i++ {
if msgbytes[i] == '[' { if msgbytes[i] == '[' {
if !bytes.Equal(msgbytes[i+1:i+7],[]byte("/rand]")) { if !bytes.Equal(msgbytes[i+1:i+7], []byte("/rand]")) {
outbytes = append(outbytes, bbcode_missing_tag...) outbytes = append(outbytes, bbcodeMissingTag...)
goto OuterComplex goto OuterComplex
} }
break break
} else if (len(msgbytes) - 1) < (i + 10) { } else if (len(msgbytes) - 1) < (i + 10) {
outbytes = append(outbytes, bbcode_missing_tag...) outbytes = append(outbytes, bbcodeMissingTag...)
goto OuterComplex goto OuterComplex
} }
} }
number, err := strconv.ParseInt(string(msgbytes[start:i]),10,64) number, err := strconv.ParseInt(string(msgbytes[start:i]), 10, 64)
if err != nil { if err != nil {
outbytes = append(outbytes, bbcode_invalid_number...) outbytes = append(outbytes, bbcodeInvalidNumber...)
goto MainLoop goto MainLoop
} }
// TO-DO: Add support for negative numbers? // TO-DO: Add support for negative numbers?
if number < 0 { if number < 0 {
outbytes = append(outbytes, bbcode_no_negative...) outbytes = append(outbytes, bbcodeNoNegative...)
goto MainLoop goto MainLoop
} }
@ -374,7 +378,7 @@ func bbcode_full_parse(msg string) string {
if number == 0 { if number == 0 {
dat = []byte("0") dat = []byte("0")
} else { } else {
dat = []byte(strconv.FormatInt((random.Int63n(number)),10)) dat = []byte(strconv.FormatInt((random.Int63n(number)), 10))
} }
outbytes = append(outbytes, dat...) outbytes = append(outbytes, dat...)
@ -395,17 +399,17 @@ func bbcode_full_parse(msg string) string {
if len(outbytes) != 0 { if len(outbytes) != 0 {
//log.Print("BBCode Post:",`"`+string(outbytes[0:len(outbytes) - 10])+`"`) //log.Print("BBCode Post:",`"`+string(outbytes[0:len(outbytes) - 10])+`"`)
//log.Print("----") //log.Print("----")
msg = string(outbytes[0:len(outbytes) - 10]) msg = string(outbytes[0 : len(outbytes)-10])
} else { } else {
msg = string(msgbytes[0:len(msgbytes) - 10]) msg = string(msgbytes[0 : len(msgbytes)-10])
} }
//msg = bbcode_url.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"nofollow\">$1$2//$3</i>") //msg = bbcode_url.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"nofollow\">$1$2//$3</i>")
msg = bbcode_url_label.ReplaceAllString(msg,"<a href='$1$2//$3' rel='nofollow'>$4</i>") msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href='$1$2//$3' rel='nofollow'>$4</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class='postQuote'>$1</span>") msg = bbcodeQuotes.ReplaceAllString(msg, "<span class='postQuote'>$1</span>")
msg = bbcode_code.ReplaceAllString(msg,"<span class='codequotes'>$1</span>") msg = bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>")
} else { } else {
msg = string(msgbytes[0:len(msgbytes) - 10]) msg = string(msgbytes[0 : len(msgbytes)-10])
} }
return msg return msg

View File

@ -1,20 +1,20 @@
package main package main
func init() { func init() {
plugins["helloworld"] = NewPlugin("helloworld","Hello World","Azareal","http://github.com/Azareal","","","",init_helloworld,nil,deactivate_helloworld,nil,nil) plugins["helloworld"] = NewPlugin("helloworld", "Hello World", "Azareal", "http://github.com/Azareal", "", "", "", initHelloworld, nil, deactivateHelloworld, nil, nil)
} }
// init_helloworld is separate from init() as we don't want the plugin to run if the plugin is disabled // init_helloworld is separate from init() as we don't want the plugin to run if the plugin is disabled
func init_helloworld() error { func initHelloworld() error {
plugins["helloworld"].AddHook("rrow_assign", helloworld_reply) plugins["helloworld"].AddHook("rrow_assign", helloworldReply)
return nil return nil
} }
func deactivate_helloworld() { func deactivateHelloworld() {
plugins["helloworld"].RemoveHook("rrow_assign", helloworld_reply) plugins["helloworld"].RemoveHook("rrow_assign", helloworldReply)
} }
func helloworld_reply(data interface{}) interface{} { func helloworldReply(data interface{}) interface{} {
reply := data.(*Reply) reply := data.(*Reply)
reply.Content = "Hello World!" reply.Content = "Hello World!"
reply.ContentHtml = "Hello World!" reply.ContentHtml = "Hello World!"

View File

@ -4,73 +4,72 @@ package main
import "regexp" import "regexp"
import "strings" import "strings"
var markdown_max_depth int = 25 // How deep the parser will go when parsing Markdown strings var markdownMaxDepth int = 25 // How deep the parser will go when parsing Markdown strings
var markdown_unclosed_element []byte var markdownUnclosedElement []byte
var markdown_bold_tag_open, markdown_bold_tag_close []byte var markdownBoldTagOpen, markdownBoldTagClose []byte
var markdown_italic_tag_open, markdown_italic_tag_close []byte var markdownItalicTagOpen, markdownItalicTagClose []byte
var markdown_underline_tag_open, markdown_underline_tag_close []byte var markdownUnderlineTagOpen, markdownUnderlineTagClose []byte
var markdown_strike_tag_open, markdown_strike_tag_close []byte var markdownStrikeTagOpen, markdownStrikeTagClose []byte
var markdown_bold_italic *regexp.Regexp var markdownBoldItalic *regexp.Regexp
var markdown_bold *regexp.Regexp var markdownBold *regexp.Regexp
var markdown_italic *regexp.Regexp var markdownItalic *regexp.Regexp
var markdown_strike *regexp.Regexp var markdownStrike *regexp.Regexp
var markdown_underline *regexp.Regexp var markdownUnderline *regexp.Regexp
func init() { func init() {
plugins["markdown"] = NewPlugin("markdown","Markdown","Azareal","http://github.com/Azareal","","","",init_markdown,nil,deactivate_markdown,nil,nil) plugins["markdown"] = NewPlugin("markdown", "Markdown", "Azareal", "http://github.com/Azareal", "", "", "", initMarkdown, nil, deactivateMarkdown, nil, nil)
} }
func init_markdown() error { func initMarkdown() error {
//plugins["markdown"].AddHook("parse_assign", markdown_regex_parse) //plugins["markdown"].AddHook("parse_assign", markdownRegexParse)
plugins["markdown"].AddHook("parse_assign", markdown_parse) plugins["markdown"].AddHook("parse_assign", markdownParse)
markdown_unclosed_element = []byte("<span style='color: red;'>[Unclosed Element]</span>") markdownUnclosedElement = []byte("<span style='color: red;'>[Unclosed Element]</span>")
markdown_bold_tag_open = []byte("<b>") markdownBoldTagOpen = []byte("<b>")
markdown_bold_tag_close = []byte("</b>") markdownBoldTagClose = []byte("</b>")
markdown_italic_tag_open = []byte("<i>") markdownItalicTagOpen = []byte("<i>")
markdown_italic_tag_close = []byte("</i>") markdownItalicTagClose = []byte("</i>")
markdown_underline_tag_open = []byte("<u>") markdownUnderlineTagOpen = []byte("<u>")
markdown_underline_tag_close = []byte("</u>") markdownUnderlineTagClose = []byte("</u>")
markdown_strike_tag_open = []byte("<s>") markdownStrikeTagOpen = []byte("<s>")
markdown_strike_tag_close = []byte("</s>") markdownStrikeTagClose = []byte("</s>")
markdown_bold_italic = regexp.MustCompile(`\*\*\*(.*)\*\*\*`) markdownBoldItalic = regexp.MustCompile(`\*\*\*(.*)\*\*\*`)
markdown_bold = regexp.MustCompile(`\*\*(.*)\*\*`) markdownBold = regexp.MustCompile(`\*\*(.*)\*\*`)
markdown_italic = regexp.MustCompile(`\*(.*)\*`) markdownItalic = regexp.MustCompile(`\*(.*)\*`)
//markdown_strike = regexp.MustCompile(`\~\~(.*)\~\~`) //markdownStrike = regexp.MustCompile(`\~\~(.*)\~\~`)
markdown_strike = regexp.MustCompile(`\~(.*)\~`) markdownStrike = regexp.MustCompile(`\~(.*)\~`)
//markdown_underline = regexp.MustCompile(`\_\_(.*)\_\_`) //markdown_underline = regexp.MustCompile(`\_\_(.*)\_\_`)
markdown_underline = regexp.MustCompile(`\_(.*)\_`) markdownUnderline = regexp.MustCompile(`\_(.*)\_`)
return nil return nil
} }
func deactivate_markdown() { func deactivateMarkdown() {
//plugins["markdown"].RemoveHook("parse_assign", markdown_regex_parse) //plugins["markdown"].RemoveHook("parse_assign", markdownRegexParse)
plugins["markdown"].RemoveHook("parse_assign", markdown_parse) plugins["markdown"].RemoveHook("parse_assign", markdownParse)
} }
func markdown_regex_parse(msg string) string { func markdownRegexParse(msg string) string {
msg = markdown_bold_italic.ReplaceAllString(msg,"<i><b>$1</b></i>") msg = markdownBoldItalic.ReplaceAllString(msg, "<i><b>$1</b></i>")
msg = markdown_bold.ReplaceAllString(msg,"<b>$1</b>") msg = markdownBold.ReplaceAllString(msg, "<b>$1</b>")
msg = markdown_italic.ReplaceAllString(msg,"<i>$1</i>") msg = markdownItalic.ReplaceAllString(msg, "<i>$1</i>")
msg = markdown_strike.ReplaceAllString(msg,"<s>$1</s>") msg = markdownStrike.ReplaceAllString(msg, "<s>$1</s>")
msg = markdown_underline.ReplaceAllString(msg,"<u>$1</u>") msg = markdownUnderline.ReplaceAllString(msg, "<u>$1</u>")
return msg return msg
} }
// An adapter for the parser, so that the parser can call itself recursively. // An adapter for the parser, so that the parser can call itself recursively.
// This is less for the simple Markdown elements like bold and italics and more for the really complicated ones I plan on adding at some point. // This is less for the simple Markdown elements like bold and italics and more for the really complicated ones I plan on adding at some point.
func markdown_parse(msg string) string { func markdownParse(msg string) string {
return strings.TrimSpace(_markdown_parse(msg + " ",0)) return strings.TrimSpace(_markdownParse(msg+" ", 0))
} }
// Under Construction! // Under Construction!
func _markdown_parse(msg string, n int) string { func _markdownParse(msg string, n int) string {
if n > markdown_max_depth { if n > markdownMaxDepth {
return "<span style='color: red;'>[Markdown Error: Overflowed the max depth of 20]</span>" return "<span style='color: red;'>[Markdown Error: Overflowed the max depth of 20]</span>"
} }
@ -87,205 +86,205 @@ func _markdown_parse(msg string, n int) string {
//log.Print("--OUTER MARKDOWN LOOP END--") //log.Print("--OUTER MARKDOWN LOOP END--")
//log.Print(" ")*/ //log.Print(" ")*/
switch(msg[index]) { switch msg[index] {
case '_': case '_':
var startIndex int = index var startIndex int = index
if (index + 1) >= len(msg) { if (index + 1) >= len(msg) {
break break
} }
index++ index++
index = markdown_skip_until_char(msg, index, '_') index = markdownSkipUntilChar(msg, index, '_')
if (index - (startIndex + 1)) < 2 || index >= len(msg) { if (index-(startIndex+1)) < 2 || index >= len(msg) {
break break
} }
sIndex := startIndex + 1 sIndex := startIndex + 1
lIndex := index lIndex := index
index++ index++
outbytes = append(outbytes, msg[lastElement:startIndex]...) outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdown_underline_tag_open...) outbytes = append(outbytes, markdownUnderlineTagOpen...)
outbytes = append(outbytes, msg[sIndex:lIndex]...) outbytes = append(outbytes, msg[sIndex:lIndex]...)
outbytes = append(outbytes, markdown_underline_tag_close...) outbytes = append(outbytes, markdownUnderlineTagClose...)
lastElement = index lastElement = index
index-- index--
case '~': case '~':
var startIndex int = index var startIndex int = index
if (index + 1) >= len(msg) { if (index + 1) >= len(msg) {
break break
} }
index++ index++
index = markdown_skip_until_char(msg, index, '~') index = markdownSkipUntilChar(msg, index, '~')
if (index - (startIndex + 1)) < 2 || index >= len(msg) { if (index-(startIndex+1)) < 2 || index >= len(msg) {
break break
} }
sIndex := startIndex + 1 sIndex := startIndex + 1
lIndex := index lIndex := index
index++ index++
outbytes = append(outbytes, msg[lastElement:startIndex]...) outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdown_strike_tag_open...) outbytes = append(outbytes, markdownStrikeTagOpen...)
outbytes = append(outbytes, msg[sIndex:lIndex]...) outbytes = append(outbytes, msg[sIndex:lIndex]...)
outbytes = append(outbytes, markdown_strike_tag_close...) outbytes = append(outbytes, markdownStrikeTagClose...)
lastElement = index lastElement = index
index-- index--
case '*': case '*':
//log.Print("------") //log.Print("------")
//log.Print("[]byte(msg):",[]byte(msg)) //log.Print("[]byte(msg):",[]byte(msg))
//log.Print("len(msg)",len(msg)) //log.Print("len(msg)",len(msg))
//log.Print("start index",index) //log.Print("start index",index)
//log.Print("start msg[index]",msg[index]) //log.Print("start msg[index]",msg[index])
//log.Print("start string(msg[index])",string(msg[index])) //log.Print("start string(msg[index])",string(msg[index]))
//log.Print("start []byte(msg[:index])",[]byte(msg[:index])) //log.Print("start []byte(msg[:index])",[]byte(msg[:index]))
var startIndex int = index var startIndex int = index
var italic bool = true var italic bool = true
var bold bool var bold bool
if (index + 2) < len(msg) { if (index + 2) < len(msg) {
//log.Print("start index + 1",index + 1) //log.Print("start index + 1",index + 1)
//log.Print("start msg[index]",msg[index + 1]) //log.Print("start msg[index]",msg[index + 1])
//log.Print("start string(msg[index])",string(msg[index + 1])) //log.Print("start string(msg[index])",string(msg[index + 1]))
if msg[index + 1] == '*' { if msg[index+1] == '*' {
//log.Print("two asterisks") //log.Print("two asterisks")
bold = true bold = true
index++
if msg[index + 1] != '*' {
italic = false
} else {
//log.Print("three asterisks")
index++
}
}
}
//log.Print("lastElement",lastElement)
//log.Print("startIndex:",startIndex)
//log.Print("msg[startIndex]",msg[startIndex])
//log.Print("string(msg[startIndex])",string(msg[startIndex]))
//log.Print("preabrupt index",index)
//log.Print("preabrupt msg[index]",msg[index])
//log.Print("preabrupt string(msg[index])",string(msg[index]))
//log.Print("preabrupt []byte(msg[:index])",[]byte(msg[:index]))
//log.Print("preabrupt msg[:index]",msg[:index])
// Does the string terminate abruptly?
if (index + 1) >= len(msg) {
break
}
index++
//log.Print("preskip index",index)
//log.Print("preskip msg[index]",msg[index])
//log.Print("preskip string(msg[index])",string(msg[index]))
index = markdown_skip_until_asterisk(msg,index)
if index >= len(msg) {
break
}
//log.Print("index",index)
//log.Print("[]byte(msg[:index])",[]byte(msg[:index]))
//log.Print("msg[index]",msg[index])
sIndex := startIndex
lIndex := index
if bold && italic {
//log.Print("bold & italic final code")
if (index + 3) >= len(msg) {
//log.Print("unclosed markdown element @ exit element")
outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdown_unclosed_element...)
lastElement = startIndex
break
}
index += 3
sIndex += 3
} else if bold {
//log.Print("bold final code")
if (index + 2) >= len(msg) {
//log.Print("true unclosed markdown element @ exit element")
outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdown_unclosed_element...)
lastElement = startIndex
break
}
index += 2
sIndex += 2
} else {
//log.Print("italic final code")
if (index + 1) >= len(msg) {
//log.Print("true unclosed markdown element @ exit element")
outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdown_unclosed_element...)
lastElement = startIndex
break
}
index++ index++
sIndex++ if msg[index+1] != '*' {
italic = false
} else {
//log.Print("three asterisks")
index++
}
} }
}
//log.Print("sIndex",sIndex) //log.Print("lastElement",lastElement)
//log.Print("lIndex",lIndex) //log.Print("startIndex:",startIndex)
//log.Print("msg[startIndex]",msg[startIndex])
//log.Print("string(msg[startIndex])",string(msg[startIndex]))
if lIndex <= sIndex { //log.Print("preabrupt index",index)
//log.Print("unclosed markdown element @ lIndex <= sIndex") //log.Print("preabrupt msg[index]",msg[index])
//log.Print("preabrupt string(msg[index])",string(msg[index]))
//log.Print("preabrupt []byte(msg[:index])",[]byte(msg[:index]))
//log.Print("preabrupt msg[:index]",msg[:index])
// Does the string terminate abruptly?
if (index + 1) >= len(msg) {
break
}
index++
//log.Print("preskip index",index)
//log.Print("preskip msg[index]",msg[index])
//log.Print("preskip string(msg[index])",string(msg[index]))
index = markdownSkipUntilAsterisk(msg, index)
if index >= len(msg) {
break
}
//log.Print("index",index)
//log.Print("[]byte(msg[:index])",[]byte(msg[:index]))
//log.Print("msg[index]",msg[index])
sIndex := startIndex
lIndex := index
if bold && italic {
//log.Print("bold & italic final code")
if (index + 3) >= len(msg) {
//log.Print("unclosed markdown element @ exit element")
outbytes = append(outbytes, msg[lastElement:startIndex]...) outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdown_unclosed_element...) outbytes = append(outbytes, markdownUnclosedElement...)
lastElement = startIndex lastElement = startIndex
break break
} }
index += 3
if sIndex < 0 || lIndex < 0 { sIndex += 3
//log.Print("unclosed markdown element @ sIndex < 0 || lIndex < 0") } else if bold {
//log.Print("bold final code")
if (index + 2) >= len(msg) {
//log.Print("true unclosed markdown element @ exit element")
outbytes = append(outbytes, msg[lastElement:startIndex]...) outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdown_unclosed_element...) outbytes = append(outbytes, markdownUnclosedElement...)
lastElement = startIndex lastElement = startIndex
break break
} }
index += 2
sIndex += 2
} else {
//log.Print("italic final code")
if (index + 1) >= len(msg) {
//log.Print("true unclosed markdown element @ exit element")
outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdownUnclosedElement...)
lastElement = startIndex
break
}
index++
sIndex++
}
//log.Print("final sIndex",sIndex) //log.Print("sIndex",sIndex)
//log.Print("final lIndex",lIndex) //log.Print("lIndex",lIndex)
//log.Print("final index",index)
//log.Print("final msg[index]",msg[index])
//log.Print("final string(msg[index])",string(msg[index]))
//log.Print("final msg[sIndex]",msg[sIndex])
//log.Print("final string(msg[sIndex])",string(msg[sIndex]))
//log.Print("final msg[lIndex]",msg[lIndex])
//log.Print("final string(msg[lIndex])",string(msg[lIndex]))
//log.Print("[]byte(msg[:sIndex])",[]byte(msg[:sIndex]))
//log.Print("[]byte(msg[:lIndex])",[]byte(msg[:lIndex]))
if lIndex <= sIndex {
//log.Print("unclosed markdown element @ lIndex <= sIndex")
outbytes = append(outbytes, msg[lastElement:startIndex]...) outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdownUnclosedElement...)
lastElement = startIndex
break
}
if bold { if sIndex < 0 || lIndex < 0 {
outbytes = append(outbytes, markdown_bold_tag_open...) //log.Print("unclosed markdown element @ sIndex < 0 || lIndex < 0")
} outbytes = append(outbytes, msg[lastElement:startIndex]...)
if italic { outbytes = append(outbytes, markdownUnclosedElement...)
outbytes = append(outbytes, markdown_italic_tag_open...) lastElement = startIndex
} break
}
outbytes = append(outbytes, msg[sIndex:lIndex]...) //log.Print("final sIndex",sIndex)
//log.Print("final lIndex",lIndex)
//log.Print("final index",index)
//log.Print("final msg[index]",msg[index])
//log.Print("final string(msg[index])",string(msg[index]))
if italic { //log.Print("final msg[sIndex]",msg[sIndex])
outbytes = append(outbytes, markdown_italic_tag_close...) //log.Print("final string(msg[sIndex])",string(msg[sIndex]))
} //log.Print("final msg[lIndex]",msg[lIndex])
if bold { //log.Print("final string(msg[lIndex])",string(msg[lIndex]))
outbytes = append(outbytes, markdown_bold_tag_close...)
}
lastElement = index //log.Print("[]byte(msg[:sIndex])",[]byte(msg[:sIndex]))
index-- //log.Print("[]byte(msg[:lIndex])",[]byte(msg[:lIndex]))
outbytes = append(outbytes, msg[lastElement:startIndex]...)
if bold {
outbytes = append(outbytes, markdownBoldTagOpen...)
}
if italic {
outbytes = append(outbytes, markdownItalicTagOpen...)
}
outbytes = append(outbytes, msg[sIndex:lIndex]...)
if italic {
outbytes = append(outbytes, markdownItalicTagClose...)
}
if bold {
outbytes = append(outbytes, markdownBoldTagClose...)
}
lastElement = index
index--
//case '`': //case '`':
//case '_': //case '_':
//case '~': //case '~':
@ -303,7 +302,7 @@ func _markdown_parse(msg string, n int) string {
return string(outbytes) return string(outbytes)
} }
func markdown_find_char(data string ,index int ,char byte) bool { func markdownFindChar(data string, index int, char byte) bool {
for ; index < len(data); index++ { for ; index < len(data); index++ {
item := data[index] item := data[index]
if item > 32 { if item > 32 {
@ -313,7 +312,7 @@ func markdown_find_char(data string ,index int ,char byte) bool {
return false return false
} }
func markdown_skip_until_char(data string, index int, char byte) int { func markdownSkipUntilChar(data string, index int, char byte) int {
for ; index < len(data); index++ { for ; index < len(data); index++ {
if data[index] == char { if data[index] == char {
break break
@ -322,22 +321,23 @@ func markdown_skip_until_char(data string, index int, char byte) int {
return index return index
} }
func markdown_skip_until_asterisk(data string, index int) int { func markdownSkipUntilAsterisk(data string, index int) int {
SwitchLoop: SwitchLoop:
for ; index < len(data); index++ { for ; index < len(data); index++ {
switch(data[index]) { switch data[index] {
case 10: case 10:
if ((index+1) < len(data)) && markdown_find_char(data,index,'*') { if ((index + 1) < len(data)) && markdownFindChar(data, index, '*') {
index = markdown_skip_list(data,index) index = markdownSkipList(data, index)
} }
case '*': break SwitchLoop case '*':
break SwitchLoop
} }
} }
return index return index
} }
// plugin_markdown doesn't support lists yet, but I want it to be easy to have nested lists when we do have them // plugin_markdown doesn't support lists yet, but I want it to be easy to have nested lists when we do have them
func markdown_skip_list(data string, index int) int { func markdownSkipList(data string, index int) int {
var lastNewline int var lastNewline int
var datalen int = len(data) var datalen int = len(data)

View File

@ -26,12 +26,12 @@ func init() {
That Uninstallation field which is currently unused is for not only deactivating this plugin, but for purging any data associated with it such a new tables or data produced by the end-user. That Uninstallation field which is currently unused is for not only deactivating this plugin, but for purging any data associated with it such a new tables or data produced by the end-user.
*/ */
plugins["skeleton"] = NewPlugin("skeleton","Skeleton","Azareal","","","","",init_skeleton, activate_skeleton, deactivate_skeleton,nil,nil) plugins["skeleton"] = NewPlugin("skeleton", "Skeleton", "Azareal", "", "", "", "", initSkeleton, activateSkeleton, deactivateSkeleton, nil, nil)
} }
func init_skeleton() error { return nil; } func initSkeleton() error { return nil }
// Any errors encountered while trying to activate the plugin are reported back to the admin and the activation is aborted // Any errors encountered while trying to activate the plugin are reported back to the admin and the activation is aborted
func activate_skeleton() error { return nil; } func activateSkeleton() error { return nil }
func deactivate_skeleton() {} func deactivateSkeleton() {}

View File

@ -3,151 +3,148 @@ package main
import ( import (
//"fmt" //"fmt"
"bytes" "bytes"
"strings"
"strconv"
"errors"
"context" "context"
"net/http" "database/sql"
"errors"
"html" "html"
"html/template" "html/template"
"database/sql" "net/http"
"strconv"
"strings"
"./query_gen/lib" "./query_gen/lib"
) )
var socialgroups_list_stmt *sql.Stmt var socialgroupsListStmt *sql.Stmt
var socialgroups_member_list_stmt *sql.Stmt var socialgroupsMemberListStmt *sql.Stmt
var socialgroups_member_list_join_stmt *sql.Stmt var socialgroupsMemberListJoinStmt *sql.Stmt
var socialgroups_get_member_stmt *sql.Stmt var socialgroupsGetMemberStmt *sql.Stmt
var socialgroups_get_group_stmt *sql.Stmt var socialgroupsGetGroupStmt *sql.Stmt
var socialgroups_create_group_stmt *sql.Stmt var socialgroupsCreateGroupStmt *sql.Stmt
var socialgroups_attach_forum_stmt *sql.Stmt var socialgroupsAttachForumStmt *sql.Stmt
var socialgroups_unattach_forum_stmt *sql.Stmt var socialgroupsUnattachForumStmt *sql.Stmt
var socialgroups_add_member_stmt *sql.Stmt var socialgroupsAddMemberStmt *sql.Stmt
// TO-DO: Add a better way of splitting up giant plugins like this // TO-DO: Add a better way of splitting up giant plugins like this
type SocialGroup struct
{ // SocialGroup is a struct representing a social group
ID int type SocialGroup struct {
Link string ID int
Name string Link string
Desc string Name string
Active bool Desc string
Active bool
Privacy int /* 0: Public, 1: Protected, 2: Private */ Privacy int /* 0: Public, 1: Protected, 2: Private */
// Who should be able to accept applications and create invites? Mods+ or just admins? Mods is a good start, we can ponder over whether we should make this more flexible in the future. // Who should be able to accept applications and create invites? Mods+ or just admins? Mods is a good start, we can ponder over whether we should make this more flexible in the future.
Joinable int /* 0: Private, 1: Anyone can join, 2: Applications, 3: Invite-only */ Joinable int /* 0: Private, 1: Anyone can join, 2: Applications, 3: Invite-only */
MemberCount int MemberCount int
Owner int Owner int
Backdrop string Backdrop string
CreatedAt string CreatedAt string
LastUpdateTime string LastUpdateTime string
MainForumID int MainForumID int
MainForum *Forum MainForum *Forum
Forums []*Forum Forums []*Forum
ExtData ExtData ExtData ExtData
} }
type SocialGroupPage struct type SocialGroupPage struct {
{ Title string
Title string
CurrentUser User CurrentUser User
Header HeaderVars Header *HeaderVars
ItemList []*TopicsRow ItemList []*TopicsRow
Forum Forum Forum Forum
SocialGroup SocialGroup SocialGroup *SocialGroup
Page int Page int
LastPage int LastPage int
ExtData ExtData
} }
type SocialGroupListPage struct // SocialGroupListPage is a page struct for constructing a list of every social group
{ type SocialGroupListPage struct {
Title string Title string
CurrentUser User CurrentUser User
Header HeaderVars Header *HeaderVars
GroupList []SocialGroup GroupList []*SocialGroup
ExtData ExtData
} }
type SocialGroupMemberListPage struct type SocialGroupMemberListPage struct {
{ Title string
Title string
CurrentUser User CurrentUser User
Header HeaderVars Header *HeaderVars
ItemList []SocialGroupMember ItemList []SocialGroupMember
SocialGroup SocialGroup SocialGroup *SocialGroup
Page int Page int
LastPage int LastPage int
ExtData ExtData
} }
type SocialGroupMember struct // SocialGroupMember is a struct representing a specific member of a group, not to be confused with the global User struct.
{ type SocialGroupMember struct {
Link string Link string
Rank int /* 0: Member. 1: Mod. 2: Admin. */ Rank int /* 0: Member. 1: Mod. 2: Admin. */
RankString string /* Member, Mod, Admin, Owner */ RankString string /* Member, Mod, Admin, Owner */
PostCount int PostCount int
JoinedAt string JoinedAt string
Offline bool // TO-DO: Need to track the online states of members when WebSockets are enabled Offline bool // TO-DO: Need to track the online states of members when WebSockets are enabled
User User User User
} }
// TO-DO: Add a plugin interface instead of having a bunch of argument to AddPlugin?
func init() { func init() {
plugins["socialgroups"] = NewPlugin("socialgroups","Social Groups","Azareal","http://github.com/Azareal","","","",init_socialgroups,nil,deactivate_socialgroups,install_socialgroups,nil) plugins["socialgroups"] = NewPlugin("socialgroups", "Social Groups", "Azareal", "http://github.com/Azareal", "", "", "", initSocialgroups, nil, deactivateSocialgroups, installSocialgroups, nil)
} }
func init_socialgroups() (err error) { func initSocialgroups() (err error) {
plugins["socialgroups"].AddHook("intercept_build_widgets", socialgroups_widgets) plugins["socialgroups"].AddHook("intercept_build_widgets", socialgroupsWidgets)
plugins["socialgroups"].AddHook("trow_assign", socialgroups_trow_assign) plugins["socialgroups"].AddHook("trow_assign", socialgroupsTrowAssign)
plugins["socialgroups"].AddHook("topic_create_pre_loop", socialgroups_topic_create_pre_loop) plugins["socialgroups"].AddHook("topic_create_pre_loop", socialgroupsTopicCreatePreLoop)
plugins["socialgroups"].AddHook("pre_render_view_forum", socialgroups_pre_render_view_forum) plugins["socialgroups"].AddHook("pre_render_view_forum", socialgroupsPreRenderViewForum)
plugins["socialgroups"].AddHook("simple_forum_check_pre_perms", socialgroups_forum_check) plugins["socialgroups"].AddHook("simple_forum_check_pre_perms", socialgroupsForumCheck)
plugins["socialgroups"].AddHook("forum_check_pre_perms", socialgroups_forum_check) plugins["socialgroups"].AddHook("forum_check_pre_perms", socialgroupsForumCheck)
// TO-DO: Auto-grant this perm to admins upon installation? // TO-DO: Auto-grant this perm to admins upon installation?
register_plugin_perm("CreateSocialGroup") registerPluginPerm("CreateSocialGroup")
router.HandleFunc("/groups/", socialgroups_group_list) router.HandleFunc("/groups/", socialgroupsGroupList)
router.HandleFunc("/group/", socialgroups_view_group) router.HandleFunc("/group/", socialgroupsViewGroup)
router.HandleFunc("/group/create/", socialgroups_create_group) router.HandleFunc("/group/create/", socialgroupsCreateGroup)
router.HandleFunc("/group/create/submit/", socialgroups_create_group_submit) router.HandleFunc("/group/create/submit/", socialgroupsCreateGroupSubmit)
router.HandleFunc("/group/members/", socialgroups_member_list) router.HandleFunc("/group/members/", socialgroupsMemberList)
socialgroups_list_stmt, err = qgen.Builder.SimpleSelect("socialgroups","sgid, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime","","","") socialgroupsListStmt, err = qgen.Builder.SimpleSelect("socialgroups", "sgid, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime", "", "", "")
if err != nil { if err != nil {
return err return err
} }
socialgroups_get_group_stmt, err = qgen.Builder.SimpleSelect("socialgroups","name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime","sgid = ?","","") socialgroupsGetGroupStmt, err = qgen.Builder.SimpleSelect("socialgroups", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "sgid = ?", "", "")
if err != nil { if err != nil {
return err return err
} }
socialgroups_member_list_stmt, err = qgen.Builder.SimpleSelect("socialgroups_members","sgid, uid, rank, posts, joinedAt","","","") socialgroupsMemberListStmt, err = qgen.Builder.SimpleSelect("socialgroups_members", "sgid, uid, rank, posts, joinedAt", "", "", "")
if err != nil { if err != nil {
return err return err
} }
socialgroups_member_list_join_stmt, err = qgen.Builder.SimpleLeftJoin("socialgroups_members","users","users.uid, socialgroups_members.rank, socialgroups_members.posts, socialgroups_members.joinedAt, users.name, users.avatar","socialgroups_members.uid = users.uid","socialgroups_members.sgid = ?","socialgroups_members.rank DESC, socialgroups_members.joinedat ASC","") socialgroupsMemberListJoinStmt, err = qgen.Builder.SimpleLeftJoin("socialgroups_members", "users", "users.uid, socialgroups_members.rank, socialgroups_members.posts, socialgroups_members.joinedAt, users.name, users.avatar", "socialgroups_members.uid = users.uid", "socialgroups_members.sgid = ?", "socialgroups_members.rank DESC, socialgroups_members.joinedat ASC", "")
if err != nil { if err != nil {
return err return err
} }
socialgroups_get_member_stmt, err = qgen.Builder.SimpleSelect("socialgroups_members","rank, posts, joinedAt","sgid = ? AND uid = ?","","") socialgroupsGetMemberStmt, err = qgen.Builder.SimpleSelect("socialgroups_members", "rank, posts, joinedAt", "sgid = ? AND uid = ?", "", "")
if err != nil { if err != nil {
return err return err
} }
socialgroups_create_group_stmt, err = qgen.Builder.SimpleInsert("socialgroups","name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime","?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()") socialgroupsCreateGroupStmt, err = qgen.Builder.SimpleInsert("socialgroups", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()")
if err != nil { if err != nil {
return err return err
} }
socialgroups_attach_forum_stmt, err = qgen.Builder.SimpleUpdate("forums","parentID = ?, parentType = 'socialgroup'","fid = ?") socialgroupsAttachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = ?, parentType = 'socialgroup'", "fid = ?")
if err != nil { if err != nil {
return err return err
} }
socialgroups_unattach_forum_stmt, err = qgen.Builder.SimpleUpdate("forums","parentID = 0, parentType = ''","fid = ?") socialgroupsUnattachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = 0, parentType = ''", "fid = ?")
if err != nil { if err != nil {
return err return err
} }
socialgroups_add_member_stmt, err = qgen.Builder.SimpleInsert("socialgroups_members","sgid, uid, rank, posts, joinedAt","?,?,?,0,UTC_TIMESTAMP()") socialgroupsAddMemberStmt, err = qgen.Builder.SimpleInsert("socialgroups_members", "sgid, uid, rank, posts, joinedAt", "?,?,?,0,UTC_TIMESTAMP()")
if err != nil { if err != nil {
return err return err
} }
@ -155,92 +152,92 @@ func init_socialgroups() (err error) {
return nil return nil
} }
func deactivate_socialgroups() { func deactivateSocialgroups() {
plugins["socialgroups"].RemoveHook("intercept_build_widgets", socialgroups_widgets) plugins["socialgroups"].RemoveHook("intercept_build_widgets", socialgroupsWidgets)
plugins["socialgroups"].RemoveHook("trow_assign", socialgroups_trow_assign) plugins["socialgroups"].RemoveHook("trow_assign", socialgroupsTrowAssign)
plugins["socialgroups"].RemoveHook("topic_create_pre_loop", socialgroups_topic_create_pre_loop) plugins["socialgroups"].RemoveHook("topic_create_pre_loop", socialgroupsTopicCreatePreLoop)
plugins["socialgroups"].RemoveHook("pre_render_view_forum", socialgroups_pre_render_view_forum) plugins["socialgroups"].RemoveHook("pre_render_view_forum", socialgroupsPreRenderViewForum)
plugins["socialgroups"].RemoveHook("simple_forum_check_pre_perms", socialgroups_forum_check) plugins["socialgroups"].RemoveHook("simple_forum_check_pre_perms", socialgroupsForumCheck)
plugins["socialgroups"].RemoveHook("forum_check_pre_perms", socialgroups_forum_check) plugins["socialgroups"].RemoveHook("forum_check_pre_perms", socialgroupsForumCheck)
deregister_plugin_perm("CreateSocialGroup") deregisterPluginPerm("CreateSocialGroup")
_ = router.RemoveFunc("/groups/") _ = router.RemoveFunc("/groups/")
_ = router.RemoveFunc("/group/") _ = router.RemoveFunc("/group/")
_ = router.RemoveFunc("/group/create/") _ = router.RemoveFunc("/group/create/")
_ = router.RemoveFunc("/group/create/submit/") _ = router.RemoveFunc("/group/create/submit/")
_ = socialgroups_list_stmt.Close() _ = socialgroupsListStmt.Close()
_ = socialgroups_member_list_stmt.Close() _ = socialgroupsMemberListStmt.Close()
_ = socialgroups_member_list_join_stmt.Close() _ = socialgroupsMemberListJoinStmt.Close()
_ = socialgroups_get_member_stmt.Close() _ = socialgroupsGetMemberStmt.Close()
_ = socialgroups_get_group_stmt.Close() _ = socialgroupsGetGroupStmt.Close()
_ = socialgroups_create_group_stmt.Close() _ = socialgroupsCreateGroupStmt.Close()
_ = socialgroups_attach_forum_stmt.Close() _ = socialgroupsAttachForumStmt.Close()
_ = socialgroups_unattach_forum_stmt.Close() _ = socialgroupsUnattachForumStmt.Close()
_ = socialgroups_add_member_stmt.Close() _ = socialgroupsAddMemberStmt.Close()
} }
// TO-DO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process // TO-DO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process
func install_socialgroups() error { func installSocialgroups() error {
sg_table_stmt, err := qgen.Builder.CreateTable("socialgroups","utf8mb4","utf8mb4_general_ci", sgTableStmt, err := qgen.Builder.CreateTable("socialgroups", "utf8mb4", "utf8mb4_general_ci",
[]qgen.DB_Table_Column{ []qgen.DB_Table_Column{
qgen.DB_Table_Column{"sgid","int",0,false,true,""}, qgen.DB_Table_Column{"sgid", "int", 0, false, true, ""},
qgen.DB_Table_Column{"name","varchar",100,false,false,""}, qgen.DB_Table_Column{"name", "varchar", 100, false, false, ""},
qgen.DB_Table_Column{"desc","varchar",200,false,false,""}, qgen.DB_Table_Column{"desc", "varchar", 200, false, false, ""},
qgen.DB_Table_Column{"active","boolean",1,false,false,""}, qgen.DB_Table_Column{"active", "boolean", 1, false, false, ""},
qgen.DB_Table_Column{"privacy","smallint",0,false,false,""}, qgen.DB_Table_Column{"privacy", "smallint", 0, false, false, ""},
qgen.DB_Table_Column{"joinable","smallint",0,false,false,"0"}, qgen.DB_Table_Column{"joinable", "smallint", 0, false, false, "0"},
qgen.DB_Table_Column{"owner","int",0,false,false,""}, qgen.DB_Table_Column{"owner", "int", 0, false, false, ""},
qgen.DB_Table_Column{"memberCount","int",0,false,false,""}, qgen.DB_Table_Column{"memberCount", "int", 0, false, false, ""},
qgen.DB_Table_Column{"mainForum","int",0,false,false,"0"}, // The board the user lands on when they click on a group, we'll make it possible for group admins to change what users land on qgen.DB_Table_Column{"mainForum", "int", 0, false, false, "0"}, // The board the user lands on when they click on a group, we'll make it possible for group admins to change what users land on
//qgen.DB_Table_Column{"boards","varchar",255,false,false,""}, // Cap the max number of boards at 8 to avoid overflowing the confines of a 64-bit integer? //qgen.DB_Table_Column{"boards","varchar",255,false,false,""}, // Cap the max number of boards at 8 to avoid overflowing the confines of a 64-bit integer?
qgen.DB_Table_Column{"backdrop","varchar",200,false,false,""}, // File extension for the uploaded file, or an external link qgen.DB_Table_Column{"backdrop", "varchar", 200, false, false, ""}, // File extension for the uploaded file, or an external link
qgen.DB_Table_Column{"createdAt","createdAt",0,false,false,""}, qgen.DB_Table_Column{"createdAt", "createdAt", 0, false, false, ""},
qgen.DB_Table_Column{"lastUpdateTime","datetime",0,false,false,""}, qgen.DB_Table_Column{"lastUpdateTime", "datetime", 0, false, false, ""},
}, },
[]qgen.DB_Table_Key{ []qgen.DB_Table_Key{
qgen.DB_Table_Key{"sgid","primary"}, qgen.DB_Table_Key{"sgid", "primary"},
}, },
) )
if err != nil { if err != nil {
return err return err
} }
_, err = sg_table_stmt.Exec() _, err = sgTableStmt.Exec()
if err != nil { if err != nil {
return err return err
} }
sg_members_table_stmt, err := qgen.Builder.CreateTable("socialgroups_members","","", sgMembersTableStmt, err := qgen.Builder.CreateTable("socialgroups_members", "", "",
[]qgen.DB_Table_Column{ []qgen.DB_Table_Column{
qgen.DB_Table_Column{"sgid","int",0,false,false,""}, qgen.DB_Table_Column{"sgid", "int", 0, false, false, ""},
qgen.DB_Table_Column{"uid","int",0,false,false,""}, qgen.DB_Table_Column{"uid", "int", 0, false, false, ""},
qgen.DB_Table_Column{"rank","int",0,false,false,"0"}, /* 0: Member. 1: Mod. 2: Admin. */ qgen.DB_Table_Column{"rank", "int", 0, false, false, "0"}, /* 0: Member. 1: Mod. 2: Admin. */
qgen.DB_Table_Column{"posts","int",0,false,false,"0"}, /* Per-Group post count. Should we do some sort of score system? */ qgen.DB_Table_Column{"posts", "int", 0, false, false, "0"}, /* Per-Group post count. Should we do some sort of score system? */
qgen.DB_Table_Column{"joinedAt","datetime",0,false,false,""}, qgen.DB_Table_Column{"joinedAt", "datetime", 0, false, false, ""},
}, },
[]qgen.DB_Table_Key{}, []qgen.DB_Table_Key{},
) )
if err != nil { if err != nil {
return err return err
} }
_, err = sg_members_table_stmt.Exec() _, err = sgMembersTableStmt.Exec()
return err return err
} }
// TO-DO; Implement an uninstallation system into Gosora. And a better installation system. // TO-DO; Implement an uninstallation system into Gosora. And a better installation system.
func uninstall_socialgroups() error { func uninstallSocialgroups() error {
return nil return nil
} }
// TO-DO: Do this properly via the widget system // TO-DO: Do this properly via the widget system
func socialgroups_common_area_widgets(headerVars *HeaderVars) { func socialgroupsCommonAreaWidgets(headerVars *HeaderVars) {
// TO-DO: Hot Groups? Featured Groups? Official Groups? // TO-DO: Hot Groups? Featured Groups? Official Groups?
var b bytes.Buffer var b bytes.Buffer
var menu WidgetMenu = WidgetMenu{"Social Groups",[]WidgetMenuItem{ var menu = WidgetMenu{"Social Groups", []WidgetMenuItem{
WidgetMenuItem{"Create Group","/group/create/",false}, WidgetMenuItem{"Create Group", "/group/create/", false},
}} }}
err := templates.ExecuteTemplate(&b,"widget_menu.html",menu) err := templates.ExecuteTemplate(&b, "widget_menu.html", menu)
if err != nil { if err != nil {
LogError(err) LogError(err)
return return
@ -255,16 +252,16 @@ func socialgroups_common_area_widgets(headerVars *HeaderVars) {
// TO-DO: Do this properly via the widget system // TO-DO: Do this properly via the widget system
// TO-DO: Make a better more customisable group widget system // TO-DO: Make a better more customisable group widget system
func socialgroups_group_widgets(headerVars *HeaderVars, sgItem SocialGroup) (success bool) { func socialgroupsGroupWidgets(headerVars *HeaderVars, sgItem *SocialGroup) (success bool) {
return false // Disabled until the next commit return false // Disabled until the next commit
var b bytes.Buffer /*var b bytes.Buffer
var menu WidgetMenu = WidgetMenu{"Group Options",[]WidgetMenuItem{ var menu WidgetMenu = WidgetMenu{"Group Options", []WidgetMenuItem{
WidgetMenuItem{"Join","/group/join/" + strconv.Itoa(sgItem.ID),false}, WidgetMenuItem{"Join", "/group/join/" + strconv.Itoa(sgItem.ID), false},
WidgetMenuItem{"Members","/group/members/" + strconv.Itoa(sgItem.ID),false}, WidgetMenuItem{"Members", "/group/members/" + strconv.Itoa(sgItem.ID), false},
}} }}
err := templates.ExecuteTemplate(&b,"widget_menu.html",menu) err := templates.ExecuteTemplate(&b, "widget_menu.html", menu)
if err != nil { if err != nil {
LogError(err) LogError(err)
return false return false
@ -277,249 +274,256 @@ func socialgroups_group_widgets(headerVars *HeaderVars, sgItem SocialGroup) (suc
} else { } else {
return false return false
} }
return true return true*/
} }
/* /*
Custom Pages Custom Pages
*/ */
func socialgroups_group_list(w http.ResponseWriter, r *http.Request, user User) { func socialgroupsGroupList(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user) headerVars, ok := SessionCheck(w, r, &user)
if !ok { if !ok {
return return
} }
socialgroups_common_area_widgets(&headerVars) socialgroupsCommonAreaWidgets(headerVars)
rows, err := socialgroups_list_stmt.Query() rows, err := socialgroupsListStmt.Query()
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
InternalError(err,w) InternalError(err, w)
return return
} }
var sgList []SocialGroup var sgList []*SocialGroup
for rows.Next() { for rows.Next() {
sgItem := SocialGroup{ID:0} sgItem := &SocialGroup{ID: 0}
err := rows.Scan(&sgItem.ID, &sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Joinable, &sgItem.Owner, &sgItem.MemberCount, &sgItem.CreatedAt, &sgItem.LastUpdateTime) err := rows.Scan(&sgItem.ID, &sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Joinable, &sgItem.Owner, &sgItem.MemberCount, &sgItem.CreatedAt, &sgItem.LastUpdateTime)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return return
} }
sgItem.Link = socialgroups_build_group_url(name_to_slug(sgItem.Name),sgItem.ID) sgItem.Link = socialgroupsBuildGroupURL(nameToSlug(sgItem.Name), sgItem.ID)
sgList = append(sgList,sgItem) sgList = append(sgList, sgItem)
} }
err = rows.Err() err = rows.Err()
if err != nil {
InternalError(err,w)
return
}
rows.Close()
pi := SocialGroupListPage{"Group List",user,headerVars,sgList,extData}
err = templates.ExecuteTemplate(w,"socialgroups_group_list.html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return
}
rows.Close()
pi := SocialGroupListPage{"Group List", user, headerVars, sgList}
err = templates.ExecuteTemplate(w, "socialgroups_group_list.html", pi)
if err != nil {
InternalError(err, w)
} }
} }
func socialgroups_get_group(sgid int) (sgItem SocialGroup, err error) { func socialgroupsGetGroup(sgid int) (sgItem *SocialGroup, err error) {
sgItem = SocialGroup{ID:sgid} sgItem = &SocialGroup{ID: sgid}
err = socialgroups_get_group_stmt.QueryRow(sgid).Scan(&sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Joinable, &sgItem.Owner, &sgItem.MemberCount, &sgItem.MainForumID, &sgItem.Backdrop, &sgItem.CreatedAt, &sgItem.LastUpdateTime) err = socialgroupsGetGroupStmt.QueryRow(sgid).Scan(&sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Joinable, &sgItem.Owner, &sgItem.MemberCount, &sgItem.MainForumID, &sgItem.Backdrop, &sgItem.CreatedAt, &sgItem.LastUpdateTime)
return sgItem, err return sgItem, err
} }
func socialgroups_view_group(w http.ResponseWriter, r *http.Request, user User) { func socialgroupsViewGroup(w http.ResponseWriter, r *http.Request, user User) {
// SEO URLs... // SEO URLs...
halves := strings.Split(r.URL.Path[len("/group/"):],".") halves := strings.Split(r.URL.Path[len("/group/"):], ".")
if len(halves) < 2 { if len(halves) < 2 {
halves = append(halves,halves[0]) halves = append(halves, halves[0])
} }
sgid, err := strconv.Atoi(halves[1]) sgid, err := strconv.Atoi(halves[1])
if err != nil { if err != nil {
PreError("Not a valid group ID",w,r) PreError("Not a valid group ID", w, r)
return return
} }
sgItem, err := socialgroups_get_group(sgid) sgItem, err := socialgroupsGetGroup(sgid)
if err != nil { if err != nil {
LocalError("Bad group",w,r,user) LocalError("Bad group", w, r, user)
return return
} }
if !sgItem.Active { if !sgItem.Active {
NotFound(w,r) NotFound(w, r)
} }
// Re-route the request to route_forums // Re-route the request to route_forums
var ctx context.Context = context.WithValue(r.Context(),"socialgroups_current_group",sgItem) var ctx = context.WithValue(r.Context(), "socialgroups_current_group", sgItem)
route_forum(w,r.WithContext(ctx),user,strconv.Itoa(sgItem.MainForumID)) route_forum(w, r.WithContext(ctx), user, strconv.Itoa(sgItem.MainForumID))
} }
func socialgroups_create_group(w http.ResponseWriter, r *http.Request, user User) { func socialgroupsCreateGroup(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user) headerVars, ok := SessionCheck(w, r, &user)
if !ok { if !ok {
return return
} }
// TO-DO: Add an approval queue mode for group creation // TO-DO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] { if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] {
NoPermissions(w,r,user) NoPermissions(w, r, user)
return return
} }
socialgroups_common_area_widgets(&headerVars) socialgroupsCommonAreaWidgets(headerVars)
pi := Page{"Create Group",user,headerVars,tList,nil} pi := Page{"Create Group", user, headerVars, tList, nil}
err := templates.ExecuteTemplate(w,"socialgroups_create_group.html", pi) err := templates.ExecuteTemplate(w, "socialgroups_create_group.html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
func socialgroups_create_group_submit(w http.ResponseWriter, r *http.Request, user User) { func socialgroupsCreateGroupSubmit(w http.ResponseWriter, r *http.Request, user User) {
// TO-DO: Add an approval queue mode for group creation // TO-DO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] { if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] {
NoPermissions(w,r,user) NoPermissions(w, r, user)
return return
} }
var group_active bool = true var groupActive = true
var group_name string = html.EscapeString(r.PostFormValue("group_name")) var groupName = html.EscapeString(r.PostFormValue("group_name"))
var group_desc string = html.EscapeString(r.PostFormValue("group_desc")) var groupDesc = html.EscapeString(r.PostFormValue("group_desc"))
var gprivacy string = r.PostFormValue("group_privacy") var gprivacy = r.PostFormValue("group_privacy")
var group_privacy int var groupPrivacy int
switch(gprivacy) { switch gprivacy {
case "0": group_privacy = 0 // Public case "0":
case "1": group_privacy = 1 // Protected groupPrivacy = 0 // Public
case "2": group_privacy = 2 // private case "1":
default: group_privacy = 0 groupPrivacy = 1 // Protected
case "2":
groupPrivacy = 2 // private
default:
groupPrivacy = 0
} }
// Create the backing forum // Create the backing forum
fid, err := fstore.CreateForum(group_name,"",true,"") fid, err := fstore.CreateForum(groupName, "", true, "")
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return return
} }
res, err := socialgroups_create_group_stmt.Exec(group_name, group_desc, group_active, group_privacy, user.ID, fid) res, err := socialgroupsCreateGroupStmt.Exec(groupName, groupDesc, groupActive, groupPrivacy, user.ID, fid)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return return
} }
lastId, err := res.LastInsertId() lastID, err := res.LastInsertId()
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return return
} }
// Add the main backing forum to the forum list // Add the main backing forum to the forum list
err = socialgroups_attach_forum(int(lastId),fid) err = socialgroupsAttachForum(int(lastID), fid)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return return
} }
_, err = socialgroups_add_member_stmt.Exec(lastId,user.ID,2) _, err = socialgroupsAddMemberStmt.Exec(lastID, user.ID, 2)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return return
} }
http.Redirect(w,r,socialgroups_build_group_url(name_to_slug(group_name),int(lastId)), http.StatusSeeOther) http.Redirect(w, r, socialgroupsBuildGroupURL(nameToSlug(groupName), int(lastID)), http.StatusSeeOther)
} }
func socialgroups_member_list(w http.ResponseWriter, r *http.Request, user User) { func socialgroupsMemberList(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user) headerVars, ok := SessionCheck(w, r, &user)
if !ok { if !ok {
return return
} }
// SEO URLs... // SEO URLs...
halves := strings.Split(r.URL.Path[len("/group/members/"):],".") halves := strings.Split(r.URL.Path[len("/group/members/"):], ".")
if len(halves) < 2 { if len(halves) < 2 {
halves = append(halves,halves[0]) halves = append(halves, halves[0])
} }
sgid, err := strconv.Atoi(halves[1]) sgid, err := strconv.Atoi(halves[1])
if err != nil { if err != nil {
PreError("Not a valid group ID",w,r) PreError("Not a valid group ID", w, r)
return return
} }
var sgItem SocialGroup = SocialGroup{ID:sgid} var sgItem = &SocialGroup{ID: sgid}
var mainForum int // Unused var mainForum int // Unused
err = socialgroups_get_group_stmt.QueryRow(sgid).Scan(&sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Joinable, &sgItem.Owner, &sgItem.MemberCount, &mainForum, &sgItem.Backdrop, &sgItem.CreatedAt, &sgItem.LastUpdateTime) err = socialgroupsGetGroupStmt.QueryRow(sgid).Scan(&sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Joinable, &sgItem.Owner, &sgItem.MemberCount, &mainForum, &sgItem.Backdrop, &sgItem.CreatedAt, &sgItem.LastUpdateTime)
if err != nil { if err != nil {
LocalError("Bad group",w,r,user) LocalError("Bad group", w, r, user)
return return
} }
sgItem.Link = socialgroups_build_group_url(name_to_slug(sgItem.Name),sgItem.ID) sgItem.Link = socialgroupsBuildGroupURL(nameToSlug(sgItem.Name), sgItem.ID)
socialgroups_group_widgets(&headerVars, sgItem) socialgroupsGroupWidgets(headerVars, sgItem)
rows, err := socialgroups_member_list_join_stmt.Query(sgid) rows, err := socialgroupsMemberListJoinStmt.Query(sgid)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
InternalError(err,w) InternalError(err, w)
return return
} }
var sgMembers []SocialGroupMember var sgMembers []SocialGroupMember
for rows.Next() { for rows.Next() {
sgMember := SocialGroupMember{PostCount:0} sgMember := SocialGroupMember{PostCount: 0}
err := rows.Scan(&sgMember.User.ID,&sgMember.Rank,&sgMember.PostCount,&sgMember.JoinedAt,&sgMember.User.Name, &sgMember.User.Avatar) err := rows.Scan(&sgMember.User.ID, &sgMember.Rank, &sgMember.PostCount, &sgMember.JoinedAt, &sgMember.User.Name, &sgMember.User.Avatar)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return return
} }
sgMember.Link = build_profile_url(name_to_slug(sgMember.User.Name),sgMember.User.ID) sgMember.Link = buildProfileURL(nameToSlug(sgMember.User.Name), sgMember.User.ID)
if sgMember.User.Avatar != "" { if sgMember.User.Avatar != "" {
if sgMember.User.Avatar[0] == '.' { if sgMember.User.Avatar[0] == '.' {
sgMember.User.Avatar = "/uploads/avatar_" + strconv.Itoa(sgMember.User.ID) + sgMember.User.Avatar sgMember.User.Avatar = "/uploads/avatar_" + strconv.Itoa(sgMember.User.ID) + sgMember.User.Avatar
} }
} else { } else {
sgMember.User.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(sgMember.User.ID),1) sgMember.User.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(sgMember.User.ID), 1)
} }
sgMember.JoinedAt, _ = relative_time(sgMember.JoinedAt) sgMember.JoinedAt, _ = relativeTime(sgMember.JoinedAt)
if sgItem.Owner == sgMember.User.ID { if sgItem.Owner == sgMember.User.ID {
sgMember.RankString = "Owner" sgMember.RankString = "Owner"
} else { } else {
switch(sgMember.Rank) { switch sgMember.Rank {
case 0: sgMember.RankString = "Member" case 0:
case 1: sgMember.RankString = "Mod" sgMember.RankString = "Member"
case 2: sgMember.RankString = "Admin" case 1:
sgMember.RankString = "Mod"
case 2:
sgMember.RankString = "Admin"
} }
} }
sgMembers = append(sgMembers,sgMember) sgMembers = append(sgMembers, sgMember)
} }
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return return
} }
rows.Close() rows.Close()
pi := SocialGroupMemberListPage{"Group Member List",user,headerVars,sgMembers,sgItem,0,0,extData} pi := SocialGroupMemberListPage{"Group Member List", user, headerVars, sgMembers, sgItem, 0, 0}
// A plugin with plugins. Pluginception! // A plugin with plugins. Pluginception!
if pre_render_hooks["pre_render_socialgroups_member_list"] != nil { if preRenderHooks["pre_render_socialgroups_member_list"] != nil {
if run_pre_render_hook("pre_render_socialgroups_member_list", w, r, &user, &pi) { if runPreRenderHook("pre_render_socialgroups_member_list", w, r, &user, &pi) {
return return
} }
} }
err = templates.ExecuteTemplate(w,"socialgroups_member_list.html", pi) err = templates.ExecuteTemplate(w, "socialgroups_member_list.html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
func socialgroups_attach_forum(sgid int, fid int) error { func socialgroupsAttachForum(sgid int, fid int) error {
_, err := socialgroups_attach_forum_stmt.Exec(sgid,fid) _, err := socialgroupsAttachForumStmt.Exec(sgid, fid)
return err return err
} }
func socialgroups_unattach_forum(fid int) error { func socialgroupsUnattachForum(fid int) error {
_, err := socialgroups_attach_forum_stmt.Exec(fid) _, err := socialgroupsAttachForumStmt.Exec(fid)
return err return err
} }
func socialgroups_build_group_url(slug string, id int) string { func socialgroupsBuildGroupURL(slug string, id int) string {
if slug == "" { if slug == "" {
return "/group/" + slug + "." + strconv.Itoa(id) return "/group/" + slug + "." + strconv.Itoa(id)
} }
@ -530,38 +534,38 @@ func socialgroups_build_group_url(slug string, id int) string {
Hooks Hooks
*/ */
func socialgroups_pre_render_view_forum(w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) { func socialgroupsPreRenderViewForum(w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
pi := data.(*ForumPage) pi := data.(*ForumPage)
if pi.Header.ExtData.items != nil { if pi.Header.ExtData.items != nil {
if sgData, ok := pi.Header.ExtData.items["socialgroups_current_group"]; ok { if sgData, ok := pi.Header.ExtData.items["socialgroups_current_group"]; ok {
sgItem := sgData.(SocialGroup) sgItem := sgData.(*SocialGroup)
sgpi := SocialGroupPage{pi.Title,pi.CurrentUser,pi.Header,pi.ItemList,pi.Forum,sgItem,pi.Page,pi.LastPage,pi.ExtData} sgpi := SocialGroupPage{pi.Title, pi.CurrentUser, pi.Header, pi.ItemList, pi.Forum, sgItem, pi.Page, pi.LastPage}
err := templates.ExecuteTemplate(w,"socialgroups_view_group.html", sgpi) err := templates.ExecuteTemplate(w, "socialgroups_view_group.html", sgpi)
if err != nil { if err != nil {
LogError(err) LogError(err)
return false return false
} }
return true return true
} }
} }
return false return false
} }
func socialgroups_trow_assign(args ...interface{}) interface{} { func socialgroupsTrowAssign(args ...interface{}) interface{} {
var forum *Forum = args[1].(*Forum) var forum = args[1].(*Forum)
if forum.ParentType == "socialgroup" { if forum.ParentType == "socialgroup" {
var topicItem *TopicsRow = args[0].(*TopicsRow) var topicItem = args[0].(*TopicsRow)
topicItem.ForumLink = "/group/" + strings.TrimPrefix(topicItem.ForumLink,get_forum_url_prefix()) topicItem.ForumLink = "/group/" + strings.TrimPrefix(topicItem.ForumLink, getForumURLPrefix())
} }
return nil return nil
} }
// TO-DO: It would be nice, if you could select one of the boards in the group from that drop-down rather than just the one you got linked from // TO-DO: It would be nice, if you could select one of the boards in the group from that drop-down rather than just the one you got linked from
func socialgroups_topic_create_pre_loop(args ...interface{}) interface{} { func socialgroupsTopicCreatePreLoop(args ...interface{}) interface{} {
var fid int = args[2].(int) var fid = args[2].(int)
if fstore.DirtyGet(fid).ParentType == "socialgroup" { if fstore.DirtyGet(fid).ParentType == "socialgroup" {
var strictmode *bool = args[5].(*bool) var strictmode = args[5].(*bool)
*strictmode = true *strictmode = true
} }
return nil return nil
@ -569,33 +573,33 @@ func socialgroups_topic_create_pre_loop(args ...interface{}) interface{} {
// TO-DO: Add privacy options // TO-DO: Add privacy options
// TO-DO: Add support for multiple boards and add per-board simplified permissions // TO-DO: Add support for multiple boards and add per-board simplified permissions
// TO-DO: Take is_js into account for routes which expect JSON responses // TO-DO: Take isJs into account for routes which expect JSON responses
func socialgroups_forum_check(args ...interface{}) (skip interface{}) { func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
var r = args[1].(*http.Request) var r = args[1].(*http.Request)
var fid *int = args[3].(*int) var fid = args[3].(*int)
var forum *Forum = fstore.DirtyGet(*fid) var forum = fstore.DirtyGet(*fid)
if forum.ParentType == "socialgroup" { if forum.ParentType == "socialgroup" {
var err error var err error
var w = args[0].(http.ResponseWriter) var w = args[0].(http.ResponseWriter)
var success *bool = args[4].(*bool) var success = args[4].(*bool)
sgItem, ok := r.Context().Value("socialgroups_current_group").(SocialGroup) sgItem, ok := r.Context().Value("socialgroups_current_group").(*SocialGroup)
if !ok { if !ok {
sgItem, err = socialgroups_get_group(forum.ParentID) sgItem, err = socialgroupsGetGroup(forum.ParentID)
if err != nil { if err != nil {
InternalError(errors.New("Unable to find the parent group for a forum"),w) InternalError(errors.New("Unable to find the parent group for a forum"), w)
*success = false *success = false
return false return false
} }
if !sgItem.Active { if !sgItem.Active {
NotFound(w,r) NotFound(w, r)
*success = false *success = false
return false return false
} }
r = r.WithContext(context.WithValue(r.Context(),"socialgroups_current_group",sgItem)) r = r.WithContext(context.WithValue(r.Context(), "socialgroups_current_group", sgItem))
} }
var user *User = args[2].(*User) var user = args[2].(*User)
var rank int var rank int
var posts int var posts int
var joinedAt string var joinedAt string
@ -604,13 +608,13 @@ func socialgroups_forum_check(args ...interface{}) (skip interface{}) {
// Clear the default group permissions // Clear the default group permissions
// TO-DO: Do this more efficiently, doing it quick and dirty for now to get this out quickly // TO-DO: Do this more efficiently, doing it quick and dirty for now to get this out quickly
override_forum_perms(&user.Perms, false) overrideForumPerms(&user.Perms, false)
user.Perms.ViewTopic = true user.Perms.ViewTopic = true
err = socialgroups_get_member_stmt.QueryRow(sgItem.ID,user.ID).Scan(&rank,&posts,&joinedAt) err = socialgroupsGetMemberStmt.QueryRow(sgItem.ID, user.ID).Scan(&rank, &posts, &joinedAt)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
*success = false *success = false
InternalError(err,w) InternalError(err, w)
return false return false
} else if err != nil { } else if err != nil {
return true return true
@ -623,13 +627,13 @@ func socialgroups_forum_check(args ...interface{}) (skip interface{}) {
// Basic permissions for members, more complicated permissions coming in the next commit! // Basic permissions for members, more complicated permissions coming in the next commit!
if sgItem.Owner == user.ID { if sgItem.Owner == user.ID {
override_forum_perms(&user.Perms,true) overrideForumPerms(&user.Perms, true)
} else if rank == 0 { } else if rank == 0 {
user.Perms.LikeItem = true user.Perms.LikeItem = true
user.Perms.CreateTopic = true user.Perms.CreateTopic = true
user.Perms.CreateReply = true user.Perms.CreateReply = true
} else { } else {
override_forum_perms(&user.Perms,true) overrideForumPerms(&user.Perms, true)
} }
return true return true
} }
@ -639,19 +643,19 @@ func socialgroups_forum_check(args ...interface{}) (skip interface{}) {
// TO-DO: Override redirects? I don't think this is needed quite yet // TO-DO: Override redirects? I don't think this is needed quite yet
func socialgroups_widgets(args ...interface{}) interface{} { func socialgroupsWidgets(args ...interface{}) interface{} {
var zone string = args[0].(string) var zone = args[0].(string)
var headerVars *HeaderVars = args[2].(*HeaderVars) var headerVars = args[2].(*HeaderVars)
var request *http.Request = args[3].(*http.Request) var request = args[3].(*http.Request)
if zone != "view_forum" { if zone != "view_forum" {
return false return false
} }
var forum *Forum = args[1].(*Forum) var forum = args[1].(*Forum)
if forum.ParentType == "socialgroup" { if forum.ParentType == "socialgroup" {
// This is why I hate using contexts, all the daisy chains and interface casts x.x // This is why I hate using contexts, all the daisy chains and interface casts x.x
sgItem, ok := request.Context().Value("socialgroups_current_group").(SocialGroup) sgItem, ok := request.Context().Value("socialgroups_current_group").(*SocialGroup)
if !ok { if !ok {
LogError(errors.New("Unable to find a parent group in the context data")) LogError(errors.New("Unable to find a parent group in the context data"))
return false return false
@ -662,7 +666,7 @@ func socialgroups_widgets(args ...interface{}) interface{} {
} }
headerVars.ExtData.items["socialgroups_current_group"] = sgItem headerVars.ExtData.items["socialgroups_current_group"] = sgItem
return socialgroups_group_widgets(headerVars,sgItem) return socialgroupsGroupWidgets(headerVars, sgItem)
} }
return false return false
} }

View File

@ -6,226 +6,225 @@ import "testing"
// TO-DO: Replace the soft tabs with hard ones // TO-DO: Replace the soft tabs with hard ones
// go test -v // go test -v
type ME_Pair struct type ME_Pair struct {
{ Msg string
Msg string Expects string
Expects string
} }
func addMEPair(msgList []ME_Pair, msg string, expects string) []ME_Pair { func addMEPair(msgList []ME_Pair, msg string, expects string) []ME_Pair {
return append(msgList,ME_Pair{msg,expects}) return append(msgList, ME_Pair{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 []ME_Pair
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>")
msgList = addMEPair(msgList,"[b]😀😀😀[/b]","<b>😀😀😀</b>") msgList = addMEPair(msgList, "[b]😀😀😀[/b]", "<b>😀😀😀</b>")
msgList = addMEPair(msgList,"[b]hi[/b]","<b>hi</b>") msgList = addMEPair(msgList, "[b]hi[/b]", "<b>hi</b>")
msgList = addMEPair(msgList,"[u]hi[/u]","<u>hi</u>") msgList = addMEPair(msgList, "[u]hi[/u]", "<u>hi</u>")
msgList = addMEPair(msgList,"[i]hi[/i]","<i>hi</i>") msgList = addMEPair(msgList, "[i]hi[/i]", "<i>hi</i>")
msgList = addMEPair(msgList,"[s]hi[/s]","<s>hi</s>") msgList = addMEPair(msgList, "[s]hi[/s]", "<s>hi</s>")
msgList = addMEPair(msgList,"[c]hi[/c]","[c]hi[/c]") msgList = addMEPair(msgList, "[c]hi[/c]", "[c]hi[/c]")
if !testing.Short() { if !testing.Short() {
msgList = addMEPair(msgList,"[b]hi[/i]","[b]hi[/i]") msgList = addMEPair(msgList, "[b]hi[/i]", "[b]hi[/i]")
msgList = addMEPair(msgList,"[/b]hi[b]","[/b]hi[b]") msgList = addMEPair(msgList, "[/b]hi[b]", "[/b]hi[b]")
msgList = addMEPair(msgList,"[/b]hi[/b]","[/b]hi[/b]") msgList = addMEPair(msgList, "[/b]hi[/b]", "[/b]hi[/b]")
msgList = addMEPair(msgList,"[b][b]hi[/b]","<b>hi</b>") msgList = addMEPair(msgList, "[b][b]hi[/b]", "<b>hi</b>")
} }
msgList = addMEPair(msgList,"[b][b]hi","[b][b]hi") msgList = addMEPair(msgList, "[b][b]hi", "[b][b]hi")
msgList = addMEPair(msgList,"[b][b][b]hi","[b][b][b]hi") msgList = addMEPair(msgList, "[b][b][b]hi", "[b][b][b]hi")
msgList = addMEPair(msgList,"[/b]hi","[/b]hi") msgList = addMEPair(msgList, "[/b]hi", "[/b]hi")
msgList = addMEPair(msgList,"[code]hi[/code]","<span class='codequotes'>hi</span>") msgList = addMEPair(msgList, "[code]hi[/code]", "<span class='codequotes'>hi</span>")
msgList = addMEPair(msgList,"[code][b]hi[/b][/code]","<span class='codequotes'>[b]hi[/b]</span>") msgList = addMEPair(msgList, "[code][b]hi[/b][/code]", "<span class='codequotes'>[b]hi[/b]</span>")
msgList = addMEPair(msgList,"[code][b]hi[/code][/b]","<span class='codequotes'>[b]hi</span>[/b]") msgList = addMEPair(msgList, "[code][b]hi[/code][/b]", "<span class='codequotes'>[b]hi</span>[/b]")
msgList = addMEPair(msgList,"[quote]hi[/quote]","<span class='postQuote'>hi</span>") msgList = addMEPair(msgList, "[quote]hi[/quote]", "<span class='postQuote'>hi</span>")
msgList = addMEPair(msgList,"[quote][b]hi[/b][/quote]","<span class='postQuote'><b>hi</b></span>") msgList = addMEPair(msgList, "[quote][b]hi[/b][/quote]", "<span class='postQuote'><b>hi</b></span>")
msgList = addMEPair(msgList,"[quote][b]h[/b][/quote]","<span class='postQuote'><b>h</b></span>") msgList = addMEPair(msgList, "[quote][b]h[/b][/quote]", "<span class='postQuote'><b>h</b></span>")
msgList = addMEPair(msgList,"[quote][b][/b][/quote]","<span class='postQuote'><b></b></span>") msgList = addMEPair(msgList, "[quote][b][/b][/quote]", "<span class='postQuote'><b></b></span>")
t.Log("Testing bbcode_full_parse") t.Log("Testing bbcodeFullParse")
for _, item := range msgList { for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'") t.Log("Testing string '" + item.Msg + "'")
res = bbcode_full_parse(item.Msg) res = bbcodeFullParse(item.Msg)
if res != item.Expects { if res != item.Expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",item.Expects) t.Error("Expected:", item.Expects)
} }
} }
var msg, expects string var msg, expects string
var err error var err error
msg = "[rand][/rand]" msg = "[rand][/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand][/rand]" expects = "<span style='color: red;'>[Invalid Number]</span>[rand][/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",expects) t.Error("Expected:", expects)
} }
msg = "[rand]-1[/rand]" msg = "[rand]-1[/rand]"
expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-1[/rand]" expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-1[/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",expects) t.Error("Expected:", expects)
} }
msg = "[rand]-01[/rand]" msg = "[rand]-01[/rand]"
expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-01[/rand]" expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-01[/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",expects) t.Error("Expected:", expects)
} }
msg = "[rand]NaN[/rand]" msg = "[rand]NaN[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]NaN[/rand]" expects = "<span style='color: red;'>[Invalid Number]</span>[rand]NaN[/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",expects) t.Error("Expected:", expects)
} }
msg = "[rand]Inf[/rand]" msg = "[rand]Inf[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]Inf[/rand]" expects = "<span style='color: red;'>[Invalid Number]</span>[rand]Inf[/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",expects) t.Error("Expected:", expects)
} }
msg = "[rand]+[/rand]" msg = "[rand]+[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]+[/rand]" expects = "<span style='color: red;'>[Invalid Number]</span>[rand]+[/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",expects) t.Error("Expected:", expects)
} }
msg = "[rand]1+1[/rand]" msg = "[rand]1+1[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]1+1[/rand]" expects = "<span style='color: red;'>[Invalid Number]</span>[rand]1+1[/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",expects) t.Error("Expected:", expects)
} }
var conv int var conv int
msg = "[rand]1[/rand]" msg = "[rand]1[/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
conv, err = strconv.Atoi(res) conv, err = strconv.Atoi(res)
if err != nil || (conv > 1 || conv < 0) { if err != nil || (conv > 1 || conv < 0) {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number in the range 0-1") t.Error("Expected a number in the range 0-1")
} }
msg = "[rand]0[/rand]" msg = "[rand]0[/rand]"
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
conv, err = strconv.Atoi(res) conv, err = strconv.Atoi(res)
if err != nil || conv != 0 { if err != nil || conv != 0 {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected the number 0") t.Error("Expected the number 0")
} }
msg = "[rand]2147483647[/rand]" // Signed 32-bit MAX msg = "[rand]2147483647[/rand]" // Signed 32-bit MAX
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
conv, err = strconv.Atoi(res) conv, err = strconv.Atoi(res)
if err != nil || (conv > 2147483647 || conv < 0) { if err != nil || (conv > 2147483647 || conv < 0) {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number between 0 and 2147483647") t.Error("Expected a number between 0 and 2147483647")
} }
msg = "[rand]9223372036854775807[/rand]" // Signed 64-bit MAX msg = "[rand]9223372036854775807[/rand]" // Signed 64-bit MAX
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
conv, err = strconv.Atoi(res) conv, err = strconv.Atoi(res)
if err != nil || (conv > 9223372036854775807 || conv < 0) { if err != nil || (conv > 9223372036854775807 || conv < 0) {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number between 0 and 9223372036854775807") t.Error("Expected a number between 0 and 9223372036854775807")
} }
// Note: conv is commented out in these two, as these numbers overflow int // Note: conv is commented out in these two, as these numbers overflow int
msg = "[rand]18446744073709551615[/rand]" // Unsigned 64-bit MAX msg = "[rand]18446744073709551615[/rand]" // Unsigned 64-bit MAX
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
conv, err = strconv.Atoi(res) conv, err = strconv.Atoi(res)
if err != nil || (/*conv > 18446744073709551615 || */conv < 0) { if err != nil || ( /*conv > 18446744073709551615 || */ conv < 0) {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number between 0 and 18446744073709551615") t.Error("Expected a number between 0 and 18446744073709551615")
} }
msg = "[rand]170141183460469231731687303715884105727[/rand]" // Signed 128-bit MAX msg = "[rand]170141183460469231731687303715884105727[/rand]" // Signed 128-bit MAX
t.Log("Testing string '"+msg+"'") t.Log("Testing string '" + msg + "'")
res = bbcode_full_parse(msg) res = bbcodeFullParse(msg)
conv, err = strconv.Atoi(res) conv, err = strconv.Atoi(res)
if err != nil || (/*conv > 170141183460469231731687303715884105727 || */conv < 0) { if err != nil || ( /*conv > 170141183460469231731687303715884105727 || */ conv < 0) {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number between 0 and 170141183460469231731687303715884105727") t.Error("Expected a number between 0 and 170141183460469231731687303715884105727")
} }
t.Log("Testing bbcode_regex_parse") t.Log("Testing bbcode_regex_parse")
for _, item := range msgList { for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'") t.Log("Testing string '" + item.Msg + "'")
res = bbcode_regex_parse(item.Msg) res = bbcodeRegexParse(item.Msg)
if res != item.Expects { if res != item.Expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",item.Expects) t.Error("Expected:", item.Expects)
} }
} }
} }
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 []ME_Pair
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>")
msgList = addMEPair(msgList,"*hi*","<i>hi</i>") msgList = addMEPair(msgList, "*hi*", "<i>hi</i>")
msgList = addMEPair(msgList,"~hi~","<s>hi</s>") msgList = addMEPair(msgList, "~hi~", "<s>hi</s>")
msgList = addMEPair(msgList,"*hi**","<i>hi</i>*") msgList = addMEPair(msgList, "*hi**", "<i>hi</i>*")
msgList = addMEPair(msgList,"**hi***","<b>hi</b>*") msgList = addMEPair(msgList, "**hi***", "<b>hi</b>*")
msgList = addMEPair(msgList,"**hi*","*<i>hi</i>") msgList = addMEPair(msgList, "**hi*", "*<i>hi</i>")
msgList = addMEPair(msgList,"***hi***","*<b><i>hi</i></b>") msgList = addMEPair(msgList, "***hi***", "*<b><i>hi</i></b>")
msgList = addMEPair(msgList,"\\*hi\\*","*hi*") msgList = addMEPair(msgList, "\\*hi\\*", "*hi*")
msgList = addMEPair(msgList,"*~hi~*","<i><s>hi</s></i>") msgList = addMEPair(msgList, "*~hi~*", "<i><s>hi</s></i>")
msgList = addMEPair(msgList,"**","**") msgList = addMEPair(msgList, "**", "**")
msgList = addMEPair(msgList,"***","***") msgList = addMEPair(msgList, "***", "***")
msgList = addMEPair(msgList,"****","****") msgList = addMEPair(msgList, "****", "****")
msgList = addMEPair(msgList,"*****","*****") msgList = addMEPair(msgList, "*****", "*****")
msgList = addMEPair(msgList,"******","******") msgList = addMEPair(msgList, "******", "******")
msgList = addMEPair(msgList,"*******","*******") msgList = addMEPair(msgList, "*******", "*******")
msgList = addMEPair(msgList,"~~","~~") msgList = addMEPair(msgList, "~~", "~~")
msgList = addMEPair(msgList,"~~~","~~~") msgList = addMEPair(msgList, "~~~", "~~~")
msgList = addMEPair(msgList,"~~~~","~~~~") msgList = addMEPair(msgList, "~~~~", "~~~~")
msgList = addMEPair(msgList,"~~~~~","~~~~~") msgList = addMEPair(msgList, "~~~~~", "~~~~~")
msgList = addMEPair(msgList,"__","__") msgList = addMEPair(msgList, "__", "__")
msgList = addMEPair(msgList,"___","___") msgList = addMEPair(msgList, "___", "___")
msgList = addMEPair(msgList,"_ _","<u> </u>") msgList = addMEPair(msgList, "_ _", "<u> </u>")
msgList = addMEPair(msgList,"* *","<i> </i>") msgList = addMEPair(msgList, "* *", "<i> </i>")
msgList = addMEPair(msgList,"** **","<b> </b>") msgList = addMEPair(msgList, "** **", "<b> </b>")
msgList = addMEPair(msgList,"*** ***","<b><i> </i></b>") msgList = addMEPair(msgList, "*** ***", "<b><i> </i></b>")
for _, item := range msgList { for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'") t.Log("Testing string '" + item.Msg + "'")
res = markdown_parse(item.Msg) res = markdownParse(item.Msg)
if res != item.Expects { if res != item.Expects {
t.Error("Bad output:","'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:",item.Expects) t.Error("Expected:", item.Expects)
} }
} }
} }

View File

@ -250,7 +250,7 @@ $(document).ready(function(){
var form_action = $(this).closest('a').attr("href"); var form_action = $(this).closest('a').attr("href");
//console.log("Form Action: " + form_action); //console.log("Form Action: " + form_action);
$.ajax({ url: form_action, type: "POST", dataType: "json", data: { is_js: "1", edit_item: newContent } $.ajax({ url: form_action, type: "POST", dataType: "json", data: { isJs: "1", edit_item: newContent }
}); });
}); });
}); });
@ -276,7 +276,7 @@ $(document).ready(function(){
url: form_action + "?session=" + session, url: form_action + "?session=" + session,
type: "POST", type: "POST",
dataType: "json", dataType: "json",
data: {is_js: "1",edit_item: newContent} data: {isJs: "1",edit_item: newContent}
}); });
}); });
}); });
@ -325,7 +325,7 @@ $(document).ready(function(){
{ {
event.preventDefault(); event.preventDefault();
//console.log("running .submit_edit event"); //console.log("running .submit_edit event");
var out_data = {is_js: "1"} var out_data = {isJs: "1"}
var block_parent = $(this).closest('.editable_parent'); var block_parent = $(this).closest('.editable_parent');
var block = block_parent.find('.editable_block').each(function(){ var block = block_parent.find('.editable_block').each(function(){
var field_name = this.getAttribute("data-field"); var field_name = this.getAttribute("data-field");

View File

@ -4,21 +4,19 @@ package qgen
var Install *installer var Install *installer
func init() { func init() {
Install = &installer{instructions:[]DB_Install_Instruction{}} Install = &installer{instructions: []DB_Install_Instruction{}}
} }
type DB_Install_Instruction struct type DB_Install_Instruction struct {
{ Table string
Table string
Contents string Contents string
Type string Type string
} }
// A set of wrappers around the generator methods, so we can use this in the installer // A set of wrappers around the generator methods, so we can use this in the installer
// TO-DO: Re-implement the query generation, query builder and installer adapters as layers on-top of a query text adapter // TO-DO: Re-implement the query generation, query builder and installer adapters as layers on-top of a query text adapter
type installer struct type installer struct {
{ adapter DB_Adapter
adapter DB_Adapter
instructions []DB_Install_Instruction instructions []DB_Install_Instruction
} }
@ -40,7 +38,7 @@ func (install *installer) CreateTable(table string, charset string, collation st
if err != nil { if err != nil {
return err return err
} }
install.instructions = append(install.instructions,DB_Install_Instruction{table,res,"create-table"}) install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "create-table"})
return nil return nil
} }
@ -49,7 +47,7 @@ func (install *installer) Write() error {
// We can't escape backticks, so we have to dump it out a file at a time // We can't escape backticks, so we have to dump it out a file at a time
for _, instr := range install.instructions { for _, instr := range install.instructions {
if instr.Type == "create-table" { if instr.Type == "create-table" {
err := write_file("./schema/" + install.adapter.GetName() + "/query_" + instr.Table + ".sql", instr.Contents) err := writeFile("./schema/"+install.adapter.GetName()+"/query_"+instr.Table+".sql", instr.Contents)
if err != nil { if err != nil {
return err return err
} }
@ -57,5 +55,5 @@ func (install *installer) Write() error {
inserts += instr.Contents + "\n" inserts += instr.Contents + "\n"
} }
} }
return write_file("./schema/" + install.adapter.GetName() + "/inserts.sql", inserts) return writeFile("./schema/"+install.adapter.GetName()+"/inserts.sql", inserts)
} }

View File

@ -8,14 +8,13 @@ import "errors"
func init() { func init() {
DB_Registry = append(DB_Registry, DB_Registry = append(DB_Registry,
&Mysql_Adapter{Name:"mysql",Buffer:make(map[string]DB_Stmt)}, &Mysql_Adapter{Name: "mysql", Buffer: make(map[string]DB_Stmt)},
) )
} }
type Mysql_Adapter struct type Mysql_Adapter struct {
{ Name string
Name string Buffer map[string]DB_Stmt
Buffer map[string]DB_Stmt
BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit
} }
@ -42,7 +41,7 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
return "", errors.New("You can't have a table with no columns") return "", errors.New("You can't have a table with no columns")
} }
var querystr string = "CREATE TABLE `" + table + "` (" var querystr = "CREATE TABLE `" + table + "` ("
for _, column := range columns { for _, column := range columns {
// Make it easier to support Cassandra in the future // Make it easier to support Cassandra in the future
if column.Type == "createdAt" { if column.Type == "createdAt" {
@ -58,7 +57,7 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
// TO-DO: Exclude the other variants of text like mediumtext and longtext too // TO-DO: Exclude the other variants of text like mediumtext and longtext too
if column.Default != "" && column.Type != "text" { if column.Default != "" && column.Type != "text" {
end = " DEFAULT " end = " DEFAULT "
if adapter.stringy_type(column.Type) && column.Default != "''" { if adapter.stringyType(column.Type) && column.Default != "''" {
end += "'" + column.Default + "'" end += "'" + column.Default + "'"
} else { } else {
end += column.Default end += column.Default
@ -75,7 +74,7 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
end += " AUTO_INCREMENT" end += " AUTO_INCREMENT"
} }
querystr += "\n\t`"+column.Name+"` " + column.Type + size + end + "," querystr += "\n\t`" + column.Name + "` " + column.Type + size + end + ","
} }
if len(keys) > 0 { if len(keys) > 0 {
@ -85,14 +84,14 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
querystr += " key" querystr += " key"
} }
querystr += "(" querystr += "("
for _, column := range strings.Split(key.Columns,",") { for _, column := range strings.Split(key.Columns, ",") {
querystr += "`" + column + "`," querystr += "`" + column + "`,"
} }
querystr = querystr[0:len(querystr) - 1] + ")," querystr = querystr[0:len(querystr)-1] + "),"
} }
} }
querystr = querystr[0:len(querystr) - 1] + "\n)" querystr = querystr[0:len(querystr)-1] + "\n)"
if charset != "" { if charset != "" {
querystr += " CHARSET=" + charset querystr += " CHARSET=" + charset
} }
@ -100,7 +99,7 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
querystr += " COLLATE " + collation querystr += " COLLATE " + collation
} }
adapter.push_statement(name,"create-table",querystr + ";") adapter.pushStatement(name, "create-table", querystr+";")
return querystr + ";", nil return querystr + ";", nil
} }
@ -118,7 +117,7 @@ func (adapter *Mysql_Adapter) SimpleInsert(name string, table string, columns st
return "", errors.New("No input data found for SimpleInsert") return "", errors.New("No input data found for SimpleInsert")
} }
var querystr string = "INSERT INTO `" + table + "`(" var querystr = "INSERT INTO `" + table + "`("
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(columns) { for _, column := range _process_columns(columns) {
@ -130,15 +129,15 @@ func (adapter *Mysql_Adapter) SimpleInsert(name string, table string, columns st
} }
// Remove the trailing comma // Remove the trailing comma
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
querystr += ") VALUES (" querystr += ") VALUES ("
for _, field := range _process_fields(fields) { for _, field := range _processFields(fields) {
querystr += field.Name + "," querystr += field.Name + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
adapter.push_statement(name,"insert",querystr + ")") adapter.pushStatement(name, "insert", querystr+")")
return querystr + ")", nil return querystr + ")", nil
} }
@ -156,7 +155,7 @@ func (adapter *Mysql_Adapter) SimpleReplace(name string, table string, columns s
return "", errors.New("No input data found for SimpleInsert") return "", errors.New("No input data found for SimpleInsert")
} }
var querystr string = "REPLACE INTO `" + table + "`(" var querystr = "REPLACE INTO `" + table + "`("
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(columns) { for _, column := range _process_columns(columns) {
@ -167,15 +166,15 @@ func (adapter *Mysql_Adapter) SimpleReplace(name string, table string, columns s
} }
} }
// Remove the trailing comma // Remove the trailing comma
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
querystr += ") VALUES (" querystr += ") VALUES ("
for _, field := range _process_fields(fields) { for _, field := range _processFields(fields) {
querystr += field.Name + "," querystr += field.Name + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
adapter.push_statement(name,"replace",querystr + ")") adapter.pushStatement(name, "replace", querystr+")")
return querystr + ")", nil return querystr + ")", nil
} }
@ -190,47 +189,47 @@ func (adapter *Mysql_Adapter) SimpleUpdate(name string, table string, set string
return "", errors.New("You need to set data in this update statement") return "", errors.New("You need to set data in this update statement")
} }
var querystr string = "UPDATE `" + table + "` SET " var querystr = "UPDATE `" + table + "` SET "
for _, item := range _process_set(set) { for _, item := range _process_set(set) {
querystr += "`" + item.Column + "` =" querystr += "`" + item.Column + "` ="
for _, token := range item.Expr { for _, token := range item.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
} }
} }
querystr += "," querystr += ","
} }
// Remove the trailing comma // Remove the trailing comma
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(where) != 0 { if len(where) != 0 {
querystr += " WHERE" querystr += " WHERE"
for _, loc := range _process_where(where) { for _, loc := range _processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
adapter.push_statement(name,"update",querystr) adapter.pushStatement(name, "update", querystr)
return querystr, nil return querystr, nil
} }
@ -245,27 +244,27 @@ func (adapter *Mysql_Adapter) SimpleDelete(name string, table string, where stri
return "", errors.New("You need to specify what data you want to delete") return "", errors.New("You need to specify what data you want to delete")
} }
var querystr string = "DELETE FROM `" + table + "` WHERE" var querystr = "DELETE FROM `" + table + "` WHERE"
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
for _, loc := range _process_where(where) { for _, loc := range _processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = strings.TrimSpace(querystr[0:len(querystr) - 4]) querystr = strings.TrimSpace(querystr[0 : len(querystr)-4])
adapter.push_statement(name,"delete",querystr) adapter.pushStatement(name, "delete", querystr)
return querystr, nil return querystr, nil
} }
@ -277,7 +276,7 @@ func (adapter *Mysql_Adapter) Purge(name string, table string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
adapter.push_statement(name,"purge","DELETE FROM `" + table + "`") adapter.pushStatement(name, "purge", "DELETE FROM `"+table+"`")
return "DELETE FROM `" + table + "`", nil return "DELETE FROM `" + table + "`", nil
} }
@ -293,38 +292,38 @@ func (adapter *Mysql_Adapter) SimpleSelect(name string, table string, columns st
} }
// Slice up the user friendly strings into something easier to process // Slice up the user friendly strings into something easier to process
var colslice []string = strings.Split(strings.TrimSpace(columns),",") var colslice = strings.Split(strings.TrimSpace(columns), ",")
var querystr string = "SELECT " var querystr = "SELECT "
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range colslice { for _, column := range colslice {
querystr += "`" + strings.TrimSpace(column) + "`," querystr += "`" + strings.TrimSpace(column) + "`,"
} }
// Remove the trailing comma // Remove the trailing comma
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
querystr += " FROM `" + table + "`" querystr += " FROM `" + table + "`"
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(where) != 0 { if len(where) != 0 {
querystr += " WHERE" querystr += " WHERE"
for _, loc := range _process_where(where) { for _, loc := range _processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
if len(orderby) != 0 { if len(orderby) != 0 {
@ -332,7 +331,7 @@ func (adapter *Mysql_Adapter) SimpleSelect(name string, table string, columns st
for _, column := range _process_orderby(orderby) { for _, column := range _process_orderby(orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + "," querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
} }
if limit != "" { if limit != "" {
@ -340,7 +339,7 @@ func (adapter *Mysql_Adapter) SimpleSelect(name string, table string, columns st
} }
querystr = strings.TrimSpace(querystr) querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,"select",querystr) adapter.pushStatement(name, "select", querystr)
return querystr, nil return querystr, nil
} }
@ -361,7 +360,7 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
return "", errors.New("No joiners found for SimpleLeftJoin") return "", errors.New("No joiners found for SimpleLeftJoin")
} }
var querystr string = "SELECT " var querystr = "SELECT "
for _, column := range _process_columns(columns) { for _, column := range _process_columns(columns) {
var source, alias string var source, alias string
@ -382,39 +381,39 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
} }
// Remove the trailing comma // Remove the trailing comma
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
querystr += " FROM `" + table1 + "` LEFT JOIN `" + table2 + "` ON " querystr += " FROM `" + table1 + "` LEFT JOIN `" + table2 + "` ON "
for _, joiner := range _process_joiner(joiners) { for _, joiner := range _processJoiner(joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
} }
// Remove the trailing AND // Remove the trailing AND
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(where) != 0 { if len(where) != 0 {
querystr += " WHERE" querystr += " WHERE"
for _, loc := range _process_where(where) { for _, loc := range _processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
halves := strings.Split(token.Contents,".") halves := strings.Split(token.Contents, ".")
if len(halves) == 2 { if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`" querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else { } else {
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
} }
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
if len(orderby) != 0 { if len(orderby) != 0 {
@ -422,7 +421,7 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
for _, column := range _process_orderby(orderby) { for _, column := range _process_orderby(orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + "," querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
} }
if limit != "" { if limit != "" {
@ -430,7 +429,7 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
} }
querystr = strings.TrimSpace(querystr) querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,"select",querystr) adapter.pushStatement(name, "select", querystr)
return querystr, nil return querystr, nil
} }
@ -451,7 +450,7 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
return "", errors.New("No joiners found for SimpleInnerJoin") return "", errors.New("No joiners found for SimpleInnerJoin")
} }
var querystr string = "SELECT " var querystr = "SELECT "
for _, column := range _process_columns(columns) { for _, column := range _process_columns(columns) {
var source, alias string var source, alias string
@ -472,39 +471,39 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
} }
// Remove the trailing comma // Remove the trailing comma
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
querystr += " FROM `" + table1 + "` INNER JOIN `" + table2 + "` ON " querystr += " FROM `" + table1 + "` INNER JOIN `" + table2 + "` ON "
for _, joiner := range _process_joiner(joiners) { for _, joiner := range _processJoiner(joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
} }
// Remove the trailing AND // Remove the trailing AND
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(where) != 0 { if len(where) != 0 {
querystr += " WHERE" querystr += " WHERE"
for _, loc := range _process_where(where) { for _, loc := range _processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
halves := strings.Split(token.Contents,".") halves := strings.Split(token.Contents, ".")
if len(halves) == 2 { if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`" querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else { } else {
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
} }
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
if len(orderby) != 0 { if len(orderby) != 0 {
@ -512,7 +511,7 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
for _, column := range _process_orderby(orderby) { for _, column := range _process_orderby(orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + "," querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
} }
if limit != "" { if limit != "" {
@ -520,14 +519,14 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
} }
querystr = strings.TrimSpace(querystr) querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,"select",querystr) adapter.pushStatement(name, "select", querystr)
return querystr, nil return querystr, nil
} }
func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) { func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) {
/* Insert Portion */ /* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`(" var querystr = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(ins.Columns) { for _, column := range _process_columns(ins.Columns) {
@ -537,7 +536,7 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
querystr += "`" + column.Left + "`," querystr += "`" + column.Left + "`,"
} }
} }
querystr = querystr[0:len(querystr) - 1] + ") SELECT" querystr = querystr[0:len(querystr)-1] + ") SELECT"
/* Select Portion */ /* Select Portion */
@ -556,29 +555,29 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
} }
querystr += " " + source + alias + "," querystr += " " + source + alias + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
querystr += " FROM `" + sel.Table + "`" querystr += " FROM `" + sel.Table + "`"
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(sel.Where) != 0 { if len(sel.Where) != 0 {
querystr += " WHERE" querystr += " WHERE"
for _, loc := range _process_where(sel.Where) { for _, loc := range _processWhere(sel.Where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
if len(sel.Orderby) != 0 { if len(sel.Orderby) != 0 {
@ -586,7 +585,7 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
for _, column := range _process_orderby(sel.Orderby) { for _, column := range _process_orderby(sel.Orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + "," querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
} }
if sel.Limit != "" { if sel.Limit != "" {
@ -594,14 +593,14 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
} }
querystr = strings.TrimSpace(querystr) querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,"insert",querystr) adapter.pushStatement(name, "insert", querystr)
return querystr, nil return querystr, nil
} }
func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) { func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
/* Insert Portion */ /* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`(" var querystr = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(ins.Columns) { for _, column := range _process_columns(ins.Columns) {
@ -611,7 +610,7 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
querystr += "`" + column.Left + "`," querystr += "`" + column.Left + "`,"
} }
} }
querystr = querystr[0:len(querystr) - 1] + ") SELECT" querystr = querystr[0:len(querystr)-1] + ") SELECT"
/* Select Portion */ /* Select Portion */
@ -632,38 +631,38 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
} }
querystr += " " + source + alias + "," querystr += " " + source + alias + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
querystr += " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON " querystr += " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON "
for _, joiner := range _process_joiner(sel.Joiners) { for _, joiner := range _processJoiner(sel.Joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(sel.Where) != 0 { if len(sel.Where) != 0 {
querystr += " WHERE" querystr += " WHERE"
for _, loc := range _process_where(sel.Where) { for _, loc := range _processWhere(sel.Where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
halves := strings.Split(token.Contents,".") halves := strings.Split(token.Contents, ".")
if len(halves) == 2 { if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`" querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else { } else {
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
} }
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
if len(sel.Orderby) != 0 { if len(sel.Orderby) != 0 {
@ -671,7 +670,7 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
for _, column := range _process_orderby(sel.Orderby) { for _, column := range _process_orderby(sel.Orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + "," querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
} }
if sel.Limit != "" { if sel.Limit != "" {
@ -679,14 +678,14 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
} }
querystr = strings.TrimSpace(querystr) querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,"insert",querystr) adapter.pushStatement(name, "insert", querystr)
return querystr, nil return querystr, nil
} }
func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) { func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
/* Insert Portion */ /* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`(" var querystr = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(ins.Columns) { for _, column := range _process_columns(ins.Columns) {
@ -696,7 +695,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
querystr += "`" + column.Left + "`," querystr += "`" + column.Left + "`,"
} }
} }
querystr = querystr[0:len(querystr) - 1] + ") SELECT" querystr = querystr[0:len(querystr)-1] + ") SELECT"
/* Select Portion */ /* Select Portion */
@ -717,38 +716,38 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
} }
querystr += " " + source + alias + "," querystr += " " + source + alias + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
querystr += " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " querystr += " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON "
for _, joiner := range _process_joiner(sel.Joiners) { for _, joiner := range _processJoiner(sel.Joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(sel.Where) != 0 { if len(sel.Where) != 0 {
querystr += " WHERE" querystr += " WHERE"
for _, loc := range _process_where(sel.Where) { for _, loc := range _processWhere(sel.Where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
halves := strings.Split(token.Contents,".") halves := strings.Split(token.Contents, ".")
if len(halves) == 2 { if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`" querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else { } else {
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
} }
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
if len(sel.Orderby) != 0 { if len(sel.Orderby) != 0 {
@ -756,7 +755,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
for _, column := range _process_orderby(sel.Orderby) { for _, column := range _process_orderby(sel.Orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + "," querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
} }
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
} }
if sel.Limit != "" { if sel.Limit != "" {
@ -764,7 +763,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
} }
querystr = strings.TrimSpace(querystr) querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,"insert",querystr) adapter.pushStatement(name, "insert", querystr)
return querystr, nil return querystr, nil
} }
@ -776,7 +775,7 @@ func (adapter *Mysql_Adapter) SimpleCount(name string, table string, where strin
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
var querystr string = "SELECT COUNT(*) AS `count` FROM `" + table + "`" var querystr = "SELECT COUNT(*) AS `count` FROM `" + table + "`"
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(where) != 0 { if len(where) != 0 {
@ -784,22 +783,22 @@ func (adapter *Mysql_Adapter) SimpleCount(name string, table string, where strin
//fmt.Println("SimpleCount:",name) //fmt.Println("SimpleCount:",name)
//fmt.Println("where:",where) //fmt.Println("where:",where)
//fmt.Println("_process_where:",_process_where(where)) //fmt.Println("_process_where:",_process_where(where))
for _, loc := range _process_where(where) { for _, loc := range _processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function","operator","number","substitute": case "function", "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
if limit != "" { if limit != "" {
@ -807,7 +806,7 @@ func (adapter *Mysql_Adapter) SimpleCount(name string, table string, where strin
} }
querystr = strings.TrimSpace(querystr) querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,"select",querystr) adapter.pushStatement(name, "select", querystr)
return querystr, nil return querystr, nil
} }
@ -837,7 +836,9 @@ package main
import "log" import "log"
import "database/sql" import "database/sql"
// nolint
` + stmts + ` ` + stmts + `
// nolint
func _gen_mysql() (err error) { func _gen_mysql() (err error) {
if dev.DebugMode { if dev.DebugMode {
log.Print("Building the generated statements") log.Print("Building the generated statements")
@ -846,16 +847,16 @@ func _gen_mysql() (err error) {
return nil return nil
} }
` `
return write_file("./gen_mysql.go", out) return writeFile("./gen_mysql.go", out)
} }
// Internal methods, not exposed in the interface // Internal methods, not exposed in the interface
func (adapter *Mysql_Adapter) push_statement(name string, stype string, querystr string) { func (adapter *Mysql_Adapter) pushStatement(name string, stype string, querystr string) {
adapter.Buffer[name] = DB_Stmt{querystr,stype} adapter.Buffer[name] = DB_Stmt{querystr, stype}
adapter.BufferOrder = append(adapter.BufferOrder,name) adapter.BufferOrder = append(adapter.BufferOrder, name)
} }
func (adapter *Mysql_Adapter) stringy_type(ctype string) bool { func (adapter *Mysql_Adapter) stringyType(ctype string) bool {
ctype = strings.ToLower(ctype) ctype = strings.ToLower(ctype)
return ctype == "varchar" || ctype == "tinytext" || ctype == "text" || ctype == "mediumtext" || ctype == "longtext" || ctype == "char" || ctype == "datetime" || ctype == "timestamp" || ctype == "time" || ctype == "date" return ctype == "varchar" || ctype == "tinytext" || ctype == "text" || ctype == "mediumtext" || ctype == "longtext" || ctype == "char" || ctype == "datetime" || ctype == "timestamp" || ctype == "time" || ctype == "date"
} }

View File

@ -7,14 +7,13 @@ import "errors"
func init() { func init() {
DB_Registry = append(DB_Registry, DB_Registry = append(DB_Registry,
&Pgsql_Adapter{Name:"pgsql",Buffer:make(map[string]DB_Stmt)}, &Pgsql_Adapter{Name: "pgsql", Buffer: make(map[string]DB_Stmt)},
) )
} }
type Pgsql_Adapter struct type Pgsql_Adapter struct {
{ Name string
Name string Buffer map[string]DB_Stmt
Buffer map[string]DB_Stmt
BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit
} }
@ -43,7 +42,7 @@ func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset str
return "", errors.New("You can't have a table with no columns") return "", errors.New("You can't have a table with no columns")
} }
var querystr string = "CREATE TABLE `" + table + "` (" var querystr = "CREATE TABLE `" + table + "` ("
for _, column := range columns { for _, column := range columns {
if column.Auto_Increment { if column.Auto_Increment {
column.Type = "serial" column.Type = "serial"
@ -61,7 +60,7 @@ func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset str
var end string var end string
if column.Default != "" { if column.Default != "" {
end = " DEFAULT " end = " DEFAULT "
if adapter.stringy_type(column.Type) && column.Default != "''" { if adapter.stringyType(column.Type) && column.Default != "''" {
end += "'" + column.Default + "'" end += "'" + column.Default + "'"
} else { } else {
end += column.Default end += column.Default
@ -72,7 +71,7 @@ func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset str
end += " not null" end += " not null"
} }
querystr += "\n\t`"+column.Name+"` " + column.Type + size + end + "," querystr += "\n\t`" + column.Name + "` " + column.Type + size + end + ","
} }
if len(keys) > 0 { if len(keys) > 0 {
@ -82,15 +81,15 @@ func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset str
querystr += " key" querystr += " key"
} }
querystr += "(" querystr += "("
for _, column := range strings.Split(key.Columns,",") { for _, column := range strings.Split(key.Columns, ",") {
querystr += "`" + column + "`," querystr += "`" + column + "`,"
} }
querystr = querystr[0:len(querystr) - 1] + ")," querystr = querystr[0:len(querystr)-1] + "),"
} }
} }
querystr = querystr[0:len(querystr) - 1] + "\n);" querystr = querystr[0:len(querystr)-1] + "\n);"
adapter.push_statement(name,"create-table",querystr) adapter.pushStatement(name, "create-table", querystr)
return querystr, nil return querystr, nil
} }
@ -139,59 +138,59 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
if set == "" { if set == "" {
return "", errors.New("You need to set data in this update statement") return "", errors.New("You need to set data in this update statement")
} }
var querystr string = "UPDATE `" + table + "` SET " var querystr = "UPDATE `" + table + "` SET "
for _, item := range _process_set(set) { for _, item := range _process_set(set) {
querystr += "`" + item.Column + "` =" querystr += "`" + item.Column + "` ="
for _, token := range item.Expr { for _, token := range item.Expr {
switch(token.Type) { switch token.Type {
case "function": case "function":
// TO-DO: Write a more sophisticated function parser on the utils side. // TO-DO: Write a more sophisticated function parser on the utils side.
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()" token.Contents = "LOCALTIMESTAMP()"
} }
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "operator","number","substitute": case "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
} }
} }
querystr += "," querystr += ","
} }
// Remove the trailing comma // Remove the trailing comma
querystr = querystr[0:len(querystr) - 1] querystr = querystr[0 : len(querystr)-1]
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
if len(where) != 0 { if len(where) != 0 {
querystr += " WHERE" querystr += " WHERE"
for _, loc := range _process_where(where) { for _, loc := range _processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch(token.Type) { switch token.Type {
case "function": case "function":
// TO-DO: Write a more sophisticated function parser on the utils side. What's the situation in regards to case sensitivity? // TO-DO: Write a more sophisticated function parser on the utils side. What's the situation in regards to case sensitivity?
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()" token.Contents = "LOCALTIMESTAMP()"
} }
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "operator","number","substitute": case "operator", "number", "substitute":
querystr += " " + token.Contents + "" querystr += " " + token.Contents
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
case "string": case "string":
querystr += " '" + token.Contents + "'" querystr += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
} }
} }
querystr += " AND" querystr += " AND"
} }
querystr = querystr[0:len(querystr) - 4] querystr = querystr[0 : len(querystr)-4]
} }
adapter.push_statement(name,"update",querystr) adapter.pushStatement(name, "update", querystr)
return querystr, nil return querystr, nil
} }
@ -326,7 +325,9 @@ package main
import "log" import "log"
import "database/sql" import "database/sql"
// nolint
` + stmts + ` ` + stmts + `
// nolint
func _gen_pgsql() (err error) { func _gen_pgsql() (err error) {
if dev.DebugMode { if dev.DebugMode {
log.Print("Building the generated statements") log.Print("Building the generated statements")
@ -335,16 +336,16 @@ func _gen_pgsql() (err error) {
return nil return nil
} }
` `
return write_file("./gen_pgsql.go", out) return writeFile("./gen_pgsql.go", out)
} }
// Internal methods, not exposed in the interface // Internal methods, not exposed in the interface
func (adapter *Pgsql_Adapter) push_statement(name string, stype string, querystr string) { func (adapter *Pgsql_Adapter) pushStatement(name string, stype string, querystr string) {
adapter.Buffer[name] = DB_Stmt{querystr,stype} adapter.Buffer[name] = DB_Stmt{querystr, stype}
adapter.BufferOrder = append(adapter.BufferOrder,name) adapter.BufferOrder = append(adapter.BufferOrder, name)
} }
func (adapter *Pgsql_Adapter) stringy_type(ctype string) bool { func (adapter *Pgsql_Adapter) stringyType(ctype string) bool {
ctype = strings.ToLower(ctype) ctype = strings.ToLower(ctype)
return ctype == "char" || ctype == "varchar" || ctype == "timestamp" || ctype == "text" return ctype == "char" || ctype == "varchar" || ctype == "timestamp" || ctype == "text"
} }

View File

@ -9,24 +9,24 @@ func _process_columns(colstr string) (columns []DB_Column) {
if colstr == "" { if colstr == "" {
return columns return columns
} }
colstr = strings.Replace(colstr," as "," AS ",-1) colstr = strings.Replace(colstr, " as ", " AS ", -1)
for _, segment := range strings.Split(colstr,",") { for _, segment := range strings.Split(colstr, ",") {
var outcol DB_Column var outcol DB_Column
dothalves := strings.Split(strings.TrimSpace(segment),".") dothalves := strings.Split(strings.TrimSpace(segment), ".")
var halves []string var halves []string
if len(dothalves) == 2 { if len(dothalves) == 2 {
outcol.Table = dothalves[0] outcol.Table = dothalves[0]
halves = strings.Split(dothalves[1]," AS ") halves = strings.Split(dothalves[1], " AS ")
} else { } else {
halves = strings.Split(dothalves[0]," AS ") halves = strings.Split(dothalves[0], " AS ")
} }
halves[0] = strings.TrimSpace(halves[0]) halves[0] = strings.TrimSpace(halves[0])
if len(halves) == 2 { if len(halves) == 2 {
outcol.Alias = strings.TrimSpace(halves[1]) outcol.Alias = strings.TrimSpace(halves[1])
} }
if halves[0][len(halves[0]) - 1] == ')' { if halves[0][len(halves[0])-1] == ')' {
outcol.Type = "function" outcol.Type = "function"
} else if halves[0] == "?" { } else if halves[0] == "?" {
outcol.Type = "substitute" outcol.Type = "substitute"
@ -35,7 +35,7 @@ func _process_columns(colstr string) (columns []DB_Column) {
} }
outcol.Left = halves[0] outcol.Left = halves[0]
columns = append(columns,outcol) columns = append(columns, outcol)
} }
return columns return columns
} }
@ -44,65 +44,64 @@ func _process_orderby(orderstr string) (order []DB_Order) {
if orderstr == "" { if orderstr == "" {
return order return order
} }
for _, segment := range strings.Split(orderstr,",") { for _, segment := range strings.Split(orderstr, ",") {
var outorder DB_Order var outorder DB_Order
halves := strings.Split(strings.TrimSpace(segment)," ") halves := strings.Split(strings.TrimSpace(segment), " ")
if len(halves) != 2 { if len(halves) != 2 {
continue continue
} }
outorder.Column = halves[0] outorder.Column = halves[0]
outorder.Order = strings.ToLower(halves[1]) outorder.Order = strings.ToLower(halves[1])
order = append(order,outorder) order = append(order, outorder)
} }
return order return order
} }
func _process_joiner(joinstr string) (joiner []DB_Joiner) { func _processJoiner(joinstr string) (joiner []DB_Joiner) {
if joinstr == "" { if joinstr == "" {
return joiner return joiner
} }
joinstr = strings.Replace(joinstr," on "," ON ",-1) joinstr = strings.Replace(joinstr, " on ", " ON ", -1)
joinstr = strings.Replace(joinstr," and "," AND ",-1) joinstr = strings.Replace(joinstr, " and ", " AND ", -1)
for _, segment := range strings.Split(joinstr," AND ") { for _, segment := range strings.Split(joinstr, " AND ") {
var outjoin DB_Joiner var outjoin DB_Joiner
var parseOffset int var parseOffset int
var left, right string var left, right string
left, parseOffset = _get_identifier(segment, parseOffset) left, parseOffset = _getIdentifier(segment, parseOffset)
outjoin.Operator, parseOffset = _get_operator(segment, parseOffset + 1) outjoin.Operator, parseOffset = _getOperator(segment, parseOffset+1)
right, parseOffset = _get_identifier(segment, parseOffset + 1) right, parseOffset = _getIdentifier(segment, parseOffset+1)
left_column := strings.Split(left, ".")
left_column := strings.Split(left,".") right_column := strings.Split(right, ".")
right_column := strings.Split(right,".")
outjoin.LeftTable = strings.TrimSpace(left_column[0]) outjoin.LeftTable = strings.TrimSpace(left_column[0])
outjoin.RightTable = strings.TrimSpace(right_column[0]) outjoin.RightTable = strings.TrimSpace(right_column[0])
outjoin.LeftColumn = strings.TrimSpace(left_column[1]) outjoin.LeftColumn = strings.TrimSpace(left_column[1])
outjoin.RightColumn = strings.TrimSpace(right_column[1]) outjoin.RightColumn = strings.TrimSpace(right_column[1])
joiner = append(joiner,outjoin) joiner = append(joiner, outjoin)
} }
return joiner return joiner
} }
func _process_where(wherestr string) (where []DB_Where) { func _processWhere(wherestr string) (where []DB_Where) {
if wherestr == "" { if wherestr == "" {
return where return where
} }
wherestr = strings.Replace(wherestr," and "," AND ",-1) wherestr = strings.Replace(wherestr, " and ", " AND ", -1)
var buffer string var buffer string
var optype int // 0: None, 1: Number, 2: Column, 3: Function, 4: String, 5: Operator var optype int // 0: None, 1: Number, 2: Column, 3: Function, 4: String, 5: Operator
for _, segment := range strings.Split(wherestr," AND ") { for _, segment := range strings.Split(wherestr, " AND ") {
var tmp_where DB_Where var tmp_where DB_Where
segment += ")" segment += ")"
for i := 0; i < len(segment); i++ { for i := 0; i < len(segment); i++ {
char := segment[i] char := segment[i]
//fmt.Println("optype",optype) //fmt.Println("optype",optype)
switch(optype) { switch optype {
case 0: // unknown case 0: // unknown
//fmt.Println("case 0:",char,string(char)) //fmt.Println("case 0:",char,string(char))
if ('0' <= char && char <= '9') { if '0' <= char && char <= '9' {
optype = 1 optype = 1
buffer = string(char) buffer = string(char)
} else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' { } else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' {
@ -116,16 +115,16 @@ func _process_where(wherestr string) (where []DB_Where) {
buffer = string(char) buffer = string(char)
} else if char == '?' { } else if char == '?' {
//fmt.Println("Expr:","?") //fmt.Println("Expr:","?")
tmp_where.Expr = append(tmp_where.Expr,DB_Token{"?","substitute"}) tmp_where.Expr = append(tmp_where.Expr, DB_Token{"?", "substitute"})
} }
case 1: // number case 1: // number
if ('0' <= char && char <= '9') { if '0' <= char && char <= '9' {
buffer += string(char) buffer += string(char)
} else { } else {
optype = 0 optype = 0
i-- i--
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_where.Expr = append(tmp_where.Expr,DB_Token{buffer,"number"}) tmp_where.Expr = append(tmp_where.Expr, DB_Token{buffer, "number"})
} }
case 2: // column case 2: // column
if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '.' || char == '_' { if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '.' || char == '_' {
@ -137,22 +136,22 @@ func _process_where(wherestr string) (where []DB_Where) {
optype = 0 optype = 0
i-- i--
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_where.Expr = append(tmp_where.Expr,DB_Token{buffer,"column"}) tmp_where.Expr = append(tmp_where.Expr, DB_Token{buffer, "column"})
} }
case 3: // function case 3: // function
var pre_i int = i var preI = i
//fmt.Println("buffer",buffer) //fmt.Println("buffer",buffer)
//fmt.Println("len(halves)",len(halves[1])) //fmt.Println("len(halves)",len(halves[1]))
//fmt.Println("pre_i",string(halves[1][pre_i])) //fmt.Println("preI",string(halves[1][preI]))
//fmt.Println("msg prior to pre_i",halves[1][0:pre_i]) //fmt.Println("msg prior to preI",halves[1][0:preI])
i = _skip_function_call(segment,i-1) i = _skipFunctionCall(segment, i-1)
//fmt.Println("i",i) //fmt.Println("i",i)
//fmt.Println("msg prior to i-1",halves[1][0:i-1]) //fmt.Println("msg prior to i-1",halves[1][0:i-1])
//fmt.Println("string(i-1)",string(halves[1][i-1])) //fmt.Println("string(i-1)",string(halves[1][i-1]))
//fmt.Println("string(i)",string(halves[1][i])) //fmt.Println("string(i)",string(halves[1][i]))
buffer += segment[pre_i:i] + string(segment[i]) buffer += segment[preI:i] + string(segment[i])
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_where.Expr = append(tmp_where.Expr,DB_Token{buffer,"function"}) tmp_where.Expr = append(tmp_where.Expr, DB_Token{buffer, "function"})
optype = 0 optype = 0
case 4: // string case 4: // string
if char != '\'' { if char != '\'' {
@ -160,7 +159,7 @@ func _process_where(wherestr string) (where []DB_Where) {
} else { } else {
optype = 0 optype = 0
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_where.Expr = append(tmp_where.Expr,DB_Token{buffer,"string"}) tmp_where.Expr = append(tmp_where.Expr, DB_Token{buffer, "string"})
} }
case 5: // operator case 5: // operator
if _is_op_byte(char) { if _is_op_byte(char) {
@ -169,13 +168,13 @@ func _process_where(wherestr string) (where []DB_Where) {
optype = 0 optype = 0
i-- i--
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_where.Expr = append(tmp_where.Expr,DB_Token{buffer,"operator"}) tmp_where.Expr = append(tmp_where.Expr, DB_Token{buffer, "operator"})
} }
default: default:
panic("Bad optype in _process_where") panic("Bad optype in _process_where")
} }
} }
where = append(where,tmp_where) where = append(where, tmp_where)
} }
return where return where
} }
@ -189,18 +188,18 @@ func _process_set(setstr string) (setter []DB_Setter) {
// First pass, splitting the string by commas while ignoring the innards of functions // First pass, splitting the string by commas while ignoring the innards of functions
var setset []string var setset []string
var buffer string var buffer string
var last_item int var lastItem int
setstr += "," setstr += ","
for i := 0; i < len(setstr); i++ { for i := 0; i < len(setstr); i++ {
if setstr[i] == '(' { if setstr[i] == '(' {
i = _skip_function_call(setstr,i-1) i = _skipFunctionCall(setstr, i-1)
setset = append(setset,setstr[last_item:i+1]) setset = append(setset, setstr[lastItem:i+1])
buffer = "" buffer = ""
last_item = i + 2 lastItem = i + 2
} else if setstr[i] == ',' && buffer != "" { } else if setstr[i] == ',' && buffer != "" {
setset = append(setset,buffer) setset = append(setset, buffer)
buffer = "" buffer = ""
last_item = i + 1 lastItem = i + 1
} else if (setstr[i] > 32) && setstr[i] != ',' && setstr[i] != ')' { } else if (setstr[i] > 32) && setstr[i] != ',' && setstr[i] != ')' {
buffer += string(setstr[i]) buffer += string(setstr[i])
} }
@ -210,7 +209,7 @@ func _process_set(setstr string) (setter []DB_Setter) {
buffer = "" buffer = ""
for _, setitem := range setset { for _, setitem := range setset {
var tmp_setter DB_Setter var tmp_setter DB_Setter
halves := strings.Split(setitem,"=") halves := strings.Split(setitem, "=")
if len(halves) != 2 { if len(halves) != 2 {
continue continue
} }
@ -222,9 +221,9 @@ func _process_set(setstr string) (setter []DB_Setter) {
for i := 0; i < len(halves[1]); i++ { for i := 0; i < len(halves[1]); i++ {
char := halves[1][i] char := halves[1][i]
//fmt.Println("optype",optype) //fmt.Println("optype",optype)
switch(optype) { switch optype {
case 0: // unknown case 0: // unknown
if ('0' <= char && char <= '9') { if '0' <= char && char <= '9' {
optype = 1 optype = 1
buffer = string(char) buffer = string(char)
} else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' { } else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' {
@ -238,16 +237,16 @@ func _process_set(setstr string) (setter []DB_Setter) {
buffer = string(char) buffer = string(char)
} else if char == '?' { } else if char == '?' {
//fmt.Println("Expr:","?") //fmt.Println("Expr:","?")
tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{"?","substitute"}) tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{"?", "substitute"})
} }
case 1: // number case 1: // number
if ('0' <= char && char <= '9') { if '0' <= char && char <= '9' {
buffer += string(char) buffer += string(char)
} else { } else {
optype = 0 optype = 0
i-- i--
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"number"}) tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "number"})
} }
case 2: // column case 2: // column
if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' { if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' {
@ -259,22 +258,22 @@ func _process_set(setstr string) (setter []DB_Setter) {
optype = 0 optype = 0
i-- i--
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"column"}) tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "column"})
} }
case 3: // function case 3: // function
var pre_i int = i var preI = i
//fmt.Println("buffer",buffer) //fmt.Println("buffer",buffer)
//fmt.Println("len(halves)",len(halves[1])) //fmt.Println("len(halves)",len(halves[1]))
//fmt.Println("pre_i",string(halves[1][pre_i])) //fmt.Println("preI",string(halves[1][preI]))
//fmt.Println("msg prior to pre_i",halves[1][0:pre_i]) //fmt.Println("msg prior to preI",halves[1][0:preI])
i = _skip_function_call(halves[1],i-1) i = _skipFunctionCall(halves[1], i-1)
//fmt.Println("i",i) //fmt.Println("i",i)
//fmt.Println("msg prior to i-1",halves[1][0:i-1]) //fmt.Println("msg prior to i-1",halves[1][0:i-1])
//fmt.Println("string(i-1)",string(halves[1][i-1])) //fmt.Println("string(i-1)",string(halves[1][i-1]))
//fmt.Println("string(i)",string(halves[1][i])) //fmt.Println("string(i)",string(halves[1][i]))
buffer += halves[1][pre_i:i] + string(halves[1][i]) buffer += halves[1][preI:i] + string(halves[1][i])
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"function"}) tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "function"})
optype = 0 optype = 0
case 4: // string case 4: // string
if char != '\'' { if char != '\'' {
@ -282,7 +281,7 @@ func _process_set(setstr string) (setter []DB_Setter) {
} else { } else {
optype = 0 optype = 0
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"string"}) tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "string"})
} }
case 5: // operator case 5: // operator
if _is_op_byte(char) { if _is_op_byte(char) {
@ -291,20 +290,20 @@ func _process_set(setstr string) (setter []DB_Setter) {
optype = 0 optype = 0
i-- i--
//fmt.Println("Expr:",buffer) //fmt.Println("Expr:",buffer)
tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"operator"}) tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "operator"})
} }
default: default:
panic("Bad optype in _process_set") panic("Bad optype in _process_set")
} }
} }
setter = append(setter,tmp_setter) setter = append(setter, tmp_setter)
} }
//fmt.Println("setter",setter) //fmt.Println("setter",setter)
return setter return setter
} }
func _process_limit(limitstr string) (limiter DB_Limit) { func _processLimit(limitstr string) (limiter DB_Limit) {
halves := strings.Split(limitstr,",") halves := strings.Split(limitstr, ",")
if len(halves) == 2 { if len(halves) == 2 {
limiter.Offset = halves[0] limiter.Offset = halves[0]
limiter.MaxCount = halves[1] limiter.MaxCount = halves[1]
@ -322,23 +321,23 @@ func _is_op_rune(char rune) bool {
return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/' return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/'
} }
func _process_fields(fieldstr string) (fields []DB_Field) { func _processFields(fieldstr string) (fields []DB_Field) {
if fieldstr == "" { if fieldstr == "" {
return fields return fields
} }
var buffer string var buffer string
var last_item int var lastItem int
fieldstr += "," fieldstr += ","
for i := 0; i < len(fieldstr); i++ { for i := 0; i < len(fieldstr); i++ {
if fieldstr[i] == '(' { if fieldstr[i] == '(' {
i = _skip_function_call(fieldstr,i-1) i = _skipFunctionCall(fieldstr, i-1)
fields = append(fields,DB_Field{Name:fieldstr[last_item:i+1],Type:_get_identifier_type(fieldstr[last_item:i+1])}) fields = append(fields, DB_Field{Name: fieldstr[lastItem : i+1], Type: _getIdentifierType(fieldstr[lastItem : i+1])})
buffer = "" buffer = ""
last_item = i + 2 lastItem = i + 2
} else if fieldstr[i] == ',' && buffer != "" { } else if fieldstr[i] == ',' && buffer != "" {
fields = append(fields,DB_Field{Name:buffer,Type:_get_identifier_type(buffer)}) fields = append(fields, DB_Field{Name: buffer, Type: _getIdentifierType(buffer)})
buffer = "" buffer = ""
last_item = i + 1 lastItem = i + 1
} else if (fieldstr[i] > 32) && fieldstr[i] != ',' && fieldstr[i] != ')' { } else if (fieldstr[i] > 32) && fieldstr[i] != ',' && fieldstr[i] != ')' {
buffer += string(fieldstr[i]) buffer += string(fieldstr[i])
} }
@ -346,9 +345,9 @@ func _process_fields(fieldstr string) (fields []DB_Field) {
return fields return fields
} }
func _get_identifier_type(identifier string) string { func _getIdentifierType(identifier string) string {
if ('a' <= identifier[0] && identifier[0] <= 'z') || ('A' <= identifier[0] && identifier[0] <= 'Z') { if ('a' <= identifier[0] && identifier[0] <= 'z') || ('A' <= identifier[0] && identifier[0] <= 'Z') {
if identifier[len(identifier) - 1] == ')' { if identifier[len(identifier)-1] == ')' {
return "function" return "function"
} }
return "column" return "column"
@ -359,12 +358,12 @@ func _get_identifier_type(identifier string) string {
return "literal" return "literal"
} }
func _get_identifier(segment string, startOffset int) (out string, i int) { func _getIdentifier(segment string, startOffset int) (out string, i int) {
segment = strings.TrimSpace(segment) segment = strings.TrimSpace(segment)
segment += " " // Avoid overflow bugs with slicing segment += " " // Avoid overflow bugs with slicing
for i = startOffset; i < len(segment); i++ { for i = startOffset; i < len(segment); i++ {
if segment[i] == '(' { if segment[i] == '(' {
i = _skip_function_call(segment,i) i = _skipFunctionCall(segment, i)
return strings.TrimSpace(segment[startOffset:i]), (i - 1) return strings.TrimSpace(segment[startOffset:i]), (i - 1)
} }
if (segment[i] == ' ' || _is_op_byte(segment[i])) && i != startOffset { if (segment[i] == ' ' || _is_op_byte(segment[i])) && i != startOffset {
@ -374,7 +373,7 @@ func _get_identifier(segment string, startOffset int) (out string, i int) {
return strings.TrimSpace(segment[startOffset:]), (i - 1) return strings.TrimSpace(segment[startOffset:]), (i - 1)
} }
func _get_operator(segment string, startOffset int) (out string, i int) { func _getOperator(segment string, startOffset int) (out string, i int) {
segment = strings.TrimSpace(segment) segment = strings.TrimSpace(segment)
segment += " " // Avoid overflow bugs with slicing segment += " " // Avoid overflow bugs with slicing
for i = startOffset; i < len(segment); i++ { for i = startOffset; i < len(segment); i++ {
@ -385,15 +384,15 @@ func _get_operator(segment string, startOffset int) (out string, i int) {
return strings.TrimSpace(segment[startOffset:]), (i - 1) return strings.TrimSpace(segment[startOffset:]), (i - 1)
} }
func _skip_function_call(data string, index int) int { func _skipFunctionCall(data string, index int) int {
var brace_count int var braceCount int
for ;index < len(data); index++{ for ; index < len(data); index++ {
char := data[index] char := data[index]
if char == '(' { if char == '(' {
brace_count++ braceCount++
} else if char == ')' { } else if char == ')' {
brace_count-- braceCount--
if brace_count == 0 { if braceCount == 0 {
return index return index
} }
} }
@ -401,7 +400,7 @@ func _skip_function_call(data string, index int) int {
return index return index
} }
func write_file(name string, content string) (err error) { func writeFile(name string, content string) (err error) {
f, err := os.Create(name) f, err := os.Create(name)
if err != nil { if err != nil {
return err return err
@ -410,7 +409,9 @@ func write_file(name string, content string) (err error) {
if err != nil { if err != nil {
return err return err
} }
f.Sync() err = f.Sync()
f.Close() if err != nil {
return return err
}
return f.Close()
} }

View File

@ -1,60 +1,64 @@
/* Copyright Azareal 2016 - 2017 */ /*
*
* Reply Resources File
* Copyright Azareal 2016 - 2018
*
*/
package main package main
// Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be seperate? // Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be seperate?
type Reply struct /* Should probably rename this to ReplyUser and rename ReplyShort to Reply */ type Reply struct /* Should probably rename this to ReplyUser and rename ReplyShort to Reply */
{ {
ID int ID int
ParentID int ParentID int
Content string Content string
ContentHtml string ContentHtml string
CreatedBy int CreatedBy int
UserLink string UserLink string
CreatedByName string CreatedByName string
Group int Group int
CreatedAt string CreatedAt string
LastEdit int LastEdit int
LastEditBy int LastEditBy int
Avatar string Avatar string
ClassName string ClassName string
ContentLines int ContentLines int
Tag string Tag string
URL string URL string
URLPrefix string URLPrefix string
URLName string URLName string
Level int Level int
IpAddress string IPAddress string
Liked bool Liked bool
LikeCount int LikeCount int
ActionType string ActionType string
ActionIcon string ActionIcon string
} }
type ReplyShort struct type ReplyShort struct {
{ ID int
ID int ParentID int
ParentID int Content string
Content string CreatedBy int
CreatedBy int Group int
Group int CreatedAt string
CreatedAt string LastEdit int
LastEdit int LastEditBy int
LastEditBy int
ContentLines int ContentLines int
IpAddress string IPAddress string
Liked bool Liked bool
LikeCount int LikeCount int
} }
func get_reply(id int) (*ReplyShort, error) { func getReply(id int) (*ReplyShort, error) {
reply := ReplyShort{ID:id} reply := ReplyShort{ID: id}
err := get_reply_stmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IpAddress, &reply.LikeCount) err := get_reply_stmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress, &reply.LikeCount)
return &reply, err return &reply, err
} }
func get_user_reply(id int) (*ReplyShort, error) { func getUserReply(id int) (*ReplyShort, error) {
reply := ReplyShort{ID:id} reply := ReplyShort{ID: id}
err := get_user_reply_stmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IpAddress) err := get_user_reply_stmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress)
return &reply, err return &reply, err
} }

View File

@ -1,86 +1,86 @@
package main package main
type Route struct { type Route struct {
Name string Name string
Path string Path string
Before string Before string
Vars []string Vars []string
} }
type RouteGroup struct { type RouteGroup struct {
Path string Path string
Routes []Route Routes []Route
} }
func addRoute(fname string, path string, before string, vars ...string) { func addRoute(fname string, path string, before string, vars ...string) {
route_list = append(route_list,Route{fname,path,before,vars}) route_list = append(route_list, Route{fname, path, before, vars})
} }
func addRouteGroup(path string, routes ...Route) { func addRouteGroup(path string, routes ...Route) {
route_groups = append(route_groups,RouteGroup{path,routes}) route_groups = append(route_groups, RouteGroup{path, routes})
} }
func routes() { func routes() {
//addRoute("default_route","","") //addRoute("default_route","","")
addRoute("route_api","/api/","") addRoute("route_api", "/api/", "")
///addRoute("route_static","/static/","req.URL.Path += extra_data") ///addRoute("route_static","/static/","req.URL.Path += extra_data")
addRoute("route_overview","/overview/","") addRoute("route_overview", "/overview/", "")
//addRoute("route_custom_page","/pages/",""/*,"&extra_data"*/) //addRoute("route_custom_page","/pages/",""/*,"&extra_data"*/)
addRoute("route_forums","/forums/",""/*,"&forums"*/) addRoute("route_forums", "/forums/", "" /*,"&forums"*/)
addRoute("route_forum","/forum/","","extra_data") addRoute("route_forum", "/forum/", "", "extra_data")
//addRoute("route_topic_create","/topics/create/","","extra_data") //addRoute("route_topic_create","/topics/create/","","extra_data")
//addRoute("route_topics","/topics/",""/*,"&groups","&forums"*/) //addRoute("route_topics","/topics/",""/*,"&groups","&forums"*/)
addRouteGroup("/report/", addRouteGroup("/report/",
Route{"route_report_submit","/report/submit/","",[]string{"extra_data"}}, Route{"route_report_submit", "/report/submit/", "", []string{"extra_data"}},
) )
addRouteGroup("/topics/", addRouteGroup("/topics/",
Route{"route_topics","/topics/","",[]string{}}, Route{"route_topics", "/topics/", "", []string{}},
Route{"route_topic_create","/topics/create/","",[]string{"extra_data"}}, Route{"route_topic_create", "/topics/create/", "", []string{"extra_data"}},
) )
// The Control Panel // The Control Panel
addRouteGroup("/panel/", addRouteGroup("/panel/",
Route{"route_panel","/panel/","",[]string{}}, Route{"route_panel", "/panel/", "", []string{}},
Route{"route_panel_forums","/panel/forums/","",[]string{}}, Route{"route_panel_forums", "/panel/forums/", "", []string{}},
Route{"route_panel_forums_create_submit","/panel/forums/create/","",[]string{}}, Route{"route_panel_forums_create_submit", "/panel/forums/create/", "", []string{}},
Route{"route_panel_forums_delete","/panel/forums/delete/","",[]string{"extra_data"}}, Route{"route_panel_forums_delete", "/panel/forums/delete/", "", []string{"extra_data"}},
Route{"route_panel_forums_delete_submit","/panel/forums/delete/submit/","",[]string{"extra_data"}}, Route{"route_panel_forums_delete_submit", "/panel/forums/delete/submit/", "", []string{"extra_data"}},
Route{"route_panel_forums_edit","/panel/forums/edit/","",[]string{"extra_data"}}, Route{"route_panel_forums_edit", "/panel/forums/edit/", "", []string{"extra_data"}},
Route{"route_panel_forums_edit_submit","/panel/forums/edit/submit/","",[]string{"extra_data"}}, Route{"route_panel_forums_edit_submit", "/panel/forums/edit/submit/", "", []string{"extra_data"}},
Route{"route_panel_forums_edit_perms_submit","/panel/forums/edit/perms/submit/","",[]string{"extra_data"}}, Route{"route_panel_forums_edit_perms_submit", "/panel/forums/edit/perms/submit/", "", []string{"extra_data"}},
Route{"route_panel_settings","/panel/settings/","",[]string{}}, Route{"route_panel_settings", "/panel/settings/", "", []string{}},
Route{"route_panel_setting","/panel/settings/edit/","",[]string{"extra_data"}}, Route{"route_panel_setting", "/panel/settings/edit/", "", []string{"extra_data"}},
Route{"route_panel_setting_edit","/panel/settings/edit/submit/","",[]string{"extra_data"}}, Route{"route_panel_setting_edit", "/panel/settings/edit/submit/", "", []string{"extra_data"}},
Route{"route_panel_word_filters","/panel/settings/word-filters/","",[]string{}}, Route{"route_panel_word_filters", "/panel/settings/word-filters/", "", []string{}},
Route{"route_panel_word_filters_create","/panel/settings/word-filters/create/","",[]string{}}, Route{"route_panel_word_filters_create", "/panel/settings/word-filters/create/", "", []string{}},
Route{"route_panel_word_filters_edit","/panel/settings/word-filters/edit/","",[]string{"extra_data"}}, Route{"route_panel_word_filters_edit", "/panel/settings/word-filters/edit/", "", []string{"extra_data"}},
Route{"route_panel_word_filters_edit_submit","/panel/settings/word-filters/edit/submit/","",[]string{"extra_data"}}, Route{"route_panel_word_filters_edit_submit", "/panel/settings/word-filters/edit/submit/", "", []string{"extra_data"}},
Route{"route_panel_word_filters_delete_submit","/panel/settings/word-filters/delete/submit/","",[]string{"extra_data"}}, Route{"route_panel_word_filters_delete_submit", "/panel/settings/word-filters/delete/submit/", "", []string{"extra_data"}},
Route{"route_panel_themes","/panel/themes/","",[]string{}}, Route{"route_panel_themes", "/panel/themes/", "", []string{}},
Route{"route_panel_themes_default","/panel/themes/default/","",[]string{"extra_data"}}, Route{"route_panel_themes_set_default", "/panel/themes/default/", "", []string{"extra_data"}},
Route{"route_panel_plugins","/panel/plugins/","",[]string{}}, Route{"route_panel_plugins", "/panel/plugins/", "", []string{}},
Route{"route_panel_plugins_activate","/panel/plugins/activate/","",[]string{"extra_data"}}, Route{"route_panel_plugins_activate", "/panel/plugins/activate/", "", []string{"extra_data"}},
Route{"route_panel_plugins_deactivate","/panel/plugins/deactivate/","",[]string{"extra_data"}}, Route{"route_panel_plugins_deactivate", "/panel/plugins/deactivate/", "", []string{"extra_data"}},
Route{"route_panel_plugins_install","/panel/plugins/install/","",[]string{"extra_data"}}, Route{"route_panel_plugins_install", "/panel/plugins/install/", "", []string{"extra_data"}},
Route{"route_panel_users","/panel/users/","",[]string{}}, Route{"route_panel_users", "/panel/users/", "", []string{}},
Route{"route_panel_users_edit","/panel/users/edit/","",[]string{"extra_data"}}, Route{"route_panel_users_edit", "/panel/users/edit/", "", []string{"extra_data"}},
Route{"route_panel_users_edit_submit","/panel/users/edit/submit/","",[]string{"extra_data"}}, Route{"route_panel_users_edit_submit", "/panel/users/edit/submit/", "", []string{"extra_data"}},
Route{"route_panel_groups","/panel/groups/","",[]string{}}, Route{"route_panel_groups", "/panel/groups/", "", []string{}},
Route{"route_panel_groups_edit","/panel/groups/edit/","",[]string{"extra_data"}}, Route{"route_panel_groups_edit", "/panel/groups/edit/", "", []string{"extra_data"}},
Route{"route_panel_groups_edit_perms","/panel/groups/edit/perms/","",[]string{"extra_data"}}, Route{"route_panel_groups_edit_perms", "/panel/groups/edit/perms/", "", []string{"extra_data"}},
Route{"route_panel_groups_edit_submit","/panel/groups/edit/submit/","",[]string{"extra_data"}}, Route{"route_panel_groups_edit_submit", "/panel/groups/edit/submit/", "", []string{"extra_data"}},
Route{"route_panel_groups_edit_perms_submit","/panel/groups/edit/perms/submit/","",[]string{"extra_data"}}, Route{"route_panel_groups_edit_perms_submit", "/panel/groups/edit/perms/submit/", "", []string{"extra_data"}},
Route{"route_panel_groups_create_submit","/panel/groups/create/","",[]string{}}, Route{"route_panel_groups_create_submit", "/panel/groups/create/", "", []string{}},
Route{"route_panel_logs_mod","/panel/logs/mod/","",[]string{}}, Route{"route_panel_logs_mod", "/panel/logs/mod/", "", []string{}},
Route{"route_panel_debug","/panel/debug/","",[]string{}}, Route{"route_panel_debug", "/panel/debug/", "", []string{}},
) )
} }

1103
routes.go

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,28 @@
package main package main
import "strconv" import "strconv"
import "strings" import "strings"
import "sync/atomic" import "sync/atomic"
// TO-DO: Move this into the phrase system // SettingBox is a map type specifically for holding the various settings admins set to toggle features on and off or to otherwise alter Gosora's behaviour from the Control Panel
var settingLabels map[string]string
type SettingBox map[string]interface{} type SettingBox map[string]interface{}
var settingBox atomic.Value // An atomic value pointing to a SettingBox var settingBox atomic.Value // An atomic value pointing to a SettingBox
type OptionLabel struct type OptionLabel struct {
{ Label string
Label string Value int
Value int
Selected bool Selected bool
} }
type Setting struct type Setting struct {
{ Name string
Name string Content string
Content string Type string
Type string
Constraint string Constraint string
} }
func init() { func init() {
settingLabels = make(map[string]string)
settingLabels["activation_type"] = "Activate All,Email Activation,Admin Approval"
settingBox.Store(SettingBox(make(map[string]interface{}))) settingBox.Store(SettingBox(make(map[string]interface{})))
//settingBox.Store(make(map[string]interface{})) //settingBox.Store(make(map[string]interface{}))
} }
@ -76,7 +73,7 @@ func (sBox SettingBox) ParseSetting(sname string, scontent string, stype string,
return "You were supposed to enter an integer x.x\nType mismatch in " + sname return "You were supposed to enter an integer x.x\nType mismatch in " + sname
} }
} else if stype == "list" { } else if stype == "list" {
cons := strings.Split(constraint,"-") cons := strings.Split(constraint, "-")
if len(cons) < 2 { if len(cons) < 2 {
return "Invalid constraint! The second field wasn't set!" return "Invalid constraint! The second field wasn't set!"
} }
@ -90,7 +87,7 @@ func (sBox SettingBox) ParseSetting(sname string, scontent string, stype string,
return "Invalid contraint! The constraint field wasn't an integer!" return "Invalid contraint! The constraint field wasn't an integer!"
} }
value, err := strconv.Atoi(scontent) value, err := strconv.Atoi(scontent)
if err != nil { if err != nil {
return "Only integers are allowed in this setting x.x\nType mismatch in " + sname return "Only integers are allowed in this setting x.x\nType mismatch in " + sname
} }

View File

@ -2,7 +2,7 @@ package main
import "time" import "time"
func handle_expired_scheduled_groups() error { func handleExpiredScheduledGroups() error {
rows, err := get_expired_scheduled_groups_stmt.Query() rows, err := get_expired_scheduled_groups_stmt.Query()
if err != nil { if err != nil {
return err return err
@ -19,7 +19,7 @@ func handle_expired_scheduled_groups() error {
if err != nil { if err != nil {
return err return err
} }
_, err = set_temp_group_stmt.Exec(0,uid) _, err = set_temp_group_stmt.Exec(0, uid)
if err != nil { if err != nil {
return err return err
} }

View File

@ -6,63 +6,68 @@ package main
import "net/http" import "net/http"
import "strconv" import "strconv"
// nolint
func init() { func init() {
template_forum_handle = template_forum template_forum_handle = template_forum
//o_template_forum_handle = template_forum //o_template_forum_handle = template_forum
ctemplates = append(ctemplates,"forum") ctemplates = append(ctemplates,"forum")
tmpl_ptr_map["forum"] = &template_forum_handle tmplPtrMap["forum"] = &template_forum_handle
tmpl_ptr_map["o_forum"] = template_forum tmplPtrMap["o_forum"] = template_forum
} }
// nolint
func template_forum(tmpl_forum_vars ForumPage, w http.ResponseWriter) { func template_forum(tmpl_forum_vars ForumPage, w http.ResponseWriter) {
w.Write(header_0) w.Write(header_0)
w.Write([]byte(tmpl_forum_vars.Title)) w.Write([]byte(tmpl_forum_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_forum_vars.Header.ThemeName))
w.Write(header_2)
if len(tmpl_forum_vars.Header.Stylesheets) != 0 { if len(tmpl_forum_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_forum_vars.Header.Stylesheets { for _, item := range tmpl_forum_vars.Header.Stylesheets {
w.Write(header_2)
w.Write([]byte(item))
w.Write(header_3) w.Write(header_3)
} w.Write([]byte(item))
}
w.Write(header_4) w.Write(header_4)
}
}
w.Write(header_5)
if len(tmpl_forum_vars.Header.Scripts) != 0 { if len(tmpl_forum_vars.Header.Scripts) != 0 {
for _, item := range tmpl_forum_vars.Header.Scripts { for _, item := range tmpl_forum_vars.Header.Scripts {
w.Write(header_5)
w.Write([]byte(item))
w.Write(header_6) w.Write(header_6)
} w.Write([]byte(item))
}
w.Write(header_7) w.Write(header_7)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Session)) }
}
w.Write(header_8) w.Write(header_8)
if !tmpl_forum_vars.CurrentUser.Is_Super_Mod {
w.Write(header_9)
}
w.Write(header_10)
w.Write(menu_0)
w.Write([]byte(tmpl_forum_vars.Header.Site.Name))
w.Write(menu_1)
if tmpl_forum_vars.CurrentUser.Loggedin {
w.Write(menu_2)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Link))
w.Write(menu_3)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Session)) w.Write([]byte(tmpl_forum_vars.CurrentUser.Session))
w.Write(menu_4) w.Write(header_9)
} else { if !tmpl_forum_vars.CurrentUser.IsSuperMod {
w.Write(menu_5) w.Write(header_10)
} }
w.Write(menu_6)
w.Write(header_11) w.Write(header_11)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" { w.Write(menu_0)
w.Write(header_12) w.Write(menu_1)
w.Write([]byte(tmpl_forum_vars.Header.Site.Name))
w.Write(menu_2)
if tmpl_forum_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
} }
w.Write(menu_7)
w.Write(header_12)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_13) w.Write(header_13)
}
w.Write(header_14)
if len(tmpl_forum_vars.Header.NoticeList) != 0 { if len(tmpl_forum_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_forum_vars.Header.NoticeList { for _, item := range tmpl_forum_vars.Header.NoticeList {
w.Write(header_14)
w.Write([]byte(item))
w.Write(header_15) w.Write(header_15)
w.Write([]byte(item))
w.Write(header_16)
} }
} }
if tmpl_forum_vars.Page > 1 { if tmpl_forum_vars.Page > 1 {
@ -107,7 +112,7 @@ w.Write(forum_17)
if item.Sticky { if item.Sticky {
w.Write(forum_18) w.Write(forum_18)
} else { } else {
if item.Is_Closed { if item.IsClosed {
w.Write(forum_19) w.Write(forum_19)
} }
} }
@ -130,7 +135,7 @@ w.Write([]byte(item.Creator.Link))
w.Write(forum_28) w.Write(forum_28)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(forum_29) w.Write(forum_29)
if item.Is_Closed { if item.IsClosed {
w.Write(forum_30) w.Write(forum_30)
} }
if item.Sticky { if item.Sticky {

View File

@ -5,63 +5,68 @@
package main package main
import "net/http" import "net/http"
// nolint
func init() { func init() {
template_forums_handle = template_forums template_forums_handle = template_forums
//o_template_forums_handle = template_forums //o_template_forums_handle = template_forums
ctemplates = append(ctemplates,"forums") ctemplates = append(ctemplates,"forums")
tmpl_ptr_map["forums"] = &template_forums_handle tmplPtrMap["forums"] = &template_forums_handle
tmpl_ptr_map["o_forums"] = template_forums tmplPtrMap["o_forums"] = template_forums
} }
// nolint
func template_forums(tmpl_forums_vars ForumsPage, w http.ResponseWriter) { func template_forums(tmpl_forums_vars ForumsPage, w http.ResponseWriter) {
w.Write(header_0) w.Write(header_0)
w.Write([]byte(tmpl_forums_vars.Title)) w.Write([]byte(tmpl_forums_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_forums_vars.Header.ThemeName))
w.Write(header_2)
if len(tmpl_forums_vars.Header.Stylesheets) != 0 { if len(tmpl_forums_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_forums_vars.Header.Stylesheets { for _, item := range tmpl_forums_vars.Header.Stylesheets {
w.Write(header_2)
w.Write([]byte(item))
w.Write(header_3) w.Write(header_3)
} w.Write([]byte(item))
}
w.Write(header_4) w.Write(header_4)
}
}
w.Write(header_5)
if len(tmpl_forums_vars.Header.Scripts) != 0 { if len(tmpl_forums_vars.Header.Scripts) != 0 {
for _, item := range tmpl_forums_vars.Header.Scripts { for _, item := range tmpl_forums_vars.Header.Scripts {
w.Write(header_5)
w.Write([]byte(item))
w.Write(header_6) w.Write(header_6)
} w.Write([]byte(item))
}
w.Write(header_7) w.Write(header_7)
w.Write([]byte(tmpl_forums_vars.CurrentUser.Session)) }
}
w.Write(header_8) w.Write(header_8)
if !tmpl_forums_vars.CurrentUser.Is_Super_Mod {
w.Write(header_9)
}
w.Write(header_10)
w.Write(menu_0)
w.Write([]byte(tmpl_forums_vars.Header.Site.Name))
w.Write(menu_1)
if tmpl_forums_vars.CurrentUser.Loggedin {
w.Write(menu_2)
w.Write([]byte(tmpl_forums_vars.CurrentUser.Link))
w.Write(menu_3)
w.Write([]byte(tmpl_forums_vars.CurrentUser.Session)) w.Write([]byte(tmpl_forums_vars.CurrentUser.Session))
w.Write(menu_4) w.Write(header_9)
} else { if !tmpl_forums_vars.CurrentUser.IsSuperMod {
w.Write(menu_5) w.Write(header_10)
} }
w.Write(menu_6)
w.Write(header_11) w.Write(header_11)
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" { w.Write(menu_0)
w.Write(header_12) w.Write(menu_1)
w.Write([]byte(tmpl_forums_vars.Header.Site.Name))
w.Write(menu_2)
if tmpl_forums_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_forums_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_forums_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
} }
w.Write(menu_7)
w.Write(header_12)
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_13) w.Write(header_13)
}
w.Write(header_14)
if len(tmpl_forums_vars.Header.NoticeList) != 0 { if len(tmpl_forums_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_forums_vars.Header.NoticeList { for _, item := range tmpl_forums_vars.Header.NoticeList {
w.Write(header_14)
w.Write([]byte(item))
w.Write(header_15) w.Write(header_15)
w.Write([]byte(item))
w.Write(header_16)
} }
} }
w.Write(forums_0) w.Write(forums_0)

View File

@ -11,81 +11,81 @@ func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) {
if !ok { if !ok {
mapping = "topic" mapping = "topic"
} }
err := templates.ExecuteTemplate(w,mapping + ".html", pi) err := templates.ExecuteTemplate(w, mapping+".html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
var template_topic_handle func(TopicPage,http.ResponseWriter) = interpreted_topic_template var template_topic_handle func(TopicPage, http.ResponseWriter) = interpreted_topic_template
var template_topic_alt_handle func(TopicPage,http.ResponseWriter) = interpreted_topic_template var template_topic_alt_handle func(TopicPage, http.ResponseWriter) = interpreted_topic_template
var template_topics_handle func(TopicsPage,http.ResponseWriter) = func(pi TopicsPage, w http.ResponseWriter) { var template_topics_handle func(TopicsPage, http.ResponseWriter) = func(pi TopicsPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["topics"] mapping, ok := themes[defaultTheme].TemplatesMap["topics"]
if !ok { if !ok {
mapping = "topics" mapping = "topics"
} }
err := templates.ExecuteTemplate(w,mapping + ".html", pi) err := templates.ExecuteTemplate(w, mapping+".html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
var template_forum_handle func(ForumPage,http.ResponseWriter) = func(pi ForumPage, w http.ResponseWriter) { var template_forum_handle func(ForumPage, http.ResponseWriter) = func(pi ForumPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["forum"] mapping, ok := themes[defaultTheme].TemplatesMap["forum"]
if !ok { if !ok {
mapping = "forum" mapping = "forum"
} }
err := templates.ExecuteTemplate(w,mapping + ".html", pi) err := templates.ExecuteTemplate(w, mapping+".html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
var template_forums_handle func(ForumsPage,http.ResponseWriter) = func(pi ForumsPage, w http.ResponseWriter) { var template_forums_handle func(ForumsPage, http.ResponseWriter) = func(pi ForumsPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["forums"] mapping, ok := themes[defaultTheme].TemplatesMap["forums"]
if !ok { if !ok {
mapping = "forums" mapping = "forums"
} }
err := templates.ExecuteTemplate(w,mapping + ".html", pi) err := templates.ExecuteTemplate(w, mapping+".html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
var template_profile_handle func(ProfilePage,http.ResponseWriter) = func(pi ProfilePage, w http.ResponseWriter) { var template_profile_handle func(ProfilePage, http.ResponseWriter) = func(pi ProfilePage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["profile"] mapping, ok := themes[defaultTheme].TemplatesMap["profile"]
if !ok { if !ok {
mapping = "profile" mapping = "profile"
} }
err := templates.ExecuteTemplate(w,mapping + ".html", pi) err := templates.ExecuteTemplate(w, mapping+".html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
var template_create_topic_handle func(CreateTopicPage,http.ResponseWriter) = func(pi CreateTopicPage, w http.ResponseWriter) { var template_create_topic_handle func(CreateTopicPage, http.ResponseWriter) = func(pi CreateTopicPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["create-topic"] mapping, ok := themes[defaultTheme].TemplatesMap["create-topic"]
if !ok { if !ok {
mapping = "create-topic" mapping = "create-topic"
} }
err := templates.ExecuteTemplate(w,mapping + ".html", pi) err := templates.ExecuteTemplate(w, mapping+".html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
func compile_templates() error { func compileTemplates() error {
var c CTemplateSet var c CTemplateSet
user := User{62,build_profile_url("fake-user",62),"Fake User","compiler@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"0.0.0.0.0",0} user := User{62, buildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0}
// TO-DO: Do a more accurate level calculation for this? // TO-DO: Do a more accurate level calculation for this?
user2 := User{1,build_profile_url("admin-alice",1),"Admin Alice","alice@localhost",1,true,true,true,true,false,false,AllPerms,make(map[string]bool),"",true,"","","","","",58,1000,"127.0.0.1",0} user2 := User{1, buildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", "", "", "", "", 58, 1000, "127.0.0.1", 0}
user3 := User{2,build_profile_url("admin-fred",62),"Admin Fred","fred@localhost",1,true,true,true,true,false,false,AllPerms,make(map[string]bool),"",true,"","","","","",42,900,"::1",0} user3 := User{2, buildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", "", "", "", "", 42, 900, "::1", 0}
headerVars := HeaderVars{ headerVars := &HeaderVars{
Site: site, Site: site,
NoticeList: []string{"test"}, NoticeList: []string{"test"},
Stylesheets: []string{"panel"}, Stylesheets: []string{"panel"},
Scripts: []string{"whatever"}, Scripts: []string{"whatever"},
Widgets: PageWidgets{ Widgets: PageWidgets{
LeftSidebar: template.HTML("lalala"), LeftSidebar: template.HTML("lalala"),
}, },
@ -93,24 +93,24 @@ func compile_templates() error {
log.Print("Compiling the templates") log.Print("Compiling the templates")
topic := TopicUser{1,"blah","Blah","Hey there!",0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"classname","weird-data",build_profile_url("fake-user",62),"Fake User",config.DefaultGroup,"",0,"","","","",58,false} topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, "Date", "Date", 0, "", "127.0.0.1", 0, 1, "classname", "weird-data", buildProfileURL("fake-user", 62), "Fake User", config.DefaultGroup, "", 0, "", "", "", "", 58, false}
var replyList []Reply var replyList []Reply
replyList = append(replyList, Reply{0,0,"Yo!","Yo!",0,"alice","Alice",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""}) replyList = append(replyList, Reply{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, "", 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
var varList map[string]VarItem = make(map[string]VarItem) var varList map[string]VarItem = make(map[string]VarItem)
tpage := TopicPage{"Title",user,headerVars,replyList,topic,1,1,extData} tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1}
topic_id_tmpl, err := c.compile_template("topic.html","templates/","TopicPage", tpage, varList) topic_id_tmpl, err := c.compileTemplate("topic.html", "templates/", "TopicPage", tpage, varList)
if err != nil { if err != nil {
return err return err
} }
topic_id_alt_tmpl, err := c.compile_template("topic_alt.html","templates/","TopicPage", tpage, varList) topic_id_alt_tmpl, err := c.compileTemplate("topic_alt.html", "templates/", "TopicPage", tpage, varList)
if err != nil { if err != nil {
return err return err
} }
varList = make(map[string]VarItem) varList = make(map[string]VarItem)
ppage := ProfilePage{"User 526",user,headerVars,replyList,user,extData} ppage := ProfilePage{"User 526", user, headerVars, replyList, user}
profile_tmpl, err := c.compile_template("profile.html","templates/","ProfilePage", ppage, varList) profile_tmpl, err := c.compileTemplate("profile.html", "templates/", "ProfilePage", ppage, varList)
if err != nil { if err != nil {
return err return err
} }
@ -123,42 +123,42 @@ func compile_templates() error {
for _, forum := range forums { for _, forum := range forums {
if forum.Active { if forum.Active {
forumList = append(forumList,*forum) forumList = append(forumList, *forum)
} }
} }
varList = make(map[string]VarItem) varList = make(map[string]VarItem)
forums_page := ForumsPage{"Forum List",user,headerVars,forumList,extData} forums_page := ForumsPage{"Forum List", user, headerVars, forumList}
forums_tmpl, err := c.compile_template("forums.html","templates/","ForumsPage",forums_page,varList) forums_tmpl, err := c.compileTemplate("forums.html", "templates/", "ForumsPage", forums_page, varList)
if err != nil { if err != nil {
return err return err
} }
var topicsList []*TopicsRow var topicsList []*TopicsRow
topicsList = append(topicsList,&TopicsRow{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",user3.ID,1,"","127.0.0.1",0,1,"classname","",&user2,"",0,&user3,"General","/forum/general.2"}) topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, "Date", "Date", user3.ID, 1, "", "127.0.0.1", 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
topics_page := TopicsPage{"Topic List",user,headerVars,topicsList,extData} topics_page := TopicsPage{"Topic List", user, headerVars, topicsList}
topics_tmpl, err := c.compile_template("topics.html","templates/","TopicsPage",topics_page,varList) topics_tmpl, err := c.compileTemplate("topics.html", "templates/", "TopicsPage", topics_page, varList)
if err != nil { if err != nil {
return err return err
} }
//var topicList []TopicUser //var topicList []TopicUser
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false}) //topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
forum_item := Forum{1,"general","General Forum","Where the general stuff happens",true,"all",0,"",0,"","",0,"",0,""} forum_item := Forum{1, "general", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0, "", "", 0, "", 0, ""}
forum_page := ForumPage{"General Forum",user,headerVars,topicsList,forum_item,1,1,extData} forum_page := ForumPage{"General Forum", user, headerVars, topicsList, forum_item, 1, 1}
forum_tmpl, err := c.compile_template("forum.html","templates/","ForumPage",forum_page,varList) forum_tmpl, err := c.compileTemplate("forum.html", "templates/", "ForumPage", forum_page, varList)
if err != nil { if err != nil {
return err return err
} }
log.Print("Writing the templates") log.Print("Writing the templates")
go write_template("topic", topic_id_tmpl) go writeTemplate("topic", topic_id_tmpl)
go write_template("topic_alt", topic_id_alt_tmpl) go writeTemplate("topic_alt", topic_id_alt_tmpl)
go write_template("profile", profile_tmpl) go writeTemplate("profile", profile_tmpl)
go write_template("forums", forums_tmpl) go writeTemplate("forums", forums_tmpl)
go write_template("topics", topics_tmpl) go writeTemplate("topics", topics_tmpl)
go write_template("forum", forum_tmpl) go writeTemplate("forum", forum_tmpl)
go func() { go func() {
err := write_file("./template_list.go","package main\n\n" + c.FragOut) err := writeFile("./template_list.go", "package main\n\n// nolint\n"+c.FragOut)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -167,66 +167,74 @@ func compile_templates() error {
return nil return nil
} }
func write_template(name string, content string) { func writeTemplate(name string, content string) {
err := write_file("./template_" + name + ".go", content) err := writeFile("./template_"+name+".go", content)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
func init_templates() { func initTemplates() {
if dev.DebugMode { if dev.DebugMode {
log.Print("Initialising the template system") log.Print("Initialising the template system")
} }
compile_templates() compileTemplates()
// TO-DO: Add support for 64-bit integers // TO-DO: Add support for 64-bit integers
// TO-DO: Add support for floats // TO-DO: Add support for floats
fmap := make(map[string]interface{}) fmap := make(map[string]interface{})
fmap["add"] = func(left interface{}, right interface{})interface{} { fmap["add"] = func(left interface{}, right interface{}) interface{} {
var left_int int var left_int int
var right_int int var right_int int
switch left := left.(type) { switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int) case uint, uint8, uint16, int, int32:
left_int = left.(int)
} }
switch right := right.(type) { switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int) case uint, uint8, uint16, int, int32:
right_int = right.(int)
} }
return left_int + right_int return left_int + right_int
} }
fmap["subtract"] = func(left interface{}, right interface{})interface{} { fmap["subtract"] = func(left interface{}, right interface{}) interface{} {
var left_int int var left_int int
var right_int int var right_int int
switch left := left.(type) { switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int) case uint, uint8, uint16, int, int32:
left_int = left.(int)
} }
switch right := right.(type) { switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int) case uint, uint8, uint16, int, int32:
right_int = right.(int)
} }
return left_int - right_int return left_int - right_int
} }
fmap["multiply"] = func(left interface{}, right interface{})interface{} { fmap["multiply"] = func(left interface{}, right interface{}) interface{} {
var left_int int var left_int int
var right_int int var right_int int
switch left := left.(type) { switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int) case uint, uint8, uint16, int, int32:
left_int = left.(int)
} }
switch right := right.(type) { switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int) case uint, uint8, uint16, int, int32:
right_int = right.(int)
} }
return left_int * right_int return left_int * right_int
} }
fmap["divide"] = func(left interface{}, right interface{})interface{} { fmap["divide"] = func(left interface{}, right interface{}) interface{} {
var left_int int var left_int int
var right_int int var right_int int
switch left := left.(type) { switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int) case uint, uint8, uint16, int, int32:
left_int = left.(int)
} }
switch right := right.(type) { switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int) case uint, uint8, uint16, int, int32:
right_int = right.(int)
} }
if left_int == 0 || right_int == 0 { if left_int == 0 || right_int == 0 {
return 0 return 0

File diff suppressed because it is too large Load Diff

View File

@ -6,63 +6,68 @@ package main
import "net/http" import "net/http"
import "strconv" import "strconv"
// nolint
func init() { func init() {
template_profile_handle = template_profile template_profile_handle = template_profile
//o_template_profile_handle = template_profile //o_template_profile_handle = template_profile
ctemplates = append(ctemplates,"profile") ctemplates = append(ctemplates,"profile")
tmpl_ptr_map["profile"] = &template_profile_handle tmplPtrMap["profile"] = &template_profile_handle
tmpl_ptr_map["o_profile"] = template_profile tmplPtrMap["o_profile"] = template_profile
} }
// nolint
func template_profile(tmpl_profile_vars ProfilePage, w http.ResponseWriter) { func template_profile(tmpl_profile_vars ProfilePage, w http.ResponseWriter) {
w.Write(header_0) w.Write(header_0)
w.Write([]byte(tmpl_profile_vars.Title)) w.Write([]byte(tmpl_profile_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_profile_vars.Header.ThemeName))
w.Write(header_2)
if len(tmpl_profile_vars.Header.Stylesheets) != 0 { if len(tmpl_profile_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_profile_vars.Header.Stylesheets { for _, item := range tmpl_profile_vars.Header.Stylesheets {
w.Write(header_2)
w.Write([]byte(item))
w.Write(header_3) w.Write(header_3)
} w.Write([]byte(item))
}
w.Write(header_4) w.Write(header_4)
}
}
w.Write(header_5)
if len(tmpl_profile_vars.Header.Scripts) != 0 { if len(tmpl_profile_vars.Header.Scripts) != 0 {
for _, item := range tmpl_profile_vars.Header.Scripts { for _, item := range tmpl_profile_vars.Header.Scripts {
w.Write(header_5)
w.Write([]byte(item))
w.Write(header_6) w.Write(header_6)
} w.Write([]byte(item))
}
w.Write(header_7) w.Write(header_7)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session)) }
}
w.Write(header_8) w.Write(header_8)
if !tmpl_profile_vars.CurrentUser.Is_Super_Mod {
w.Write(header_9)
}
w.Write(header_10)
w.Write(menu_0)
w.Write([]byte(tmpl_profile_vars.Header.Site.Name))
w.Write(menu_1)
if tmpl_profile_vars.CurrentUser.Loggedin {
w.Write(menu_2)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Link))
w.Write(menu_3)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session)) w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(menu_4) w.Write(header_9)
} else { if !tmpl_profile_vars.CurrentUser.IsSuperMod {
w.Write(menu_5) w.Write(header_10)
} }
w.Write(menu_6)
w.Write(header_11) w.Write(header_11)
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" { w.Write(menu_0)
w.Write(header_12) w.Write(menu_1)
w.Write([]byte(tmpl_profile_vars.Header.Site.Name))
w.Write(menu_2)
if tmpl_profile_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
} }
w.Write(menu_7)
w.Write(header_12)
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_13) w.Write(header_13)
}
w.Write(header_14)
if len(tmpl_profile_vars.Header.NoticeList) != 0 { if len(tmpl_profile_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_profile_vars.Header.NoticeList { for _, item := range tmpl_profile_vars.Header.NoticeList {
w.Write(header_14)
w.Write([]byte(item))
w.Write(header_15) w.Write(header_15)
w.Write([]byte(item))
w.Write(header_16)
} }
} }
w.Write(profile_0) w.Write(profile_0)
@ -76,9 +81,9 @@ w.Write([]byte(tmpl_profile_vars.ProfileOwner.Tag))
w.Write(profile_4) w.Write(profile_4)
} }
w.Write(profile_5) w.Write(profile_5)
if tmpl_profile_vars.CurrentUser.Is_Super_Mod && !tmpl_profile_vars.ProfileOwner.Is_Super_Mod { if tmpl_profile_vars.CurrentUser.IsSuperMod && !tmpl_profile_vars.ProfileOwner.IsSuperMod {
w.Write(profile_6) w.Write(profile_6)
if tmpl_profile_vars.ProfileOwner.Is_Banned { if tmpl_profile_vars.ProfileOwner.IsBanned {
w.Write(profile_7) w.Write(profile_7)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID))) w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_8) w.Write(profile_8)
@ -124,7 +129,7 @@ w.Write([]byte(item.UserLink))
w.Write(profile_28) w.Write(profile_28)
w.Write([]byte(item.CreatedByName)) w.Write([]byte(item.CreatedByName))
w.Write(profile_29) w.Write(profile_29)
if tmpl_profile_vars.CurrentUser.Is_Mod { if tmpl_profile_vars.CurrentUser.IsMod {
w.Write(profile_30) w.Write(profile_30)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_31) w.Write(profile_31)
@ -145,7 +150,7 @@ w.Write(profile_38)
} }
} }
w.Write(profile_39) w.Write(profile_39)
if !tmpl_profile_vars.CurrentUser.Is_Banned { if !tmpl_profile_vars.CurrentUser.IsBanned {
w.Write(profile_40) w.Write(profile_40)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID))) w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_41) w.Write(profile_41)

View File

@ -6,63 +6,68 @@ package main
import "net/http" import "net/http"
import "strconv" import "strconv"
// nolint
func init() { func init() {
template_topic_handle = template_topic template_topic_handle = template_topic
//o_template_topic_handle = template_topic //o_template_topic_handle = template_topic
ctemplates = append(ctemplates,"topic") ctemplates = append(ctemplates,"topic")
tmpl_ptr_map["topic"] = &template_topic_handle tmplPtrMap["topic"] = &template_topic_handle
tmpl_ptr_map["o_topic"] = template_topic tmplPtrMap["o_topic"] = template_topic
} }
// nolint
func template_topic(tmpl_topic_vars TopicPage, w http.ResponseWriter) { func template_topic(tmpl_topic_vars TopicPage, w http.ResponseWriter) {
w.Write(header_0) w.Write(header_0)
w.Write([]byte(tmpl_topic_vars.Title)) w.Write([]byte(tmpl_topic_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_topic_vars.Header.ThemeName))
w.Write(header_2)
if len(tmpl_topic_vars.Header.Stylesheets) != 0 { if len(tmpl_topic_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_topic_vars.Header.Stylesheets { for _, item := range tmpl_topic_vars.Header.Stylesheets {
w.Write(header_2)
w.Write([]byte(item))
w.Write(header_3) w.Write(header_3)
} w.Write([]byte(item))
}
w.Write(header_4) w.Write(header_4)
}
}
w.Write(header_5)
if len(tmpl_topic_vars.Header.Scripts) != 0 { if len(tmpl_topic_vars.Header.Scripts) != 0 {
for _, item := range tmpl_topic_vars.Header.Scripts { for _, item := range tmpl_topic_vars.Header.Scripts {
w.Write(header_5)
w.Write([]byte(item))
w.Write(header_6) w.Write(header_6)
} w.Write([]byte(item))
}
w.Write(header_7) w.Write(header_7)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) }
}
w.Write(header_8) w.Write(header_8)
if !tmpl_topic_vars.CurrentUser.Is_Super_Mod {
w.Write(header_9)
}
w.Write(header_10)
w.Write(menu_0)
w.Write([]byte(tmpl_topic_vars.Header.Site.Name))
w.Write(menu_1)
if tmpl_topic_vars.CurrentUser.Loggedin {
w.Write(menu_2)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Link))
w.Write(menu_3)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(menu_4) w.Write(header_9)
} else { if !tmpl_topic_vars.CurrentUser.IsSuperMod {
w.Write(menu_5) w.Write(header_10)
} }
w.Write(menu_6)
w.Write(header_11) w.Write(header_11)
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" { w.Write(menu_0)
w.Write(header_12) w.Write(menu_1)
w.Write([]byte(tmpl_topic_vars.Header.Site.Name))
w.Write(menu_2)
if tmpl_topic_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
} }
w.Write(menu_7)
w.Write(header_12)
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_13) w.Write(header_13)
}
w.Write(header_14)
if len(tmpl_topic_vars.Header.NoticeList) != 0 { if len(tmpl_topic_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_topic_vars.Header.NoticeList { for _, item := range tmpl_topic_vars.Header.NoticeList {
w.Write(header_14)
w.Write([]byte(item))
w.Write(header_15) w.Write(header_15)
w.Write([]byte(item))
w.Write(header_16)
} }
} }
w.Write(topic_0) w.Write(topic_0)
@ -90,14 +95,14 @@ w.Write(topic_10)
if tmpl_topic_vars.Topic.Sticky { if tmpl_topic_vars.Topic.Sticky {
w.Write(topic_11) w.Write(topic_11)
} else { } else {
if tmpl_topic_vars.Topic.Is_Closed { if tmpl_topic_vars.Topic.IsClosed {
w.Write(topic_12) w.Write(topic_12)
} }
} }
w.Write(topic_13) w.Write(topic_13)
w.Write([]byte(tmpl_topic_vars.Topic.Title)) w.Write([]byte(tmpl_topic_vars.Topic.Title))
w.Write(topic_14) w.Write(topic_14)
if tmpl_topic_vars.Topic.Is_Closed { if tmpl_topic_vars.Topic.IsClosed {
w.Write(topic_15) w.Write(topic_15)
} }
if tmpl_topic_vars.CurrentUser.Perms.EditTopic { if tmpl_topic_vars.CurrentUser.Perms.EditTopic {
@ -160,103 +165,113 @@ w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_42) w.Write(topic_42)
} }
} }
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_43) w.Write(topic_43)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
w.Write(topic_44) w.Write(topic_44)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) }
w.Write(topic_45) w.Write(topic_45)
if tmpl_topic_vars.Topic.LikeCount > 0 { w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_46) w.Write(topic_46)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount))) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_47) w.Write(topic_47)
if tmpl_topic_vars.Topic.LikeCount > 0 {
w.Write(topic_48)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount)))
w.Write(topic_49)
} }
if tmpl_topic_vars.Topic.Tag != "" { if tmpl_topic_vars.Topic.Tag != "" {
w.Write(topic_48)
w.Write([]byte(tmpl_topic_vars.Topic.Tag))
w.Write(topic_49)
} else {
w.Write(topic_50) w.Write(topic_50)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level))) w.Write([]byte(tmpl_topic_vars.Topic.Tag))
w.Write(topic_51) w.Write(topic_51)
} } else {
w.Write(topic_52) w.Write(topic_52)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level)))
w.Write(topic_53)
}
w.Write(topic_54)
if len(tmpl_topic_vars.ItemList) != 0 { if len(tmpl_topic_vars.ItemList) != 0 {
for _, item := range tmpl_topic_vars.ItemList { for _, item := range tmpl_topic_vars.ItemList {
if item.ActionType != "" { if item.ActionType != "" {
w.Write(topic_53)
w.Write([]byte(item.ActionIcon))
w.Write(topic_54)
w.Write([]byte(item.ActionType))
w.Write(topic_55) w.Write(topic_55)
} else { w.Write([]byte(item.ActionIcon))
w.Write(topic_56) w.Write(topic_56)
w.Write([]byte(item.ClassName)) w.Write([]byte(item.ActionType))
w.Write(topic_57) w.Write(topic_57)
if item.Avatar != "" { } else {
w.Write(topic_58) w.Write(topic_58)
w.Write([]byte(item.Avatar)) w.Write([]byte(item.ClassName))
w.Write(topic_59) w.Write(topic_59)
if item.ContentLines <= 5 { if item.Avatar != "" {
w.Write(topic_60) w.Write(topic_60)
} w.Write([]byte(item.Avatar))
w.Write(topic_61) w.Write(topic_61)
} if item.ContentLines <= 5 {
w.Write(topic_62) w.Write(topic_62)
w.Write([]byte(item.ContentHtml)) }
w.Write(topic_63) w.Write(topic_63)
w.Write([]byte(item.UserLink)) }
w.Write(topic_64) w.Write(topic_64)
w.Write([]byte(item.CreatedByName)) w.Write([]byte(item.ContentHtml))
w.Write(topic_65) w.Write(topic_65)
if tmpl_topic_vars.CurrentUser.Perms.LikeItem { w.Write([]byte(item.UserLink))
w.Write(topic_66) w.Write(topic_66)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(item.CreatedByName))
w.Write(topic_67) w.Write(topic_67)
if item.Liked { if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_68) w.Write(topic_68)
}
w.Write(topic_69)
}
if tmpl_topic_vars.CurrentUser.Perms.EditReply {
w.Write(topic_70)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_69)
if item.Liked {
w.Write(topic_70)
}
w.Write(topic_71) w.Write(topic_71)
} }
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply { if tmpl_topic_vars.CurrentUser.Perms.EditReply {
w.Write(topic_72) w.Write(topic_72)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_73) w.Write(topic_73)
} }
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply {
w.Write(topic_74) w.Write(topic_74)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_75) w.Write(topic_75)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_76)
if item.LikeCount > 0 {
w.Write(topic_77)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topic_78)
} }
if item.Tag != "" { if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_76)
w.Write([]byte(item.IPAddress))
w.Write(topic_77)
}
w.Write(topic_78)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_79) w.Write(topic_79)
w.Write([]byte(item.Tag)) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_80) w.Write(topic_80)
} else { if item.LikeCount > 0 {
w.Write(topic_81) w.Write(topic_81)
w.Write([]byte(strconv.Itoa(item.Level))) w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topic_82) w.Write(topic_82)
} }
if item.Tag != "" {
w.Write(topic_83) w.Write(topic_83)
} w.Write([]byte(item.Tag))
}
}
w.Write(topic_84) w.Write(topic_84)
if tmpl_topic_vars.CurrentUser.Perms.CreateReply { } else {
w.Write(topic_85) w.Write(topic_85)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_86) w.Write(topic_86)
} }
w.Write(topic_87) w.Write(topic_87)
}
}
}
w.Write(topic_88)
if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_89)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_90)
}
w.Write(topic_91)
w.Write(footer_0) w.Write(footer_0)
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" { if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1) w.Write(footer_1)

View File

@ -6,63 +6,68 @@ package main
import "net/http" import "net/http"
import "strconv" import "strconv"
// nolint
func init() { func init() {
template_topic_alt_handle = template_topic_alt template_topic_alt_handle = template_topic_alt
//o_template_topic_alt_handle = template_topic_alt //o_template_topic_alt_handle = template_topic_alt
ctemplates = append(ctemplates,"topic_alt") ctemplates = append(ctemplates,"topic_alt")
tmpl_ptr_map["topic_alt"] = &template_topic_alt_handle tmplPtrMap["topic_alt"] = &template_topic_alt_handle
tmpl_ptr_map["o_topic_alt"] = template_topic_alt tmplPtrMap["o_topic_alt"] = template_topic_alt
} }
// nolint
func template_topic_alt(tmpl_topic_alt_vars TopicPage, w http.ResponseWriter) { func template_topic_alt(tmpl_topic_alt_vars TopicPage, w http.ResponseWriter) {
w.Write(header_0) w.Write(header_0)
w.Write([]byte(tmpl_topic_alt_vars.Title)) w.Write([]byte(tmpl_topic_alt_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_topic_alt_vars.Header.ThemeName))
w.Write(header_2)
if len(tmpl_topic_alt_vars.Header.Stylesheets) != 0 { if len(tmpl_topic_alt_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.Stylesheets { for _, item := range tmpl_topic_alt_vars.Header.Stylesheets {
w.Write(header_2)
w.Write([]byte(item))
w.Write(header_3) w.Write(header_3)
} w.Write([]byte(item))
}
w.Write(header_4) w.Write(header_4)
}
}
w.Write(header_5)
if len(tmpl_topic_alt_vars.Header.Scripts) != 0 { if len(tmpl_topic_alt_vars.Header.Scripts) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.Scripts { for _, item := range tmpl_topic_alt_vars.Header.Scripts {
w.Write(header_5)
w.Write([]byte(item))
w.Write(header_6) w.Write(header_6)
} w.Write([]byte(item))
}
w.Write(header_7) w.Write(header_7)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) }
}
w.Write(header_8) w.Write(header_8)
if !tmpl_topic_alt_vars.CurrentUser.Is_Super_Mod {
w.Write(header_9)
}
w.Write(header_10)
w.Write(menu_0)
w.Write([]byte(tmpl_topic_alt_vars.Header.Site.Name))
w.Write(menu_1)
if tmpl_topic_alt_vars.CurrentUser.Loggedin {
w.Write(menu_2)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link))
w.Write(menu_3)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(menu_4) w.Write(header_9)
} else { if !tmpl_topic_alt_vars.CurrentUser.IsSuperMod {
w.Write(menu_5) w.Write(header_10)
} }
w.Write(menu_6)
w.Write(header_11) w.Write(header_11)
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" { w.Write(menu_0)
w.Write(header_12) w.Write(menu_1)
w.Write([]byte(tmpl_topic_alt_vars.Header.Site.Name))
w.Write(menu_2)
if tmpl_topic_alt_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
} }
w.Write(menu_7)
w.Write(header_12)
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_13) w.Write(header_13)
}
w.Write(header_14)
if len(tmpl_topic_alt_vars.Header.NoticeList) != 0 { if len(tmpl_topic_alt_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.NoticeList { for _, item := range tmpl_topic_alt_vars.Header.NoticeList {
w.Write(header_14)
w.Write([]byte(item))
w.Write(header_15) w.Write(header_15)
w.Write([]byte(item))
w.Write(header_16)
} }
} }
if tmpl_topic_alt_vars.Page > 1 { if tmpl_topic_alt_vars.Page > 1 {
@ -89,14 +94,14 @@ w.Write(topic_alt_9)
if tmpl_topic_alt_vars.Topic.Sticky { if tmpl_topic_alt_vars.Topic.Sticky {
w.Write(topic_alt_10) w.Write(topic_alt_10)
} else { } else {
if tmpl_topic_alt_vars.Topic.Is_Closed { if tmpl_topic_alt_vars.Topic.IsClosed {
w.Write(topic_alt_11) w.Write(topic_alt_11)
} }
} }
w.Write(topic_alt_12) w.Write(topic_alt_12)
w.Write([]byte(tmpl_topic_alt_vars.Topic.Title)) w.Write([]byte(tmpl_topic_alt_vars.Topic.Title))
w.Write(topic_alt_13) w.Write(topic_alt_13)
if tmpl_topic_alt_vars.Topic.Is_Closed { if tmpl_topic_alt_vars.Topic.IsClosed {
w.Write(topic_alt_14) w.Write(topic_alt_14)
} }
if tmpl_topic_alt_vars.CurrentUser.Perms.EditTopic { if tmpl_topic_alt_vars.CurrentUser.Perms.EditTopic {
@ -163,7 +168,7 @@ w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_42) w.Write(topic_alt_42)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_43) w.Write(topic_alt_43)
w.Write([]byte(tmpl_topic_alt_vars.Topic.IpAddress)) w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress))
w.Write(topic_alt_44) w.Write(topic_alt_44)
} }
} }
@ -236,7 +241,7 @@ w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_76) w.Write(topic_alt_76)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_77) w.Write(topic_alt_77)
w.Write([]byte(item.IpAddress)) w.Write([]byte(item.IPAddress))
w.Write(topic_alt_78) w.Write(topic_alt_78)
} }
} }

View File

@ -3,66 +3,71 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "net/http"
import "strconv" import "strconv"
import "net/http"
// nolint
func init() { func init() {
template_topics_handle = template_topics template_topics_handle = template_topics
//o_template_topics_handle = template_topics //o_template_topics_handle = template_topics
ctemplates = append(ctemplates,"topics") ctemplates = append(ctemplates,"topics")
tmpl_ptr_map["topics"] = &template_topics_handle tmplPtrMap["topics"] = &template_topics_handle
tmpl_ptr_map["o_topics"] = template_topics tmplPtrMap["o_topics"] = template_topics
} }
// nolint
func template_topics(tmpl_topics_vars TopicsPage, w http.ResponseWriter) { func template_topics(tmpl_topics_vars TopicsPage, w http.ResponseWriter) {
w.Write(header_0) w.Write(header_0)
w.Write([]byte(tmpl_topics_vars.Title)) w.Write([]byte(tmpl_topics_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_topics_vars.Header.ThemeName))
w.Write(header_2)
if len(tmpl_topics_vars.Header.Stylesheets) != 0 { if len(tmpl_topics_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_topics_vars.Header.Stylesheets { for _, item := range tmpl_topics_vars.Header.Stylesheets {
w.Write(header_2)
w.Write([]byte(item))
w.Write(header_3) w.Write(header_3)
} w.Write([]byte(item))
}
w.Write(header_4) w.Write(header_4)
}
}
w.Write(header_5)
if len(tmpl_topics_vars.Header.Scripts) != 0 { if len(tmpl_topics_vars.Header.Scripts) != 0 {
for _, item := range tmpl_topics_vars.Header.Scripts { for _, item := range tmpl_topics_vars.Header.Scripts {
w.Write(header_5)
w.Write([]byte(item))
w.Write(header_6) w.Write(header_6)
} w.Write([]byte(item))
}
w.Write(header_7) w.Write(header_7)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session)) }
}
w.Write(header_8) w.Write(header_8)
if !tmpl_topics_vars.CurrentUser.Is_Super_Mod {
w.Write(header_9)
}
w.Write(header_10)
w.Write(menu_0)
w.Write([]byte(tmpl_topics_vars.Header.Site.Name))
w.Write(menu_1)
if tmpl_topics_vars.CurrentUser.Loggedin {
w.Write(menu_2)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Link))
w.Write(menu_3)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session)) w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
w.Write(menu_4) w.Write(header_9)
} else { if !tmpl_topics_vars.CurrentUser.IsSuperMod {
w.Write(menu_5) w.Write(header_10)
} }
w.Write(menu_6)
w.Write(header_11) w.Write(header_11)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" { w.Write(menu_0)
w.Write(header_12) w.Write(menu_1)
w.Write([]byte(tmpl_topics_vars.Header.Site.Name))
w.Write(menu_2)
if tmpl_topics_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
} }
w.Write(menu_7)
w.Write(header_12)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_13) w.Write(header_13)
}
w.Write(header_14)
if len(tmpl_topics_vars.Header.NoticeList) != 0 { if len(tmpl_topics_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_topics_vars.Header.NoticeList { for _, item := range tmpl_topics_vars.Header.NoticeList {
w.Write(header_14)
w.Write([]byte(item))
w.Write(header_15) w.Write(header_15)
w.Write([]byte(item))
w.Write(header_16)
} }
} }
w.Write(topics_0) w.Write(topics_0)
@ -72,7 +77,7 @@ w.Write(topics_1)
if item.Sticky { if item.Sticky {
w.Write(topics_2) w.Write(topics_2)
} else { } else {
if item.Is_Closed { if item.IsClosed {
w.Write(topics_3) w.Write(topics_3)
} }
} }
@ -103,7 +108,7 @@ w.Write([]byte(item.Creator.Link))
w.Write(topics_16) w.Write(topics_16)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(topics_17) w.Write(topics_17)
if item.Is_Closed { if item.IsClosed {
w.Write(topics_18) w.Write(topics_18)
} }
if item.Sticky { if item.Sticky {

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@
{{end}} {{end}}
</div> </div>
<div id="forum_topic_list" class="rowblock topic_list"> <div id="forum_topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}"> {{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br /> <span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span> <span class="lastReplyAt">{{.LastReplyAt}}</span>
@ -25,8 +25,8 @@
<span> <span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> <a class="rowtopic" href="{{.Link}}">{{.Title}}</a>
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a> <br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{/** TO-DO: Avoid the double '|' when both .Is_Closed and .Sticky are set to true. We could probably do this with CSS **/}} {{/** TO-DO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}} {{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}} {{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span> </span>
</div> </div>

View File

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>{{.Title}}</title> <title>{{.Title}}</title>
<link href="/static/main.css" rel="stylesheet" type="text/css"> <link href="/static/{{.Header.ThemeName}}/main.css" rel="stylesheet" type="text/css">
{{range .Header.Stylesheets}} {{range .Header.Stylesheets}}
<link href="/static/{{.}}" rel="stylesheet" type="text/css"> <link href="/static/{{.}}" rel="stylesheet" type="text/css">
{{end}} {{end}}
@ -15,7 +15,7 @@
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" /> <meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
</head> </head>
<body> <body>
<style>{{if not .CurrentUser.Is_Super_Mod}}.supermod_only { display: none !important; }{{end}}</style> <style>{{if not .CurrentUser.IsSuperMod}}.supermod_only { display: none !important; }{{end}}</style>
<div class="container"> <div class="container">
{{template "menu.html" .}} {{template "menu.html" .}}
<div id="back"><div id="main" {{if .Header.Widgets.RightSidebar}}class="shrink_main"{{end}}> <div id="back"><div id="main" {{if .Header.Widgets.RightSidebar}}class="shrink_main"{{end}}>

18
templates/ip-search.html Normal file
View File

@ -0,0 +1,18 @@
{{template "header.html" . }}
<main>
<div class="rowblock opthead">
<div class="rowitem"><a>IP Search</a></div>
</div>
<div class="rowblock">
<div class="rowitem passive">Searching for {{.IP}}</div>
</div>
<div class="rowblock rowlist bgavatars">
{{range .ItemList}}<div class="rowitem" style="{{if .Avatar}}background-image: url('{{.Avatar}}');{{end}}">
<a href="{{.Link}}">{{.Name}}</a>
</div>
{{else}}<div class="rowitem passive">No users found.</div>{{end}}
</div>
</main>
{{template "footer.html" . }}

View File

@ -1,7 +1,7 @@
<nav class="nav"> <nav class="nav">
<div class="move_left"> <div class="move_left">
<div class="move_right"> <div class="move_right">
<ul> <ul>{{/** Add a menu manager **/}}
<li class="menu_left menu_overview"><a href="/" rel="home">{{.Header.Site.Name}}</a></li> <li class="menu_left menu_overview"><a href="/" rel="home">{{.Header.Site.Name}}</a></li>
<li class="menu_left menu_forums"><a href="/forums/">Forums</a></li> <li class="menu_left menu_forums"><a href="/forums/">Forums</a></li>
<li class="menu_left menu_topics"><a href="/">Topics</a></li> <li class="menu_left menu_topics"><a href="/">Topics</a></li>

View File

@ -34,7 +34,7 @@
<div class="rowitem passive"> <div class="rowitem passive">
<a href="/panel/logs/mod/">Logs</a> <a href="/panel/logs/mod/">Logs</a>
</div> </div>
{{if .CurrentUser.Is_Admin}}<div class="rowitem passive"> {{if .CurrentUser.IsAdmin}}<div class="rowitem passive">
<a href="/panel/debug/">Debug</a> <a href="/panel/debug/">Debug</a>
</div>{{end}} </div>{{end}}
</div> </div>

View File

@ -10,10 +10,10 @@
<div class="rowitem editable_parent" style="{{if .Avatar}}background-image: url('{{.Avatar}}');{{end}}"> <div class="rowitem editable_parent" style="{{if .Avatar}}background-image: url('{{.Avatar}}');{{end}}">
<a {{if $.CurrentUser.Perms.EditUser}}href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}} "{{end}}class="editable_block">{{.Name}}</a> <a {{if $.CurrentUser.Perms.EditUser}}href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}} "{{end}}class="editable_block">{{.Name}}</a>
<a href="/user/{{.ID}}" class="tag-mini">Profile</a> <a href="/user/{{.ID}}" class="tag-mini">Profile</a>
{{if (.Tag) and (.Is_Super_Mod)}}<span style="float: right;"><span class="panel_tag" style="margin-left 4px;">{{.Tag}}</span></span>{{end}} {{if (.Tag) and (.IsSuperMod)}}<span style="float: right;"><span class="panel_tag" style="margin-left 4px;">{{.Tag}}</span></span>{{end}}
<span class="panel_floater"> <span class="panel_floater">
{{if .Is_Banned}}<a href="/users/unban/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button ban_button">Unban</a>{{else if not .Is_Super_Mod}}<a href="/user/{{.ID}}#ban_user" class="panel_tag panel_right_button ban_button">Ban</a>{{end}} {{if .IsBanned}}<a href="/users/unban/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button ban_button">Unban</a>{{else if not .IsSuperMod}}<a href="/user/{{.ID}}#ban_user" class="panel_tag panel_right_button ban_button">Ban</a>{{end}}
{{if not .Active}}<a href="/users/activate/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button">Activate</a>{{end}} {{if not .Active}}<a href="/users/activate/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button">Activate</a>{{end}}
</span> </span>
</div> </div>

View File

@ -14,8 +14,8 @@
<div class="rowitem passive"> <div class="rowitem passive">
<a class="profile_menu_item">Add Friend</a> <a class="profile_menu_item">Add Friend</a>
</div> </div>
{{if (.CurrentUser.Is_Super_Mod) and not (.ProfileOwner.Is_Super_Mod) }}<div class="rowitem passive"> {{if (.CurrentUser.IsSuperMod) and not (.ProfileOwner.IsSuperMod) }}<div class="rowitem passive">
{{if .ProfileOwner.Is_Banned }}<a href="/users/unban/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" class="profile_menu_item">Unban</a> {{if .ProfileOwner.IsBanned }}<a href="/users/unban/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" class="profile_menu_item">Unban</a>
{{else}}<a href="#ban_user" class="profile_menu_item">Ban</a>{{end}} {{else}}<a href="#ban_user" class="profile_menu_item">Ban</a>{{end}}
</div>{{end}} </div>{{end}}
<div class="rowitem passive"> <div class="rowitem passive">
@ -73,7 +73,7 @@
<span class="controls"> <span class="controls">
<a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>&nbsp;&nbsp; <a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>&nbsp;&nbsp;
{{if $.CurrentUser.Is_Mod}}<a href="/profile/reply/edit/submit/{{.ID}}" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a> {{if $.CurrentUser.IsMod}}<a href="/profile/reply/edit/submit/{{.ID}}" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/{{.ID}}" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>{{end}} <a href="/profile/reply/delete/submit/{{.ID}}" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>{{end}}
@ -84,7 +84,7 @@
</div> </div>
{{end}}</div> {{end}}</div>
{{if not .CurrentUser.Is_Banned}} {{if not .CurrentUser.IsBanned}}
<form id="profile_comments_form" class="hash_hide" action="/profile/reply/create/" method="post"> <form id="profile_comments_form" class="hash_hide" action="/profile/reply/create/" method="post">
<input name="uid" value='{{.ProfileOwner.ID}}' type="hidden" /> <input name="uid" value='{{.ProfileOwner.ID}}' type="hidden" />
<div class="colstack_item topic_reply_form" style="border-top: none;"> <div class="colstack_item topic_reply_form" style="border-top: none;">

View File

@ -19,7 +19,7 @@
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>
<main id="forum_topic_list" class="rowblock topic_list" style="position: relative;z-index: 50;"> <main id="forum_topic_list" class="rowblock topic_list" style="position: relative;z-index: 50;">
{{range .ItemList}}<div class="rowitem topic_left passive datarow" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}{{if .Sticky}}background-color: #FFFFCC;{{else if .Is_Closed}}background-color: #eaeaea;{{end}}"> {{range .ItemList}}<div class="rowitem topic_left passive datarow" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}{{if .Sticky}}background-color: #FFFFCC;{{else if .IsClosed}}background-color: #eaeaea;{{end}}">
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br /> <span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span> <span class="lastReplyAt">{{.LastReplyAt}}</span>
@ -27,7 +27,7 @@
<span> <span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> <a class="rowtopic" href="{{.Link}}">{{.Title}}</a>
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a> <br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E{{end}}</span> {{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E{{end}}</span>
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}"> <div class="rowitem topic_right passive datarow" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">

View File

@ -11,9 +11,9 @@
<main> <main>
<div class="rowblock rowhead topic_block"> <div class="rowblock rowhead topic_block">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.Is_Closed}} topic_closed_head{{end}}"> <div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}">
<h1 class='topic_name hide_on_edit'>{{.Topic.Title}}</h1> <h1 class='topic_name hide_on_edit'>{{.Topic.Title}}</h1>
{{if .Topic.Is_Closed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}} {{if .Topic.IsClosed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
{{if .CurrentUser.Perms.EditTopic}} {{if .CurrentUser.Perms.EditTopic}}
<input form='edit_topic_form' class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" /> <input form='edit_topic_form' class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" />
{{if .CurrentUser.Perms.CloseTopic}}<select form='edit_topic_form' name="topic_status" class='show_on_edit topic_status_input' style='float: right;'> {{if .CurrentUser.Perms.CloseTopic}}<select form='edit_topic_form' name="topic_status" class='show_on_edit topic_status_input' style='float: right;'>
@ -41,6 +41,7 @@
{{if .CurrentUser.Perms.DeleteTopic}}<a href='/topic/delete/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>{{end}} {{if .CurrentUser.Perms.DeleteTopic}}<a href='/topic/delete/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>{{end}}
{{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}<a class="mod_button" href='/topic/unstick/submit/{{.Topic.ID}}' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>{{end}}{{end}} {{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}<a class="mod_button" href='/topic/unstick/submit/{{.Topic.ID}}' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>{{end}}{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a class="mod_button" href='/users/ips/{{.Topic.IPAddress}}' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>{{end}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a> <a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
{{if .Topic.LikeCount}}<a class="username hide_on_micro like_count">{{.Topic.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>{{end}} {{if .Topic.LikeCount}}<a class="username hide_on_micro like_count">{{.Topic.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>{{end}}
@ -67,6 +68,7 @@
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>{{end}} {{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>{{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>{{end}} {{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>{{end}}
{{if $.CurrentUser.Perms.ViewIPs}}<a class="mod_button" href='/users/ips/{{.IPAddress}}' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>{{end}}
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a> <a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
{{if .LikeCount}}<a class="username hide_on_micro like_count">{{.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>{{end}} {{if .LikeCount}}<a class="username hide_on_micro like_count">{{.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>{{end}}

View File

@ -7,9 +7,9 @@
<div class="rowblock rowhead topic_block"> <div class="rowblock rowhead topic_block">
<form action='/topic/edit/submit/{{.Topic.ID}}' method="post"> <form action='/topic/edit/submit/{{.Topic.ID}}' method="post">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.Is_Closed}} topic_closed_head{{end}}"> <div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}">
<h1 class='topic_name hide_on_edit'>{{.Topic.Title}}</h1> <h1 class='topic_name hide_on_edit'>{{.Topic.Title}}</h1>
{{if .Topic.Is_Closed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}} {{if .Topic.IsClosed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
{{if .CurrentUser.Perms.EditTopic}} {{if .CurrentUser.Perms.EditTopic}}
<input class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" /> <input class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" />
{{if .CurrentUser.Perms.CloseTopic}}<select name="topic_status" class='show_on_edit topic_status_input' style='float: right;'> {{if .CurrentUser.Perms.CloseTopic}}<select name="topic_status" class='show_on_edit topic_status_input' style='float: right;'>
@ -42,7 +42,7 @@
{{if .CurrentUser.Perms.PinTopic}} {{if .CurrentUser.Perms.PinTopic}}
{{if .Topic.Sticky}}<a href='/topic/unstick/submit/{{.Topic.ID}}' class="action_button">Unpin</a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="action_button">Pin</a>{{end}}{{end}} {{if .Topic.Sticky}}<a href='/topic/unstick/submit/{{.Topic.ID}}' class="action_button">Unpin</a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="action_button">Pin</a>{{end}}{{end}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="action_button report_item">Report</a> <a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="action_button report_item">Report</a>
{{if .CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">{{.Topic.IpAddress}}</a>{{end}} {{if .CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">{{.Topic.IPAddress}}</a>{{end}}
{{end}} {{end}}
<a class="action_button action_button_right hide_on_mobile">{{.Topic.CreatedAt}}</a> <a class="action_button action_button_right hide_on_mobile">{{.Topic.CreatedAt}}</a>
{{if .Topic.LikeCount}}<a class="action_button action_button_right hide_on_micro">{{.Topic.LikeCount}} up</a>{{end}} {{if .Topic.LikeCount}}<a class="action_button action_button_right hide_on_micro">{{.Topic.LikeCount}} up</a>{{end}}
@ -68,7 +68,7 @@
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="action_button edit_item">Edit</a>{{end}} {{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="action_button edit_item">Edit</a>{{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="action_button delete_item">Delete</a>{{end}} {{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="action_button delete_item">Delete</a>{{end}}
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="action_button report_item">Report</a> <a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="action_button report_item">Report</a>
{{if $.CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">{{.IpAddress}}</a>{{end}} {{if $.CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">{{.IPAddress}}</a>{{end}}
{{end}} {{end}}
<a class="action_button action_button_right hide_on_mobile">{{.CreatedAt}}</a> <a class="action_button action_button_right hide_on_mobile">{{.CreatedAt}}</a>
{{if .LikeCount}}<a class="action_button action_button_right hide_on_micro">{{.LikeCount}} up</a>{{end}} {{if .LikeCount}}<a class="action_button action_button_right hide_on_micro">{{.LikeCount}} up</a>{{end}}

View File

@ -5,7 +5,7 @@
<div class="rowitem"><h1>Topic List</h1></div> <div class="rowitem"><h1>Topic List</h1></div>
</div> </div>
<div id="topic_list" class="rowblock topic_list"> <div id="topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}"> {{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br /> <span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span> <span class="lastReplyAt">{{.LastReplyAt}}</span>
@ -13,8 +13,8 @@
<span> <span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}} <a class="rowtopic" href="{{.Link}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}}
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a> <br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{/** TO-DO: Avoid the double '|' when both .Is_Closed and .Sticky are set to true. We could probably do this with CSS **/}} {{/** TO-DO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}} {{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}} {{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span> </span>
</div> </div>

460
themes.go
View File

@ -3,66 +3,64 @@ package main
import ( import (
//"fmt" //"fmt"
"log"
"os"
"bytes" "bytes"
"strings"
"errors"
"mime"
"io/ioutil"
"path/filepath"
"encoding/json" "encoding/json"
"errors"
"io/ioutil"
"log"
"mime"
"net/http" "net/http"
"os"
"path/filepath"
"reflect"
"strings"
"text/template" "text/template"
) )
var defaultTheme string var defaultTheme string
var themes map[string]Theme = make(map[string]Theme) var themes = make(map[string]Theme)
//var overriden_templates map[string]interface{} = make(map[string]interface{})
var overriden_templates map[string]bool = make(map[string]bool)
type Theme struct //var overridenTemplates map[string]interface{} = make(map[string]interface{})
{ var overridenTemplates = make(map[string]bool)
Name string
FriendlyName string type Theme struct {
Version string Name string
Creator string FriendlyName string
FullImage string Version string
Creator string
FullImage string
MobileFriendly bool MobileFriendly bool
Disabled bool Disabled bool
HideFromThemes bool HideFromThemes bool
ForkOf string ForkOf string
Tag string Tag string
URL string URL string
Sidebars string // Allowed Values: left, right, both, false Sidebars string // Allowed Values: left, right, both, false
//DisableMinifier // Is this really a good idea? I don't think themes should be fighting against the minifier //DisableMinifier // Is this really a good idea? I don't think themes should be fighting against the minifier
Settings map[string]ThemeSetting Settings map[string]ThemeSetting
Templates []TemplateMapping Templates []TemplateMapping
TemplatesMap map[string]string TemplatesMap map[string]string
TmplPtr map[string]interface{} // Coming Soon TmplPtr map[string]interface{}
Resources []ThemeResource Resources []ThemeResource
ResourceTemplates *template.Template ResourceTemplates *template.Template
// This variable should only be set and unset by the system, not the theme meta file // This variable should only be set and unset by the system, not the theme meta file
Active bool Active bool
} }
type ThemeSetting struct type ThemeSetting struct {
{
FriendlyName string FriendlyName string
Options []string Options []string
} }
type TemplateMapping struct type TemplateMapping struct {
{ Name string
Name string
Source string Source string
//When string //When string
} }
type ThemeResource struct type ThemeResource struct {
{ Name string
Name string
Location string Location string
} }
@ -88,9 +86,11 @@ func LoadThemes() error {
} }
theme.TemplatesMap = make(map[string]string) theme.TemplatesMap = make(map[string]string)
theme.TmplPtr = make(map[string]interface{})
if theme.Templates != nil { if theme.Templates != nil {
for _, themeTmpl := range theme.Templates { for _, themeTmpl := range theme.Templates {
theme.TemplatesMap[themeTmpl.Name] = themeTmpl.Source theme.TemplatesMap[themeTmpl.Name] = themeTmpl.Source
theme.TmplPtr[themeTmpl.Name] = tmplPtrMap["o_"+themeTmpl.Source]
} }
} }
@ -101,24 +101,25 @@ func LoadThemes() error {
log.Print("Loading the theme '" + theme.Name + "'") log.Print("Loading the theme '" + theme.Name + "'")
theme.Active = true theme.Active = true
defaultTheme = uname defaultTheme = uname
add_theme_static_files(theme) mapThemeTemplates(theme)
map_theme_templates(theme)
} else { } else {
theme.Active = false theme.Active = false
} }
// It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes
err = addThemeStaticFiles(theme)
if err != nil {
return err
}
themes[uname] = theme themes[uname] = theme
} }
err = rows.Err() return rows.Err()
if err != nil {
return err
}
return nil
} }
func init_themes() { func initThemes() error {
themeFiles, err := ioutil.ReadDir("./themes") themeFiles, err := ioutil.ReadDir("./themes")
if err != nil { if err != nil {
log.Fatal(err) return err
} }
for _, themeFile := range themeFiles { for _, themeFile := range themeFiles {
@ -130,13 +131,13 @@ func init_themes() {
log.Print("Adding theme '" + themeName + "'") log.Print("Adding theme '" + themeName + "'")
themeFile, err := ioutil.ReadFile("./themes/" + themeName + "/theme.json") themeFile, err := ioutil.ReadFile("./themes/" + themeName + "/theme.json")
if err != nil { if err != nil {
log.Fatal(err) return err
} }
var theme Theme var theme Theme
err = json.Unmarshal(themeFile, &theme) err = json.Unmarshal(themeFile, &theme)
if err != nil { if err != nil {
log.Fatal(err) return err
} }
theme.Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file theme.Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file
@ -145,19 +146,20 @@ func init_themes() {
if dev.DebugMode { if dev.DebugMode {
log.Print("Adding theme image") log.Print("Adding theme image")
} }
err = add_static_file("./themes/" + themeName + "/" + theme.FullImage, "./themes/" + themeName) err = addStaticFile("./themes/"+themeName+"/"+theme.FullImage, "./themes/"+themeName)
if err != nil { if err != nil {
log.Fatal(err) return err
} }
} }
themes[theme.Name] = theme themes[theme.Name] = theme
} }
return nil
} }
func add_theme_static_files(theme Theme) { func addThemeStaticFiles(theme Theme) error {
// TO-DO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account? // TO-DO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
err := filepath.Walk("./themes/" + theme.Name + "/public", func(path string, f os.FileInfo, err error) error { return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error {
if dev.DebugMode { if dev.DebugMode {
log.Print("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'") log.Print("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
} }
@ -168,42 +170,39 @@ func add_theme_static_files(theme Theme) {
return nil return nil
} }
path = strings.Replace(path,"\\","/",-1) path = strings.Replace(path, "\\", "/", -1)
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return err return err
} }
var ext string = filepath.Ext(path) var ext = filepath.Ext(path)
//log.Print("path ",path) //log.Print("path ",path)
//log.Print("ext ",ext) //log.Print("ext ",ext)
if ext == ".css" && len(data) != 0 { if ext == ".css" && len(data) != 0 {
var b bytes.Buffer var b bytes.Buffer
var pieces []string = strings.Split(path,"/") var pieces = strings.Split(path, "/")
var filename string = pieces[len(pieces) - 1] var filename = pieces[len(pieces)-1]
//log.Print("filename ", filename) //log.Print("filename ", filename)
err = theme.ResourceTemplates.ExecuteTemplate(&b,filename, CssData{ComingSoon:"We don't have any data to pass you yet!"}) err = theme.ResourceTemplates.ExecuteTemplate(&b, filename, CssData{ComingSoon: "We don't have any data to pass you yet!"})
if err != nil { if err != nil {
return err return err
} }
data = b.Bytes() data = b.Bytes()
} }
path = strings.TrimPrefix(path,"themes/" + theme.Name + "/public") path = strings.TrimPrefix(path, "themes/"+theme.Name+"/public")
gzip_data := compress_bytes_gzip(data) gzipData := compressBytesGzip(data)
static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)} staticFiles["/static/"+theme.Name+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}
if dev.DebugMode { if dev.DebugMode {
log.Print("Added the '" + path + "' static file for default theme " + theme.Name + ".") log.Print("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".")
} }
return nil return nil
}) })
if err != nil {
panic(err)
}
} }
func map_theme_templates(theme Theme) { func mapThemeTemplates(theme Theme) {
if theme.Templates != nil { if theme.Templates != nil {
for _, themeTmpl := range theme.Templates { for _, themeTmpl := range theme.Templates {
if themeTmpl.Name == "" { if themeTmpl.Name == "" {
@ -215,100 +214,100 @@ func map_theme_templates(theme Theme) {
// `go generate` is one possibility for letting plugins inject custom page structs, but it would simply add another step of compilation. It might be simpler than the current build process from the perspective of the administrator? // `go generate` is one possibility for letting plugins inject custom page structs, but it would simply add another step of compilation. It might be simpler than the current build process from the perspective of the administrator?
dest_tmpl_ptr, ok := tmpl_ptr_map[themeTmpl.Name] destTmplPtr, ok := tmplPtrMap[themeTmpl.Name]
if !ok { if !ok {
return return
} }
source_tmpl_ptr, ok := tmpl_ptr_map[themeTmpl.Source] sourceTmplPtr, ok := tmplPtrMap[themeTmpl.Source]
if !ok { if !ok {
log.Fatal("The source template doesn't exist!") log.Fatal("The source template doesn't exist!")
} }
switch d_tmpl_ptr := dest_tmpl_ptr.(type) { switch dTmplPtr := destTmplPtr.(type) {
case *func(TopicPage,http.ResponseWriter): case *func(TopicPage, http.ResponseWriter):
switch s_tmpl_ptr := source_tmpl_ptr.(type) { switch sTmplPtr := sourceTmplPtr.(type) {
case *func(TopicPage,http.ResponseWriter): case *func(TopicPage, http.ResponseWriter):
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr //overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates[themeTmpl.Name] = true overridenTemplates[themeTmpl.Name] = true
*d_tmpl_ptr = *s_tmpl_ptr *dTmplPtr = *sTmplPtr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(TopicsPage,http.ResponseWriter):
switch s_tmpl_ptr := source_tmpl_ptr.(type) {
case *func(TopicsPage,http.ResponseWriter):
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates[themeTmpl.Name] = true
*d_tmpl_ptr = *s_tmpl_ptr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(ForumPage,http.ResponseWriter):
switch s_tmpl_ptr := source_tmpl_ptr.(type) {
case *func(ForumPage,http.ResponseWriter):
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates[themeTmpl.Name] = true
*d_tmpl_ptr = *s_tmpl_ptr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(ForumsPage,http.ResponseWriter):
switch s_tmpl_ptr := source_tmpl_ptr.(type) {
case *func(ForumsPage,http.ResponseWriter):
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates[themeTmpl.Name] = true
*d_tmpl_ptr = *s_tmpl_ptr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(ProfilePage,http.ResponseWriter):
switch s_tmpl_ptr := source_tmpl_ptr.(type) {
case *func(ProfilePage,http.ResponseWriter):
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates[themeTmpl.Name] = true
*d_tmpl_ptr = *s_tmpl_ptr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(CreateTopicPage,http.ResponseWriter):
switch s_tmpl_ptr := source_tmpl_ptr.(type) {
case *func(CreateTopicPage,http.ResponseWriter):
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates[themeTmpl.Name] = true
*d_tmpl_ptr = *s_tmpl_ptr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(Page,http.ResponseWriter):
switch s_tmpl_ptr := source_tmpl_ptr.(type) {
case *func(Page,http.ResponseWriter):
//overriden_templates[themeTmpl.Name] = d_tmpl_ptr
overriden_templates[themeTmpl.Name] = true
*d_tmpl_ptr = *s_tmpl_ptr
default:
log.Fatal("The source and destination templates are incompatible")
}
default: default:
log.Fatal("Unknown destination template type!") log.Fatal("The source and destination templates are incompatible")
}
case *func(TopicsPage, http.ResponseWriter):
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(TopicsPage, http.ResponseWriter):
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(ForumPage, http.ResponseWriter):
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(ForumPage, http.ResponseWriter):
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(ForumsPage, http.ResponseWriter):
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(ForumsPage, http.ResponseWriter):
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(ProfilePage, http.ResponseWriter):
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(ProfilePage, http.ResponseWriter):
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(CreateTopicPage, http.ResponseWriter):
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(CreateTopicPage, http.ResponseWriter):
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(Page, http.ResponseWriter):
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(Page, http.ResponseWriter):
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
log.Fatal("The source and destination templates are incompatible")
}
default:
log.Fatal("Unknown destination template type!")
} }
} }
} }
} }
func reset_template_overrides() { func resetTemplateOverrides() {
log.Print("Resetting the template overrides") log.Print("Resetting the template overrides")
for name, _ := range overriden_templates { for name := range overridenTemplates {
log.Print("Resetting '" + name + "' template override") log.Print("Resetting '" + name + "' template override")
origin_pointer, ok := tmpl_ptr_map["o_" + name] originPointer, ok := tmplPtrMap["o_"+name]
if !ok { if !ok {
//log.Fatal("The origin template doesn't exist!") //log.Fatal("The origin template doesn't exist!")
log.Print("The origin template doesn't exist!") log.Print("The origin template doesn't exist!")
return return
} }
dest_tmpl_ptr, ok := tmpl_ptr_map[name] destTmplPtr, ok := tmplPtrMap[name]
if !ok { if !ok {
//log.Fatal("The destination template doesn't exist!") //log.Fatal("The destination template doesn't exist!")
log.Print("The destination template doesn't exist!") log.Print("The destination template doesn't exist!")
@ -316,97 +315,156 @@ func reset_template_overrides() {
} }
// Not really a pointer, more of a function handle, an artifact from one of the earlier versions of themes.go // Not really a pointer, more of a function handle, an artifact from one of the earlier versions of themes.go
switch o_ptr := origin_pointer.(type) { switch oPtr := originPointer.(type) {
case func(TopicPage,http.ResponseWriter): case func(TopicPage, http.ResponseWriter):
switch d_ptr := dest_tmpl_ptr.(type) { switch dPtr := destTmplPtr.(type) {
case *func(TopicPage,http.ResponseWriter): case *func(TopicPage, http.ResponseWriter):
*d_ptr = o_ptr *dPtr = oPtr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(TopicsPage,http.ResponseWriter):
switch d_ptr := dest_tmpl_ptr.(type) {
case *func(TopicsPage,http.ResponseWriter):
*d_ptr = o_ptr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(ForumPage,http.ResponseWriter):
switch d_ptr := dest_tmpl_ptr.(type) {
case *func(ForumPage,http.ResponseWriter):
*d_ptr = o_ptr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(ForumsPage,http.ResponseWriter):
switch d_ptr := dest_tmpl_ptr.(type) {
case *func(ForumsPage,http.ResponseWriter):
*d_ptr = o_ptr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(ProfilePage,http.ResponseWriter):
switch d_ptr := dest_tmpl_ptr.(type) {
case *func(ProfilePage,http.ResponseWriter):
*d_ptr = o_ptr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(CreateTopicPage,http.ResponseWriter):
switch d_ptr := dest_tmpl_ptr.(type) {
case *func(CreateTopicPage,http.ResponseWriter):
*d_ptr = o_ptr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(Page,http.ResponseWriter):
switch d_ptr := dest_tmpl_ptr.(type) {
case *func(Page,http.ResponseWriter):
*d_ptr = o_ptr
default:
log.Fatal("The origin and destination templates are incompatible")
}
default: default:
log.Fatal("Unknown destination template type!") log.Fatal("The origin and destination templates are incompatible")
}
case func(TopicsPage, http.ResponseWriter):
switch dPtr := destTmplPtr.(type) {
case *func(TopicsPage, http.ResponseWriter):
*dPtr = oPtr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(ForumPage, http.ResponseWriter):
switch dPtr := destTmplPtr.(type) {
case *func(ForumPage, http.ResponseWriter):
*dPtr = oPtr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(ForumsPage, http.ResponseWriter):
switch dPtr := destTmplPtr.(type) {
case *func(ForumsPage, http.ResponseWriter):
*dPtr = oPtr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(ProfilePage, http.ResponseWriter):
switch dPtr := destTmplPtr.(type) {
case *func(ProfilePage, http.ResponseWriter):
*dPtr = oPtr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(CreateTopicPage, http.ResponseWriter):
switch dPtr := destTmplPtr.(type) {
case *func(CreateTopicPage, http.ResponseWriter):
*dPtr = oPtr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(Page, http.ResponseWriter):
switch dPtr := destTmplPtr.(type) {
case *func(Page, http.ResponseWriter):
*dPtr = oPtr
default:
log.Fatal("The origin and destination templates are incompatible")
}
default:
log.Fatal("Unknown destination template type!")
} }
log.Print("The template override was reset") log.Print("The template override was reset")
} }
overriden_templates = make(map[string]bool) overridenTemplates = make(map[string]bool)
log.Print("All of the template overrides have been reset") log.Print("All of the template overrides have been reset")
} }
// NEW method of doing theme templates to allow one user to have a different theme to another. Under construction. // NEW method of doing theme templates to allow one user to have a different theme to another. Under construction.
// TO-DO: Generate the type switch instead of writing it by hand // TO-DO: Generate the type switch instead of writing it by hand
// TO-DO: Cut the number of types in half
func RunThemeTemplate(theme string, template string, pi interface{}, w http.ResponseWriter) { func RunThemeTemplate(theme string, template string, pi interface{}, w http.ResponseWriter) {
switch tmpl := GetThemeTemplate(theme,template).(type) { switch tmplO := GetThemeTemplate(theme, template).(type) {
case func(TopicPage,http.ResponseWriter): tmpl(pi.(TopicPage),w) case *func(TopicPage, http.ResponseWriter):
case func(TopicsPage,http.ResponseWriter): tmpl(pi.(TopicsPage),w) var tmpl = *tmplO
case func(ForumPage,http.ResponseWriter): tmpl(pi.(ForumPage),w) tmpl(pi.(TopicPage), w)
case func(ForumsPage,http.ResponseWriter): tmpl(pi.(ForumsPage),w) case *func(TopicsPage, http.ResponseWriter):
case func(ProfilePage,http.ResponseWriter): tmpl(pi.(ProfilePage),w) var tmpl = *tmplO
case func(CreateTopicPage,http.ResponseWriter): tmpl(pi.(CreateTopicPage),w) tmpl(pi.(TopicsPage), w)
case func(Page,http.ResponseWriter): tmpl(pi.(Page),w) case *func(ForumPage, http.ResponseWriter):
default: LogError(errors.New("Unknown template type")) var tmpl = *tmplO
tmpl(pi.(ForumPage), w)
case *func(ForumsPage, http.ResponseWriter):
var tmpl = *tmplO
tmpl(pi.(ForumsPage), w)
case *func(ProfilePage, http.ResponseWriter):
var tmpl = *tmplO
tmpl(pi.(ProfilePage), w)
case *func(CreateTopicPage, http.ResponseWriter):
var tmpl = *tmplO
tmpl(pi.(CreateTopicPage), w)
case *func(Page, http.ResponseWriter):
var tmpl = *tmplO
tmpl(pi.(Page), w)
case func(TopicPage, http.ResponseWriter):
tmplO(pi.(TopicPage), w)
case func(TopicsPage, http.ResponseWriter):
tmplO(pi.(TopicsPage), w)
case func(ForumPage, http.ResponseWriter):
tmplO(pi.(ForumPage), w)
case func(ForumsPage, http.ResponseWriter):
tmplO(pi.(ForumsPage), w)
case func(ProfilePage, http.ResponseWriter):
tmplO(pi.(ProfilePage), w)
case func(CreateTopicPage, http.ResponseWriter):
tmplO(pi.(CreateTopicPage), w)
case func(Page, http.ResponseWriter):
tmplO(pi.(Page), w)
case string:
mapping, ok := themes[defaultTheme].TemplatesMap[template]
if !ok {
mapping = template
}
err := templates.ExecuteTemplate(w, mapping+".html", pi)
if err != nil {
LogError(err)
}
default:
log.Print("theme ", theme)
log.Print("template ", template)
log.Print("pi ", pi)
log.Print("tmplO ", tmplO)
valueOf := reflect.ValueOf(tmplO)
log.Print("initial valueOf.Type()", valueOf.Type())
for valueOf.Kind() == reflect.Interface || valueOf.Kind() == reflect.Ptr {
valueOf = valueOf.Elem()
log.Print("valueOf.Elem().Type() ", valueOf.Type())
}
log.Print("deferenced valueOf.Type() ", valueOf.Type())
log.Print("valueOf.Kind() ", valueOf.Kind())
LogError(errors.New("Unknown template type"))
} }
} }
// GetThemeTemplate attempts to get the template for a specific theme, otherwise it falls back on the default template pointer, which if absent will fallback onto the template interpreter
func GetThemeTemplate(theme string, template string) interface{} { func GetThemeTemplate(theme string, template string) interface{} {
tmpl, ok := themes[theme].TmplPtr[template] tmpl, ok := themes[theme].TmplPtr[template]
if !ok { if ok {
return tmpl return tmpl
} }
return nil tmpl, ok = tmplPtrMap[template]
if ok {
return tmpl
}
return template
} }
// CreateThemeTemplate creates a theme template on the current default theme
func CreateThemeTemplate(theme string, name string) { func CreateThemeTemplate(theme string, name string) {
themes[theme].TmplPtr[name] = func(pi ProfilePage, w http.ResponseWriter) { themes[theme].TmplPtr[name] = func(pi Page, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap[name] mapping, ok := themes[defaultTheme].TemplatesMap[name]
if !ok { if !ok {
mapping = name mapping = name
} }
err := templates.ExecuteTemplate(w, mapping + ".html", pi) err := templates.ExecuteTemplate(w, mapping+".html", pi)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
} }
} }
} }

View File

@ -247,6 +247,9 @@ a {
.unpin_label:before { .unpin_label:before {
content: "Unpin"; content: "Unpin";
} }
.ip_label:before {
content: "IP";
}
.flag_label:before { .flag_label:before {
content: "Flag"; content: "Flag";
} }

View File

@ -158,6 +158,9 @@ li a {
.rowblock:empty { .rowblock:empty {
display: none; display: none;
} }
.rowmenu {
border: 1px solid #ccc;
}
.rowsmall { .rowsmall {
font-size: 12px; font-size: 12px;
} }
@ -234,12 +237,15 @@ li a {
.stat_red { background-color: #ffb2b2; border-color: #ffb2b2; } .stat_red { background-color: #ffb2b2; border-color: #ffb2b2; }
.stat_disabled { background-color: lightgray; border-color: lightgray; } .stat_disabled { background-color: lightgray; border-color: lightgray; }
.rowhead, .colstack_head {
border-bottom: none;
}
.rowhead .rowitem, .colstack_head .rowitem { .rowhead .rowitem, .colstack_head .rowitem {
background-color: rgb(252,252,252); background-color: rgb(252,252,252);
} }
.rowhead h1, .colstack_head h1 {
-webkit-margin-before: 0;
-webkit-margin-after: 0;
font-weight: normal;
font-size: 16px;
}
.rowitem { .rowitem {
width: 100%; width: 100%;
@ -465,6 +471,7 @@ button.username {
.pin_label:before { content: "📌"; } .pin_label:before { content: "📌"; }
.unpin_label:before { content: "📌"; } .unpin_label:before { content: "📌"; }
.unpin_label { background-color: #D6FFD6; } .unpin_label { background-color: #D6FFD6; }
.ip_label:before { content: "🔍"; }
.flag_label:before { content: "🚩"; } .flag_label:before { content: "🚩"; }
.level_label:before { content: "👑"; } .level_label:before { content: "👑"; }
.level_label { color: #505050; opacity:0.85; } .level_label { color: #505050; opacity:0.85; }
@ -558,5 +565,33 @@ button.username {
background-size: 128px; background-size: 128px;
padding-left: 136px; padding-left: 136px;
} }
/* Profiles */
#profile_left_lane {
width: 220px;
margin-top: 5px;
}
#profile_left_lane .avatarRow {
overflow: hidden;
max-height: 220px;
}
#profile_left_lane .avatar {
width: 100%;
margin: 0;
display: block;
}
#profile_left_lane .username {
font-size: 14px;
display: block;
margin-top: 3px;
}
#profile_left_lane .profileName {
font-size: 18px;
}
#profile_right_lane {
width: calc(100% - 245px);
}
.simple .user_tag {
font-size: 14px;
}
{{template "media.partial.css" }} {{template "media.partial.css" }}

View File

@ -71,3 +71,7 @@
.perm_preset_can_moderate:before { content: "Can Moderate"; color: darkblue; } .perm_preset_can_moderate:before { content: "Can Moderate"; color: darkblue; }
.perm_preset_custom:before { content: "Custom"; color: black; } .perm_preset_custom:before { content: "Custom"; color: black; }
.perm_preset_default:before { content: "Default"; } .perm_preset_default:before { content: "Default"; }
#panel_dashboard_right .colstack_head {
display: none;
}

158
topic.go
View File

@ -1,128 +1,126 @@
package main package main
//import "fmt" //import "fmt"
import "strconv" import "strconv"
import "html/template" import "html/template"
type Topic struct type Topic struct {
{ ID int
ID int Link string
Link string Title string
Title string Content string
Content string CreatedBy int
CreatedBy int IsClosed bool
Is_Closed bool Sticky bool
Sticky bool CreatedAt string
CreatedAt string
LastReplyAt string LastReplyAt string
//LastReplyBy int //LastReplyBy int
ParentID int ParentID int
Status string // Deprecated. Marked for removal. Status string // Deprecated. Marked for removal.
IpAddress string IPAddress string
PostCount int PostCount int
LikeCount int LikeCount int
ClassName string // CSS Class Name ClassName string // CSS Class Name
Data string // Used for report metadata Data string // Used for report metadata
} }
type TopicUser struct type TopicUser struct {
{ ID int
ID int Link string
Link string Title string
Title string Content string
Content string CreatedBy int
CreatedBy int IsClosed bool
Is_Closed bool Sticky bool
Sticky bool CreatedAt string
CreatedAt string
LastReplyAt string LastReplyAt string
//LastReplyBy int //LastReplyBy int
ParentID int ParentID int
Status string // Deprecated. Marked for removal. Status string // Deprecated. Marked for removal.
IpAddress string IPAddress string
PostCount int PostCount int
LikeCount int LikeCount int
ClassName string ClassName string
Data string // Used for report metadata Data string // Used for report metadata
UserLink string UserLink string
CreatedByName string CreatedByName string
Group int Group int
Avatar string Avatar string
ContentLines int ContentLines int
Tag string Tag string
URL string URL string
URLPrefix string URLPrefix string
URLName string URLName string
Level int Level int
Liked bool Liked bool
} }
type TopicsRow struct type TopicsRow struct {
{ ID int
ID int Link string
Link string Title string
Title string Content string
Content string CreatedBy int
CreatedBy int IsClosed bool
Is_Closed bool Sticky bool
Sticky bool CreatedAt string
CreatedAt string
LastReplyAt string LastReplyAt string
LastReplyBy int LastReplyBy int
ParentID int ParentID int
Status string // Deprecated. Marked for removal. -Is there anything we could use it for? Status string // Deprecated. Marked for removal. -Is there anything we could use it for?
IpAddress string IPAddress string
PostCount int PostCount int
LikeCount int LikeCount int
ClassName string ClassName string
Data string // Used for report metadata Data string // Used for report metadata
Creator *User Creator *User
Css template.CSS CSS template.CSS
ContentLines int ContentLines int
LastUser *User LastUser *User
ForumName string //TopicsRow ForumName string //TopicsRow
ForumLink string ForumLink string
} }
func get_topicuser(tid int) (TopicUser,error) { func getTopicuser(tid int) (TopicUser, error) {
if config.CacheTopicUser != CACHE_SQL { if config.CacheTopicUser != CACHE_SQL {
topic, err := topics.Get(tid) topic, err := topics.Get(tid)
if err == nil { if err == nil {
user, err := users.CascadeGet(topic.CreatedBy) user, err := users.CascadeGet(topic.CreatedBy)
if err != nil { if err != nil {
return TopicUser{ID:tid}, err return TopicUser{ID: tid}, err
} }
// We might be better off just passing seperate topic and user structs to the caller? // We might be better off just passing seperate topic and user structs to the caller?
return copy_topic_to_topicuser(topic, user), nil return copyTopicToTopicuser(topic, user), nil
} else if users.GetLength() < users.GetCapacity() { } else if users.GetLength() < users.GetCapacity() {
topic, err = topics.CascadeGet(tid) topic, err = topics.CascadeGet(tid)
if err != nil { if err != nil {
return TopicUser{ID:tid}, err return TopicUser{ID: tid}, err
} }
user, err := users.CascadeGet(topic.CreatedBy) user, err := users.CascadeGet(topic.CreatedBy)
if err != nil { if err != nil {
return TopicUser{ID:tid}, err return TopicUser{ID: tid}, err
} }
return copy_topic_to_topicuser(topic, user), nil return copyTopicToTopicuser(topic, user), nil
} }
} }
tu := TopicUser{ID:tid} tu := TopicUser{ID: tid}
err := get_topic_user_stmt.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.Is_Closed, &tu.Sticky, &tu.ParentID, &tu.IpAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level) err := get_topic_user_stmt.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
tu.Link = build_topic_url(name_to_slug(tu.Title),tu.ID) tu.Link = buildTopicURL(nameToSlug(tu.Title), tu.ID)
tu.UserLink = build_profile_url(name_to_slug(tu.CreatedByName),tu.CreatedBy) tu.UserLink = buildProfileURL(nameToSlug(tu.CreatedByName), tu.CreatedBy)
the_topic := Topic{ID:tu.ID, Link:tu.Link, Title:tu.Title, Content:tu.Content, CreatedBy:tu.CreatedBy, Is_Closed:tu.Is_Closed, Sticky:tu.Sticky, CreatedAt:tu.CreatedAt, LastReplyAt:tu.LastReplyAt, ParentID:tu.ParentID, IpAddress:tu.IpAddress, PostCount:tu.PostCount, LikeCount:tu.LikeCount} theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, ParentID: tu.ParentID, IPAddress: tu.IPAddress, PostCount: tu.PostCount, LikeCount: tu.LikeCount}
//log.Printf("the_topic: %+v\n", the_topic) //log.Printf("the_topic: %+v\n", the_topic)
tu.Tag = groups[tu.Group].Tag tu.Tag = groups[tu.Group].Tag
topics.Add(&the_topic) _ = topics.Add(&theTopic)
return tu, err return tu, err
} }
func copy_topic_to_topicuser(topic *Topic, user *User) (tu TopicUser) { func copyTopicToTopicuser(topic *Topic, user *User) (tu TopicUser) {
tu.UserLink = user.Link tu.UserLink = user.Link
tu.CreatedByName = user.Name tu.CreatedByName = user.Name
tu.Group = user.Group tu.Group = user.Group
@ -136,12 +134,12 @@ func copy_topic_to_topicuser(topic *Topic, user *User) (tu TopicUser) {
tu.Title = topic.Title tu.Title = topic.Title
tu.Content = topic.Content tu.Content = topic.Content
tu.CreatedBy = topic.CreatedBy tu.CreatedBy = topic.CreatedBy
tu.Is_Closed = topic.Is_Closed tu.IsClosed = topic.IsClosed
tu.Sticky = topic.Sticky tu.Sticky = topic.Sticky
tu.CreatedAt = topic.CreatedAt tu.CreatedAt = topic.CreatedAt
tu.LastReplyAt = topic.LastReplyAt tu.LastReplyAt = topic.LastReplyAt
tu.ParentID = topic.ParentID tu.ParentID = topic.ParentID
tu.IpAddress = topic.IpAddress tu.IPAddress = topic.IPAddress
tu.PostCount = topic.PostCount tu.PostCount = topic.PostCount
tu.LikeCount = topic.LikeCount tu.LikeCount = topic.LikeCount
tu.Data = topic.Data tu.Data = topic.Data
@ -149,20 +147,22 @@ func copy_topic_to_topicuser(topic *Topic, user *User) (tu TopicUser) {
return tu return tu
} }
func get_topic_by_reply(rid int) (*Topic, error) { func getTopicByReply(rid int) (*Topic, error) {
topic := Topic{ID:0} topic := Topic{ID: 0}
err := get_topic_by_reply_stmt.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := get_topic_by_reply_stmt.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = build_topic_url(name_to_slug(topic.Title),topic.ID) topic.Link = buildTopicURL(nameToSlug(topic.Title), topic.ID)
return &topic, err return &topic, err
} }
func build_topic_url(slug string, tid int) string { func buildTopicURL(slug string, tid int) string {
if slug == "" { if slug == "" {
return "/topic/" + strconv.Itoa(tid) return "/topic/" + strconv.Itoa(tid)
} }
return "/topic/" + slug + "." + strconv.Itoa(tid) return "/topic/" + slug + "." + strconv.Itoa(tid)
} }
func get_topic_url_prefix() string { // I don't care if it isn't used,, it will likely be in the future. Nolint.
// nolint
func getTopicURLPrefix() string {
return "/topic/" return "/topic/"
} }

View File

@ -25,22 +25,22 @@ type TopicStore interface {
} }
type MemoryTopicStore struct { type MemoryTopicStore struct {
items map[int]*Topic items map[int]*Topic
length int length int
capacity int capacity int
get *sql.Stmt get *sql.Stmt
sync.RWMutex sync.RWMutex
} }
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 {
log.Fatal(err) log.Fatal(err)
} }
return &MemoryTopicStore{ return &MemoryTopicStore{
items: make(map[int]*Topic), items: make(map[int]*Topic),
capacity: capacity, capacity: capacity,
get: stmt, get: stmt,
} }
} }
@ -70,30 +70,30 @@ func (sts *MemoryTopicStore) CascadeGet(id int) (*Topic, error) {
return topic, nil return topic, nil
} }
topic = &Topic{ID:id} topic = &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
if err == nil { if err == nil {
topic.Link = build_topic_url(name_to_slug(topic.Title),id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
sts.Add(topic) _ = sts.Add(topic)
} }
return topic, err return topic, err
} }
func (sts *MemoryTopicStore) BypassGet(id int) (*Topic, error) { func (sts *MemoryTopicStore) BypassGet(id int) (*Topic, error) {
topic := &Topic{ID:id} topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = build_topic_url(name_to_slug(topic.Title),id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return topic, err return topic, err
} }
func (sts *MemoryTopicStore) Load(id int) error { func (sts *MemoryTopicStore) Load(id int) error {
topic := &Topic{ID:id} topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
if err == nil { if err == nil {
topic.Link = build_topic_url(name_to_slug(topic.Title),id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
sts.Set(topic) _ = sts.Set(topic)
} else { } else {
sts.Remove(id) _ = sts.Remove(id)
} }
return err return err
} }
@ -136,14 +136,14 @@ func (sts *MemoryTopicStore) AddUnsafe(item *Topic) error {
func (sts *MemoryTopicStore) Remove(id int) error { func (sts *MemoryTopicStore) Remove(id int) error {
sts.Lock() sts.Lock()
delete(sts.items,id) delete(sts.items, id)
sts.Unlock() sts.Unlock()
sts.length-- sts.length--
return nil return nil
} }
func (sts *MemoryTopicStore) RemoveUnsafe(id int) error { func (sts *MemoryTopicStore) RemoveUnsafe(id int) error {
delete(sts.items,id) delete(sts.items, id)
sts.length-- sts.length--
return nil return nil
} }
@ -166,11 +166,11 @@ func (sts *MemoryTopicStore) GetCapacity() int {
} }
type SqlTopicStore struct { type SqlTopicStore struct {
get *sql.Stmt get *sql.Stmt
} }
func NewSqlTopicStore() *SqlTopicStore { func NewSqlTopicStore() *SqlTopicStore {
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 {
log.Fatal(err) log.Fatal(err)
} }
@ -178,37 +178,37 @@ func NewSqlTopicStore() *SqlTopicStore {
} }
func (sts *SqlTopicStore) Get(id int) (*Topic, error) { func (sts *SqlTopicStore) Get(id int) (*Topic, error) {
topic := Topic{ID:id} topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = build_topic_url(name_to_slug(topic.Title),id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return &topic, err return &topic, err
} }
func (sts *SqlTopicStore) GetUnsafe(id int) (*Topic, error) { func (sts *SqlTopicStore) GetUnsafe(id int) (*Topic, error) {
topic := Topic{ID:id} topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = build_topic_url(name_to_slug(topic.Title),id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return &topic, err return &topic, err
} }
func (sts *SqlTopicStore) CascadeGet(id int) (*Topic, error) { func (sts *SqlTopicStore) CascadeGet(id int) (*Topic, error) {
topic := Topic{ID:id} topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = build_topic_url(name_to_slug(topic.Title),id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return &topic, err return &topic, err
} }
func (sts *SqlTopicStore) BypassGet(id int) (*Topic, error) { func (sts *SqlTopicStore) BypassGet(id int) (*Topic, error) {
topic := &Topic{ID:id} topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = build_topic_url(name_to_slug(topic.Title),id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return topic, err return topic, err
} }
func (sts *SqlTopicStore) Load(id int) error { func (sts *SqlTopicStore) Load(id int) error {
topic := Topic{ID:id} topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = build_topic_url(name_to_slug(topic.Title),id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return err return err
} }

291
user.go
View File

@ -3,71 +3,72 @@ package main
import ( import (
//"log" //"log"
//"fmt" //"fmt"
"strings" "html/template"
"strconv"
"time"
"net" "net"
"net/http" "net/http"
"html/template" "strconv"
"strings"
"time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
var guest_user User = User{ID:0,Link:"#",Group:6,Perms:GuestPerms} var guestUser = User{ID: 0, Link: "#", Group: 6, Perms: GuestPerms}
var PreRoute func(http.ResponseWriter, *http.Request) (User,bool) = _pre_route var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = _pre_route
// TO-DO: Are these even session checks anymore? We might need to rethink these names // TO-DO: Are these even session checks anymore? We might need to rethink these names
var PanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (HeaderVars,PanelStats,bool) = _panel_session_check var PanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, bool) = _panel_session_check
var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (HeaderLite,bool) = _simple_panel_session_check var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, bool) = _simple_panel_session_check
var SimpleForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (success bool) = _simple_forum_session_check var SimpleForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, success bool) = _simple_forum_session_check
var ForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars HeaderVars, success bool) = _forum_session_check var ForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, success bool) = _forum_session_check
var SimpleSessionCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite HeaderLite, success bool) = _simple_session_check var SimpleSessionCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) = _simple_session_check
var SessionCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, success bool) = _session_check var SessionCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) = _session_check
var CheckPassword func(real_password string, password string, salt string) (err error) = BcryptCheckPassword //func(real_password string, password string, salt string) (err error)
var GeneratePassword func(password string) (hashed_password string, salt string, err error) = BcryptGeneratePassword var CheckPassword = BcryptCheckPassword
type User struct //func(password string) (hashed_password string, salt string, err error)
{ var GeneratePassword = BcryptGeneratePassword
ID int
Link string type User struct {
Name string ID int
Email string Link string
Group int Name string
Active bool Email string
Is_Mod bool Group int
Is_Super_Mod bool Active bool
Is_Admin bool IsMod bool
Is_Super_Admin bool IsSuperMod bool
Is_Banned bool IsAdmin bool
Perms Perms IsSuperAdmin bool
PluginPerms map[string]bool IsBanned bool
Session string Perms Perms
Loggedin bool PluginPerms map[string]bool
Avatar string Session string
Message string Loggedin bool
URLPrefix string // Move this to another table? Create a user lite? Avatar string
URLName string Message string
Tag string URLPrefix string // Move this to another table? Create a user lite?
Level int URLName string
Score int Tag string
Last_IP string Level int
TempGroup int Score int
LastIP string
TempGroup int
} }
type Email struct type Email struct {
{ UserID int
UserID int Email string
Email string
Validated bool Validated bool
Primary bool Primary bool
Token string Token string
} }
// duration in seconds // duration in seconds
func (user *User) Ban(duration time.Duration, issuedBy int) error { func (user *User) Ban(duration time.Duration, issuedBy int) error {
return user.ScheduleGroupUpdate(4,issuedBy,duration) return user.ScheduleGroupUpdate(4, issuedBy, duration)
} }
func (user *User) Unban() error { func (user *User) Unban() error {
@ -91,7 +92,7 @@ func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Durat
if err != nil { if err != nil {
return err return err
} }
_, err = set_temp_group_stmt.Exec(gid,user.ID) _, err = set_temp_group_stmt.Exec(gid, user.ID)
if err != nil { if err != nil {
return err return err
} }
@ -104,15 +105,15 @@ func (user *User) RevertGroupUpdate() error {
if err != nil { if err != nil {
return err return err
} }
_, err = set_temp_group_stmt.Exec(0,user.ID) _, err = set_temp_group_stmt.Exec(0, user.ID)
if err != nil { if err != nil {
return err return err
} }
return users.Load(user.ID) return users.Load(user.ID)
} }
func BcryptCheckPassword(real_password string, password string, salt string) (err error) { func BcryptCheckPassword(realPassword string, password string, salt string) (err error) {
return bcrypt.CompareHashAndPassword([]byte(real_password), []byte(password + salt)) return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password+salt))
} }
// Investigate. Do we need the extra salt? // Investigate. Do we need the extra salt?
@ -131,23 +132,20 @@ func BcryptGeneratePassword(password string) (hashed_password string, salt strin
} }
func BcryptGeneratePasswordNoSalt(password string) (hash string, err error) { func BcryptGeneratePasswordNoSalt(password string) (hash string, err error) {
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return "", err return "", err
} }
return string(hashed_password), nil return string(hashedPassword), nil
} }
func SetPassword(uid int, password string) error { func SetPassword(uid int, password string) error {
hashed_password, salt, err := GeneratePassword(password) hashedPassword, salt, err := GeneratePassword(password)
if err != nil { if err != nil {
return err return err
} }
_, err = set_password_stmt.Exec(hashed_password, salt, uid) _, err = set_password_stmt.Exec(hashedPassword, salt, uid)
if err != nil { return err
return err
}
return nil
} }
func SendValidationEmail(username string, email string, token string) bool { func SendValidationEmail(username string, email string, token string) bool {
@ -165,43 +163,43 @@ func SendValidationEmail(username string, email string, token string) bool {
// http.Request is for context.Context middleware. Mostly for plugin_socialgroups right now // http.Request is for context.Context middleware. Mostly for plugin_socialgroups right now
func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) { func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) {
if vhooks["intercept_build_widgets"] != nil { if vhooks["intercept_build_widgets"] != nil {
if run_vhook("intercept_build_widgets", zone, data, headerVars, r).(bool) { if runVhook("intercept_build_widgets", zone, data, headerVars, r).(bool) {
return return
} }
} }
//log.Print("themes[defaultTheme].Sidebars",themes[defaultTheme].Sidebars) //log.Print("themes[defaultTheme].Sidebars",themes[defaultTheme].Sidebars)
if themes[defaultTheme].Sidebars == "right" { if themes[defaultTheme].Sidebars == "right" {
if len(docks.RightSidebar) != 0 { if len(docks.RightSidebar) != 0 {
var sbody string var sbody string
for _, widget := range docks.RightSidebar { for _, widget := range docks.RightSidebar {
if widget.Enabled { if widget.Enabled {
if widget.Location == "global" || widget.Location == zone { if widget.Location == "global" || widget.Location == zone {
sbody += widget.Body sbody += widget.Body
}
} }
} }
headerVars.Widgets.RightSidebar = template.HTML(sbody)
} }
headerVars.Widgets.RightSidebar = template.HTML(sbody)
}
} }
} }
func _simple_forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fid int) (success bool) { func _simple_forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, success bool) {
if !fstore.Exists(fid) { if !fstore.Exists(fid) {
PreError("The target forum doesn't exist.",w,r) PreError("The target forum doesn't exist.", w, r)
return false return nil, false
} }
success = true success = true
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns? // Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
if vhooks["simple_forum_check_pre_perms"] != nil { if vhooks["simple_forum_check_pre_perms"] != nil {
if run_vhook("simple_forum_check_pre_perms", w, r, user, &fid, &success).(bool) { if runVhook("simple_forum_check_pre_perms", w, r, user, &fid, &success, &headerLite).(bool) {
return success return headerLite, success
} }
} }
fperms := groups[user.Group].Forums[fid] fperms := groups[user.Group].Forums[fid]
if fperms.Overrides && !user.Is_Super_Admin { if fperms.Overrides && !user.IsSuperAdmin {
user.Perms.ViewTopic = fperms.ViewTopic user.Perms.ViewTopic = fperms.ViewTopic
user.Perms.LikeItem = fperms.LikeItem user.Perms.LikeItem = fperms.LikeItem
user.Perms.CreateTopic = fperms.CreateTopic user.Perms.CreateTopic = fperms.CreateTopic
@ -219,18 +217,18 @@ func _simple_forum_session_check(w http.ResponseWriter, r *http.Request, user *U
} }
} }
} }
return true return headerLite, true
} }
func _forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars HeaderVars, success bool) { func _forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, success bool) {
headerVars, success = SessionCheck(w,r,user) headerVars, success = SessionCheck(w, r, user)
if !fstore.Exists(fid) { if !fstore.Exists(fid) {
NotFound(w,r) NotFound(w, r)
return headerVars, false return headerVars, false
} }
if vhooks["forum_check_pre_perms"] != nil { if vhooks["forum_check_pre_perms"] != nil {
if run_vhook("forum_check_pre_perms", w, r, user, &fid, &success, &headerVars).(bool) { if runVhook("forum_check_pre_perms", w, r, user, &fid, &success, &headerVars).(bool) {
return headerVars, success return headerVars, success
} }
} }
@ -238,7 +236,7 @@ func _forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fi
fperms := groups[user.Group].Forums[fid] fperms := groups[user.Group].Forums[fid]
//log.Printf("user.Perms: %+v\n", user.Perms) //log.Printf("user.Perms: %+v\n", user.Perms)
//log.Printf("fperms: %+v\n", fperms) //log.Printf("fperms: %+v\n", fperms)
if fperms.Overrides && !user.Is_Super_Admin { if fperms.Overrides && !user.IsSuperAdmin {
user.Perms.ViewTopic = fperms.ViewTopic user.Perms.ViewTopic = fperms.ViewTopic
user.Perms.LikeItem = fperms.LikeItem user.Perms.LikeItem = fperms.LikeItem
user.Perms.CreateTopic = fperms.CreateTopic user.Perms.CreateTopic = fperms.CreateTopic
@ -260,27 +258,32 @@ func _forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fi
} }
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with // Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, stats PanelStats, success bool) { func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, success bool) {
headerVars.Site = site headerVars = &HeaderVars{
headerVars.Settings = settingBox.Load().(SettingBox) Site: site,
if !user.Is_Super_Mod { Settings: settingBox.Load().(SettingBox),
NoPermissions(w,r,*user) ThemeName: defaultTheme, // TO-DO: Is this racey?
}
// TO-DO: We should probably initialise headerVars.ExtData
if !user.IsSuperMod {
NoPermissions(w, r, *user)
return headerVars, stats, false return headerVars, stats, false
} }
headerVars.Stylesheets = append(headerVars.Stylesheets,"panel.css") headerVars.Stylesheets = append(headerVars.Stylesheets, headerVars.ThemeName+"/panel.css")
if len(themes[defaultTheme].Resources) != 0 { if len(themes[defaultTheme].Resources) != 0 {
rlist := themes[defaultTheme].Resources rlist := themes[defaultTheme].Resources
for _, resource := range rlist { for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "panel" { if resource.Location == "global" || resource.Location == "panel" {
halves := strings.Split(resource.Name,".") halves := strings.Split(resource.Name, ".")
if len(halves) != 2 { if len(halves) != 2 {
continue continue
} }
if halves[1] == "css" { if halves[1] == "css" {
headerVars.Stylesheets = append(headerVars.Stylesheets,resource.Name) headerVars.Stylesheets = append(headerVars.Stylesheets, resource.Name)
} else if halves[1] == "js" { } else if halves[1] == "js" {
headerVars.Scripts = append(headerVars.Scripts,resource.Name) headerVars.Scripts = append(headerVars.Scripts, resource.Name)
} }
} }
} }
@ -288,7 +291,7 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
err := group_count_stmt.QueryRow().Scan(&stats.Groups) err := group_count_stmt.QueryRow().Scan(&stats.Groups)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return headerVars, stats, false return headerVars, stats, false
} }
@ -301,8 +304,8 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
pusher, ok := w.(http.Pusher) pusher, ok := w.(http.Pusher)
if ok { if ok {
pusher.Push("/static/main.css", nil) pusher.Push("/static/"+headerVars.ThemeName+"/main.css", nil)
pusher.Push("/static/panel.css", nil) pusher.Push("/static/"+headerVars.ThemeName+"/panel.css", nil)
pusher.Push("/static/global.js", nil) pusher.Push("/static/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil) pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TO-DO: Push the theme CSS files // TO-DO: Push the theme CSS files
@ -313,42 +316,50 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
return headerVars, stats, true return headerVars, stats, true
} }
func _simple_panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerLite HeaderLite, success bool) { func _simple_panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) {
if !user.Is_Super_Mod { if !user.IsSuperMod {
NoPermissions(w,r,*user) NoPermissions(w, r, *user)
return headerLite, false return headerLite, false
} }
headerLite.Site = site headerLite = &HeaderLite{
headerLite.Settings = settingBox.Load().(SettingBox) Site: site,
Settings: settingBox.Load().(SettingBox),
}
return headerLite, true return headerLite, true
} }
// SimpleSessionCheck is back from the grave, yay :D // SimpleSessionCheck is back from the grave, yay :D
func _simple_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerLite HeaderLite, success bool) { func _simple_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) {
headerLite.Site = site headerLite = &HeaderLite{
headerLite.Settings = settingBox.Load().(SettingBox) Site: site,
Settings: settingBox.Load().(SettingBox),
}
return headerLite, true return headerLite, true
} }
func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, success bool) { func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) {
headerVars.Site = site headerVars = &HeaderVars{
headerVars.Settings = settingBox.Load().(SettingBox) Site: site,
if user.Is_Banned { Settings: settingBox.Load().(SettingBox),
headerVars.NoticeList = append(headerVars.NoticeList,"Your account has been suspended. Some of your permissions may have been revoked.") ThemeName: defaultTheme, // TO-DO: Is this racey?
}
if user.IsBanned {
headerVars.NoticeList = append(headerVars.NoticeList, "Your account has been suspended. Some of your permissions may have been revoked.")
} }
if len(themes[defaultTheme].Resources) != 0 { if len(themes[defaultTheme].Resources) != 0 {
rlist := themes[defaultTheme].Resources rlist := themes[defaultTheme].Resources
for _, resource := range rlist { for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "frontend" { if resource.Location == "global" || resource.Location == "frontend" {
halves := strings.Split(resource.Name,".") halves := strings.Split(resource.Name, ".")
if len(halves) != 2 { if len(halves) != 2 {
continue continue
} }
if halves[1] == "css" { if halves[1] == "css" {
headerVars.Stylesheets = append(headerVars.Stylesheets,resource.Name) headerVars.Stylesheets = append(headerVars.Stylesheets, resource.Name)
} else if halves[1] == "js" { } else if halves[1] == "js" {
headerVars.Scripts = append(headerVars.Scripts,resource.Name) headerVars.Scripts = append(headerVars.Scripts, resource.Name)
} }
} }
} }
@ -356,7 +367,7 @@ func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerV
pusher, ok := w.(http.Pusher) pusher, ok := w.(http.Pusher)
if ok { if ok {
pusher.Push("/static/main.css", nil) pusher.Push("/static/"+headerVars.ThemeName+"/main.css", nil)
pusher.Push("/static/global.js", nil) pusher.Push("/static/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil) pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TO-DO: Push the theme CSS files // TO-DO: Push the theme CSS files
@ -367,27 +378,27 @@ func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerV
return headerVars, true return headerVars, true
} }
func _pre_route(w http.ResponseWriter, r *http.Request) (User,bool) { func _pre_route(w http.ResponseWriter, r *http.Request) (User, bool) {
user, halt := auth.SessionCheck(w,r) user, halt := auth.SessionCheck(w, r)
if halt { if halt {
return *user, false return *user, false
} }
if user == &guest_user { if user == &guestUser {
return *user, true return *user, true
} }
host, _, err := net.SplitHostPort(r.RemoteAddr) host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil { if err != nil {
PreError("Bad IP",w,r) PreError("Bad IP", w, r)
return *user, false return *user, false
} }
if host != user.Last_IP { if host != user.LastIP {
_, err = update_last_ip_stmt.Exec(host, user.ID) _, err = update_last_ip_stmt.Exec(host, user.ID)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err, w)
return *user, false return *user, false
} }
user.Last_IP = host user.LastIP = host
} }
// TO-DO: Set the X-Frame-Options header // TO-DO: Set the X-Frame-Options header
@ -405,91 +416,91 @@ func words_to_score(wcount int, topic bool) (score int) {
if wcount >= settings["megapost_min_words"].(int) { if wcount >= settings["megapost_min_words"].(int) {
score += 4 score += 4
} else if wcount >= settings["bigpost_min_words"].(int) { } else if wcount >= settings["bigpost_min_words"].(int) {
score += 1 score++
} }
return score return score
} }
func increase_post_user_stats(wcount int, uid int, topic bool, user User) error { func increase_post_user_stats(wcount int, uid int, topic bool, user User) error {
var mod int var mod int
base_score := 1 baseScore := 1
if topic { if topic {
_, err := increment_user_topics_stmt.Exec(1, uid) _, err := increment_user_topics_stmt.Exec(1, uid)
if err != nil { if err != nil {
return err return err
} }
base_score = 2 baseScore = 2
} }
settings := settingBox.Load().(SettingBox) settings := settingBox.Load().(SettingBox)
if wcount >= settings["megapost_min_words"].(int) { if wcount >= settings["megapost_min_words"].(int) {
_, err := increment_user_megaposts_stmt.Exec(1,1,1,uid) _, err := increment_user_megaposts_stmt.Exec(1, 1, 1, uid)
if err != nil { if err != nil {
return err return err
} }
mod = 4 mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) { } else if wcount >= settings["bigpost_min_words"].(int) {
_, err := increment_user_bigposts_stmt.Exec(1,1,uid) _, err := increment_user_bigposts_stmt.Exec(1, 1, uid)
if err != nil { if err != nil {
return err return err
} }
mod = 1 mod = 1
} else { } else {
_, err := increment_user_posts_stmt.Exec(1,uid) _, err := increment_user_posts_stmt.Exec(1, uid)
if err != nil { if err != nil {
return err return err
} }
} }
_, err := increment_user_score_stmt.Exec(base_score + mod, uid) _, err := increment_user_score_stmt.Exec(baseScore+mod, uid)
if err != nil { if err != nil {
return err return err
} }
//log.Print(user.Score + base_score + mod) //log.Print(user.Score + base_score + mod)
//log.Print(getLevel(user.Score + base_score + mod)) //log.Print(getLevel(user.Score + base_score + mod))
_, err = update_user_level_stmt.Exec(getLevel(user.Score + base_score + mod), uid) _, err = update_user_level_stmt.Exec(getLevel(user.Score+baseScore+mod), uid)
return err return err
} }
func decrease_post_user_stats(wcount int, uid int, topic bool, user User) error { func decrease_post_user_stats(wcount int, uid int, topic bool, user User) error {
var mod int var mod int
base_score := -1 baseScore := -1
if topic { if topic {
_, err := increment_user_topics_stmt.Exec(-1, uid) _, err := increment_user_topics_stmt.Exec(-1, uid)
if err != nil { if err != nil {
return err return err
} }
base_score = -2 baseScore = -2
} }
settings := settingBox.Load().(SettingBox) settings := settingBox.Load().(SettingBox)
if wcount >= settings["megapost_min_words"].(int) { if wcount >= settings["megapost_min_words"].(int) {
_, err := increment_user_megaposts_stmt.Exec(-1,-1,-1,uid) _, err := increment_user_megaposts_stmt.Exec(-1, -1, -1, uid)
if err != nil { if err != nil {
return err return err
} }
mod = 4 mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) { } else if wcount >= settings["bigpost_min_words"].(int) {
_, err := increment_user_bigposts_stmt.Exec(-1,-1,uid) _, err := increment_user_bigposts_stmt.Exec(-1, -1, uid)
if err != nil { if err != nil {
return err return err
} }
mod = 1 mod = 1
} else { } else {
_, err := increment_user_posts_stmt.Exec(-1,uid) _, err := increment_user_posts_stmt.Exec(-1, uid)
if err != nil { if err != nil {
return err return err
} }
} }
_, err := increment_user_score_stmt.Exec(base_score - mod, uid) _, err := increment_user_score_stmt.Exec(baseScore-mod, uid)
if err != nil { if err != nil {
return err return err
} }
_, err = update_user_level_stmt.Exec(getLevel(user.Score - base_score - mod), uid) _, err = update_user_level_stmt.Exec(getLevel(user.Score-baseScore-mod), uid)
return err return err
} }
func init_user_perms(user *User) { func initUserPerms(user *User) {
if user.Is_Super_Admin { if user.IsSuperAdmin {
user.Perms = AllPerms user.Perms = AllPerms
user.PluginPerms = AllPluginPerms user.PluginPerms = AllPluginPerms
} else { } else {
@ -501,16 +512,16 @@ func init_user_perms(user *User) {
user.Group = user.TempGroup user.Group = user.TempGroup
} }
user.Is_Admin = user.Is_Super_Admin || groups[user.Group].Is_Admin user.IsAdmin = user.IsSuperAdmin || groups[user.Group].IsAdmin
user.Is_Super_Mod = user.Is_Admin || groups[user.Group].Is_Mod user.IsSuperMod = user.IsAdmin || groups[user.Group].IsMod
user.Is_Mod = user.Is_Super_Mod user.IsMod = user.IsSuperMod
user.Is_Banned = groups[user.Group].Is_Banned user.IsBanned = groups[user.Group].IsBanned
if user.Is_Banned && user.Is_Super_Mod { if user.IsBanned && user.IsSuperMod {
user.Is_Banned = false user.IsBanned = false
} }
} }
func build_profile_url(slug string, uid int) string { func buildProfileURL(slug string, uid int) string {
if slug == "" { if slug == "" {
return "/user/" + strconv.Itoa(uid) return "/user/" + strconv.Itoa(uid)
} }

View File

@ -1,12 +1,12 @@
package main package main
import ( import (
"log"
"sync"
"errors"
"strings"
"strconv"
"database/sql" "database/sql"
"errors"
"log"
"strconv"
"strings"
"sync"
"./query_gen/lib" "./query_gen/lib"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -14,7 +14,7 @@ import (
// TO-DO: Add the watchdog goroutine // TO-DO: Add the watchdog goroutine
var users UserStore var users UserStore
var err_account_exists = errors.New("This username is already in use.") var errAccountExists = errors.New("this username is already in use")
type UserStore interface { type UserStore interface {
Load(id int) error Load(id int) error
@ -36,46 +36,46 @@ type UserStore interface {
} }
type MemoryUserStore struct { type MemoryUserStore struct {
items map[int]*User items map[int]*User
length int length int
capacity int capacity int
get *sql.Stmt get *sql.Stmt
register *sql.Stmt register *sql.Stmt
username_exists *sql.Stmt username_exists *sql.Stmt
user_count *sql.Stmt user_count *sql.Stmt
sync.RWMutex sync.RWMutex
} }
func NewMemoryUserStore(capacity int) *MemoryUserStore { func NewMemoryUserStore(capacity int) *MemoryUserStore {
get_stmt, 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 = ?","","") get_stmt, 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 {
log.Fatal(err) log.Fatal(err)
} }
// Add an admin version of register_stmt with more flexibility? // Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO // create_account_stmt, err = db.Prepare("INSERT INTO
register_stmt, err := qgen.Builder.SimpleInsert("users","name, email, password, salt, group, is_super_admin, session, active, message","?,?,?,?,?,0,'',?,''") register_stmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message", "?,?,?,?,?,0,'',?,''")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
username_exists_stmt, err := qgen.Builder.SimpleSelect("users","name","name = ?","","") username_exists_stmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
user_count_stmt, err := qgen.Builder.SimpleCount("users","","") user_count_stmt, err := qgen.Builder.SimpleCount("users", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &MemoryUserStore{ return &MemoryUserStore{
items: make(map[int]*User), items: make(map[int]*User),
capacity: capacity, capacity: capacity,
get: get_stmt, get: get_stmt,
register: register_stmt, register: register_stmt,
username_exists: username_exists_stmt, username_exists: username_exists_stmt,
user_count: user_count_stmt, user_count: user_count_stmt,
} }
} }
@ -105,19 +105,19 @@ func (sus *MemoryUserStore) CascadeGet(id int) (*User, error) {
return user, nil return user, nil
} }
user = &User{ID:id,Loggedin:true} user = &User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" { if user.Avatar != "" {
if user.Avatar[0] == '.' { if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(user) initUserPerms(user)
if err == nil { if err == nil {
sus.Set(user) sus.Set(user)
} }
@ -126,7 +126,7 @@ func (sus *MemoryUserStore) CascadeGet(id int) (*User, error) {
// WARNING: We did a little hack to make this as thin and quick as possible to reduce lock contention, use the * Cascade* methods instead for normal use // WARNING: We did a little hack to make this as thin and quick as possible to reduce lock contention, use the * Cascade* methods instead for normal use
func (sus *MemoryUserStore) bulkGet(ids []int) (list []*User) { func (sus *MemoryUserStore) bulkGet(ids []int) (list []*User) {
list = make([]*User,len(ids)) list = make([]*User, len(ids))
sus.RLock() sus.RLock()
for i, id := range ids { for i, id := range ids {
list[i] = sus.items[id] list[i] = sus.items[id]
@ -138,22 +138,22 @@ func (sus *MemoryUserStore) bulkGet(ids []int) (list []*User) {
// TO-DO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts? // TO-DO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
// TO-DO: ID of 0 should always error? // TO-DO: ID of 0 should always error?
func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) { func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) {
var id_count int = len(ids) var idCount int = len(ids)
list = make(map[int]*User) list = make(map[int]*User)
if id_count == 0 { if idCount == 0 {
return list, nil return list, nil
} }
var still_here []int var stillHere []int
slice_list := sus.bulkGet(ids) sliceList := sus.bulkGet(ids)
for i, slice_item := range slice_list { for i, sliceItem := range sliceList {
if slice_item != nil { if sliceItem != nil {
list[slice_item.ID] = slice_item list[sliceItem.ID] = sliceItem
} else { } else {
still_here = append(still_here,ids[i]) stillHere = append(stillHere, ids[i])
} }
} }
ids = still_here ids = stillHere
// If every user is in the cache, then return immediately // If every user is in the cache, then return immediately
if len(ids) == 0 { if len(ids) == 0 {
@ -163,12 +163,12 @@ func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, er
var qlist string var qlist string
var uidList []interface{} var uidList []interface{}
for _, id := range ids { for _, id := range ids {
uidList = append(uidList,strconv.Itoa(id)) uidList = append(uidList, strconv.Itoa(id))
qlist += "?," qlist += "?,"
} }
qlist = qlist[0:len(qlist) - 1] qlist = qlist[0 : len(qlist)-1]
stmt, err := qgen.Builder.SimpleSelect("users","uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group","uid IN("+qlist+")","","") stmt, err := qgen.Builder.SimpleSelect("users", "uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid IN("+qlist+")", "", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -179,8 +179,8 @@ func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, er
} }
for rows.Next() { for rows.Next() {
user := &User{Loggedin:true} user := &User{Loggedin: true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -191,67 +191,67 @@ func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, er
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),user.ID) user.Link = buildProfileURL(nameToSlug(user.Name), user.ID)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(user) initUserPerms(user)
// Add it to the cache... // Add it to the cache...
sus.Set(user) _ = sus.Set(user)
// Add it to the list to be returned // Add it to the list to be returned
list[user.ID] = user list[user.ID] = user
} }
// Did we miss any users? // Did we miss any users?
if id_count > len(list) { if idCount > len(list) {
var sid_list string var sidList string
for _, id := range ids { for _, id := range ids {
_, ok := list[id] _, ok := list[id]
if !ok { if !ok {
sid_list += strconv.Itoa(id) + "," sidList += strconv.Itoa(id) + ","
} }
} }
// We probably don't need this, but it might be useful in case of bugs in BulkCascadeGetMap // We probably don't need this, but it might be useful in case of bugs in BulkCascadeGetMap
if sid_list == "" { if sidList == "" {
if dev.DebugMode { if dev.DebugMode {
log.Print("This data is sampled later in the BulkCascadeGetMap function, so it might miss the cached IDs") log.Print("This data is sampled later in the BulkCascadeGetMap function, so it might miss the cached IDs")
log.Print("id_count",id_count) log.Print("idCount", idCount)
log.Print("ids",ids) log.Print("ids", ids)
log.Print("list",list) log.Print("list", list)
} }
return list, errors.New("We weren't able to find a user, but we don't know which one") return list, errors.New("We weren't able to find a user, but we don't know which one")
} }
sid_list = sid_list[0:len(sid_list) - 1] sidList = sidList[0 : len(sidList)-1]
return list, errors.New("Unable to find the users with the following IDs: " + sid_list) return list, errors.New("Unable to find the users with the following IDs: " + sidList)
} }
return list, nil return list, nil
} }
func (sus *MemoryUserStore) BypassGet(id int) (*User, error) { func (sus *MemoryUserStore) BypassGet(id int) (*User, error) {
user := &User{ID:id,Loggedin:true} user := &User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" { if user.Avatar != "" {
if user.Avatar[0] == '.' { if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(user) initUserPerms(user)
return user, err return user, err
} }
func (sus *MemoryUserStore) Load(id int) error { func (sus *MemoryUserStore) Load(id int) error {
user := &User{ID:id,Loggedin:true} user := &User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil { if err != nil {
sus.Remove(id) sus.Remove(id)
return err return err
@ -262,12 +262,12 @@ func (sus *MemoryUserStore) Load(id int) error {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(user) initUserPerms(user)
sus.Set(user) _ = sus.Set(user)
return nil return nil
} }
@ -310,14 +310,14 @@ func (sus *MemoryUserStore) AddUnsafe(item *User) error {
func (sus *MemoryUserStore) Remove(id int) error { func (sus *MemoryUserStore) Remove(id int) error {
sus.Lock() sus.Lock()
delete(sus.items,id) delete(sus.items, id)
sus.Unlock() sus.Unlock()
sus.length-- sus.length--
return nil return nil
} }
func (sus *MemoryUserStore) RemoveUnsafe(id int) error { func (sus *MemoryUserStore) RemoveUnsafe(id int) error {
delete(sus.items,id) delete(sus.items, id)
sus.length-- sus.length--
return nil return nil
} }
@ -326,7 +326,7 @@ func (sus *MemoryUserStore) CreateUser(username string, password string, email s
// Is this username already taken..? // Is this username already taken..?
err := sus.username_exists.QueryRow(username).Scan(&username) err := sus.username_exists.QueryRow(username).Scan(&username)
if err != ErrNoRows { if err != ErrNoRows {
return 0, err_account_exists return 0, errAccountExists
} }
salt, err := GenerateSafeString(saltLength) salt, err := GenerateSafeString(saltLength)
@ -334,18 +334,18 @@ func (sus *MemoryUserStore) CreateUser(username string, password string, email s
return 0, err return 0, err
} }
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password + salt), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password+salt), bcrypt.DefaultCost)
if err != nil { if err != nil {
return 0, err return 0, err
} }
res, err := sus.register.Exec(username,email,string(hashed_password),salt,group,active) res, err := sus.register.Exec(username, email, string(hashedPassword), salt, group, active)
if err != nil { if err != nil {
return 0, err return 0, err
} }
lastId, err := res.LastInsertId() lastID, err := res.LastInsertId()
return int(lastId), err return int(lastID), err
} }
func (sus *MemoryUserStore) GetLength() int { func (sus *MemoryUserStore) GetLength() int {
@ -371,91 +371,91 @@ func (sus *MemoryUserStore) GetGlobalCount() int {
} }
type SqlUserStore struct { type SqlUserStore struct {
get *sql.Stmt get *sql.Stmt
register *sql.Stmt register *sql.Stmt
username_exists *sql.Stmt usernameExists *sql.Stmt
user_count *sql.Stmt userCount *sql.Stmt
} }
func NewSqlUserStore() *SqlUserStore { func NewSqlUserStore() *SqlUserStore {
get_stmt, 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 {
log.Fatal(err) log.Fatal(err)
} }
// Add an admin version of register_stmt with more flexibility? // Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO // create_account_stmt, err = db.Prepare("INSERT INTO
register_stmt, err := qgen.Builder.SimpleInsert("users","name, email, password, salt, group, is_super_admin, session, active, message","?,?,?,?,?,0,'',?,''") registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message", "?,?,?,?,?,0,'',?,''")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
username_exists_stmt, err := qgen.Builder.SimpleSelect("users","name","name = ?","","") usernameExistsStmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
user_count_stmt, err := qgen.Builder.SimpleCount("users","","") userCountStmt, err := qgen.Builder.SimpleCount("users", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &SqlUserStore{ return &SqlUserStore{
get: get_stmt, get: getStmt,
register: register_stmt, register: registerStmt,
username_exists: username_exists_stmt, usernameExists: usernameExistsStmt,
user_count: user_count_stmt, userCount: userCountStmt,
} }
} }
func (sus *SqlUserStore) Get(id int) (*User, error) { func (sus *SqlUserStore) Get(id int) (*User, error) {
user := User{ID:id,Loggedin:true} user := User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" { if user.Avatar != "" {
if user.Avatar[0] == '.' { if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(&user) initUserPerms(&user)
return &user, err return &user, err
} }
func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) { func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) {
user := User{ID:id,Loggedin:true} user := User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" { if user.Avatar != "" {
if user.Avatar[0] == '.' { if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(&user) initUserPerms(&user)
return &user, err return &user, err
} }
func (sus *SqlUserStore) CascadeGet(id int) (*User, error) { func (sus *SqlUserStore) CascadeGet(id int) (*User, error) {
user := User{ID:id,Loggedin:true} user := User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" { if user.Avatar != "" {
if user.Avatar[0] == '.' { if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(&user) initUserPerms(&user)
return &user, err return &user, err
} }
@ -464,12 +464,12 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
var qlist string var qlist string
var uidList []interface{} var uidList []interface{}
for _, id := range ids { for _, id := range ids {
uidList = append(uidList,strconv.Itoa(id)) uidList = append(uidList, strconv.Itoa(id))
qlist += "?," qlist += "?,"
} }
qlist = qlist[0:len(qlist) - 1] qlist = qlist[0 : len(qlist)-1]
stmt, err := qgen.Builder.SimpleSelect("users","uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group","uid IN("+qlist+")","","") stmt, err := qgen.Builder.SimpleSelect("users", "uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid IN("+qlist+")", "", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -481,8 +481,8 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
list = make(map[int]*User) list = make(map[int]*User)
for rows.Next() { for rows.Next() {
user := &User{Loggedin:true} user := &User{Loggedin: true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -493,11 +493,11 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),user.ID) user.Link = buildProfileURL(nameToSlug(user.Name), user.ID)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(user) initUserPerms(user)
// Add it to the list to be returned // Add it to the list to be returned
list[user.ID] = user list[user.ID] = user
@ -507,33 +507,33 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
} }
func (sus *SqlUserStore) BypassGet(id int) (*User, error) { func (sus *SqlUserStore) BypassGet(id int) (*User, error) {
user := User{ID:id,Loggedin:true} user := User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" { if user.Avatar != "" {
if user.Avatar[0] == '.' { if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
} }
} else { } else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = build_profile_url(name_to_slug(user.Name),id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = groups[user.Group].Tag
init_user_perms(&user) initUserPerms(&user)
return &user, err return &user, err
} }
func (sus *SqlUserStore) Load(id int) error { func (sus *SqlUserStore) Load(id int) error {
user := &User{ID:id} user := &User{ID: id}
// Simplify this into a quick check to see whether the user exists. Add an Exists method to facilitate this? // Simplify this into a quick check to see whether the user exists. Add an Exists method to facilitate this?
return sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup) return sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
} }
func (sus *SqlUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) { func (sus *SqlUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) {
// Is this username already taken..? // Is this username already taken..?
err := sus.username_exists.QueryRow(username).Scan(&username) err := sus.usernameExists.QueryRow(username).Scan(&username)
if err != ErrNoRows { if err != ErrNoRows {
return 0, err_account_exists return 0, errAccountExists
} }
salt, err := GenerateSafeString(saltLength) salt, err := GenerateSafeString(saltLength)
@ -541,18 +541,18 @@ func (sus *SqlUserStore) CreateUser(username string, password string, email stri
return 0, err return 0, err
} }
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password + salt), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password+salt), bcrypt.DefaultCost)
if err != nil { if err != nil {
return 0, err return 0, err
} }
res, err := sus.register.Exec(username,email,string(hashed_password),salt,group,active) res, err := sus.register.Exec(username, email, string(hashedPassword), salt, group, active)
if err != nil { if err != nil {
return 0, err return 0, err
} }
lastId, err := res.LastInsertId() lastID, err := res.LastInsertId()
return int(lastId), err return int(lastID), err
} }
// Placeholder methods, as we're not don't need to do any cache management with this implementation ofr the UserStore // Placeholder methods, as we're not don't need to do any cache management with this implementation ofr the UserStore
@ -578,7 +578,7 @@ func (sus *SqlUserStore) GetCapacity() int {
// Return the total number of users registered on the forums // Return the total number of users registered on the forums
func (sus *SqlUserStore) GetLength() int { func (sus *SqlUserStore) GetLength() int {
var ucount int var ucount int
err := sus.user_count.QueryRow().Scan(&ucount) err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
@ -586,7 +586,7 @@ func (sus *SqlUserStore) GetLength() int {
} }
func (sus *SqlUserStore) GetGlobalCount() int { func (sus *SqlUserStore) GetGlobalCount() int {
var ucount int var ucount int
err := sus.user_count.QueryRow().Scan(&ucount) err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }

205
utils.go
View File

@ -1,25 +1,25 @@
package main package main
import ( import (
"fmt"
"time"
"os"
"math"
"errors"
"strings"
"unicode"
"strconv"
"encoding/base64"
"crypto/rand" "crypto/rand"
"encoding/base64"
"errors"
"fmt"
"math"
"net/smtp" "net/smtp"
"os"
"strconv"
"strings"
"time"
"unicode"
) )
type Version struct // Version stores a Gosora version
{ type Version struct {
Major int Major int
Minor int Minor int
Patch int Patch int
Tag string Tag string
TagID int TagID int
} }
@ -34,9 +34,9 @@ func (version *Version) String() (out string) {
return return
} }
// Generate a cryptographically secure set of random bytes.. // GenerateSafeString is for generating a cryptographically secure set of random bytes..
func GenerateSafeString(length int) (string, error) { func GenerateSafeString(length int) (string, error) {
rb := make([]byte,length) rb := make([]byte, length)
_, err := rand.Read(rb) _, err := rand.Read(rb)
if err != nil { if err != nil {
return "", err return "", err
@ -44,7 +44,7 @@ func GenerateSafeString(length int) (string, error) {
return base64.URLEncoding.EncodeToString(rb), nil return base64.URLEncoding.EncodeToString(rb), nil
} }
func relative_time(in string) (string, error) { func relativeTime(in string) (string, error) {
if in == "" { if in == "" {
return "", nil return "", nil
} }
@ -61,41 +61,63 @@ func relative_time(in string) (string, error) {
weeks := int(hours / 24 / 7) weeks := int(hours / 24 / 7)
months := int(hours / 24 / 31) months := int(hours / 24 / 31)
switch { switch {
case months > 11: case months > 11:
//return t.Format("Mon Jan 2 2006"), err //return t.Format("Mon Jan 2 2006"), err
return t.Format("Jan 2 2006"), err return t.Format("Jan 2 2006"), err
case months > 1: return fmt.Sprintf("%d months ago", months), err case months > 1:
case months == 1: return "a month ago", err return fmt.Sprintf("%d months ago", months), err
case weeks > 1: return fmt.Sprintf("%d weeks ago", weeks), err case months == 1:
case int(hours / 24) == 7: return "a week ago", err return "a month ago", err
case int(hours / 24) == 1: return "1 day ago", err case weeks > 1:
case int(hours / 24) > 1: return fmt.Sprintf("%d days ago", int(hours / 24)), err return fmt.Sprintf("%d weeks ago", weeks), err
case seconds <= 1: return "a moment ago", err case int(hours/24) == 7:
case seconds < 60: return fmt.Sprintf("%d seconds ago", int(seconds)), err return "a week ago", err
case seconds < 120: return "a minute ago", err case int(hours/24) == 1:
case seconds < 3600: return fmt.Sprintf("%d minutes ago", int(seconds / 60)), err return "1 day ago", err
case seconds < 7200: return "an hour ago", err case int(hours/24) > 1:
default: return fmt.Sprintf("%d hours ago", int(seconds / 60 / 60)), err return fmt.Sprintf("%d days ago", int(hours/24)), err
case seconds <= 1:
return "a moment ago", err
case seconds < 60:
return fmt.Sprintf("%d seconds ago", int(seconds)), err
case seconds < 120:
return "a minute ago", err
case seconds < 3600:
return fmt.Sprintf("%d minutes ago", int(seconds/60)), err
case seconds < 7200:
return "an hour ago", err
default:
return fmt.Sprintf("%d hours ago", int(seconds/60/60)), err
} }
} }
func convert_byte_unit(bytes float64) (float64,string) { func convertByteUnit(bytes float64) (float64, string) {
switch { switch {
case bytes >= float64(terabyte): return bytes / float64(terabyte), "TB" case bytes >= float64(terabyte):
case bytes >= float64(gigabyte): return bytes / float64(gigabyte), "GB" return bytes / float64(terabyte), "TB"
case bytes >= float64(megabyte): return bytes / float64(megabyte), "MB" case bytes >= float64(gigabyte):
case bytes >= float64(kilobyte): return bytes / float64(kilobyte), "KB" return bytes / float64(gigabyte), "GB"
default: return bytes, " bytes" case bytes >= float64(megabyte):
return bytes / float64(megabyte), "MB"
case bytes >= float64(kilobyte):
return bytes / float64(kilobyte), "KB"
default:
return bytes, " bytes"
} }
} }
func convert_byte_in_unit(bytes float64,unit string) (count float64) { func convertByteInUnit(bytes float64, unit string) (count float64) {
switch(unit) { switch unit {
case "TB": count = bytes / float64(terabyte) case "TB":
case "GB": count = bytes / float64(gigabyte) count = bytes / float64(terabyte)
case "MB": count = bytes / float64(megabyte) case "GB":
case "KB": count = bytes / float64(kilobyte) count = bytes / float64(gigabyte)
default: count = 0.1 case "MB":
count = bytes / float64(megabyte)
case "KB":
count = bytes / float64(kilobyte)
default:
count = 0.1
} }
if count < 0.1 { if count < 0.1 {
@ -104,29 +126,39 @@ func convert_byte_in_unit(bytes float64,unit string) (count float64) {
return return
} }
func convert_unit(num int) (int,string) { func convertUnit(num int) (int, string) {
switch { switch {
case num >= 1000000000000: return 0, "∞" case num >= 1000000000000:
case num >= 1000000000: return num / 1000000000, "B" return 0, "∞"
case num >= 1000000: return num / 1000000, "M" case num >= 1000000000:
case num >= 1000: return num / 1000, "K" return num / 1000000000, "B"
default: return num, "" case num >= 1000000:
return num / 1000000, "M"
case num >= 1000:
return num / 1000, "K"
default:
return num, ""
} }
} }
func convert_friendly_unit(num int) (int,string) { func convertFriendlyUnit(num int) (int, string) {
switch { switch {
case num >= 1000000000000: return 0, " zillion" case num >= 1000000000000:
case num >= 1000000000: return num / 1000000000, " billion" return 0, " zillion"
case num >= 1000000: return num / 1000000, " million" case num >= 1000000000:
case num >= 1000: return num / 1000, " thousand" return num / 1000000000, " billion"
default: return num, "" case num >= 1000000:
return num / 1000000, " million"
case num >= 1000:
return num / 1000, " thousand"
default:
return num, ""
} }
} }
func name_to_slug(name string) (slug string) { func nameToSlug(name string) (slug string) {
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
name = strings.Replace(name," "," ",-1) name = strings.Replace(name, " ", " ", -1)
for _, char := range name { for _, char := range name {
if unicode.IsLower(char) || unicode.IsNumber(char) { if unicode.IsLower(char) || unicode.IsNumber(char) {
@ -157,7 +189,7 @@ func SendEmail(email string, subject string, msg string) (res bool) {
} }
if config.SmtpUsername != "" { if config.SmtpUsername != "" {
auth := smtp.PlainAuth("",config.SmtpUsername,config.SmtpPassword,config.SmtpServer) auth := smtp.PlainAuth("", config.SmtpUsername, config.SmtpPassword, config.SmtpServer)
err = con.Auth(auth) err = con.Auth(auth)
if err != nil { if err != nil {
return return
@ -193,12 +225,12 @@ func SendEmail(email string, subject string, msg string) (res bool) {
return true return true
} }
func weak_password(password string) error { func weakPassword(password string) error {
if len(password) < 8 { if len(password) < 8 {
return errors.New("Your password needs to be at-least eight characters long.") return errors.New("your password needs to be at-least eight characters long")
} }
var charMap map[rune]int = make(map[rune]int) var charMap map[rune]int = make(map[rune]int)
var numbers, /*letters, */symbols, upper, lower int var numbers /*letters, */, symbols, upper, lower int
for _, char := range password { for _, char := range password {
charItem, ok := charMap[char] charItem, ok := charMap[char]
if ok { if ok {
@ -222,35 +254,38 @@ func weak_password(password string) error {
} }
} }
// TO-DO: Disable the linter on these and fix up the grammar
if numbers == 0 { if numbers == 0 {
return errors.New("You don't have any numbers in your password.") return errors.New("you don't have any numbers in your password")
} }
/*if letters == 0 { /*if letters == 0 {
return errors.New("You don't have any letters in your password.") return errors.New("You don't have any letters in your password.")
}*/ }*/
if upper == 0 { if upper == 0 {
return errors.New("You don't have any uppercase characters in your password.") return errors.New("you don't have any uppercase characters in your password")
} }
if lower == 0 { if lower == 0 {
return errors.New("You don't have any lowercase characters in your password.") return errors.New("you don't have any lowercase characters in your password")
} }
if (len(password) / 2) > len(charMap) { if (len(password) / 2) > len(charMap) {
return errors.New("You don't have enough unique characters in your password.") return errors.New("you don't have enough unique characters in your password")
} }
if strings.Contains(strings.ToLower(password),"test") || /*strings.Contains(strings.ToLower(password),"123456") || */strings.Contains(strings.ToLower(password),"123") || strings.Contains(strings.ToLower(password),"password") || strings.Contains(strings.ToLower(password),"qwerty") { if strings.Contains(strings.ToLower(password), "test") || /*strings.Contains(strings.ToLower(password),"123456") || */ strings.Contains(strings.ToLower(password), "123") || strings.Contains(strings.ToLower(password), "password") || strings.Contains(strings.ToLower(password), "qwerty") {
return errors.New("You may not have 'test', '123', 'password' or 'qwerty' in your password.") return errors.New("you may not have 'test', '123', 'password' or 'qwerty' in your password")
} }
return nil return nil
} }
func create_file(name string) error { func createFile(name string) error {
f, err := os.Create(name) f, err := os.Create(name)
f.Close() if err != nil {
return err return err
}
return f.Close()
} }
func write_file(name string, content string) (err error) { func writeFile(name string, content string) (err error) {
f, err := os.Create(name) f, err := os.Create(name)
if err != nil { if err != nil {
return err return err
@ -259,12 +294,14 @@ func write_file(name string, content string) (err error) {
if err != nil { if err != nil {
return err return err
} }
f.Sync() err = f.Sync()
f.Close() if err != nil {
return return err
}
return f.Close()
} }
func word_count(input string) (count int) { func wordCount(input string) (count int) {
input = strings.TrimSpace(input) input = strings.TrimSpace(input)
if input == "" { if input == "" {
return 0 return 0
@ -288,7 +325,7 @@ func getLevel(score int) (level int) {
var current, prev float64 var current, prev float64
exp_factor := 2.8 exp_factor := 2.8
for i := 1;;i++ { for i := 1; ; i++ {
_, bit := math.Modf(float64(i) / 10) _, bit := math.Modf(float64(i) / 10)
if bit == 0 { if bit == 0 {
exp_factor += 0.1 exp_factor += 0.1
@ -309,7 +346,7 @@ func getLevelScore(getLevel int) (score int) {
var level int var level int
exp_factor := 2.8 exp_factor := 2.8
for i := 1;;i++ { for i := 1; ; i++ {
_, bit := math.Modf(float64(i) / 10) _, bit := math.Modf(float64(i) / 10)
if bit == 0 { if bit == 0 {
exp_factor += 0.1 exp_factor += 0.1
@ -331,7 +368,7 @@ func getLevels(maxLevel int) []float64 {
var out []float64 var out []float64
out = append(out, 0) out = append(out, 0)
for i := 1;i <= maxLevel;i++ { for i := 1; i <= maxLevel; i++ {
_, bit := math.Modf(float64(i) / 10) _, bit := math.Modf(float64(i) / 10)
if bit == 0 { if bit == 0 {
exp_factor += 0.1 exp_factor += 0.1
@ -343,22 +380,22 @@ func getLevels(maxLevel int) []float64 {
return out return out
} }
func fill_group_id_gap(biggerID int, smallerID int) { func fillGroupIDGap(biggerID int, smallerID int) {
dummy := Group{ID:0, Name:""} dummy := Group{ID: 0, Name: ""}
for i := smallerID; i > biggerID; i++ { for i := smallerID; i > biggerID; i++ {
groups = append(groups, dummy) groups = append(groups, dummy)
} }
} }
func build_slug(slug string, id int) string { func buildSlug(slug string, id int) string {
if slug == "" { if slug == "" {
return strconv.Itoa(id) return strconv.Itoa(id)
} }
return slug + "." + strconv.Itoa(id) return slug + "." + strconv.Itoa(id)
} }
func addModLog(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { func addModLog(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
_, err = add_modlog_entry_stmt.Exec(action,elementID,elementType,ipaddress,actorID) _, err = add_modlog_entry_stmt.Exec(action, elementID, elementType, ipaddress, actorID)
if err != nil { if err != nil {
return err return err
} }
@ -366,7 +403,7 @@ func addModLog(action string, elementID int, elementType string, ipaddress strin
} }
func addAdminLog(action string, elementID string, elementType int, ipaddress string, actorID int) (err error) { func addAdminLog(action string, elementID string, elementType int, ipaddress string, actorID int) (err error) {
_, err = add_adminlog_entry_stmt.Exec(action,elementID,elementType,ipaddress,actorID) _, err = add_adminlog_entry_stmt.Exec(action, elementID, elementType, ipaddress, actorID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -2,64 +2,62 @@
package main package main
import( import (
"bytes"
"errors"
"fmt" "fmt"
"net/http"
"runtime"
"strconv"
"sync" "sync"
"time" "time"
"bytes"
"strconv"
"errors"
"runtime"
"net/http"
"github.com/gorilla/websocket"
"github.com/Azareal/gopsutil/cpu" "github.com/Azareal/gopsutil/cpu"
"github.com/Azareal/gopsutil/mem" "github.com/Azareal/gopsutil/mem"
"github.com/gorilla/websocket"
) )
type WS_User struct type WS_User struct {
{
conn *websocket.Conn conn *websocket.Conn
User *User User *User
} }
type WS_Hub struct type WS_Hub struct {
{ onlineUsers map[int]*WS_User
online_users map[int]*WS_User onlineGuests map[*WS_User]bool
online_guests map[*WS_User]bool guests sync.RWMutex
guests sync.RWMutex users sync.RWMutex
users sync.RWMutex
} }
var ws_hub WS_Hub var wsHub WS_Hub
var ws_upgrader = websocket.Upgrader{ReadBufferSize:1024,WriteBufferSize:1024} var wsUpgrader = websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}
var ws_nouser error = errors.New("This user isn't connected via WebSockets") var errWsNouser = errors.New("This user isn't connected via WebSockets")
func init() { func init() {
enable_websockets = true enableWebsockets = true
admin_stats_watchers = make(map[*WS_User]bool) adminStatsWatchers = make(map[*WS_User]bool)
ws_hub = WS_Hub{ wsHub = WS_Hub{
online_users: make(map[int]*WS_User), onlineUsers: make(map[int]*WS_User),
online_guests: make(map[*WS_User]bool), onlineGuests: make(map[*WS_User]bool),
} }
} }
func (hub *WS_Hub) guest_count() int { func (hub *WS_Hub) guestCount() int {
defer hub.guests.RUnlock() defer hub.guests.RUnlock()
hub.guests.RLock() hub.guests.RLock()
return len(hub.online_guests) return len(hub.onlineGuests)
} }
func (hub *WS_Hub) user_count() int { func (hub *WS_Hub) userCount() int {
defer hub.users.RUnlock() defer hub.users.RUnlock()
hub.users.RLock() hub.users.RLock()
return len(hub.online_users) return len(hub.onlineUsers)
} }
func (hub *WS_Hub) broadcast_message(msg string) error { func (hub *WS_Hub) broadcastMessage(msg string) error {
hub.users.RLock() hub.users.RLock()
for _, ws_user := range hub.online_users { for _, wsUser := range hub.onlineUsers {
w, err := ws_user.conn.NextWriter(websocket.TextMessage) w, err := wsUser.conn.NextWriter(websocket.TextMessage)
if err != nil { if err != nil {
return err return err
} }
@ -69,15 +67,15 @@ func (hub *WS_Hub) broadcast_message(msg string) error {
return nil return nil
} }
func (hub *WS_Hub) push_message(targetUser int, msg string) error { func (hub *WS_Hub) pushMessage(targetUser int, msg string) error {
hub.users.RLock() hub.users.RLock()
ws_user, ok := hub.online_users[targetUser] wsUser, ok := hub.onlineUsers[targetUser]
hub.users.RUnlock() hub.users.RUnlock()
if !ok { if !ok {
return ws_nouser return errWsNouser
} }
w, err := ws_user.conn.NextWriter(websocket.TextMessage) w, err := wsUser.conn.NextWriter(websocket.TextMessage)
if err != nil { if err != nil {
return err return err
} }
@ -87,23 +85,23 @@ func (hub *WS_Hub) push_message(targetUser int, msg string) error {
return nil return nil
} }
func(hub *WS_Hub) push_alert(targetUser int, asid int, event string, elementType string, actor_id int, targetUser_id int, elementID int) error { func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUser_id int, elementID int) error {
//log.Print("In push_alert") //log.Print("In push_alert")
hub.users.RLock() hub.users.RLock()
ws_user, ok := hub.online_users[targetUser] wsUser, ok := hub.onlineUsers[targetUser]
hub.users.RUnlock() hub.users.RUnlock()
if !ok { if !ok {
return ws_nouser return errWsNouser
} }
//log.Print("Building alert") //log.Print("Building alert")
alert, err := build_alert(asid, event, elementType, actor_id, targetUser_id, elementID, *ws_user.User) alert, err := buildAlert(asid, event, elementType, actorID, targetUser_id, elementID, *wsUser.User)
if err != nil { if err != nil {
return err return err
} }
//log.Print("Getting WS Writer") //log.Print("Getting WS Writer")
w, err := ws_user.conn.NextWriter(websocket.TextMessage) w, err := wsUser.conn.NextWriter(websocket.TextMessage)
if err != nil { if err != nil {
return err return err
} }
@ -113,35 +111,35 @@ func(hub *WS_Hub) push_alert(targetUser int, asid int, event string, elementType
return nil return nil
} }
func(hub *WS_Hub) push_alerts(users []int, asid int, event string, elementType string, actor_id int, targetUser_id int, elementID int) error { func (hub *WS_Hub) pushAlerts(users []int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
//log.Print("In push_alerts") //log.Print("In pushAlerts")
var ws_users []*WS_User var wsUsers []*WS_User
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 {
ws_users = append(ws_users, hub.online_users[uid]) wsUsers = append(wsUsers, hub.onlineUsers[uid])
} }
hub.users.RUnlock() hub.users.RUnlock()
if len(ws_users) == 0 { if len(wsUsers) == 0 {
return ws_nouser return errWsNouser
} }
var errs []error var errs []error
for _, ws_user := range ws_users { for _, wsUser := range wsUsers {
if ws_user == nil { if wsUser == nil {
continue continue
} }
//log.Print("Building alert") //log.Print("Building alert")
alert, err := build_alert(asid, event, elementType, actor_id, targetUser_id, elementID, *ws_user.User) alert, err := buildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User)
if err != nil { if err != nil {
errs = append(errs,err) errs = append(errs, err)
} }
//log.Print("Getting WS Writer") //log.Print("Getting WS Writer")
w, err := ws_user.conn.NextWriter(websocket.TextMessage) w, err := wsUser.conn.NextWriter(websocket.TextMessage)
if err != nil { if err != nil {
errs = append(errs,err) errs = append(errs, err)
} }
w.Write([]byte(alert)) w.Write([]byte(alert))
@ -158,7 +156,7 @@ func(hub *WS_Hub) push_alerts(users []int, asid int, event string, elementType s
} }
func route_websockets(w http.ResponseWriter, r *http.Request, user User) { func route_websockets(w http.ResponseWriter, r *http.Request, user User) {
conn, err := ws_upgrader.Upgrade(w,r,nil) conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
return return
} }
@ -167,53 +165,53 @@ func route_websockets(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
ws_user := &WS_User{conn,userptr} wsUser := &WS_User{conn, userptr}
if user.ID == 0 { if user.ID == 0 {
ws_hub.guests.Lock() wsHub.guests.Lock()
ws_hub.online_guests[ws_user] = true wsHub.onlineGuests[wsUser] = true
ws_hub.guests.Unlock() wsHub.guests.Unlock()
} else { } else {
ws_hub.users.Lock() wsHub.users.Lock()
ws_hub.online_users[user.ID] = ws_user wsHub.onlineUsers[user.ID] = wsUser
ws_hub.users.Unlock() wsHub.users.Unlock()
} }
//conn.SetReadLimit(/* put the max request size from earlier here? */) //conn.SetReadLimit(/* put the max request size from earlier here? */)
//conn.SetReadDeadline(time.Now().Add(60 * time.Second)) //conn.SetReadDeadline(time.Now().Add(60 * time.Second))
var current_page []byte var currentPage []byte
for { for {
_, message, err := conn.ReadMessage() _, message, err := conn.ReadMessage()
if err != nil { if err != nil {
if user.ID == 0 { if user.ID == 0 {
ws_hub.guests.Lock() wsHub.guests.Lock()
delete(ws_hub.online_guests,ws_user) delete(wsHub.onlineGuests, wsUser)
ws_hub.guests.Unlock() wsHub.guests.Unlock()
} else { } else {
ws_hub.users.Lock() wsHub.users.Lock()
delete(ws_hub.online_users,user.ID) delete(wsHub.onlineUsers, user.ID)
ws_hub.users.Unlock() wsHub.users.Unlock()
} }
break break
} }
//log.Print("Message",message) //log.Print("Message",message)
//log.Print("string(Message)",string(message)) //log.Print("string(Message)",string(message))
messages := bytes.Split(message,[]byte("\r")) messages := bytes.Split(message, []byte("\r"))
for _, msg := range messages { for _, msg := range messages {
//log.Print("Submessage",msg) //log.Print("Submessage",msg)
//log.Print("Submessage",string(msg)) //log.Print("Submessage",string(msg))
if bytes.HasPrefix(msg,[]byte("page ")) { if bytes.HasPrefix(msg, []byte("page ")) {
msgblocks := bytes.SplitN(msg,[]byte(" "),2) msgblocks := bytes.SplitN(msg, []byte(" "), 2)
if len(msgblocks) < 2 { if len(msgblocks) < 2 {
continue continue
} }
if !bytes.Equal(msgblocks[1],current_page) { if !bytes.Equal(msgblocks[1], currentPage) {
ws_leave_page(ws_user, current_page) wsLeavePage(wsUser, currentPage)
current_page = msgblocks[1] currentPage = msgblocks[1]
//log.Print("Current Page:",current_page) //log.Print("Current Page:",currentPage)
//log.Print("Current Page:",string(current_page)) //log.Print("Current Page:",string(currentPage))
ws_page_responses(ws_user, current_page) wsPageResponses(wsUser, currentPage)
} }
} }
/*if bytes.Equal(message,[]byte(`start-view`)) { /*if bytes.Equal(message,[]byte(`start-view`)) {
@ -226,49 +224,50 @@ func route_websockets(w http.ResponseWriter, r *http.Request, user User) {
conn.Close() conn.Close()
} }
func ws_page_responses(ws_user *WS_User, page []byte) { func wsPageResponses(wsUser *WS_User, page []byte) {
switch(string(page)) { switch string(page) {
case "/panel/": case "/panel/":
//log.Print("/panel/ WS Route") //log.Print("/panel/ WS Route")
/*w, err := ws_user.conn.NextWriter(websocket.TextMessage) /*w, err := wsUser.conn.NextWriter(websocket.TextMessage)
if err != nil { if err != nil {
//log.Print(err.Error()) //log.Print(err.Error())
return return
} }
log.Print(ws_hub.online_users) log.Print(wsHub.online_users)
uonline := ws_hub.user_count() uonline := wsHub.userCount()
gonline := ws_hub.guest_count() gonline := wsHub.guestCount()
totonline := uonline + gonline totonline := uonline + gonline
w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + " online\r")) w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + " online\r"))
w.Write([]byte("set #dash-gonline " + strconv.Itoa(gonline) + " guests online\r")) w.Write([]byte("set #dash-gonline " + strconv.Itoa(gonline) + " guests online\r"))
w.Write([]byte("set #dash-uonline " + strconv.Itoa(uonline) + " users online\r")) w.Write([]byte("set #dash-uonline " + strconv.Itoa(uonline) + " users online\r"))
w.Close()*/ w.Close()*/
// Listen for changes and inform the admins... // Listen for changes and inform the admins...
admin_stats_mutex.Lock() adminStatsMutex.Lock()
watchers := len(admin_stats_watchers) watchers := len(adminStatsWatchers)
admin_stats_watchers[ws_user] = true adminStatsWatchers[wsUser] = true
if watchers == 0 { if watchers == 0 {
go admin_stats_ticker() go adminStatsTicker()
} }
admin_stats_mutex.Unlock() adminStatsMutex.Unlock()
} }
} }
func ws_leave_page(ws_user *WS_User, page []byte) { func wsLeavePage(wsUser *WS_User, page []byte) {
switch(string(page)) { switch string(page) {
case "/panel/": case "/panel/":
admin_stats_mutex.Lock() adminStatsMutex.Lock()
delete(admin_stats_watchers,ws_user) delete(adminStatsWatchers, wsUser)
admin_stats_mutex.Unlock() adminStatsMutex.Unlock()
} }
} }
var admin_stats_watchers map[*WS_User]bool var adminStatsWatchers map[*WS_User]bool
var admin_stats_mutex sync.RWMutex var adminStatsMutex sync.RWMutex
func admin_stats_ticker() {
func adminStatsTicker() {
time.Sleep(time.Second) time.Sleep(time.Second)
var last_uonline int = -1 var last_uonline int = -1
@ -288,17 +287,17 @@ func admin_stats_ticker() {
AdminStatLoop: AdminStatLoop:
for { for {
admin_stats_mutex.RLock() adminStatsMutex.RLock()
watch_count := len(admin_stats_watchers) watch_count := len(adminStatsWatchers)
admin_stats_mutex.RUnlock() adminStatsMutex.RUnlock()
if watch_count == 0 { if watch_count == 0 {
break AdminStatLoop break AdminStatLoop
} }
cpu_perc, cpuerr = cpu.Percent(time.Duration(time.Second),true) cpu_perc, cpuerr = cpu.Percent(time.Duration(time.Second), true)
memres, ramerr = mem.VirtualMemory() memres, ramerr = mem.VirtualMemory()
uonline := ws_hub.user_count() uonline := wsHub.userCount()
gonline := ws_hub.guest_count() gonline := wsHub.guestCount()
totonline := uonline + gonline totonline := uonline + gonline
// It's far more likely that the CPU Usage will change than the other stats, so we'll optimise them seperately... // It's far more likely that the CPU Usage will change than the other stats, so we'll optimise them seperately...
@ -319,7 +318,7 @@ AdminStatLoop:
} }
if gonline > 10 { if gonline > 10 {
onlineGuestsColour = "stat_green" onlineGuestsColour = "stat_green"
} else if gonline > 1 { } else if gonline > 1 {
onlineGuestsColour = "stat_orange" onlineGuestsColour = "stat_orange"
} else { } else {
@ -334,9 +333,9 @@ AdminStatLoop:
onlineUsersColour = "stat_red" onlineUsersColour = "stat_red"
} }
totonline, totunit = convert_friendly_unit(totonline) totonline, totunit = convertFriendlyUnit(totonline)
uonline, uunit = convert_friendly_unit(uonline) uonline, uunit = convertFriendlyUnit(uonline)
gonline, gunit = convert_friendly_unit(gonline) gonline, gunit = convertFriendlyUnit(gonline)
} }
if cpuerr != nil { if cpuerr != nil {
@ -357,8 +356,8 @@ AdminStatLoop:
if ramerr != nil { if ramerr != nil {
ramstr = "Unknown" ramstr = "Unknown"
} else { } else {
total_count, total_unit := convert_byte_unit(float64(memres.Total)) total_count, total_unit := convertByteUnit(float64(memres.Total))
used_count := convert_byte_in_unit(float64(memres.Total - memres.Available),total_unit) used_count := convertByteInUnit(float64(memres.Total-memres.Available), total_unit)
// Round totals with .9s up, it's how most people see it anyway. Floats are notoriously imprecise, so do it off 0.85 // Round totals with .9s up, it's how most people see it anyway. Floats are notoriously imprecise, so do it off 0.85
var totstr string var totstr string
@ -366,13 +365,13 @@ AdminStatLoop:
used_count += 1.0 - (total_count - float64(int(total_count))) used_count += 1.0 - (total_count - float64(int(total_count)))
totstr = strconv.Itoa(int(total_count) + 1) totstr = strconv.Itoa(int(total_count) + 1)
} else { } else {
totstr = fmt.Sprintf("%.1f",total_count) totstr = fmt.Sprintf("%.1f", total_count)
} }
if used_count > total_count { if used_count > total_count {
used_count = total_count used_count = total_count
} }
ramstr = fmt.Sprintf("%.1f",used_count) + " / " + totstr + total_unit ramstr = fmt.Sprintf("%.1f", used_count) + " / " + totstr + total_unit
ramperc := ((memres.Total - memres.Available) * 100) / memres.Total ramperc := ((memres.Total - memres.Available) * 100) / memres.Total
if ramperc < 50 { if ramperc < 50 {
@ -385,17 +384,17 @@ AdminStatLoop:
} }
} }
admin_stats_mutex.RLock() adminStatsMutex.RLock()
watchers := admin_stats_watchers watchers := adminStatsWatchers
admin_stats_mutex.RUnlock() adminStatsMutex.RUnlock()
for watcher, _ := range watchers { for watcher, _ := range watchers {
w, err := watcher.conn.NextWriter(websocket.TextMessage) w, err := watcher.conn.NextWriter(websocket.TextMessage)
if err != nil { if err != nil {
//log.Print(err.Error()) //log.Print(err.Error())
admin_stats_mutex.Lock() adminStatsMutex.Lock()
delete(admin_stats_watchers,watcher) delete(adminStatsWatchers, watcher)
admin_stats_mutex.Unlock() adminStatsMutex.Unlock()
continue continue
} }

View File

@ -5,46 +5,42 @@ import "log"
import "bytes" import "bytes"
import "sync" import "sync"
import "encoding/json" import "encoding/json"
//import "html/template" //import "html/template"
var docks WidgetDocks var docks WidgetDocks
var widget_update_mutex sync.RWMutex var widgetUpdateMutex sync.RWMutex
type WidgetDocks struct type WidgetDocks struct {
{ LeftSidebar []Widget
LeftSidebar []Widget
RightSidebar []Widget RightSidebar []Widget
//PanelLeft []Menus //PanelLeft []Menus
} }
type Widget struct type Widget struct {
{ Enabled bool
Enabled bool
Location string // Coming Soon: overview, topics, topic / topic_view, forums, forum, global Location string // Coming Soon: overview, topics, topic / topic_view, forums, forum, global
Position int Position int
Body string Body string
} }
type WidgetMenu struct type WidgetMenu struct {
{ Name string
Name string
MenuList []WidgetMenuItem MenuList []WidgetMenuItem
} }
type WidgetMenuItem struct type WidgetMenuItem struct {
{ Text string
Text string
Location string Location string
Compact bool Compact bool
} }
type NameTextPair struct type NameTextPair struct {
{
Name string Name string
Text string Text string
} }
func init_widgets() error { func initWidgets() error {
rows, err := get_widgets_stmt.Query() rows, err := get_widgets_stmt.Query()
if err != nil { if err != nil {
return err return err
@ -65,28 +61,28 @@ func init_widgets() error {
} }
sbytes = []byte(data) sbytes = []byte(data)
switch(wtype) { switch wtype {
case "simple": case "simple":
var tmp NameTextPair var tmp NameTextPair
err = json.Unmarshal(sbytes, &tmp) err = json.Unmarshal(sbytes, &tmp)
if err != nil { if err != nil {
return err return err
} }
var b bytes.Buffer var b bytes.Buffer
err = templates.ExecuteTemplate(&b,"widget_simple.html",tmp) err = templates.ExecuteTemplate(&b, "widget_simple.html", tmp)
if err != nil { if err != nil {
return err return err
} }
widget.Body = string(b.Bytes()) widget.Body = string(b.Bytes())
default: default:
widget.Body = data widget.Body = data
} }
if side == "left" { if side == "left" {
leftWidgets = append(leftWidgets,widget) leftWidgets = append(leftWidgets, widget)
} else if side == "right" { } else if side == "right" {
rightWidgets = append(rightWidgets,widget) rightWidgets = append(rightWidgets, widget)
} }
} }
err = rows.Err() err = rows.Err()
@ -94,14 +90,14 @@ func init_widgets() error {
return err return err
} }
widget_update_mutex.Lock() widgetUpdateMutex.Lock()
docks.LeftSidebar = leftWidgets docks.LeftSidebar = leftWidgets
docks.RightSidebar = rightWidgets docks.RightSidebar = rightWidgets
widget_update_mutex.Unlock() widgetUpdateMutex.Unlock()
if dev.SuperDebug { if dev.SuperDebug {
log.Print("docks.LeftSidebar",docks.LeftSidebar) log.Print("docks.LeftSidebar", docks.LeftSidebar)
log.Print("docks.RightSidebar",docks.RightSidebar) log.Print("docks.RightSidebar", docks.RightSidebar)
} }
return nil return nil