Moving the files into subpackages 1/?

Moved more routes into the router generator.
Renamed a few of the routes.
Refactored the router generator.
Fixed PreRoute.

Began work on letting plugins plug into the template transpiler.
This commit is contained in:
Azareal 2017-11-10 03:33:11 +00:00
parent f8e892da20
commit 20bb909c54
35 changed files with 1108 additions and 1050 deletions

6
common/common.go Normal file
View File

@ -0,0 +1,6 @@
package common
import "database/sql"
// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores
var ErrNoRows = sql.ErrNoRows

View File

@ -1,4 +1,4 @@
package main
package common
import "log"
@ -14,6 +14,9 @@ var errorBuffer []error
//var notfoundCountPerSecond int
//var nopermsCountPerSecond int
// A blank list to fill out that parameter in Page for routes which don't use it
var tList []interface{}
// WIP, a new system to propagate errors up from routes
type RouteError interface {
Type() string
@ -82,7 +85,7 @@ func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError
// TODO: Centralise the user struct somewhere else
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, getDefaultHeaderVar(), tList, "A problem has occurred in the system."}
pi := Page{"Internal Server Error", user, DefaultHeaderVar(), tList, "A problem has occurred in the system."}
err = templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
log.Print(err)
@ -120,7 +123,7 @@ func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteErr
// LoginRequired is an error shown to the end-user when they try to access an area which requires them to login
func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(401)
pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You need to login to do that."}
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
@ -136,7 +139,7 @@ func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError
func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
w.WriteHeader(500)
user := User{ID: 0, Group: 6, Perms: GuestPerms}
pi := Page{"Error", user, getDefaultHeaderVar(), tList, errmsg}
pi := Page{"Error", user, DefaultHeaderVar(), tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
@ -165,7 +168,7 @@ func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs boo
// LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(500)
pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, errmsg}
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
@ -195,7 +198,7 @@ func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteEr
// NoPermissions is an error shown to the end-user when they try to access an area which they aren't authorised to access
func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(403)
pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You don't have permission to do that."}
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You don't have permission to do that."}
// TODO: What to do about this hook?
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
@ -225,7 +228,7 @@ func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) RouteErr
// ? - Is this actually used? Should it be used? A ban in Gosora should be more of a permission revocation to stop them posting rather than something which spits up an error page, right?
func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(403)
pi := Page{"Banned", user, getDefaultHeaderVar(), tList, "You have been banned from this site."}
pi := Page{"Banned", user, DefaultHeaderVar(), tList, "You have been banned from this site."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
@ -257,7 +260,7 @@ func BannedJS(w http.ResponseWriter, r *http.Request, user User) RouteError {
func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
w.WriteHeader(401)
if !isJs {
pi := Page{"Local Error", user, getDefaultHeaderVar(), tList, "You need to login to do that."}
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
@ -277,7 +280,7 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bo
// ? - Should we add JS and JSQ versions of this?
func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(403)
pi := Page{"Security Error", user, getDefaultHeaderVar(), tList, "There was a security issue with your request."}
pi := Page{"Security Error", user, DefaultHeaderVar(), tList, "There was a security issue with your request."}
if preRenderHooks["pre_render_security_error"] != nil {
if runPreRenderHook("pre_render_security_error", w, r, &user, &pi) {
return nil
@ -297,7 +300,7 @@ func NotFound(w http.ResponseWriter, r *http.Request) RouteError {
w.WriteHeader(404)
// TODO: Centralise the user struct somewhere else
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{"Not Found", user, getDefaultHeaderVar(), tList, "The requested page doesn't exist."}
pi := Page{"Not Found", user, DefaultHeaderVar(), tList, "The requested page doesn't exist."}
err := templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
@ -308,7 +311,7 @@ func NotFound(w http.ResponseWriter, r *http.Request) RouteError {
// CustomError lets us make custom error types which aren't covered by the generic functions above
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(errcode)
pi := Page{errtitle, user, getDefaultHeaderVar(), tList, errmsg}
pi := Page{errtitle, user, DefaultHeaderVar(), tList, errmsg}
if preRenderHooks["pre_render_error"] != nil {
if runPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil

View File

@ -4,14 +4,18 @@
* Copyright Azareal 2016 - 2018
*
*/
package main
package common
import (
"log"
"net/http"
"../query_gen/lib"
)
var plugins = make(map[string]*Plugin)
type PluginList map[string]*Plugin
var Plugins PluginList = 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?
var hooks = map[string][]func(interface{}) interface{}{
@ -138,12 +142,16 @@ func initExtend() (err error) {
if err != nil {
return err
}
return LoadPlugins()
return Plugins.Load()
}
// LoadPlugins polls the database to see which plugins have been activated and which have been installed
func LoadPlugins() error {
rows, err := stmts.getPlugins.Query()
func (plugins PluginList) Load() error {
getPlugins, err := qgen.Builder.SimpleSelect("plugins", "uname, active, installed", "", "", "")
if err != nil {
return err
}
rows, err := getPlugins.Query()
if err != nil {
return err
}
@ -278,12 +286,12 @@ func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
var pluginsInited = false
func initPlugins() {
for name, body := range plugins {
for name, body := range Plugins {
log.Print("Added plugin " + name)
if body.Active {
log.Print("Initialised plugin " + name)
if plugins[name].Init != nil {
err := plugins[name].Init()
if Plugins[name].Init != nil {
err := Plugins[name].Init()
if err != nil {
log.Print(err)
}

View File

@ -1,4 +1,4 @@
package main
package common
//import "fmt"
import (

View File

@ -4,7 +4,7 @@
* Copyright Azareal 2017 - 2018
*
*/
package main
package common
import (
"database/sql"
@ -14,7 +14,7 @@ import (
"sync"
"sync/atomic"
"./query_gen/lib"
"../query_gen/lib"
)
var forumUpdateMutex sync.Mutex

View File

@ -1,4 +1,4 @@
package main
package common
var blankGroup = Group{ID: 0, Name: ""}

286
common/pages.go Normal file
View File

@ -0,0 +1,286 @@
package common
import (
"html/template"
"sync"
"time"
)
type HeaderVars struct {
NoticeList []string
Scripts []string
Stylesheets []string
Widgets PageWidgets
Site *Site
Settings map[string]interface{}
Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed
ThemeName string
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?
ExtData ExtData
}
// TODO: Add this to routes which don't use templates. E.g. Json APIs.
type HeaderLite struct {
Site *Site
Settings SettingMap
ExtData ExtData
}
type PageWidgets struct {
LeftSidebar template.HTML
RightSidebar template.HTML
}
// TODO: Add a ExtDataHolder interface with methods for manipulating the contents?
// ? - Could we use a sync.Map instead?
type ExtData struct {
items map[string]interface{} // Key: pluginname
sync.RWMutex
}
type Page struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []interface{}
Something interface{}
}
type TopicPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []ReplyUser
Topic TopicUser
Page int
LastPage int
}
type TopicsPage struct {
Title string
CurrentUser User
Header *HeaderVars
TopicList []*TopicsRow
ForumList []Forum
DefaultForum int
}
type ForumPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []*TopicsRow
Forum *Forum
Page int
LastPage int
}
type ForumsPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []Forum
}
type ProfilePage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []ReplyUser
ProfileOwner User
}
type CreateTopicPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []Forum
FID int
}
type IPSearchPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList map[int]*User
IP string
}
type PanelStats struct {
Users int
Groups int
Forums int
Settings int
WordFilters int
Themes int
Reports int
}
type PanelPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ItemList []interface{}
Something interface{}
}
type GridElement struct {
ID string
Body string
Order int // For future use
Class string
Background string
TextColour string
Note string
}
type PanelDashboardPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
GridItems []GridElement
}
type PanelThemesPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
PrimaryThemes []Theme
VariantThemes []Theme
}
type PanelUserPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ItemList []User
PageList []int
Page int
LastPage int
}
type PanelGroupPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ItemList []GroupAdmin
PageList []int
Page int
LastPage int
}
type PanelEditGroupPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ID int
Name string
Tag string
Rank string
DisableRank bool
}
type GroupForumPermPreset struct {
Group *Group
Preset string
}
type PanelEditForumPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ID int
Name string
Desc string
Active bool
Preset string
Groups []GroupForumPermPreset
}
/*type NameLangPair struct {
Name string
LangStr string
}*/
type NameLangToggle struct {
Name string
LangStr string
Toggle bool
}
type PanelEditGroupPermsPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ID int
Name string
LocalPerms []NameLangToggle
GlobalPerms []NameLangToggle
}
type backupItem struct {
SQLURL string
// TODO: Add an easier to parse format here for Gosora to be able to more easily reimport portions of the dump and to strip unnecessary data (e.g. table defs and parsed post data)
Timestamp time.Time
}
type PanelBackupPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
Backups []backupItem
}
type logItem struct {
Action template.HTML
IPAddress string
DoneAt string
}
type PanelLogsPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
Logs []logItem
PageList []int
Page int
LastPage int
}
type PanelDebugPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
Uptime string
OpenConns int
DBAdapter string
}
type PageSimple struct {
Title string
Something interface{}
}
type AreYouSure struct {
URL string
Message string
}
// This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible
// TODO: Write a test for this
func DefaultHeaderVar() *HeaderVars {
return &HeaderVars{Site: site, ThemeName: fallbackTheme}
}

View File

@ -1,12 +1,10 @@
package main
package common
import (
"database/sql"
"encoding/json"
"log"
"sync"
"./query_gen/lib"
)
// TODO: Refactor the perms system

View File

@ -1,4 +1,4 @@
package main
package common
import (
"encoding/json"

View File

@ -1,9 +1,7 @@
package main
package common
import (
"database/sql"
"./query_gen/lib"
)
var prstore ProfileReplyStore

View File

@ -4,7 +4,7 @@
* Copyright Azareal 2016 - 2018
*
*/
package main
package common
import (
"errors"

View File

@ -1,7 +1,7 @@
package main
package common
import "database/sql"
import "./query_gen/lib"
import "../query_gen/lib"
var rstore ReplyStore

View File

@ -1,13 +1,13 @@
package main
package common
import "strconv"
import "strings"
import "sync/atomic"
// 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
type SettingBox map[string]interface{}
// SettingMap 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
type SettingMap 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 {
Label string
@ -23,7 +23,7 @@ type Setting struct {
}
func init() {
settingBox.Store(SettingBox(make(map[string]interface{})))
SettingBox.Store(SettingMap(make(map[string]interface{})))
}
func LoadSettings() error {
@ -33,7 +33,7 @@ func LoadSettings() error {
}
defer rows.Close()
var sBox = SettingBox(make(map[string]interface{}))
var sBox = SettingMap(make(map[string]interface{}))
var sname, scontent, stype, sconstraints string
for rows.Next() {
err = rows.Scan(&sname, &scontent, &stype, &sconstraints)
@ -50,12 +50,12 @@ func LoadSettings() error {
return err
}
settingBox.Store(sBox)
SettingBox.Store(sBox)
return nil
}
// TODO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions.
func (sBox SettingBox) ParseSetting(sname string, scontent string, stype string, constraint string) string {
func (sBox SettingMap) ParseSetting(sname string, scontent string, stype string, constraint string) string {
var err error
var ssBox = map[string]interface{}(sBox)
if stype == "bool" {

View File

@ -1,4 +1,4 @@
package main
package common
import (
"errors"

View File

@ -1,4 +1,4 @@
package main
package common
import (
"html/template"
@ -8,6 +8,15 @@ import (
)
var templates = template.New("")
var prebuildTmplList []func(*User, *HeaderVars) CTmpl
type CTmpl struct {
Name string
Filename string
Path string
StructName string
Data interface{}
}
// nolint
func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) error {
@ -150,6 +159,17 @@ func compileTemplates() error {
return err
}
// Let plugins register their own templates
for _, tmplfunc := range prebuildTmplList {
tmpl := tmplfunc(user, headerVars)
varList = make(map[string]VarItem)
compiledTmpl, err := c.compileTemplate(tmpl.Filename, tmpl.Path, tmpl.StructName, tmpl.Data, varList)
if err != nil {
return err
}
go writeTemplate(tmpl.Name, compiledTmpl)
}
log.Print("Writing the templates")
go writeTemplate("topic", topicIDTmpl)
go writeTemplate("topic_alt", topicIDAltTmpl)

View File

@ -1,5 +1,5 @@
/* Copyright Azareal 2016 - 2018 */
package main
package common
import (
//"fmt"
@ -19,11 +19,10 @@ import (
"text/template"
)
//var themes = make(map[string]Theme)
type ThemeList map[string]Theme
var themes ThemeList = make(map[string]Theme)
var defaultThemeBox atomic.Value
//var themes ThemeList = make(map[string]Theme)
var DefaultThemeBox atomic.Value
var changeDefaultThemeMutex sync.Mutex
// TODO: Use this when the default theme doesn't exist

View File

@ -4,7 +4,7 @@
* Copyright Azareal 2017 - 2018
*
*/
package main
package common
//import "fmt"
import (

View File

@ -4,7 +4,7 @@
* Copyright Azareal 2017 - 2018
*
*/
package main
package common
import (
"database/sql"
@ -13,7 +13,7 @@ import (
"sync"
"sync/atomic"
"./query_gen/lib"
"../query_gen/lib"
)
// TODO: Add the watchdog goroutine

View File

@ -4,7 +4,7 @@
* Copyright Azareal 2017 - 2018
*
*/
package main
package common
import (
//"log"
@ -15,14 +15,13 @@ import (
"strings"
"time"
"./query_gen/lib"
"golang.org/x/crypto/bcrypt"
)
// TODO: Replace any literals with this
var banGroup = 4
var BanGroup = 4
var guestUser = User{ID: 0, Link: "#", Group: 6, Perms: GuestPerms}
var GuestUser = User{ID: 0, Link: "#", Group: 6, Perms: GuestPerms}
//func(real_password string, password string, salt string) (err error)
var CheckPassword = BcryptCheckPassword

View File

@ -1,4 +1,4 @@
package main
package common
import (
"database/sql"
@ -8,7 +8,6 @@ import (
"sync"
"sync/atomic"
"./query_gen/lib"
"golang.org/x/crypto/bcrypt"
)

View File

@ -4,7 +4,7 @@
* Copyright Azareal 2017 - 2018
*
*/
package main
package common
import (
"crypto/rand"

536
extend/guilds/lib/guilds.go Normal file
View File

@ -0,0 +1,536 @@
package guilds
import (
"bytes"
"context"
"database/sql"
"errors"
"html"
"html/template"
"net/http"
"strconv"
"strings"
"../../../common"
)
var ListStmt *sql.Stmt
var MemberListStmt *sql.Stmt
var MemberListJoinStmt *sql.Stmt
var GetMemberStmt *sql.Stmt
var GetGuildStmt *sql.Stmt
var CreateGuildStmt *sql.Stmt
var AttachForumStmt *sql.Stmt
var UnattachForumStmt *sql.Stmt
var AddMemberStmt *sql.Stmt
// Guild is a struct representing a guild
type Guild struct {
ID int
Link string
Name string
Desc string
Active bool
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.
Joinable int /* 0: Private, 1: Anyone can join, 2: Applications, 3: Invite-only */
MemberCount int
Owner int
Backdrop string
CreatedAt string
LastUpdateTime string
MainForumID int
MainForum *common.Forum
Forums []*common.Forum
ExtData ExtData
}
type Page struct {
Title string
CurrentUser User
Header *common.HeaderVars
ItemList []*TopicsRow
Forum *commmon.Forum
Guild *Guild
Page int
LastPage int
}
// ListPage is a page struct for constructing a list of every guild
type ListPage struct {
Title string
CurrentUser User
Header *HeaderVars
GuildList []*Guild
}
type MemberListPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []GuildMember
Guild *Guild
Page int
LastPage int
}
// Member is a struct representing a specific member of a guild, not to be confused with the global User struct.
type Member struct {
Link string
Rank int /* 0: Member. 1: Mod. 2: Admin. */
RankString string /* Member, Mod, Admin, Owner */
PostCount int
JoinedAt string
Offline bool // TODO: Need to track the online states of members when WebSockets are enabled
User User
}
func PrebuildTmplList(user *User, headerVars *HeaderVars) CTmpl {
var guildList = []*Guild{
&Guild{
ID: 1,
Name: "lol",
Link: guildsBuildGuildURL(nameToSlug("lol"), 1),
Desc: "A group for people who like to laugh",
Active: true,
MemberCount: 1,
Owner: 1,
CreatedAt: "date",
LastUpdateTime: "date",
MainForumID: 1,
MainForum: fstore.DirtyGet(1),
Forums: []*Forum{fstore.DirtyGet(1)},
},
}
listPage := ListPage{"Guild List", user, headerVars, guildList}
return CTmpl{"guilds-guild-list", "guilds_guild_list", "templates/", "guilds.ListPage", listPage}
}
// TODO: Do this properly via the widget system
func CommonAreaWidgets(headerVars *HeaderVars) {
// TODO: Hot Groups? Featured Groups? Official Groups?
var b bytes.Buffer
var menu = WidgetMenu{"Guilds", []WidgetMenuItem{
WidgetMenuItem{"Create Guild", "/guild/create/", false},
}}
err := templates.ExecuteTemplate(&b, "widget_menu.html", menu)
if err != nil {
LogError(err)
return
}
if themes[headerVars.ThemeName].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
}
}
// TODO: Do this properly via the widget system
// TODO: Make a better more customisable group widget system
func GuildWidgets(headerVars *HeaderVars, guildItem *Guild) (success bool) {
return false // Disabled until the next commit
/*var b bytes.Buffer
var menu WidgetMenu = WidgetMenu{"Guild Options", []WidgetMenuItem{
WidgetMenuItem{"Join", "/guild/join/" + strconv.Itoa(guildItem.ID), false},
WidgetMenuItem{"Members", "/guild/members/" + strconv.Itoa(guildItem.ID), false},
}}
err := templates.ExecuteTemplate(&b, "widget_menu.html", menu)
if err != nil {
LogError(err)
return false
}
if themes[headerVars.ThemeName].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
} else {
return false
}
return true*/
}
/*
Custom Pages
*/
func routeGuildList(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
guildsCommonAreaWidgets(headerVars)
rows, err := guildsListStmt.Query()
if err != nil && err != ErrNoRows {
return InternalError(err, w, r)
}
defer rows.Close()
var guildList []*Guild
for rows.Next() {
guildItem := &Guild{ID: 0}
err := rows.Scan(&guildItem.ID, &guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.CreatedAt, &guildItem.LastUpdateTime)
if err != nil {
return InternalError(err, w, r)
}
guildItem.Link = guildsBuildGuildURL(nameToSlug(guildItem.Name), guildItem.ID)
guildList = append(guildList, guildItem)
}
err = rows.Err()
if err != nil {
return InternalError(err, w, r)
}
pi := GuildListPage{"Guild List", user, headerVars, guildList}
err = RunThemeTemplate(headerVars.ThemeName, "guilds_guild_list", pi, w)
if err != nil {
return InternalError(err, w, r)
}
return nil
}
func GetGuild(guildID int) (guildItem *Guild, err error) {
guildItem = &Guild{ID: guildID}
err = guildsGetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.MainForumID, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime)
return guildItem, err
}
func middleViewGuild(w http.ResponseWriter, r *http.Request, user User) RouteError {
// SEO URLs...
halves := strings.Split(r.URL.Path[len("/guild/"):], ".")
if len(halves) < 2 {
halves = append(halves, halves[0])
}
guildID, err := strconv.Atoi(halves[1])
if err != nil {
return PreError("Not a valid guild ID", w, r)
}
guildItem, err := guildsGetGuild(guildID)
if err != nil {
return LocalError("Bad guild", w, r, user)
}
if !guildItem.Active {
return NotFound(w, r)
}
// Re-route the request to routeForums
var ctx = context.WithValue(r.Context(), "guilds_current_guild", guildItem)
return routeForum(w, r.WithContext(ctx), user, strconv.Itoa(guildItem.MainForumID))
}
func CreateGuild(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
// TODO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateGuild"] {
return NoPermissions(w, r, user)
}
guildsCommonAreaWidgets(headerVars)
pi := Page{"Create Guild", user, headerVars, tList, nil}
err := templates.ExecuteTemplate(w, "guilds_create_guild.html", pi)
if err != nil {
return InternalError(err, w, r)
}
return nil
}
func CreateGuildSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError {
// TODO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateGuild"] {
return NoPermissions(w, r, user)
}
var guildActive = true
var guildName = html.EscapeString(r.PostFormValue("group_name"))
var guildDesc = html.EscapeString(r.PostFormValue("group_desc"))
var gprivacy = r.PostFormValue("group_privacy")
var guildPrivacy int
switch gprivacy {
case "0":
guildPrivacy = 0 // Public
case "1":
guildPrivacy = 1 // Protected
case "2":
guildPrivacy = 2 // private
default:
guildPrivacy = 0
}
// Create the backing forum
fid, err := fstore.Create(guildName, "", true, "")
if err != nil {
return InternalError(err, w, r)
}
res, err := guildsCreateGuildStmt.Exec(guildName, guildDesc, guildActive, guildPrivacy, user.ID, fid)
if err != nil {
return InternalError(err, w, r)
}
lastID, err := res.LastInsertId()
if err != nil {
return InternalError(err, w, r)
}
// Add the main backing forum to the forum list
err = guildsAttachForum(int(lastID), fid)
if err != nil {
return InternalError(err, w, r)
}
_, err = guildsAddMemberStmt.Exec(lastID, user.ID, 2)
if err != nil {
return InternalError(err, w, r)
}
http.Redirect(w, r, guildsBuildGuildURL(nameToSlug(guildName), int(lastID)), http.StatusSeeOther)
return nil
}
func MemberList(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
// SEO URLs...
halves := strings.Split(r.URL.Path[len("/guild/members/"):], ".")
if len(halves) < 2 {
halves = append(halves, halves[0])
}
guildID, err := strconv.Atoi(halves[1])
if err != nil {
return PreError("Not a valid group ID", w, r)
}
var guildItem = &Guild{ID: guildID}
var mainForum int // Unused
err = guildsGetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &mainForum, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime)
if err != nil {
return LocalError("Bad group", w, r, user)
}
guildItem.Link = guildsBuildGuildURL(nameToSlug(guildItem.Name), guildItem.ID)
guildsGuildWidgets(headerVars, guildItem)
rows, err := guildsMemberListJoinStmt.Query(guildID)
if err != nil && err != ErrNoRows {
return InternalError(err, w, r)
}
var guildMembers []GuildMember
for rows.Next() {
guildMember := GuildMember{PostCount: 0}
err := rows.Scan(&guildMember.User.ID, &guildMember.Rank, &guildMember.PostCount, &guildMember.JoinedAt, &guildMember.User.Name, &guildMember.User.Avatar)
if err != nil {
return InternalError(err, w, r)
}
guildMember.Link = buildProfileURL(nameToSlug(guildMember.User.Name), guildMember.User.ID)
if guildMember.User.Avatar != "" {
if guildMember.User.Avatar[0] == '.' {
guildMember.User.Avatar = "/uploads/avatar_" + strconv.Itoa(guildMember.User.ID) + guildMember.User.Avatar
}
} else {
guildMember.User.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(guildMember.User.ID), 1)
}
guildMember.JoinedAt, _ = relativeTimeFromString(guildMember.JoinedAt)
if guildItem.Owner == guildMember.User.ID {
guildMember.RankString = "Owner"
} else {
switch guildMember.Rank {
case 0:
guildMember.RankString = "Member"
case 1:
guildMember.RankString = "Mod"
case 2:
guildMember.RankString = "Admin"
}
}
guildMembers = append(guildMembers, guildMember)
}
err = rows.Err()
if err != nil {
return InternalError(err, w, r)
}
rows.Close()
pi := GuildMemberListPage{"Guild Member List", user, headerVars, guildMembers, guildItem, 0, 0}
// A plugin with plugins. Pluginception!
if preRenderHooks["pre_render_guilds_member_list"] != nil {
if runPreRenderHook("pre_render_guilds_member_list", w, r, &user, &pi) {
return nil
}
}
err = RunThemeTemplate(headerVars.ThemeName, "guilds_member_list", pi, w)
if err != nil {
return InternalError(err, w, r)
}
return nil
}
func AttachForum(guildID int, fid int) error {
_, err := guildsAttachForumStmt.Exec(guildID, fid)
return err
}
func UnattachForum(fid int) error {
_, err := guildsAttachForumStmt.Exec(fid)
return err
}
func BuildGuildURL(slug string, id int) string {
if slug == "" {
return "/guild/" + slug + "." + strconv.Itoa(id)
}
return "/guild/" + strconv.Itoa(id)
}
/*
Hooks
*/
// TODO: Prebuild this template
func PreRenderViewForum(w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
pi := data.(*ForumPage)
if pi.Header.ExtData.items != nil {
if guildData, ok := pi.Header.ExtData.items["guilds_current_group"]; ok {
guildItem := guildData.(*Guild)
guildpi := GuildPage{pi.Title, pi.CurrentUser, pi.Header, pi.ItemList, pi.Forum, guildItem, pi.Page, pi.LastPage}
err := templates.ExecuteTemplate(w, "guilds_view_guild.html", guildpi)
if err != nil {
LogError(err)
return false
}
return true
}
}
return false
}
func TrowAssign(args ...interface{}) interface{} {
var forum = args[1].(*Forum)
if forum.ParentType == "guild" {
var topicItem = args[0].(*TopicsRow)
topicItem.ForumLink = "/guild/" + strings.TrimPrefix(topicItem.ForumLink, getForumURLPrefix())
}
return nil
}
// TODO: 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 TopicCreatePreLoop(args ...interface{}) interface{} {
var fid = args[2].(int)
if fstore.DirtyGet(fid).ParentType == "guild" {
var strictmode = args[5].(*bool)
*strictmode = true
}
return nil
}
// TODO: Add privacy options
// TODO: Add support for multiple boards and add per-board simplified permissions
// TODO: Take isJs into account for routes which expect JSON responses
func ForumCheck(args ...interface{}) (skip bool, rerr RouteError) {
var r = args[1].(*http.Request)
var fid = args[3].(*int)
var forum = fstore.DirtyGet(*fid)
if forum.ParentType == "guild" {
var err error
var w = args[0].(http.ResponseWriter)
guildItem, ok := r.Context().Value("guilds_current_group").(*Guild)
if !ok {
guildItem, err = guildsGetGuild(forum.ParentID)
if err != nil {
return true, InternalError(errors.New("Unable to find the parent group for a forum"), w, r)
}
if !guildItem.Active {
return true, NotFound(w, r)
}
r = r.WithContext(context.WithValue(r.Context(), "guilds_current_group", guildItem))
}
var user = args[2].(*User)
var rank int
var posts int
var joinedAt string
// TODO: Group privacy settings. For now, groups are all globally visible
// Clear the default group permissions
// TODO: Do this more efficiently, doing it quick and dirty for now to get this out quickly
overrideForumPerms(&user.Perms, false)
user.Perms.ViewTopic = true
err = guildsGetMemberStmt.QueryRow(guildItem.ID, user.ID).Scan(&rank, &posts, &joinedAt)
if err != nil && err != ErrNoRows {
return true, InternalError(err, w, r)
} else if err != nil {
// TODO: Should we let admins / guests into public groups?
return true, LocalError("You're not part of this group!", w, r, *user)
}
// TODO: Implement bans properly by adding the Local Ban API in the next commit
// TODO: How does this even work? Refactor it along with the rest of this plugin!
if rank < 0 {
return true, LocalError("You've been banned from this group!", w, r, *user)
}
// Basic permissions for members, more complicated permissions coming in the next commit!
if guildItem.Owner == user.ID {
overrideForumPerms(&user.Perms, true)
} else if rank == 0 {
user.Perms.LikeItem = true
user.Perms.CreateTopic = true
user.Perms.CreateReply = true
} else {
overrideForumPerms(&user.Perms, true)
}
return true, nil
}
return false, nil
}
// TODO: Override redirects? I don't think this is needed quite yet
func Widgets(args ...interface{}) interface{} {
var zone = args[0].(string)
var headerVars = args[2].(*HeaderVars)
var request = args[3].(*http.Request)
if zone != "view_forum" {
return false
}
var forum = args[1].(*Forum)
if forum.ParentType == "guild" {
// This is why I hate using contexts, all the daisy chains and interface casts x.x
guildItem, ok := request.Context().Value("guilds_current_group").(*Guild)
if !ok {
LogError(errors.New("Unable to find a parent group in the context data"))
return false
}
if headerVars.ExtData.items == nil {
headerVars.ExtData.items = make(map[string]interface{})
}
headerVars.ExtData.items["guilds_current_group"] = guildItem
return guildsGuildWidgets(headerVars, guildItem)
}
return false
}

View File

@ -266,15 +266,21 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeAccountOwnEditCritical(w,req,user)
err = routeAccountEditCritical(w,req,user)
case "/user/edit/critical/submit/":
err = NoSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = MemberOnly(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = routeAccountOwnEditCriticalSubmit(w,req,user)
err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/":
err = MemberOnly(w,req,user)
if err != nil {
@ -282,7 +288,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeAccountOwnEditAvatar(w,req,user)
err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/":
err = MemberOnly(w,req,user)
if err != nil {
@ -290,7 +296,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeAccountOwnEditAvatarSubmit(w,req,user)
err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/":
err = MemberOnly(w,req,user)
if err != nil {
@ -298,7 +304,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeAccountOwnEditUsername(w,req,user)
err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/":
err = MemberOnly(w,req,user)
if err != nil {
@ -306,7 +312,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeAccountOwnEditUsernameSubmit(w,req,user)
err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/":
err = MemberOnly(w,req,user)
if err != nil {
@ -314,7 +320,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeAccountOwnEditEmail(w,req,user)
err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/":
err = MemberOnly(w,req,user)
if err != nil {
@ -322,7 +328,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeAccountOwnEditEmailTokenSubmit(w,req,user,extra_data)
err = routeAccountEditEmailTokenSubmit(w,req,user,extra_data)
default:
req.URL.Path += extra_data
err = routeProfile(w,req,user)
@ -330,6 +336,44 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if err != nil {
router.handleError(err,w,req,user)
}
case "/users":
err = MemberOnly(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
switch(req.URL.Path) {
case "/users/ban/submit/":
err = NoSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = routeBanSubmit(w,req,user)
case "/users/unban/":
err = NoSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = routeUnban(w,req,user)
case "/users/activate/":
err = NoSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = routeActivate(w,req,user)
case "/users/ips/":
err = routeIps(w,req,user)
}
if err != nil {
router.handleError(err,w,req,user)
}
case "/uploads":
if extra_data == "" {
NotFound(w,req)

58
main.go
View File

@ -228,12 +228,6 @@ func main() {
log.Print("Initialising the router")
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
////router.HandleFunc("/static/", routeStatic)
////router.HandleFunc("/overview/", routeOverview)
////router.HandleFunc("/topics/create/", routeTopicCreate)
////router.HandleFunc("/topics/", routeTopics)
////router.HandleFunc("/forums/", routeForums)
////router.HandleFunc("/forum/", routeForum)
router.HandleFunc("/topic/create/submit/", routeTopicCreateSubmit)
router.HandleFunc("/topic/", routeTopicID)
router.HandleFunc("/reply/create/", routeCreateReply)
@ -242,7 +236,6 @@ func main() {
router.HandleFunc("/reply/edit/submit/", routeReplyEditSubmit)
router.HandleFunc("/reply/delete/submit/", routeReplyDeleteSubmit)
router.HandleFunc("/reply/like/submit/", routeReplyLikeSubmit)
///router.HandleFunc("/report/submit/", route_report_submit)
router.HandleFunc("/topic/edit/submit/", routeEditTopic)
router.HandleFunc("/topic/delete/submit/", routeDeleteTopic)
router.HandleFunc("/topic/stick/submit/", routeStickTopic)
@ -260,65 +253,14 @@ func main() {
router.HandleFunc("/accounts/logout/", routeLogout)
router.HandleFunc("/accounts/login/submit/", routeLoginSubmit)
router.HandleFunc("/accounts/create/submit/", routeRegisterSubmit)
//router.HandleFunc("/accounts/list/", routeLogin) // Redirect /accounts/ and /user/ to here.. // Get a list of all of the accounts on the forum
//router.HandleFunc("/accounts/create/full/", routeLogout) // Advanced account creator for admins?
//router.HandleFunc("/user/edit/", routeLogout)
////router.HandleFunc("/user/edit/critical/", routeAccountOwnEditCritical) // Password & Email
////router.HandleFunc("/user/edit/critical/submit/", routeAccountOwnEditCriticalSubmit)
////router.HandleFunc("/user/edit/avatar/", routeAccountOwnEditAvatar)
////router.HandleFunc("/user/edit/avatar/submit/", routeAccountOwnEditAvatarSubmit)
////router.HandleFunc("/user/edit/username/", routeAccountOwnEditUsername)
////router.HandleFunc("/user/edit/username/submit/", routeAccountOwnEditUsernameSubmit)
////router.HandleFunc("/user/edit/email/", routeAccountOwnEditEmail)
////router.HandleFunc("/user/edit/token/", routeAccountOwnEditEmailTokenSubmit)
////router.HandleFunc("/user/", routeProfile)
// TODO: Move these into /user/?
router.HandleFunc("/profile/reply/create/", routeProfileReplyCreate)
router.HandleFunc("/profile/reply/edit/submit/", routeProfileReplyEditSubmit)
router.HandleFunc("/profile/reply/delete/submit/", routeProfileReplyDeleteSubmit)
//router.HandleFunc("/user/edit/submit/", routeLogout) // routeLogout? what on earth? o.o
//router.HandleFunc("/users/ban/", routeBan)
router.HandleFunc("/users/ban/submit/", routeBanSubmit)
router.HandleFunc("/users/unban/", routeUnban)
router.HandleFunc("/users/activate/", routeActivate)
router.HandleFunc("/users/ips/", routeIps)
// The Control Panel
// TODO: Rename the commented route handlers to the new camelCase format :'(
////router.HandleFunc("/panel/", routePanel)
////router.HandleFunc("/panel/forums/", routePanelForums)
////router.HandleFunc("/panel/forums/create/", routePanelForumsCreateSubmit)
////router.HandleFunc("/panel/forums/delete/", routePanelForumsDelete)
////router.HandleFunc("/panel/forums/delete/submit/", routePanelForumsDeleteSubmit)
////router.HandleFunc("/panel/forums/edit/", routePanelForumsEdit)
////router.HandleFunc("/panel/forums/edit/submit/", routePanelForumsEditSubmit)
////router.HandleFunc("/panel/forums/edit/perms/submit/", routePanelForumsEditPermsSubmit)
////router.HandleFunc("/panel/settings/", routePanelSettings)
////router.HandleFunc("/panel/settings/edit/", routePanelSetting)
////router.HandleFunc("/panel/settings/edit/submit/", routePanelSettingEdit)
///router.HandleFunc("/panel/themes/", route_panel_themes)
///router.HandleFunc("/panel/themes/default/", route_panel_themes_default)
///router.HandleFunc("/panel/plugins/", route_panel_plugins)
///router.HandleFunc("/panel/plugins/activate/", route_panel_plugins_activate)
///router.HandleFunc("/panel/plugins/deactivate/", route_panel_plugins_deactivate)
///router.HandleFunc("/panel/users/", route_panel_users)
///router.HandleFunc("/panel/users/edit/", route_panel_users_edit)
///router.HandleFunc("/panel/users/edit/submit/", route_panel_users_edit_submit)
///router.HandleFunc("/panel/groups/", route_panel_groups)
///router.HandleFunc("/panel/groups/edit/", route_panel_groups_edit)
///router.HandleFunc("/panel/groups/edit/perms/", route_panel_groups_edit_perms)
///router.HandleFunc("/panel/groups/edit/submit/", route_panel_groups_edit_submit)
///router.HandleFunc("/panel/groups/edit/perms/submit/", route_panel_groups_edit_perms_submit)
///router.HandleFunc("/panel/groups/create/", route_panel_groups_create_submit)
///router.HandleFunc("/panel/logs/mod/", route_panel_logs_mod)
///router.HandleFunc("/panel/debug/", route_panel_debug)
////router.HandleFunc("/api/", routeAPI)
//router.HandleFunc("/exit/", routeExit)
////router.HandleFunc("/", config.DefaultRoute)
router.HandleFunc("/ws/", routeWebsockets)
log.Print("Initialising the plugins")

View File

@ -96,7 +96,7 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid st
}
}
err = template_create_topic_handle(ctpage, w)
err = RunThemeTemplate(headerVars.ThemeName, "create-topic", ctpage, w)
if err != nil {
return InternalError(err, w, r)
}
@ -630,7 +630,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI
return nil
}
func routeAccountOwnEditCritical(w http.ResponseWriter, r *http.Request, user User) RouteError {
func routeAccountEditCritical(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
@ -649,23 +649,18 @@ func routeAccountOwnEditCritical(w http.ResponseWriter, r *http.Request, user Us
return nil
}
func routeAccountOwnEditCriticalSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError {
func routeAccountEditCriticalSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
err := r.ParseForm()
if err != nil {
return LocalError("Bad Form", w, r, user)
}
var realPassword, salt string
currentPassword := r.PostFormValue("account-current-password")
newPassword := r.PostFormValue("account-new-password")
confirmPassword := r.PostFormValue("account-confirm-password")
err = stmts.getPassword.QueryRow(user.ID).Scan(&realPassword, &salt)
err := stmts.getPassword.QueryRow(user.ID).Scan(&realPassword, &salt)
if err == ErrNoRows {
return LocalError("Your account no longer exists.", w, r, user)
} else if err != nil {
@ -700,7 +695,7 @@ func routeAccountOwnEditCriticalSubmit(w http.ResponseWriter, r *http.Request, u
return nil
}
func routeAccountOwnEditAvatar(w http.ResponseWriter, r *http.Request, user User) RouteError {
func routeAccountEditAvatar(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
@ -719,7 +714,7 @@ func routeAccountOwnEditAvatar(w http.ResponseWriter, r *http.Request, user User
return nil
}
func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError {
func routeAccountEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError {
if r.ContentLength > int64(config.MaxRequestSize) {
size, unit := convertByteUnit(float64(config.MaxRequestSize))
return CustomError("Your avatar's too big. Avatars must be smaller than "+strconv.Itoa(int(size))+unit, http.StatusExpectationFailed, "Error", w, r, user)
@ -805,7 +800,7 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use
return nil
}
func routeAccountOwnEditUsername(w http.ResponseWriter, r *http.Request, user User) RouteError {
func routeAccountEditUsername(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
@ -824,18 +819,14 @@ func routeAccountOwnEditUsername(w http.ResponseWriter, r *http.Request, user Us
return nil
}
func routeAccountOwnEditUsernameSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError {
func routeAccountEditUsernameSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
err := r.ParseForm()
if err != nil {
return LocalError("Bad Form", w, r, user)
}
newUsername := html.EscapeString(r.PostFormValue("account-new-username"))
err = user.ChangeName(newUsername)
err := user.ChangeName(newUsername)
if err != nil {
return LocalError("Unable to change the username. Does someone else already have this name?", w, r, user)
}
@ -855,7 +846,7 @@ func routeAccountOwnEditUsernameSubmit(w http.ResponseWriter, r *http.Request, u
return nil
}
func routeAccountOwnEditEmail(w http.ResponseWriter, r *http.Request, user User) RouteError {
func routeAccountEditEmail(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
@ -910,7 +901,8 @@ func routeAccountOwnEditEmail(w http.ResponseWriter, r *http.Request, user User)
return nil
}
func routeAccountOwnEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user User, token string) RouteError {
// TODO: Do a session check on this?
func routeAccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user User, token string) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr

View File

@ -603,9 +603,6 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user User) RouteErro
if !user.Perms.BanUsers {
return NoPermissions(w, r, user)
}
if r.FormValue("session") != user.Session {
return SecurityError(w, r, user)
}
uid, err := strconv.Atoi(r.URL.Path[len("/users/ban/submit/"):])
if err != nil {
@ -679,9 +676,6 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user User) RouteError {
if !user.Perms.BanUsers {
return NoPermissions(w, r, user)
}
if r.FormValue("session") != user.Session {
return SecurityError(w, r, user)
}
uid, err := strconv.Atoi(r.URL.Path[len("/users/unban/"):])
if err != nil {
@ -721,9 +715,6 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user User) RouteError
if !user.Perms.ActivateUsers {
return NoPermissions(w, r, user)
}
if r.FormValue("session") != user.Session {
return SecurityError(w, r, user)
}
uid, err := strconv.Atoi(r.URL.Path[len("/users/activate/"):])
if err != nil {

279
pages.go
View File

@ -3,287 +3,12 @@ package main
import (
//"fmt"
"bytes"
"html/template"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
type HeaderVars struct {
NoticeList []string
Scripts []string
Stylesheets []string
Widgets PageWidgets
Site *Site
Settings map[string]interface{}
Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed
ThemeName string
ExtData ExtData
}
// TODO: Add this to routes which don't use templates. E.g. Json APIs.
type HeaderLite struct {
Site *Site
Settings SettingBox
ExtData ExtData
}
type PageWidgets struct {
LeftSidebar template.HTML
RightSidebar template.HTML
}
// TODO: Add a ExtDataHolder interface with methods for manipulating the contents?
// ? - Could we use a sync.Map instead?
type ExtData struct {
items map[string]interface{} // Key: pluginname
sync.RWMutex
}
type Page struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []interface{}
Something interface{}
}
type TopicPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []ReplyUser
Topic TopicUser
Page int
LastPage int
}
type TopicsPage struct {
Title string
CurrentUser User
Header *HeaderVars
TopicList []*TopicsRow
ForumList []Forum
DefaultForum int
}
type ForumPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []*TopicsRow
Forum *Forum
Page int
LastPage int
}
type ForumsPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []Forum
}
type ProfilePage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []ReplyUser
ProfileOwner User
}
type CreateTopicPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []Forum
FID int
}
type IPSearchPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList map[int]*User
IP string
}
type PanelStats struct {
Users int
Groups int
Forums int
Settings int
WordFilters int
Themes int
Reports int
}
type PanelPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ItemList []interface{}
Something interface{}
}
type GridElement struct {
ID string
Body string
Order int // For future use
Class string
Background string
TextColour string
Note string
}
type PanelDashboardPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
GridItems []GridElement
}
type PanelThemesPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
PrimaryThemes []Theme
VariantThemes []Theme
}
type PanelUserPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ItemList []User
PageList []int
Page int
LastPage int
}
type PanelGroupPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ItemList []GroupAdmin
PageList []int
Page int
LastPage int
}
type PanelEditGroupPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ID int
Name string
Tag string
Rank string
DisableRank bool
}
type GroupForumPermPreset struct {
Group *Group
Preset string
}
type PanelEditForumPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ID int
Name string
Desc string
Active bool
Preset string
Groups []GroupForumPermPreset
}
/*type NameLangPair struct {
Name string
LangStr string
}*/
type NameLangToggle struct {
Name string
LangStr string
Toggle bool
}
type PanelEditGroupPermsPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
ID int
Name string
LocalPerms []NameLangToggle
GlobalPerms []NameLangToggle
}
type backupItem struct {
SQLURL string
// TODO: Add an easier to parse format here for Gosora to be able to more easily reimport portions of the dump and to strip unnecessary data (e.g. table defs and parsed post data)
Timestamp time.Time
}
type PanelBackupPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
Backups []backupItem
}
type logItem struct {
Action template.HTML
IPAddress string
DoneAt string
}
type PanelLogsPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
Logs []logItem
PageList []int
Page int
LastPage int
}
type PanelDebugPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
Uptime string
OpenConns int
DBAdapter string
}
type PageSimple struct {
Title string
Something interface{}
}
type AreYouSure struct {
URL string
Message string
}
var spaceGap = []byte(" ")
var httpProtBytes = []byte("http://")
var invalidURL = []byte("<span style='color: red;'>[Invalid URL]</span>")
@ -300,11 +25,11 @@ var urlClose = []byte("</a>")
var imageOpen = []byte("<a href=\"")
var imageOpen2 = []byte("\"><img src='")
var imageClose = []byte("' class='postImage' /></a>")
var urlpattern = `(?s)([ {1}])((http|https|ftp|mailto)*)(:{??)\/\/([\.a-zA-Z\/]+)([ {1}])`
var urlPattern = `(?s)([ {1}])((http|https|ftp|mailto)*)(:{??)\/\/([\.a-zA-Z\/]+)([ {1}])`
var urlReg *regexp.Regexp
func init() {
urlReg = regexp.MustCompile(urlpattern)
urlReg = regexp.MustCompile(urlPattern)
}
// TODO: Write a test for this

View File

@ -2,149 +2,69 @@ package main
import (
//"fmt"
"bytes"
"context"
"database/sql"
"errors"
"html"
"html/template"
"net/http"
"strconv"
"strings"
"./extend/guilds/lib"
"./query_gen/lib"
)
var guildsListStmt *sql.Stmt
var guildsMemberListStmt *sql.Stmt
var guildsMemberListJoinStmt *sql.Stmt
var guildsGetMemberStmt *sql.Stmt
var guildsGetGuildStmt *sql.Stmt
var guildsCreateGuildStmt *sql.Stmt
var guildsAttachForumStmt *sql.Stmt
var guildsUnattachForumStmt *sql.Stmt
var guildsAddMemberStmt *sql.Stmt
// TODO: Add a better way of splitting up giant plugins like this
// Guild is a struct representing a guild
type Guild struct {
ID int
Link string
Name string
Desc string
Active bool
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.
Joinable int /* 0: Private, 1: Anyone can join, 2: Applications, 3: Invite-only */
MemberCount int
Owner int
Backdrop string
CreatedAt string
LastUpdateTime string
MainForumID int
MainForum *Forum
Forums []*Forum
ExtData ExtData
}
type GuildPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []*TopicsRow
Forum *Forum
Guild *Guild
Page int
LastPage int
}
// GuildListPage is a page struct for constructing a list of every guild
type GuildListPage struct {
Title string
CurrentUser User
Header *HeaderVars
GuildList []*Guild
}
type GuildMemberListPage struct {
Title string
CurrentUser User
Header *HeaderVars
ItemList []GuildMember
Guild *Guild
Page int
LastPage int
}
// GuildMember is a struct representing a specific member of a guild, not to be confused with the global User struct.
type GuildMember struct {
Link string
Rank int /* 0: Member. 1: Mod. 2: Admin. */
RankString string /* Member, Mod, Admin, Owner */
PostCount int
JoinedAt string
Offline bool // TODO: Need to track the online states of members when WebSockets are enabled
User User
}
// TODO: Add a plugin interface instead of having a bunch of argument to AddPlugin?
func init() {
plugins["guilds"] = NewPlugin("guilds", "Guilds", "Azareal", "http://github.com/Azareal", "", "", "", initGuilds, nil, deactivateGuilds, installGuilds, nil)
// TODO: Is it possible to avoid doing this when the plugin isn't activated?
prebuildTmplList = append(prebuildTmplList, guilds.PrebuildTmplList)
}
func initGuilds() (err error) {
plugins["guilds"].AddHook("intercept_build_widgets", guildsWidgets)
plugins["guilds"].AddHook("trow_assign", guildsTrowAssign)
plugins["guilds"].AddHook("topic_create_pre_loop", guildsTopicCreatePreLoop)
plugins["guilds"].AddHook("pre_render_view_forum", guildsPreRenderViewForum)
plugins["guilds"].AddHook("simple_forum_check_pre_perms", guildsForumCheck)
plugins["guilds"].AddHook("forum_check_pre_perms", guildsForumCheck)
plugins["guilds"].AddHook("intercept_build_widgets", guilds.Widgets)
plugins["guilds"].AddHook("trow_assign", guilds.TrowAssign)
plugins["guilds"].AddHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
plugins["guilds"].AddHook("pre_render_view_forum", guilds.PreRenderViewForum)
plugins["guilds"].AddHook("simple_forum_check_pre_perms", guilds.ForumCheck)
plugins["guilds"].AddHook("forum_check_pre_perms", guilds.ForumCheck)
// TODO: Auto-grant this perm to admins upon installation?
registerPluginPerm("CreateGuild")
router.HandleFunc("/guilds/", guildsGuildList)
router.HandleFunc("/guild/", guildsViewGuild)
router.HandleFunc("/guild/create/", guildsCreateGuild)
router.HandleFunc("/guild/create/submit/", guildsCreateGuildSubmit)
router.HandleFunc("/guild/members/", guildsMemberList)
router.HandleFunc("/guilds/", guilds.GuildList)
router.HandleFunc("/guild/", guilds.ViewGuild)
router.HandleFunc("/guild/create/", guilds.CreateGuild)
router.HandleFunc("/guild/create/submit/", guilds.CreateGuildSubmit)
router.HandleFunc("/guild/members/", guilds.MemberList)
guildsListStmt, err = qgen.Builder.SimpleSelect("guilds", "guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime", "", "", "")
guilds.ListStmt, err = qgen.Builder.SimpleSelect("guilds", "guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime", "", "", "")
if err != nil {
return err
}
guildsGetGuildStmt, err = qgen.Builder.SimpleSelect("guilds", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "guildID = ?", "", "")
guilds.GetGuildStmt, err = qgen.Builder.SimpleSelect("guilds", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "guildID = ?", "", "")
if err != nil {
return err
}
guildsMemberListStmt, err = qgen.Builder.SimpleSelect("guilds_members", "guildID, uid, rank, posts, joinedAt", "", "", "")
guilds.MemberListStmt, err = qgen.Builder.SimpleSelect("guilds_members", "guildID, uid, rank, posts, joinedAt", "", "", "")
if err != nil {
return err
}
guildsMemberListJoinStmt, err = qgen.Builder.SimpleLeftJoin("guilds_members", "users", "users.uid, guilds_members.rank, guilds_members.posts, guilds_members.joinedAt, users.name, users.avatar", "guilds_members.uid = users.uid", "guilds_members.guildID = ?", "guilds_members.rank DESC, guilds_members.joinedat ASC", "")
guilds.MemberListJoinStmt, err = qgen.Builder.SimpleLeftJoin("guilds_members", "users", "users.uid, guilds_members.rank, guilds_members.posts, guilds_members.joinedAt, users.name, users.avatar", "guilds_members.uid = users.uid", "guilds_members.guildID = ?", "guilds_members.rank DESC, guilds_members.joinedat ASC", "")
if err != nil {
return err
}
guildsGetMemberStmt, err = qgen.Builder.SimpleSelect("guilds_members", "rank, posts, joinedAt", "guildID = ? AND uid = ?", "", "")
guilds.GetMemberStmt, err = qgen.Builder.SimpleSelect("guilds_members", "rank, posts, joinedAt", "guildID = ? AND uid = ?", "", "")
if err != nil {
return err
}
guildsCreateGuildStmt, err = qgen.Builder.SimpleInsert("guilds", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()")
guilds.CreateGuildStmt, err = qgen.Builder.SimpleInsert("guilds", "name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime", "?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()")
if err != nil {
return err
}
guildsAttachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = ?, parentType = 'guild'", "fid = ?")
guilds.AttachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = ?, parentType = 'guild'", "fid = ?")
if err != nil {
return err
}
guildsUnattachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = 0, parentType = ''", "fid = ?")
guilds.UnattachForumStmt, err = qgen.Builder.SimpleUpdate("forums", "parentID = 0, parentType = ''", "fid = ?")
if err != nil {
return err
}
guildsAddMemberStmt, err = qgen.Builder.SimpleInsert("guilds_members", "guildID, uid, rank, posts, joinedAt", "?,?,?,0,UTC_TIMESTAMP()")
guilds.AddMemberStmt, err = qgen.Builder.SimpleInsert("guilds_members", "guildID, uid, rank, posts, joinedAt", "?,?,?,0,UTC_TIMESTAMP()")
if err != nil {
return err
}
@ -153,26 +73,26 @@ func initGuilds() (err error) {
}
func deactivateGuilds() {
plugins["guilds"].RemoveHook("intercept_build_widgets", guildsWidgets)
plugins["guilds"].RemoveHook("trow_assign", guildsTrowAssign)
plugins["guilds"].RemoveHook("topic_create_pre_loop", guildsTopicCreatePreLoop)
plugins["guilds"].RemoveHook("pre_render_view_forum", guildsPreRenderViewForum)
plugins["guilds"].RemoveHook("simple_forum_check_pre_perms", guildsForumCheck)
plugins["guilds"].RemoveHook("forum_check_pre_perms", guildsForumCheck)
plugins["guilds"].RemoveHook("intercept_build_widgets", guilds.Widgets)
plugins["guilds"].RemoveHook("trow_assign", guilds.TrowAssign)
plugins["guilds"].RemoveHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
plugins["guilds"].RemoveHook("pre_render_view_forum", guilds.PreRenderViewForum)
plugins["guilds"].RemoveHook("simple_forum_check_pre_perms", guilds.ForumCheck)
plugins["guilds"].RemoveHook("forum_check_pre_perms", guilds.ForumCheck)
deregisterPluginPerm("CreateGuild")
_ = router.RemoveFunc("/guilds/")
_ = router.RemoveFunc("/guild/")
_ = router.RemoveFunc("/guild/create/")
_ = router.RemoveFunc("/guild/create/submit/")
_ = guildsListStmt.Close()
_ = guildsMemberListStmt.Close()
_ = guildsMemberListJoinStmt.Close()
_ = guildsGetMemberStmt.Close()
_ = guildsGetGuildStmt.Close()
_ = guildsCreateGuildStmt.Close()
_ = guildsAttachForumStmt.Close()
_ = guildsUnattachForumStmt.Close()
_ = guildsAddMemberStmt.Close()
_ = guilds.ListStmt.Close()
_ = guilds.MemberListStmt.Close()
_ = guilds.MemberListJoinStmt.Close()
_ = guilds.GetMemberStmt.Close()
_ = guilds.GetGuildStmt.Close()
_ = guilds.CreateGuildStmt.Close()
_ = guilds.AttachForumStmt.Close()
_ = guilds.UnattachForumStmt.Close()
_ = guilds.AddMemberStmt.Close()
}
// TODO: 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
@ -228,427 +148,3 @@ func installGuilds() error {
func uninstallGuilds() error {
return nil
}
// TODO: Do this properly via the widget system
func guildsCommonAreaWidgets(headerVars *HeaderVars) {
// TODO: Hot Groups? Featured Groups? Official Groups?
var b bytes.Buffer
var menu = WidgetMenu{"Guilds", []WidgetMenuItem{
WidgetMenuItem{"Create Guild", "/guild/create/", false},
}}
err := templates.ExecuteTemplate(&b, "widget_menu.html", menu)
if err != nil {
LogError(err)
return
}
if themes[headerVars.ThemeName].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
}
}
// TODO: Do this properly via the widget system
// TODO: Make a better more customisable group widget system
func guildsGuildWidgets(headerVars *HeaderVars, guildItem *Guild) (success bool) {
return false // Disabled until the next commit
/*var b bytes.Buffer
var menu WidgetMenu = WidgetMenu{"Guild Options", []WidgetMenuItem{
WidgetMenuItem{"Join", "/guild/join/" + strconv.Itoa(guildItem.ID), false},
WidgetMenuItem{"Members", "/guild/members/" + strconv.Itoa(guildItem.ID), false},
}}
err := templates.ExecuteTemplate(&b, "widget_menu.html", menu)
if err != nil {
LogError(err)
return false
}
if themes[headerVars.ThemeName].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
} else {
return false
}
return true*/
}
/*
Custom Pages
*/
func guildsGuildList(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
guildsCommonAreaWidgets(headerVars)
rows, err := guildsListStmt.Query()
if err != nil && err != ErrNoRows {
return InternalError(err, w, r)
}
var guildList []*Guild
for rows.Next() {
guildItem := &Guild{ID: 0}
err := rows.Scan(&guildItem.ID, &guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.CreatedAt, &guildItem.LastUpdateTime)
if err != nil {
return InternalError(err, w, r)
}
guildItem.Link = guildsBuildGuildURL(nameToSlug(guildItem.Name), guildItem.ID)
guildList = append(guildList, guildItem)
}
err = rows.Err()
if err != nil {
return InternalError(err, w, r)
}
rows.Close()
pi := GuildListPage{"Guild List", user, headerVars, guildList}
err = templates.ExecuteTemplate(w, "guilds_guild_list.html", pi)
if err != nil {
return InternalError(err, w, r)
}
return nil
}
func guildsGetGuild(guildID int) (guildItem *Guild, err error) {
guildItem = &Guild{ID: guildID}
err = guildsGetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.MainForumID, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime)
return guildItem, err
}
func guildsViewGuild(w http.ResponseWriter, r *http.Request, user User) RouteError {
// SEO URLs...
halves := strings.Split(r.URL.Path[len("/guild/"):], ".")
if len(halves) < 2 {
halves = append(halves, halves[0])
}
guildID, err := strconv.Atoi(halves[1])
if err != nil {
return PreError("Not a valid guild ID", w, r)
}
guildItem, err := guildsGetGuild(guildID)
if err != nil {
return LocalError("Bad guild", w, r, user)
}
if !guildItem.Active {
return NotFound(w, r)
}
// Re-route the request to routeForums
var ctx = context.WithValue(r.Context(), "guilds_current_guild", guildItem)
return routeForum(w, r.WithContext(ctx), user, strconv.Itoa(guildItem.MainForumID))
}
func guildsCreateGuild(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
// TODO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateGuild"] {
return NoPermissions(w, r, user)
}
guildsCommonAreaWidgets(headerVars)
pi := Page{"Create Guild", user, headerVars, tList, nil}
err := templates.ExecuteTemplate(w, "guilds_create_guild.html", pi)
if err != nil {
return InternalError(err, w, r)
}
return nil
}
func guildsCreateGuildSubmit(w http.ResponseWriter, r *http.Request, user User) RouteError {
// TODO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateGuild"] {
return NoPermissions(w, r, user)
}
var guildActive = true
var guildName = html.EscapeString(r.PostFormValue("group_name"))
var guildDesc = html.EscapeString(r.PostFormValue("group_desc"))
var gprivacy = r.PostFormValue("group_privacy")
var guildPrivacy int
switch gprivacy {
case "0":
guildPrivacy = 0 // Public
case "1":
guildPrivacy = 1 // Protected
case "2":
guildPrivacy = 2 // private
default:
guildPrivacy = 0
}
// Create the backing forum
fid, err := fstore.Create(guildName, "", true, "")
if err != nil {
return InternalError(err, w, r)
}
res, err := guildsCreateGuildStmt.Exec(guildName, guildDesc, guildActive, guildPrivacy, user.ID, fid)
if err != nil {
return InternalError(err, w, r)
}
lastID, err := res.LastInsertId()
if err != nil {
return InternalError(err, w, r)
}
// Add the main backing forum to the forum list
err = guildsAttachForum(int(lastID), fid)
if err != nil {
return InternalError(err, w, r)
}
_, err = guildsAddMemberStmt.Exec(lastID, user.ID, 2)
if err != nil {
return InternalError(err, w, r)
}
http.Redirect(w, r, guildsBuildGuildURL(nameToSlug(guildName), int(lastID)), http.StatusSeeOther)
return nil
}
func guildsMemberList(w http.ResponseWriter, r *http.Request, user User) RouteError {
headerVars, ferr := UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
// SEO URLs...
halves := strings.Split(r.URL.Path[len("/guild/members/"):], ".")
if len(halves) < 2 {
halves = append(halves, halves[0])
}
guildID, err := strconv.Atoi(halves[1])
if err != nil {
return PreError("Not a valid group ID", w, r)
}
var guildItem = &Guild{ID: guildID}
var mainForum int // Unused
err = guildsGetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &mainForum, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime)
if err != nil {
return LocalError("Bad group", w, r, user)
}
guildItem.Link = guildsBuildGuildURL(nameToSlug(guildItem.Name), guildItem.ID)
guildsGuildWidgets(headerVars, guildItem)
rows, err := guildsMemberListJoinStmt.Query(guildID)
if err != nil && err != ErrNoRows {
return InternalError(err, w, r)
}
var guildMembers []GuildMember
for rows.Next() {
guildMember := GuildMember{PostCount: 0}
err := rows.Scan(&guildMember.User.ID, &guildMember.Rank, &guildMember.PostCount, &guildMember.JoinedAt, &guildMember.User.Name, &guildMember.User.Avatar)
if err != nil {
return InternalError(err, w, r)
}
guildMember.Link = buildProfileURL(nameToSlug(guildMember.User.Name), guildMember.User.ID)
if guildMember.User.Avatar != "" {
if guildMember.User.Avatar[0] == '.' {
guildMember.User.Avatar = "/uploads/avatar_" + strconv.Itoa(guildMember.User.ID) + guildMember.User.Avatar
}
} else {
guildMember.User.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(guildMember.User.ID), 1)
}
guildMember.JoinedAt, _ = relativeTimeFromString(guildMember.JoinedAt)
if guildItem.Owner == guildMember.User.ID {
guildMember.RankString = "Owner"
} else {
switch guildMember.Rank {
case 0:
guildMember.RankString = "Member"
case 1:
guildMember.RankString = "Mod"
case 2:
guildMember.RankString = "Admin"
}
}
guildMembers = append(guildMembers, guildMember)
}
err = rows.Err()
if err != nil {
return InternalError(err, w, r)
}
rows.Close()
pi := GuildMemberListPage{"Guild Member List", user, headerVars, guildMembers, guildItem, 0, 0}
// A plugin with plugins. Pluginception!
if preRenderHooks["pre_render_guilds_member_list"] != nil {
if runPreRenderHook("pre_render_guilds_member_list", w, r, &user, &pi) {
return nil
}
}
err = templates.ExecuteTemplate(w, "guilds_member_list.html", pi)
if err != nil {
return InternalError(err, w, r)
}
return nil
}
func guildsAttachForum(guildID int, fid int) error {
_, err := guildsAttachForumStmt.Exec(guildID, fid)
return err
}
func guildsUnattachForum(fid int) error {
_, err := guildsAttachForumStmt.Exec(fid)
return err
}
func guildsBuildGuildURL(slug string, id int) string {
if slug == "" {
return "/guild/" + slug + "." + strconv.Itoa(id)
}
return "/guild/" + strconv.Itoa(id)
}
/*
Hooks
*/
func guildsPreRenderViewForum(w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
pi := data.(*ForumPage)
if pi.Header.ExtData.items != nil {
if guildData, ok := pi.Header.ExtData.items["guilds_current_group"]; ok {
guildItem := guildData.(*Guild)
guildpi := GuildPage{pi.Title, pi.CurrentUser, pi.Header, pi.ItemList, pi.Forum, guildItem, pi.Page, pi.LastPage}
err := templates.ExecuteTemplate(w, "guilds_view_guild.html", guildpi)
if err != nil {
LogError(err)
return false
}
return true
}
}
return false
}
func guildsTrowAssign(args ...interface{}) interface{} {
var forum = args[1].(*Forum)
if forum.ParentType == "guild" {
var topicItem = args[0].(*TopicsRow)
topicItem.ForumLink = "/guild/" + strings.TrimPrefix(topicItem.ForumLink, getForumURLPrefix())
}
return nil
}
// TODO: 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 guildsTopicCreatePreLoop(args ...interface{}) interface{} {
var fid = args[2].(int)
if fstore.DirtyGet(fid).ParentType == "guild" {
var strictmode = args[5].(*bool)
*strictmode = true
}
return nil
}
// TODO: Add privacy options
// TODO: Add support for multiple boards and add per-board simplified permissions
// TODO: Take isJs into account for routes which expect JSON responses
func guildsForumCheck(args ...interface{}) (skip bool, rerr RouteError) {
var r = args[1].(*http.Request)
var fid = args[3].(*int)
var forum = fstore.DirtyGet(*fid)
if forum.ParentType == "guild" {
var err error
var w = args[0].(http.ResponseWriter)
guildItem, ok := r.Context().Value("guilds_current_group").(*Guild)
if !ok {
guildItem, err = guildsGetGuild(forum.ParentID)
if err != nil {
return true, InternalError(errors.New("Unable to find the parent group for a forum"), w, r)
}
if !guildItem.Active {
return true, NotFound(w, r)
}
r = r.WithContext(context.WithValue(r.Context(), "guilds_current_group", guildItem))
}
var user = args[2].(*User)
var rank int
var posts int
var joinedAt string
// TODO: Group privacy settings. For now, groups are all globally visible
// Clear the default group permissions
// TODO: Do this more efficiently, doing it quick and dirty for now to get this out quickly
overrideForumPerms(&user.Perms, false)
user.Perms.ViewTopic = true
err = guildsGetMemberStmt.QueryRow(guildItem.ID, user.ID).Scan(&rank, &posts, &joinedAt)
if err != nil && err != ErrNoRows {
return true, InternalError(err, w, r)
} else if err != nil {
// TODO: Should we let admins / guests into public groups?
return true, LocalError("You're not part of this group!", w, r, *user)
}
// TODO: Implement bans properly by adding the Local Ban API in the next commit
// TODO: How does this even work? Refactor it along with the rest of this plugin!
if rank < 0 {
return true, LocalError("You've been banned from this group!", w, r, *user)
}
// Basic permissions for members, more complicated permissions coming in the next commit!
if guildItem.Owner == user.ID {
overrideForumPerms(&user.Perms, true)
} else if rank == 0 {
user.Perms.LikeItem = true
user.Perms.CreateTopic = true
user.Perms.CreateReply = true
} else {
overrideForumPerms(&user.Perms, true)
}
return true, nil
}
return false, nil
}
// TODO: Override redirects? I don't think this is needed quite yet
func guildsWidgets(args ...interface{}) interface{} {
var zone = args[0].(string)
var headerVars = args[2].(*HeaderVars)
var request = args[3].(*http.Request)
if zone != "view_forum" {
return false
}
var forum = args[1].(*Forum)
if forum.ParentType == "guild" {
// This is why I hate using contexts, all the daisy chains and interface casts x.x
guildItem, ok := request.Context().Value("guilds_current_group").(*Guild)
if !ok {
LogError(errors.New("Unable to find a parent group in the context data"))
return false
}
if headerVars.ExtData.items == nil {
headerVars.ExtData.items = make(map[string]interface{})
}
headerVars.ExtData.items["guilds_current_group"] = guildItem
return guildsGuildWidgets(headerVars, guildItem)
}
return false
}

View File

@ -232,8 +232,6 @@ func writeSelects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("getFullSettings", "settings", "name, content, type, constraints", "", "", "")
adapter.SimpleSelect("getPlugins", "plugins", "uname, active, installed", "", "", "")
adapter.SimpleSelect("getThemes", "themes", "uname, default", "", "", "")
adapter.SimpleSelect("getWidgets", "widgets", "position, side, type, active, location, data", "", "position ASC", "")

View File

@ -30,12 +30,17 @@ func inStringList(needle string, list []string) bool {
return false
}
func (group *RouteGroup) Before(line string, literal ...bool) *RouteGroup {
var litItem bool
if len(literal) > 0 {
litItem = literal[0]
func (group *RouteGroup) Before(lines ...string) *RouteGroup {
for _, line := range lines {
group.RunBefore = append(group.RunBefore, Runnable{line, false})
}
return group
}
func (group *RouteGroup) LitBefore(lines ...string) *RouteGroup {
for _, line := range lines {
group.RunBefore = append(group.RunBefore, Runnable{line, true})
}
group.RunBefore = append(group.RunBefore, Runnable{line, litItem})
return group
}

View File

@ -16,12 +16,17 @@ func addRoute(route *RouteImpl) {
routeList = append(routeList, route)
}
func (route *RouteImpl) Before(item string, literal ...bool) *RouteImpl {
var litItem bool
if len(literal) > 0 {
litItem = literal[0]
func (route *RouteImpl) Before(items ...string) *RouteImpl {
for _, item := range items {
route.RunBefore = append(route.RunBefore, Runnable{item, false})
}
return route
}
func (route *RouteImpl) LitBefore(items ...string) *RouteImpl {
for _, item := range items {
route.RunBefore = append(route.RunBefore, Runnable{item, true})
}
route.RunBefore = append(route.RunBefore, Runnable{item, litItem})
return route
}

View File

@ -4,13 +4,20 @@ type RouteSubset struct {
RouteList []*RouteImpl
}
func (set *RouteSubset) Before(line string, literal ...bool) *RouteSubset {
var litItem bool
if len(literal) > 0 {
litItem = literal[0]
}
func (set *RouteSubset) Before(lines ...string) *RouteSubset {
for _, line := range lines {
for _, route := range set.RouteList {
route.RunBefore = append(route.RunBefore, Runnable{line, litItem})
route.RunBefore = append(route.RunBefore, Runnable{line, false})
}
}
return set
}
func (set *RouteSubset) LitBefore(lines ...string) *RouteSubset {
for _, line := range lines {
for _, route := range set.RouteList {
route.RunBefore = append(route.RunBefore, Runnable{line, true})
}
}
return set
}

View File

@ -17,7 +17,7 @@ func routes() {
// TODO: Reduce the number of Befores. With a new method, perhaps?
reportGroup := newRouteGroup("/report/",
Route("routeReportSubmit", "/report/submit/", "extra_data"),
).Before("MemberOnly").Before("NoBanned").Before("NoSessionMismatch")
).Before("MemberOnly", "NoBanned", "NoSessionMismatch")
addRouteGroup(reportGroup)
topicGroup := newRouteGroup("/topics/",
@ -34,17 +34,27 @@ func routes() {
func buildUserRoutes() {
userGroup := newRouteGroup("/user/")
userGroup.Routes(
Route("routeProfile", "/user/").Before("req.URL.Path += extra_data", true),
Route("routeAccountOwnEditCritical", "/user/edit/critical/"),
Route("routeAccountOwnEditCriticalSubmit", "/user/edit/critical/submit/"),
Route("routeAccountOwnEditAvatar", "/user/edit/avatar/"),
Route("routeAccountOwnEditAvatarSubmit", "/user/edit/avatar/submit/"),
Route("routeAccountOwnEditUsername", "/user/edit/username/"),
Route("routeAccountOwnEditUsernameSubmit", "/user/edit/username/submit/"),
Route("routeAccountOwnEditEmail", "/user/edit/email/"),
Route("routeAccountOwnEditEmailTokenSubmit", "/user/edit/token/", "extra_data"),
Route("routeProfile", "/user/").LitBefore("req.URL.Path += extra_data"),
Route("routeAccountEditCritical", "/user/edit/critical/"),
Route("routeAccountEditCriticalSubmit", "/user/edit/critical/submit/").Before("NoSessionMismatch"), // TODO: Full test this
Route("routeAccountEditAvatar", "/user/edit/avatar/"),
Route("routeAccountEditAvatarSubmit", "/user/edit/avatar/submit/"),
Route("routeAccountEditUsername", "/user/edit/username/"),
Route("routeAccountEditUsernameSubmit", "/user/edit/username/submit/"), // TODO: Full test this
Route("routeAccountEditEmail", "/user/edit/email/"),
Route("routeAccountEditEmailTokenSubmit", "/user/edit/token/", "extra_data"),
).Not("/user/").Before("MemberOnly")
addRouteGroup(userGroup)
// TODO: Auto test and manual test these routes
userGroup = newRouteGroup("/users/").Before("MemberOnly")
userGroup.Routes(
Route("routeBanSubmit", "/users/ban/submit/"),
Route("routeUnban", "/users/unban/"),
Route("routeActivate", "/users/activate/"),
Route("routeIps", "/users/ips/"),
).Not("/users/ips/").Before("NoSessionMismatch")
addRouteGroup(userGroup)
}
func buildPanelRoutes() {

View File

@ -17,6 +17,7 @@ import (
"strings"
"time"
"./common"
"./query_gen/lib"
)
@ -102,7 +103,7 @@ func routeOverview(w http.ResponseWriter, r *http.Request, user User) RouteError
}
BuildWidgets("overview", nil, headerVars, r)
pi := Page{"Overview", user, headerVars, tList, nil}
pi := common.Page{"Overview", user, headerVars, tList, nil}
if preRenderHooks["pre_render_overview"] != nil {
if runPreRenderHook("pre_render_overview", w, r, &user, &pi) {
return nil
@ -128,7 +129,7 @@ func routeCustomPage(w http.ResponseWriter, r *http.Request, user User) RouteErr
}
BuildWidgets("custom_page", name, headerVars, r)
pi := Page{"Page", user, headerVars, tList, nil}
pi := common.Page{"Page", user, headerVars, tList, nil}
if preRenderHooks["pre_render_custom_page"] != nil {
if runPreRenderHook("pre_render_custom_page", w, r, &user, &pi) {
return nil
@ -252,10 +253,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) RouteError {
topicItem.ForumName = forum.Name
topicItem.ForumLink = forum.Link
/*topicItem.CreatedAt, err = relativeTimeFromString(topicItem.CreatedAt)
if err != nil {
replyItem.CreatedAt = ""
}*/
//topicItem.CreatedAt = relativeTime(topicItem.CreatedAt)
topicItem.RelativeLastReplyAt = relativeTime(topicItem.LastReplyAt)
if vhooks["topics_topic_row_assign"] != nil {

View File

@ -7,6 +7,8 @@ import (
"net"
"net/http"
"strings"
"./common"
)
// nolint
@ -14,23 +16,17 @@ var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = preRoute
// TODO: Come up with a better middleware solution
// nolint We need these types so people can tell what they are without scrolling to the bottom of the file
var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, RouteError) = panelUserCheck
var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, RouteError) = simplePanelUserCheck
var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, err RouteError) = simpleForumUserCheck
var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, err RouteError) = forumUserCheck
var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, err RouteError) = memberCheck
var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, err RouteError) = simpleUserCheck
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, err RouteError) = userCheck
// This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible
// TODO: Write a test for this
func getDefaultHeaderVar() *HeaderVars {
return &HeaderVars{Site: site, ThemeName: fallbackTheme}
}
var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*common.HeaderVars, common.PanelStats, RouteError) = panelUserCheck
var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*common.HeaderLite, RouteError) = simplePanelUserCheck
var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *common.HeaderLite, err RouteError) = simpleForumUserCheck
var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *common.HeaderVars, err RouteError) = forumUserCheck
var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, err RouteError) = memberCheck
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, err RouteError) = userCheck
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, err RouteError) = userCheck
// TODO: Support for left sidebars and sidebars on both sides
// http.Request is for context.Context middleware. Mostly for plugin_guilds right now
func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) {
func BuildWidgets(zone string, data interface{}, headerVars *common.HeaderVars, r *http.Request) {
if vhooks["intercept_build_widgets"] != nil {
if runVhook("intercept_build_widgets", zone, data, headerVars, r).(bool) {
return
@ -53,7 +49,7 @@ func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http
}
}
func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, rerr RouteError) {
func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *common.HeaderLite, rerr RouteError) {
if !fstore.Exists(fid) {
return nil, PreError("The target forum doesn't exist.", w, r)
}
@ -77,7 +73,7 @@ func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fi
return headerLite, nil
}
func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, rerr RouteError) {
func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *common.HeaderVars, rerr RouteError) {
headerVars, rerr = UserCheck(w, r, user)
if rerr != nil {
return headerVars, rerr
@ -130,7 +126,7 @@ func cascadeForumPerms(fperms ForumPerms, user *User) {
// 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
// TODO: Do a panel specific theme?
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) {
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, stats common.PanelStats, rerr RouteError) {
var themeName = defaultThemeBox.Load().(string)
cookie, err := r.Cookie("current_theme")
@ -192,7 +188,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV
return headerVars, stats, nil
}
func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) {
func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *common.HeaderLite, rerr RouteError) {
return &HeaderLite{
Site: site,
Settings: settingBox.Load().(SettingBox),
@ -200,7 +196,7 @@ func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (h
}
// TODO: Add this to the member routes
func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) {
func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, rerr RouteError) {
headerVars, rerr = UserCheck(w, r, user)
if !user.Loggedin {
return headerVars, NoPermissions(w, r, *user)
@ -209,7 +205,7 @@ func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars
}
// SimpleUserCheck is back from the grave, yay :D
func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) {
func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *common.HeaderLite, rerr RouteError) {
headerLite = &HeaderLite{
Site: site,
Settings: settingBox.Load().(SettingBox),
@ -218,7 +214,7 @@ func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
}
// TODO: Add the ability for admins to restrict certain themes to certain groups?
func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) {
func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *common.HeaderVars, rerr RouteError) {
var themeName = defaultThemeBox.Load().(string)
cookie, err := r.Cookie("current_theme")
@ -270,7 +266,6 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *
}
func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
user, ok := func(w http.ResponseWriter, r *http.Request) (User, bool) {
user, halt := auth.SessionCheck(w, r)
if halt {
return *user, false
@ -279,31 +274,29 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
return *user, true
}
h := w.Header()
h.Set("X-Frame-Options", "deny")
//h.Set("X-XSS-Protection", "1")
// TODO: Set the content policy header
return *user, true
}(w, r)
if !ok {
return user, false
}
var usercpy = *user
// TODO: WIP. Refactor this to eliminate the unnecessary query
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
PreError("Bad IP", w, r)
return user, false
return usercpy, false
}
if host != user.LastIP {
_, err = stmts.updateLastIP.Exec(host, user.ID)
if host != usercpy.LastIP {
_, err = stmts.updateLastIP.Exec(host, usercpy.ID)
if err != nil {
InternalError(err, w, r)
return user, false
return usercpy, false
}
user.LastIP = host
usercpy.LastIP = host
}
return user, ok
h := w.Header()
h.Set("X-Frame-Options", "deny")
//h.Set("X-XSS-Protection", "1")
// TODO: Set the content policy header
return usercpy, true
}
// SuperModeOnly makes sure that only super mods or higher can access the panel routes