NoEmbeds privacy and config settings.

Phase out url_prefix and url_name.
Eliminate some boilerplate and allocations while we're at it.
Reduce the number of conditional statements in url validator loops.
Better parsing of Site.URL to handle user error better.

You will have to run the patcher / updater for this commit.
This commit is contained in:
Azareal 2019-12-08 13:40:56 +10:00
parent 3715c800da
commit 0dede6a329
31 changed files with 689 additions and 561 deletions

View File

@ -31,11 +31,15 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"session", "varchar", 200, false, false, "''"},
//tC{"authToken", "varchar", 200, false, false, "''"},
tC{"last_ip", "varchar", 200, false, false, "0.0.0.0.0"},
tC{"enable_embeds", "int", 0, false, false, "-1"},
tC{"email", "varchar", 200, false, false, "''"},
tC{"avatar", "varchar", 100, false, false, "''"},
tC{"message", "text", 0, false, false, "''"},
// TODO: Drop these columns?
tC{"url_prefix", "varchar", 20, false, false, "''"},
tC{"url_name", "varchar", 100, false, false, "''"},
tC{"level", "smallint", 0, false, false, "0"},
tC{"score", "int", 0, false, false, "0"},
tC{"posts", "int", 0, false, false, "0"},

View File

@ -248,6 +248,13 @@ type AccountBlocksPage struct {
Paginator
}
type AccountPrivacyPage struct {
*Header
ProfileComments bool
ReceiveConvos bool
EnableEmbeds bool
}
type AccountDashPage struct {
*Header
MFASetup bool

View File

@ -449,10 +449,26 @@ var hashLinkMap = map[string]func(*strings.Builder, string, *int){
// TODO: Forum Shortcode Link
}
// TODO: Pack multiple bit flags into an integer instead of using a struct?
var DefaultParseSettings = &ParseSettings{}
type ParseSettings struct {
NoEmbed bool
}
func (ps *ParseSettings) CopyPtr() *ParseSettings {
n := &ParseSettings{}
*n = *ps
return n
}
// TODO: Write a test for this
// TODO: We need a lot more hooks here. E.g. To add custom media types and handlers.
// TODO: Use templates to reduce the amount of boilerplate?
func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/) string {
func ParseMessage(msg string, sectionID int, sectionType string, settings *ParseSettings /*, user User*/) string {
if settings == nil {
settings = DefaultParseSettings
}
// TODO: Word boundary detection for these to avoid mangling code
msg = strings.Replace(msg, ":)", "😀", -1)
msg = strings.Replace(msg, ":(", "😞", -1)
@ -544,10 +560,10 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
i--
} else if msg[i] == 'h' || msg[i] == 'f' || msg[i] == 'g' || msg[i] == '/' {
//fmt.Println("s3")
if len(msg) > i+3 && msg[i+1] == 't' && msg[i+2] == 't' && msg[i+3] == 'p' {
if len(msg) > i+5 && msg[i+1] == 't' && msg[i+2] == 't' && msg[i+3] == 'p' {
if len(msg) > i+6 && msg[i+4] == 's' && msg[i+5] == ':' && msg[i+6] == '/' {
// Do nothing
} else if len(msg) > i+5 && msg[i+4] == ':' && msg[i+5] == '/' {
} else if msg[i+4] == ':' && msg[i+5] == '/' {
// Do nothing
} else {
continue
@ -593,7 +609,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
continue
}
media, ok := parseMediaString(msg[i : i+urlLen])
media, ok := parseMediaString(msg[i:i+urlLen], settings)
if !ok {
//fmt.Println("o3")
sb.Write(InvalidURL)
@ -702,8 +718,8 @@ func validateURLString(data string) bool {
// ? - There should only be one : and that's only if the URL is on a non-standard port. Same for ?s.
for ; len(data) > i; i++ {
ch := data[i] // char
if ch != '\\' && ch != '_' && ch != ':' && ch != '?' && ch != '&' && ch != '=' && ch != ';' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 58) && !(ch > 64 && ch < 92) && !(ch > 96 && ch < 123) { // 90 is Z, 91 is [
ch := data[i]
if ch != '\\' && ch != '_' && ch != '?' && ch != '&' && ch != '=' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 60) && !(ch > 64 && ch < 92) && !(ch > 96 && ch < 123) { // 57 is 9, 58 is :, 59 is ;, 90 is Z, 91 is [
return false
}
}
@ -728,8 +744,8 @@ func validatedURLBytes(data []byte) (url []byte) {
// ? - There should only be one : and that's only if the URL is on a non-standard port. Same for ?s.
for ; datalen > i; i++ {
ch := data[i] // char
if ch != '\\' && ch != '_' && ch != ':' && ch != '?' && ch != '&' && ch != '=' && ch != ';' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 58) && !(ch > 64 && ch < 92) && !(ch > 96 && ch < 123) { // 90 is Z, 91 is [
ch := data[i]
if ch != '\\' && ch != '_' && ch != '?' && ch != '&' && ch != '=' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 60) && !(ch > 64 && ch < 92) && !(ch > 96 && ch < 123) { // 57 is 9, 58 is :, 59 is ;, 90 is Z, 91 is [
return InvalidURL
}
}
@ -756,8 +772,8 @@ func PartialURLString(data string) (url []byte) {
// ? - There should only be one : and that's only if the URL is on a non-standard port. Same for ?s.
for ; end >= i; i++ {
ch := data[i] // char
if ch != '\\' && ch != '_' && ch != ':' && ch != '?' && ch != '&' && ch != '=' && ch != ';' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 58) && !(ch > 64 && ch < 92) && !(ch > 96 && ch < 123) { // 90 is Z, 91 is [
ch := data[i]
if ch != '\\' && ch != '_' && ch != '?' && ch != '&' && ch != '=' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 60) && !(ch > 64 && ch < 92) && !(ch > 96 && ch < 123) { // 57 is 9, 58 is :, 59 is ;, 90 is Z, 91 is [
end = i
}
}
@ -796,7 +812,7 @@ func PartialURLStringLen(data string) (int, bool) {
if ch < 33 { // space and invisibles
//fmt.Println("e2:",i)
return i, i != f
} else if ch != '\\' && ch != '_' && ch != ':' && ch != '?' && ch != '&' && ch != '=' && ch != ';' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 58) && !(ch > 64 && ch < 92) && !(ch > 96 && ch < 123) { // 90 is Z, 91 is [
} else if ch != '\\' && ch != '_' && ch != '?' && ch != '&' && ch != '=' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 60) && !(ch > 64 && ch < 92) && !(ch > 96 && ch < 123) { // 57 is 9, 58 is :, 59 is ;, 90 is Z, 91 is [
//log.Print("Bad Character: ", ch)
//fmt.Println("e3")
return i, false
@ -830,8 +846,8 @@ func PartialURLStringLen2(data string) int {
// ? - There should only be one : and that's only if the URL is on a non-standard port. Same for ?s.
for ; len(data) > i; i++ {
ch := data[i] //char
if ch != '\\' && ch != '_' && ch != ':' && ch != '?' && ch != '&' && ch != '=' && ch != ';' && ch != '@' && ch != '#' && !(ch > 44 && ch < 58) && !(ch > 64 && ch < 91) && !(ch > 96 && ch < 123) { // 90 is Z, 91 is [
ch := data[i]
if ch != '\\' && ch != '_' && ch != '?' && ch != '&' && ch != '=' && ch != '@' && ch != '#' && ch != ']' && !(ch > 44 && ch < 60) && !(ch > 64 && ch < 91) && !(ch > 96 && ch < 123) { // 57 is 9, 58 is :, 59 is ;, 90 is Z, 91 is [
//log.Print("Bad Character: ", ch)
return i
}
@ -850,7 +866,7 @@ type MediaEmbed struct {
}
// TODO: Write a test for this
func parseMediaString(data string) (media MediaEmbed, ok bool) {
func parseMediaString(data string, settings *ParseSettings) (media MediaEmbed, ok bool) {
if !validateURLString(data) {
return media, false
}
@ -907,6 +923,7 @@ func parseMediaString(data string) (media MediaEmbed, ok bool) {
}
}
if !settings.NoEmbed {
// ? - I don't think this hostname will hit every YT domain
// TODO: Make this a more customisable handler rather than hard-coding it in here
if strings.HasSuffix(host, ".youtube.com") && path == "/watch" {
@ -934,6 +951,7 @@ func parseMediaString(data string) (media MediaEmbed, ok bool) {
}
}
}
}
var sport string
if port != "443" && port != "80" && port != "" {
@ -947,8 +965,8 @@ func parseMediaString(data string) (media MediaEmbed, ok bool) {
if len(uurl.Fragment) > 0 {
frag = "#" + uurl.Fragment
}
media.URL = scheme + "//" + host + sport + path + q + frag
media.FURL = host + sport + path + q + frag
media.URL = scheme + "//" + media.FURL
return media, true
}
@ -978,7 +996,7 @@ func CoerceIntString(data string) (res int, length int) {
// TODO: Write tests for this
// Make sure we reflect changes to this in the JS port in /public/global.js
func Paginate(currentPage int, lastPage int, maxPages int) (out []int) {
func Paginate(currentPage, lastPage, maxPages int) (out []int) {
diff := lastPage - currentPage
pre := 3
if diff < 3 {
@ -998,7 +1016,7 @@ func Paginate(currentPage int, lastPage int, maxPages int) (out []int) {
// TODO: Write tests for this
// Make sure we reflect changes to this in the JS port in /public/global.js
func PageOffset(count int, page int, perPage int) (int, int, int) {
func PageOffset(count, page, perPage int) (int, int, int) {
var offset int
lastPage := LastPage(count, perPage)
if page > 1 {
@ -1020,6 +1038,6 @@ func PageOffset(count int, page int, perPage int) (int, int, int) {
// TODO: Write tests for this
// Make sure we reflect changes to this in the JS port in /public/global.js
func LastPage(count int, perPage int) int {
func LastPage(count, perPage int) int {
return (count / perPage) + 1
}

View File

@ -5,7 +5,7 @@ import (
"html"
"time"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
)
var profileReplyStmts ProfileReplyStmts
@ -30,9 +30,10 @@ type ProfileReplyStmts struct {
func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
ur := "users_replies"
profileReplyStmts = ProfileReplyStmts{
edit: acc.Update("users_replies").Set("content = ?, parsed_content = ?").Where("rid = ?").Prepare(),
delete: acc.Delete("users_replies").Where("rid = ?").Prepare(),
edit: acc.Update(ur).Set("content = ?, parsed_content = ?").Where("rid = ?").Prepare(),
delete: acc.Delete(ur).Where("rid = ?").Prepare(),
}
return acc.FirstError()
})
@ -51,7 +52,7 @@ func (r *ProfileReply) Delete() error {
func (r *ProfileReply) SetBody(content string) error {
content = PreparseMessage(html.UnescapeString(content))
_, err := profileReplyStmts.edit.Exec(content, ParseMessage(content, 0, ""), r.ID)
_, err := profileReplyStmts.edit.Exec(content, ParseMessage(content, 0, "", nil), r.ID)
return err
}

View File

@ -38,7 +38,7 @@ func (s *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) {
}
func (s *SQLProfileReplyStore) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error) {
res, err := s.create.Exec(profileID, content, ParseMessage(content, 0, ""), createdBy, ipaddress)
res, err := s.create.Exec(profileID, content, ParseMessage(content, 0, "", nil), createdBy, ipaddress)
if err != nil {
return 0, err
}

View File

@ -17,31 +17,18 @@ import (
type ReplyUser struct {
Reply
//ID int
//ParentID int
//Content string
ContentHtml string
//CreatedBy int
UserLink string
CreatedByName string
//Group int
//CreatedAt time.Time
//LastEdit int
//LastEditBy int
Avatar string
MicroAvatar string
ClassName string
//ContentLines int
Tag string
URL string
URLPrefix string
URLName string
//URLPrefix string
//URLName string
Level int
//IP string
//Liked bool
//LikeCount int
//AttachCount int
//ActionType string
ActionIcon string
Attachments []*MiniAttachment
@ -139,7 +126,7 @@ func (r *Reply) SetPost(content string) error {
return err
}
content = PreparseMessage(html.UnescapeString(content))
parsedContent := ParseMessage(content, topic.ParentID, "forums")
parsedContent := ParseMessage(content, topic.ParentID, "forums", nil)
_, err = replyStmts.edit.Exec(content, parsedContent, r.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
_ = Rstore.GetCache().Remove(r.ID)
return err

View File

@ -52,7 +52,7 @@ func (s *SQLReplyStore) Get(id int) (*Reply, error) {
// TODO: Write a test for this
func (s *SQLReplyStore) Create(t *Topic, content string, ip string, uid int) (rid int, err error) {
res, err := s.create.Exec(t.ID, content, ParseMessage(content, t.ParentID, "forums"), ip, WordCount(content), uid)
res, err := s.create.Exec(t.ID, content, ParseMessage(content, t.ParentID, "forums", nil), ip, WordCount(content), uid)
if err != nil {
return 0, err
}

View File

@ -34,7 +34,7 @@ func NewDefaultReportStore(acc *qgen.Accumulator) (*DefaultReportStore, error) {
}
// ! There's a data race in this. If two users report one item at the exact same time, then both reports will go through
func (s *DefaultReportStore) Create(title string, content string, user *User, itemType string, itemID int) (tid int, err error) {
func (s *DefaultReportStore) Create(title string, content string, u *User, itemType string, itemID int) (tid int, err error) {
var count int
err = s.exists.QueryRow(itemType+"_"+strconv.Itoa(itemID), ReportForumID).Scan(&count)
if err != nil && err != sql.ErrNoRows {
@ -44,7 +44,7 @@ func (s *DefaultReportStore) Create(title string, content string, user *User, it
return 0, ErrAlreadyReported
}
res, err := s.create.Exec(title, content, ParseMessage(content, 0, ""), user.LastIP, user.ID, user.ID, itemType+"_"+strconv.Itoa(itemID), ReportForumID)
res, err := s.create.Exec(title, content, ParseMessage(content, 0, "", nil), u.LastIP, u.ID, u.ID, itemType+"_"+strconv.Itoa(itemID), ReportForumID)
if err != nil {
return 0, err
}
@ -53,5 +53,6 @@ func (s *DefaultReportStore) Create(title string, content string, user *User, it
return 0, err
}
tid = int(lastID)
return tid, Forums.AddTopic(tid, user.ID, ReportForumID)
return tid, Forums.AddTopic(tid, u.ID, ReportForumID)
}

View File

@ -91,6 +91,7 @@ type config struct {
PrimaryServer bool
ServerCount int
LastIPCutoff int // Currently just -1, non--1, but will accept the number of months a user's last IP should be retained for in the future before being purged. Please note that the other two cutoffs below operate off the numbers of days instead.
PostIPCutoff int
LogPruneCutoff int
@ -107,6 +108,7 @@ type config struct {
RefNoTrack bool
RefNoRef bool
NoEmbed bool
Noavatar string // ? - Move this into the settings table?
ItemsPerPage int // ? - Move this into the settings table?
@ -163,6 +165,10 @@ func LoadConfig() error {
func ProcessConfig() (err error) {
Config.Noavatar = strings.Replace(Config.Noavatar, "{site_url}", Site.URL, -1)
guestAvatar = GuestAvatar{buildNoavatar(0, 200), buildNoavatar(0, 48)}
// Strip these unnecessary bits, if we find them.
Site.URL = strings.TrimPrefix(Site.URL, "http://")
Site.URL = strings.TrimPrefix(Site.URL, "https://")
Site.Host = Site.URL
Site.LocalHost = Site.Host == "localhost" || Site.Host == "127.0.0.1" || Site.Host == "::1"
Site.PortInt, err = strconv.Atoi(Site.Port)
@ -216,6 +222,9 @@ func ProcessConfig() (err error) {
if Config.LogPruneCutoff == 0 {
Config.LogPruneCutoff = 365 // Default cutoff
}
if Config.NoEmbed {
DefaultParseSettings.NoEmbed = true
}
// ? Find a way of making these unlimited if zero? It might rule out some optimisations, waste memory, and break layouts
if Config.MaxTopicTitleLength == 0 {
@ -240,20 +249,18 @@ func ProcessConfig() (err error) {
return nil
}
func VerifyConfig() error {
if !Forums.Exists(Config.DefaultForum) {
return errors.New("Invalid default forum")
func VerifyConfig() (err error) {
switch {
case !Forums.Exists(Config.DefaultForum):
err = errors.New("Invalid default forum")
case Config.ServerCount < 1:
err = errors.New("You can't have less than one server")
case Config.MaxTopicTitleLength > 100:
err = errors.New("The max topic title length cannot be over 100 as that's unable to fit in the database row")
case Config.MaxUsernameLength > 100:
err = errors.New("The max username length cannot be over 100 as that's unable to fit in the database row")
}
if Config.ServerCount < 1 {
return errors.New("You can't have less than one server")
}
if Config.MaxTopicTitleLength > 100 {
return errors.New("The max topic title length cannot be over 100 as that's unable to fit in the database row")
}
if Config.MaxUsernameLength > 100 {
return errors.New("The max username length cannot be over 100 as that's unable to fit in the database row")
}
return nil
return err
}
func SwitchToTestDB() {

View File

@ -14,8 +14,8 @@ import (
"github.com/Azareal/Gosora/common/alerts"
p "github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/common/templates"
"github.com/Azareal/Gosora/query_gen"
tmpl "github.com/Azareal/Gosora/common/templates"
qgen "github.com/Azareal/Gosora/query_gen"
)
var Ctemplates []string // TODO: Use this to filter out top level templates we don't need
@ -93,14 +93,14 @@ var Template_account_handle = genIntTmpl("account")
func tmplInitUsers() (User, User, User) {
avatar, microAvatar := BuildAvatar(62, "")
user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", "", "", 0, 0, 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, "", avatar, microAvatar, "", "", 0, 0, 0, 0, "0.0.0.0.0", "", 0, nil}
// TODO: Do a more accurate level calculation for this?
avatar, microAvatar = BuildAvatar(1, "")
user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 58, 1000, 0, 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, "", avatar, microAvatar, "", "", 58, 1000, 0, 1000, "127.0.0.1", "", 0, nil}
avatar, microAvatar = BuildAvatar(2, "")
user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 42, 900, 0, 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, "", avatar, microAvatar, "", "", 42, 900, 0, 900, "::1", "", 0, nil}
return user, user2, user3
}
@ -239,11 +239,11 @@ func compileCommons(c *tmpl.CTemplateSet, head *Header, head2 *Header, forumList
}, VoteCount: 7}
avatar, microAvatar := BuildAvatar(62, "")
miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach, nil,false}
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", 58, false, miniAttach, nil, false}
var replyList []*ReplyUser
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, URLPrefix: "", URLName: "", Level: 0, Attachments: miniAttach}
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Level: 0, Attachments: miniAttach}
ru.Init()
replyList = append(replyList, ru)
tpage := TopicPage{htitle("Topic Name"), replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
@ -271,7 +271,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
// TODO: Do we want the UID on this to be 0?
//avatar, microAvatar = BuildAvatar(0, "")
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: "", URLPrefix: "", URLName: "", Level: 0, Attachments: miniAttach}
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: "", Level: 0, Attachments: miniAttach}
ru.Init()
replyList = append(replyList, ru)
@ -531,12 +531,12 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
}, VoteCount: 7}
avatar, microAvatar := BuildAvatar(62, "")
miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
topic := TopicUser{1, "blah", "Blah", "Hey there!", 62, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach, nil,false}
topic := TopicUser{1, "blah", "Blah", "Hey there!", 62, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "","","", 58, false, miniAttach, nil, false}
var replyList []*ReplyUser
// TODO: Do we really want the UID here to be zero?
avatar, microAvatar = BuildAvatar(0, "")
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, URLPrefix: "", URLName: "", Level: 0, Attachments: miniAttach}
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Level: 0, Attachments: miniAttach}
ru.Init()
replyList = append(replyList, ru)

View File

@ -11,7 +11,6 @@ import (
"html"
"html/template"
//"log"
"strconv"
"strings"
"time"
@ -83,8 +82,8 @@ type TopicUser struct {
ContentHTML string // TODO: Avoid converting this to bytes in templates, particularly if it's long
Tag string
URL string
URLPrefix string
URLName string
//URLPrefix string
//URLName string
Level int
Liked bool
@ -215,7 +214,7 @@ func init() {
t := "topics"
topicStmts = TopicStmts{
getRids: acc.Select("replies").Columns("rid").Where("tid = ?").Orderby("rid ASC").Limit("?,?").Prepare(),
getReplies: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.url_prefix, u.url_name, u.level, r.ipaddress, r.likeCount, r.attachCount, r.actionType", "r.createdBy = u.uid", "r.tid = ?", "r.rid ASC", "?,?"),
getReplies: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.level, r.ipaddress, r.likeCount, r.attachCount, r.actionType", "r.createdBy = u.uid", "r.tid = ?", "r.rid ASC", "?,?"),
addReplies: acc.Update(t).Set("postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()").Where("tid = ?").Prepare(),
updateLastReply: acc.Update(t).Set("lastReplyID = ?").Where("lastReplyID > ? AND tid = ?").Prepare(),
lock: acc.Update(t).Set("is_closed = 1").Where("tid = ?").Prepare(),
@ -233,7 +232,7 @@ func init() {
setPoll: acc.Update(t).Set("poll = ?").Where("tid = ? AND poll = 0").Prepare(),
createAction: acc.Insert("replies").Columns("tid, actionType, ipaddress, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(),
getTopicUser: acc.SimpleLeftJoin("topics AS t", "users AS u", "t.title, t.content, t.createdBy, t.createdAt, t.lastReplyAt, t.lastReplyBy, t.lastReplyID, t.is_closed, t.sticky, t.parentID, t.ipaddress, t.views, t.postCount, t.likeCount, t.attachCount,t.poll, u.name, u.avatar, u.group, u.url_prefix, u.url_name, u.level", "t.createdBy = u.uid", "tid = ?", "", ""),
getTopicUser: acc.SimpleLeftJoin("topics AS t", "users AS u", "t.title, t.content, t.createdBy, t.createdAt, t.lastReplyAt, t.lastReplyBy, t.lastReplyID, t.is_closed, t.sticky, t.parentID, t.ipaddress, t.views, t.postCount, t.likeCount, t.attachCount,t.poll, u.name, u.avatar, u.group, u.level", "t.createdBy = u.uid", "tid = ?", "", ""),
getByReplyID: acc.SimpleLeftJoin("replies AS r", "topics AS t", "t.tid, t.title, t.content, t.createdBy, t.createdAt, t.is_closed, t.sticky, t.parentID, t.ipaddress, t.views, t.postCount, t.likeCount, t.poll, t.data", "r.tid = t.tid", "rid = ?", "", ""),
}
return acc.FirstError()
@ -250,7 +249,7 @@ func (t *Topic) cacheRemove() {
}
// TODO: Write a test for this
func (t *Topic) AddReply(rid int, uid int) (err error) {
func (t *Topic) AddReply(rid, uid int) (err error) {
_, err = topicStmts.addReplies.Exec(1, uid, t.ID)
if err != nil {
return err
@ -300,7 +299,7 @@ func (t *Topic) Unstick() (err error) {
// TODO: Test this
// TODO: Use a transaction for this
func (t *Topic) Like(score int, uid int) (err error) {
func (t *Topic) Like(score, uid int) (err error) {
var disp int // Unused
err = topicStmts.hasLikedTopic.QueryRow(uid, t.ID).Scan(&disp)
if err != nil && err != ErrNoRows {
@ -308,12 +307,10 @@ func (t *Topic) Like(score int, uid int) (err error) {
} else if err != ErrNoRows {
return ErrAlreadyLiked
}
_, err = topicStmts.createLike.Exec(score, t.ID, "topics", uid)
if err != nil {
return err
}
_, err = topicStmts.addLikesToTopic.Exec(1, t.ID)
if err != nil {
return err
@ -360,7 +357,7 @@ func (t *Topic) Delete() error {
}
// TODO: Write tests for this
func (t *Topic) Update(name string, content string) error {
func (t *Topic) Update(name, content string) error {
name = SanitiseSingleLine(html.UnescapeString(name))
if name == "" {
return ErrNoTitle
@ -371,7 +368,7 @@ func (t *Topic) Update(name string, content string) error {
}
content = PreparseMessage(html.UnescapeString(content))
parsedContent := ParseMessage(content, t.ParentID, "forums")
parsedContent := ParseMessage(content, t.ParentID, "forums", nil)
_, err := topicStmts.edit.Exec(name, content, parsedContent, t.ID)
t.cacheRemove()
return err
@ -404,7 +401,7 @@ func (t *Topic) CreateActionReply(action string, ip string, uid int) (err error)
return err
}
func GetRidsForTopic(tid int, offset int) (rids []int, err error) {
func GetRidsForTopic(tid, offset int) (rids []int, err error) {
rows, err := topicStmts.getRids.Query(tid, offset, Config.ItemsPerPage)
if err != nil {
return nil, err
@ -450,20 +447,16 @@ func (ru *ReplyUser) Init() error {
// We really shouldn't have inline HTML, we should do something about this...
if ru.ActionType != "" {
var action string
aarr := strings.Split(ru.ActionType, "-")
switch aarr[0] {
action := aarr[0]
switch action {
case "lock":
action = aarr[0]
ru.ActionIcon = lockai
case "unlock":
action = aarr[0]
ru.ActionIcon = unlockai
case "stick":
action = aarr[0]
ru.ActionIcon = stickai
case "unstick":
action = aarr[0]
ru.ActionIcon = unstickai
case "move":
if len(aarr) == 2 {
@ -471,20 +464,16 @@ func (ru *ReplyUser) Init() error {
forum, err := Forums.Get(fid)
if err == nil {
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_move_dest", forum.Link, forum.Name, ru.UserLink, ru.CreatedByName)
} else {
action = aarr[0]
return nil
}
} else {
action = aarr[0]
}
default:
// TODO: Only fire this off if a corresponding phrase for the ActionType doesn't exist? Or maybe have some sort of action registry?
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_default", ru.ActionType)
return nil
}
if action != "" {
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_"+action, ru.UserLink, ru.CreatedByName)
}
}
return nil
}
@ -516,42 +505,52 @@ func (t *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*ReplyUs
ruser, err = ucache.Get(re.CreatedBy)
}
// TODO: Factor the user fields out and embed a user struct instead
var reply *ReplyUser
hTbl := GetHookTable()
if err == nil {
//log.Print("reply cached serve")
reply = &ReplyUser{ClassName: "", Reply: *re, CreatedByName: ruser.Name, Avatar: ruser.Avatar, URLPrefix: ruser.URLPrefix, URLName: ruser.URLName, Level: ruser.Level}
err := reply.Init()
rf := func(r *ReplyUser) error {
err := r.Init()
if err != nil {
return nil, "", err
}
reply.ContentHtml = ParseMessage(reply.Content, t.ParentID, "forums")
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
if reply.ContentHtml == reply.Content {
reply.ContentHtml = reply.Content
return err
}
if reply.ID == pFrag {
ogdesc = reply.Content
r.ContentHtml = ParseMessage(r.Content, t.ParentID, "forums", user.ParseSettings)
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
if r.ContentHtml == r.Content {
r.ContentHtml = r.Content
}
// TODO: This doesn't work properly so pick the first one instead?
if r.ID == pFrag {
ogdesc = r.Content
if len(ogdesc) > 200 {
ogdesc = ogdesc[:197] + "..."
}
}
if reply.LikeCount > 0 && user.Liked > 0 {
likedMap[reply.ID] = len(rlist)
likedQueryList = append(likedQueryList, reply.ID)
if r.LikeCount > 0 && user.Liked > 0 {
likedMap[r.ID] = len(rlist)
likedQueryList = append(likedQueryList, r.ID)
}
if user.Perms.EditReply && reply.AttachCount > 0 {
attachMap[reply.ID] = len(rlist)
attachQueryList = append(attachQueryList, reply.ID)
if user.Perms.EditReply && r.AttachCount > 0 {
attachMap[r.ID] = len(rlist)
attachQueryList = append(attachQueryList, r.ID)
}
reply.Deletable = user.Perms.DeleteReply || reply.CreatedBy == user.ID
r.Deletable = user.Perms.DeleteReply || r.CreatedBy == user.ID
hTbl.VhookNoRet("topic_reply_row_assign", &rlist, &reply)
rlist = append(rlist, reply)
hTbl.VhookNoRet("topic_reply_row_assign", &rlist, &r)
// TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis?
rlist = append(rlist, r)
//log.Printf("r: %d-%d", r.ID, len(rlist)-1)
return nil
}
// TODO: Factor the user fields out and embed a user struct instead
if err == nil {
//log.Print("reply cached serve")
reply := &ReplyUser{ClassName: "", Reply: *re, CreatedByName: ruser.Name, Avatar: ruser.Avatar /*URLPrefix: ruser.URLPrefix, URLName: ruser.URLName, */, Level: ruser.Level}
err = rf(reply)
if err != nil {
return nil, "", err
}
} else {
rows, err := topicStmts.getReplies.Query(t.ID, offset, Config.ItemsPerPage)
if err != nil {
@ -560,39 +559,15 @@ func (t *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*ReplyUs
defer rows.Close()
for rows.Next() {
reply = &ReplyUser{}
err := rows.Scan(&reply.ID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.Avatar, &reply.CreatedByName, &reply.Group, &reply.URLPrefix, &reply.URLName, &reply.Level, &reply.IP, &reply.LikeCount, &reply.AttachCount, &reply.ActionType)
r := &ReplyUser{}
err := rows.Scan(&r.ID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.Avatar, &r.CreatedByName, &r.Group /*&r.URLPrefix, &r.URLName,*/, &r.Level, &r.IP, &r.LikeCount, &r.AttachCount, &r.ActionType)
if err != nil {
return nil, "", err
}
if err := reply.Init(); err != nil {
err = rf(r)
if err != nil {
return nil, "", err
}
reply.ContentHtml = ParseMessage(reply.Content, t.ParentID, "forums")
// TODO: This doesn't work properly so pick the first one instead?
if reply.ID == pFrag {
ogdesc = reply.Content
if len(ogdesc) > 200 {
ogdesc = ogdesc[:197] + "..."
}
}
if reply.LikeCount > 0 && user.Liked > 0 {
likedMap[reply.ID] = len(rlist)
likedQueryList = append(likedQueryList, reply.ID)
}
if user.Perms.EditReply && reply.AttachCount > 0 {
attachMap[reply.ID] = len(rlist)
attachQueryList = append(attachQueryList, reply.ID)
}
reply.Deletable = user.Perms.DeleteReply || reply.CreatedBy == user.ID
hTbl.VhookNoRet("topic_reply_row_assign", &rlist, &reply)
// TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis?
rlist = append(rlist, reply)
//log.Printf("r: %d-%d", reply.ID, len(rlist)-1)
}
err = rows.Err()
if err != nil {
@ -689,7 +664,7 @@ func GetTopicUser(user *User, tid int) (tu TopicUser, err error) {
tu = TopicUser{ID: tid}
// TODO: This misses some important bits...
err = topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.LastReplyAt, &tu.LastReplyBy, &tu.LastReplyID, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.AttachCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
err = topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.LastReplyAt, &tu.LastReplyBy, &tu.LastReplyID, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.AttachCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.Level)
tu.Avatar, tu.MicroAvatar = BuildAvatar(tu.CreatedBy, tu.Avatar)
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy)
@ -709,8 +684,8 @@ func copyTopicToTopicUser(t *Topic, u *User) (tu TopicUser) {
tu.Group = u.Group
tu.Avatar = u.Avatar
tu.MicroAvatar = u.MicroAvatar
tu.URLPrefix = u.URLPrefix
tu.URLName = u.URLName
//tu.URLPrefix = u.URLPrefix
//tu.URLName = u.URLName
tu.Level = u.Level
tu.ID = t.ID

View File

@ -29,7 +29,7 @@ type TopicStore interface {
BypassGet(id int) (*Topic, error)
BulkGetMap(ids []int) (list map[int]*Topic, err error)
Exists(id int) bool
Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error)
Create(fid int, name string, content string, uid int, ip string) (tid int, err error)
AddLastTopic(item *Topic, fid int) error // unimplemented
Reload(id int) error // Too much SQL logic to move into TopicCache
// TODO: Implement these two methods
@ -197,22 +197,22 @@ func (s *DefaultTopicStore) Exists(id int) bool {
return s.exists.QueryRow(id).Scan(&id) == nil
}
func (s *DefaultTopicStore) Create(fid int, topicName string, content string, uid int, ip string) (tid int, err error) {
if topicName == "" {
func (s *DefaultTopicStore) Create(fid int, name string, content string, uid int, ip string) (tid int, err error) {
if name == "" {
return 0, ErrNoTitle
}
// ? This number might be a little screwy with Unicode, but it's the only consistent thing we have, as Unicode characters can be any number of bytes in theory?
if len(topicName) > Config.MaxTopicTitleLength {
if len(name) > Config.MaxTopicTitleLength {
return 0, ErrLongTitle
}
parsedContent := strings.TrimSpace(ParseMessage(content, fid, "forums"))
parsedContent := strings.TrimSpace(ParseMessage(content, fid, "forums", nil))
if parsedContent == "" {
return 0, ErrNoBody
}
// TODO: Move this statement into the topic store
res, err := s.create.Exec(fid, topicName, content, parsedContent, uid, ip, WordCount(content), uid)
res, err := s.create.Exec(fid, name, content, parsedContent, uid, ip, WordCount(content), uid)
if err != nil {
return 0, err
}

View File

@ -46,8 +46,9 @@ type User struct {
Avatar string
MicroAvatar string
Message string
URLPrefix string // Move this to another table? Create a user lite?
URLName string
// TODO: Implement something like this for profiles?
//URLPrefix string // Move this to another table? Create a user lite?
//URLName string
Tag string
Level int
Score int
@ -56,6 +57,8 @@ type User struct {
LastIP string // ! This part of the UserCache data might fall out of date
LastAgent string // ! Temporary hack, don't use
TempGroup int
ParseSettings *ParseSettings
}
func (u *User) WebSockets() *WsJSONUser {
@ -119,7 +122,7 @@ type UserStmts struct {
changeGroup *sql.Stmt
delete *sql.Stmt
setAvatar *sql.Stmt
setUsername *sql.Stmt
setName *sql.Stmt
incTopics *sql.Stmt
updateLevel *sql.Stmt
update *sql.Stmt
@ -133,6 +136,7 @@ type UserStmts struct {
decLiked *sql.Stmt
updateLastIP *sql.Stmt
updatePrivacy *sql.Stmt
setPassword *sql.Stmt
@ -143,27 +147,29 @@ var userStmts UserStmts
func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
u := "users"
w := "uid=?"
userStmts = UserStmts{
activate: acc.SimpleUpdate("users", "active = 1", w),
changeGroup: acc.SimpleUpdate("users", "group = ?", w), // TODO: Implement user_count for users_groups here
delete: acc.SimpleDelete("users", w),
setAvatar: acc.Update("users").Set("avatar = ?").Where(w).Prepare(),
setUsername: acc.Update("users").Set("name = ?").Where(w).Prepare(),
incTopics: acc.SimpleUpdate("users", "topics = topics + ?", w),
updateLevel: acc.SimpleUpdate("users", "level = ?", w),
update: acc.Update("users").Set("name = ?, email = ?, group = ?").Where(w).Prepare(), // TODO: Implement user_count for users_groups on things which use this
activate: acc.SimpleUpdate(u, "active=1", w),
changeGroup: acc.SimpleUpdate(u, "group=?", w), // TODO: Implement user_count for users_groups here
delete: acc.Delete(u).Where(w).Prepare(),
setAvatar: acc.Update(u).Set("avatar=?").Where(w).Prepare(),
setName: acc.Update(u).Set("name=?").Where(w).Prepare(),
incTopics: acc.SimpleUpdate(u, "topics=topics+?", w),
updateLevel: acc.SimpleUpdate(u, "level=?", w),
update: acc.Update(u).Set("name=?,email=?,group=?").Where(w).Prepare(), // TODO: Implement user_count for users_groups on things which use this
incScore: acc.Update("users").Set("score = score + ?").Where(w).Prepare(),
incPosts: acc.Update("users").Set("posts = posts + ?").Where(w).Prepare(),
incBigposts: acc.Update("users").Set("posts = posts + ?, bigposts = bigposts + ?").Where(w).Prepare(),
incMegaposts: acc.Update("users").Set("posts = posts + ?, bigposts = bigposts + ?, megaposts = megaposts + ?").Where(w).Prepare(),
incLiked: acc.Update("users").Set("liked = liked + ?, lastLiked = UTC_TIMESTAMP()").Where(w).Prepare(),
decLiked: acc.Update("users").Set("liked = liked - ?").Where(w).Prepare(),
incScore: acc.Update(u).Set("score=score+?").Where(w).Prepare(),
incPosts: acc.Update(u).Set("posts=posts+?").Where(w).Prepare(),
incBigposts: acc.Update(u).Set("posts=posts+?,bigposts=bigposts+?").Where(w).Prepare(),
incMegaposts: acc.Update(u).Set("posts=posts+?,bigposts=bigposts+?,megaposts=megaposts+?").Where(w).Prepare(),
incLiked: acc.Update(u).Set("liked=liked+?,lastLiked=UTC_TIMESTAMP()").Where(w).Prepare(),
decLiked: acc.Update(u).Set("liked=liked-?").Where(w).Prepare(),
//recalcLastLiked: acc...
updateLastIP: acc.SimpleUpdate("users", "last_ip = ?", w),
updateLastIP: acc.SimpleUpdate(u, "last_ip=?", w),
updatePrivacy: acc.Update(u).Set("enable_embeds=?").Where(w).Prepare(),
setPassword: acc.Update("users").Set("password = ?, salt = ?").Where(w).Prepare(),
setPassword: acc.Update(u).Set("password=?,salt=?").Where(w).Prepare(),
scheduleAvatarResize: acc.Insert("users_avatar_queue").Columns("uid").Fields("?").Prepare(),
}
@ -304,7 +310,7 @@ func (u *User) bindStmt(stmt *sql.Stmt, params ...interface{}) (err error) {
}
func (u *User) ChangeName(name string) (err error) {
return u.bindStmt(userStmts.setUsername, name)
return u.bindStmt(userStmts.setName, name)
}
func (u *User) ChangeAvatar(avatar string) (err error) {
@ -341,7 +347,15 @@ func (u *User) UpdateIP(host string) error {
return err
}
func (u *User) Update(name string, email string, group int) (err error) {
func (u *User) UpdatePrivacy(enableEmbeds int) error {
_, err := userStmts.updatePrivacy.Exec(enableEmbeds, u.ID)
if uc := Users.GetCache(); uc != nil {
uc.Remove(u.ID)
}
return err
}
func (u *User) Update(name, email string, group int) (err error) {
return u.bindStmt(userStmts.update, name, email, group)
}
@ -461,7 +475,7 @@ type GuestAvatar struct {
Micro string
}
func buildNoavatar(uid int, width int) string {
func buildNoavatar(uid, width int) string {
if !Config.DisableNoavatarRange {
// TODO: Find a faster algorithm
if uid > 50000 {
@ -520,7 +534,6 @@ func wordsToScore(wcount int, topic bool) (score int) {
} else {
score = 1
}
settings := SettingBox.Load().(SettingMap)
if wcount >= settings["megapost_min_words"].(int) {
score += 4

View File

@ -5,7 +5,7 @@ import (
"errors"
"strconv"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
"golang.org/x/crypto/bcrypt"
)
@ -20,11 +20,11 @@ type UserStore interface {
Get(id int) (*User, error)
GetByName(name string) (*User, error)
Exists(id int) bool
GetOffset(offset int, perPage int) (users []*User, err error)
GetOffset(offset, perPage int) ([]*User, error)
//BulkGet(ids []int) ([]*User, error)
BulkGetMap(ids []int) (map[int]*User, error)
BypassGet(id int) (*User, error)
Create(username string, password string, email string, group int, active bool) (int, error)
Create(name string, password string, email string, group int, active bool) (int, error)
Reload(id int) error
Count() int
@ -40,7 +40,7 @@ type DefaultUserStore struct {
getOffset *sql.Stmt
exists *sql.Stmt
register *sql.Stmt
usernameExists *sql.Stmt
nameExists *sql.Stmt
count *sql.Stmt
}
@ -50,16 +50,17 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
if cache == nil {
cache = NewNullUserCache()
}
u := "users"
// TODO: Add an admin version of registerStmt with more flexibility?
return &DefaultUserStore{
cache: cache,
get: acc.SimpleSelect("users", "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, posts, liked, last_ip, temp_group", "uid = ?", "", ""),
getByName: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, posts, liked, last_ip, temp_group").Where("name = ?").Prepare(),
getOffset: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, posts, liked, last_ip, temp_group").Orderby("uid ASC").Limit("?,?").Prepare(),
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
register: acc.Insert("users").Columns("name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt").Fields("?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), // TODO: Implement user_count on users_groups here
usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""),
count: acc.Count("users").Prepare(),
get: acc.Select(u).Columns("name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Where("uid = ?").Prepare(),
getByName: acc.Select(u).Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Where("name = ?").Prepare(),
getOffset: acc.Select(u).Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Orderby("uid ASC").Limit("?,?").Prepare(),
exists: acc.Exists(u, "uid").Prepare(),
register: acc.Insert(u).Columns("name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt").Fields("?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), // TODO: Implement user_count on users_groups here
nameExists: acc.Exists(u, "name").Prepare(),
count: acc.Count(u).Prepare(),
}, acc.FirstError()
}
@ -86,8 +87,13 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
//log.Print("uncached user")
u = &User{ID: id, Loggedin: true}
err = s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts,&u.Liked, &u.LastIP, &u.TempGroup)
var embeds int
err = s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
if err == nil {
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
s.cache.Set(u)
}
@ -98,8 +104,13 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
// ! This bypasses the cache, use frugally
func (s *DefaultUserStore) GetByName(name string) (*User, error) {
u := &User{Loggedin: true}
err := s.getByName.QueryRow(name).Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts,&u.Liked, &u.LastIP, &u.TempGroup)
var embeds int
err := s.getByName.QueryRow(name).Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
if err == nil {
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
s.cache.Set(u)
}
@ -115,12 +126,17 @@ func (s *DefaultUserStore) GetOffset(offset int, perPage int) (users []*User, er
}
defer rows.Close()
var embeds int
for rows.Next() {
u := &User{Loggedin: true}
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup)
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
if err != nil {
return nil, err
}
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
s.cache.Set(u)
users = append(users, u)
@ -171,24 +187,28 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
}
q = q[0 : len(q)-1]
rows, err := qgen.NewAcc().Select("users").Columns("uid,name,group,active,is_super_admin,session,email,avatar,message,url_prefix,url_name,level,score,posts,liked,last_ip,temp_group").Where("uid IN(" + q + ")").Query(idList...)
rows, err := qgen.NewAcc().Select("users").Columns("uid,name,group,active,is_super_admin,session,email,avatar,message,level,score,posts,liked,last_ip,temp_group,enable_embeds").Where("uid IN(" + q + ")").Query(idList...)
if err != nil {
return list, err
}
defer rows.Close()
var embeds int
for rows.Next() {
u := &User{Loggedin: true}
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup)
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
if err != nil {
return list, err
}
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
s.cache.Set(u)
list[u.ID] = u
}
err = rows.Err()
if err != nil {
if err = rows.Err(); err != nil {
return list, err
}
@ -212,8 +232,13 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
func (s *DefaultUserStore) BypassGet(id int) (*User, error) {
u := &User{ID: id, Loggedin: true}
err := s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup)
var embeds int
err := s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
if err == nil {
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
}
return u, err
@ -240,16 +265,16 @@ func (s *DefaultUserStore) Exists(id int) bool {
// TODO: Change active to a bool?
// TODO: Use unique keys for the usernames
func (s *DefaultUserStore) Create(username string, password string, email string, group int, active bool) (int, error) {
func (s *DefaultUserStore) Create(name string, password string, email string, group int, active bool) (int, error) {
// TODO: Strip spaces?
// ? This number might be a little screwy with Unicode, but it's the only consistent thing we have, as Unicode characters can be any number of bytes in theory?
if len(username) > Config.MaxUsernameLength {
if len(name) > Config.MaxUsernameLength {
return 0, ErrLongUsername
}
// Is this username already taken..?
err := s.usernameExists.QueryRow(username).Scan(&username)
// Is this name already taken..?
err := s.nameExists.QueryRow(name).Scan(&name)
if err != ErrNoRows {
return 0, ErrAccountExists
}
@ -262,7 +287,7 @@ func (s *DefaultUserStore) Create(username string, password string, email string
return 0, err
}
res, err := s.register.Exec(username, email, string(hashedPassword), salt, group, active)
res, err := s.register.Exec(name, email, string(hashedPassword), salt, group, active)
if err != nil {
return 0, err
}

View File

@ -31,12 +31,12 @@ type Version struct {
}
// TODO: Write a test for this
func (version *Version) String() (out string) {
out = strconv.Itoa(version.Major) + "." + strconv.Itoa(version.Minor) + "." + strconv.Itoa(version.Patch)
if version.Tag != "" {
out += "-" + version.Tag
if version.TagID != 0 {
out += strconv.Itoa(version.TagID)
func (ver *Version) String() (out string) {
out = strconv.Itoa(ver.Major) + "." + strconv.Itoa(ver.Minor) + "." + strconv.Itoa(ver.Patch)
if ver.Tag != "" {
out += "-" + ver.Tag
if ver.TagID != 0 {
out += strconv.Itoa(ver.TagID)
}
}
return
@ -338,7 +338,7 @@ func HasSuspiciousEmail(email string) bool {
var weakPassStrings = []string{"test", "123", "6969", "password", "qwerty", "fuck", "love"}
// TODO: Write a test for this
func WeakPassword(password string, username string, email string) error {
func WeakPassword(password, username, email string) error {
lowPassword := strings.ToLower(password)
switch {
case password == "":
@ -422,7 +422,7 @@ func createFile(name string) error {
}
// TODO: Write a test for this
func writeFile(name string, content string) (err error) {
func writeFile(name, content string) (err error) {
f, err := os.Create(name)
if err != nil {
return err

View File

@ -110,6 +110,8 @@ RefNoTrack - This switch disables tracking the referrers of users who click from
RefNoRef - This switch makes it so that if a user clicks on a link, then the incoming site won't know which site they're coming from.
NoEmbed - Avoid expanding links into videos or images. Default: false
NoAvatar - The default avatar to use for users when they don't have their own. The default for this may change in the near future to better utilise HTTP/2. Example: https://api.adorable.io/avatars/{width}/{id}.png
ItemsPerPage - The number of posts, topics, etc. you want on each page.

View File

@ -373,7 +373,6 @@ func markdownSkipList(data string, index int) int {
goto SkipListInnerLoop
}
}
if index >= datalen {
if data[index] != '*' && data[index] != '-' {
if (lastNewline + 1) < datalen {

View File

@ -121,6 +121,8 @@ var RouteMap = map[string]interface{}{
"routes.AccountEditAvatarSubmit": routes.AccountEditAvatarSubmit,
"routes.AccountEditRevokeAvatarSubmit": routes.AccountEditRevokeAvatarSubmit,
"routes.AccountEditUsernameSubmit": routes.AccountEditUsernameSubmit,
"routes.AccountEditPrivacy": routes.AccountEditPrivacy,
"routes.AccountEditPrivacySubmit": routes.AccountEditPrivacySubmit,
"routes.AccountEditMFA": routes.AccountEditMFA,
"routes.AccountEditMFASetup": routes.AccountEditMFASetup,
"routes.AccountEditMFASetupSubmit": routes.AccountEditMFASetupSubmit,
@ -290,73 +292,75 @@ var routeMapEnum = map[string]int{
"routes.AccountEditAvatarSubmit": 95,
"routes.AccountEditRevokeAvatarSubmit": 96,
"routes.AccountEditUsernameSubmit": 97,
"routes.AccountEditMFA": 98,
"routes.AccountEditMFASetup": 99,
"routes.AccountEditMFASetupSubmit": 100,
"routes.AccountEditMFADisableSubmit": 101,
"routes.AccountEditEmail": 102,
"routes.AccountEditEmailTokenSubmit": 103,
"routes.AccountLogins": 104,
"routes.AccountBlocked": 105,
"routes.LevelList": 106,
"routes.Convos": 107,
"routes.ConvosCreate": 108,
"routes.Convo": 109,
"routes.ConvosCreateSubmit": 110,
"routes.ConvosCreateReplySubmit": 111,
"routes.ConvosDeleteReplySubmit": 112,
"routes.ConvosEditReplySubmit": 113,
"routes.RelationsBlockCreate": 114,
"routes.RelationsBlockCreateSubmit": 115,
"routes.RelationsBlockRemove": 116,
"routes.RelationsBlockRemoveSubmit": 117,
"routes.ViewProfile": 118,
"routes.BanUserSubmit": 119,
"routes.UnbanUser": 120,
"routes.ActivateUser": 121,
"routes.IPSearch": 122,
"routes.CreateTopicSubmit": 123,
"routes.EditTopicSubmit": 124,
"routes.DeleteTopicSubmit": 125,
"routes.StickTopicSubmit": 126,
"routes.UnstickTopicSubmit": 127,
"routes.LockTopicSubmit": 128,
"routes.UnlockTopicSubmit": 129,
"routes.MoveTopicSubmit": 130,
"routes.LikeTopicSubmit": 131,
"routes.AddAttachToTopicSubmit": 132,
"routes.RemoveAttachFromTopicSubmit": 133,
"routes.ViewTopic": 134,
"routes.CreateReplySubmit": 135,
"routes.ReplyEditSubmit": 136,
"routes.ReplyDeleteSubmit": 137,
"routes.ReplyLikeSubmit": 138,
"routes.AddAttachToReplySubmit": 139,
"routes.RemoveAttachFromReplySubmit": 140,
"routes.ProfileReplyCreateSubmit": 141,
"routes.ProfileReplyEditSubmit": 142,
"routes.ProfileReplyDeleteSubmit": 143,
"routes.PollVote": 144,
"routes.PollResults": 145,
"routes.AccountLogin": 146,
"routes.AccountRegister": 147,
"routes.AccountLogout": 148,
"routes.AccountLoginSubmit": 149,
"routes.AccountLoginMFAVerify": 150,
"routes.AccountLoginMFAVerifySubmit": 151,
"routes.AccountRegisterSubmit": 152,
"routes.AccountPasswordReset": 153,
"routes.AccountPasswordResetSubmit": 154,
"routes.AccountPasswordResetToken": 155,
"routes.AccountPasswordResetTokenSubmit": 156,
"routes.DynamicRoute": 157,
"routes.UploadedFile": 158,
"routes.StaticFile": 159,
"routes.RobotsTxt": 160,
"routes.SitemapXml": 161,
"routes.OpenSearchXml": 162,
"routes.BadRoute": 163,
"routes.HTTPSRedirect": 164,
"routes.AccountEditPrivacy": 98,
"routes.AccountEditPrivacySubmit": 99,
"routes.AccountEditMFA": 100,
"routes.AccountEditMFASetup": 101,
"routes.AccountEditMFASetupSubmit": 102,
"routes.AccountEditMFADisableSubmit": 103,
"routes.AccountEditEmail": 104,
"routes.AccountEditEmailTokenSubmit": 105,
"routes.AccountLogins": 106,
"routes.AccountBlocked": 107,
"routes.LevelList": 108,
"routes.Convos": 109,
"routes.ConvosCreate": 110,
"routes.Convo": 111,
"routes.ConvosCreateSubmit": 112,
"routes.ConvosCreateReplySubmit": 113,
"routes.ConvosDeleteReplySubmit": 114,
"routes.ConvosEditReplySubmit": 115,
"routes.RelationsBlockCreate": 116,
"routes.RelationsBlockCreateSubmit": 117,
"routes.RelationsBlockRemove": 118,
"routes.RelationsBlockRemoveSubmit": 119,
"routes.ViewProfile": 120,
"routes.BanUserSubmit": 121,
"routes.UnbanUser": 122,
"routes.ActivateUser": 123,
"routes.IPSearch": 124,
"routes.CreateTopicSubmit": 125,
"routes.EditTopicSubmit": 126,
"routes.DeleteTopicSubmit": 127,
"routes.StickTopicSubmit": 128,
"routes.UnstickTopicSubmit": 129,
"routes.LockTopicSubmit": 130,
"routes.UnlockTopicSubmit": 131,
"routes.MoveTopicSubmit": 132,
"routes.LikeTopicSubmit": 133,
"routes.AddAttachToTopicSubmit": 134,
"routes.RemoveAttachFromTopicSubmit": 135,
"routes.ViewTopic": 136,
"routes.CreateReplySubmit": 137,
"routes.ReplyEditSubmit": 138,
"routes.ReplyDeleteSubmit": 139,
"routes.ReplyLikeSubmit": 140,
"routes.AddAttachToReplySubmit": 141,
"routes.RemoveAttachFromReplySubmit": 142,
"routes.ProfileReplyCreateSubmit": 143,
"routes.ProfileReplyEditSubmit": 144,
"routes.ProfileReplyDeleteSubmit": 145,
"routes.PollVote": 146,
"routes.PollResults": 147,
"routes.AccountLogin": 148,
"routes.AccountRegister": 149,
"routes.AccountLogout": 150,
"routes.AccountLoginSubmit": 151,
"routes.AccountLoginMFAVerify": 152,
"routes.AccountLoginMFAVerifySubmit": 153,
"routes.AccountRegisterSubmit": 154,
"routes.AccountPasswordReset": 155,
"routes.AccountPasswordResetSubmit": 156,
"routes.AccountPasswordResetToken": 157,
"routes.AccountPasswordResetTokenSubmit": 158,
"routes.DynamicRoute": 159,
"routes.UploadedFile": 160,
"routes.StaticFile": 161,
"routes.RobotsTxt": 162,
"routes.SitemapXml": 163,
"routes.OpenSearchXml": 164,
"routes.BadRoute": 165,
"routes.HTTPSRedirect": 166,
}
var reverseRouteMapEnum = map[int]string{
0: "routes.Overview",
@ -457,73 +461,75 @@ var reverseRouteMapEnum = map[int]string{
95: "routes.AccountEditAvatarSubmit",
96: "routes.AccountEditRevokeAvatarSubmit",
97: "routes.AccountEditUsernameSubmit",
98: "routes.AccountEditMFA",
99: "routes.AccountEditMFASetup",
100: "routes.AccountEditMFASetupSubmit",
101: "routes.AccountEditMFADisableSubmit",
102: "routes.AccountEditEmail",
103: "routes.AccountEditEmailTokenSubmit",
104: "routes.AccountLogins",
105: "routes.AccountBlocked",
106: "routes.LevelList",
107: "routes.Convos",
108: "routes.ConvosCreate",
109: "routes.Convo",
110: "routes.ConvosCreateSubmit",
111: "routes.ConvosCreateReplySubmit",
112: "routes.ConvosDeleteReplySubmit",
113: "routes.ConvosEditReplySubmit",
114: "routes.RelationsBlockCreate",
115: "routes.RelationsBlockCreateSubmit",
116: "routes.RelationsBlockRemove",
117: "routes.RelationsBlockRemoveSubmit",
118: "routes.ViewProfile",
119: "routes.BanUserSubmit",
120: "routes.UnbanUser",
121: "routes.ActivateUser",
122: "routes.IPSearch",
123: "routes.CreateTopicSubmit",
124: "routes.EditTopicSubmit",
125: "routes.DeleteTopicSubmit",
126: "routes.StickTopicSubmit",
127: "routes.UnstickTopicSubmit",
128: "routes.LockTopicSubmit",
129: "routes.UnlockTopicSubmit",
130: "routes.MoveTopicSubmit",
131: "routes.LikeTopicSubmit",
132: "routes.AddAttachToTopicSubmit",
133: "routes.RemoveAttachFromTopicSubmit",
134: "routes.ViewTopic",
135: "routes.CreateReplySubmit",
136: "routes.ReplyEditSubmit",
137: "routes.ReplyDeleteSubmit",
138: "routes.ReplyLikeSubmit",
139: "routes.AddAttachToReplySubmit",
140: "routes.RemoveAttachFromReplySubmit",
141: "routes.ProfileReplyCreateSubmit",
142: "routes.ProfileReplyEditSubmit",
143: "routes.ProfileReplyDeleteSubmit",
144: "routes.PollVote",
145: "routes.PollResults",
146: "routes.AccountLogin",
147: "routes.AccountRegister",
148: "routes.AccountLogout",
149: "routes.AccountLoginSubmit",
150: "routes.AccountLoginMFAVerify",
151: "routes.AccountLoginMFAVerifySubmit",
152: "routes.AccountRegisterSubmit",
153: "routes.AccountPasswordReset",
154: "routes.AccountPasswordResetSubmit",
155: "routes.AccountPasswordResetToken",
156: "routes.AccountPasswordResetTokenSubmit",
157: "routes.DynamicRoute",
158: "routes.UploadedFile",
159: "routes.StaticFile",
160: "routes.RobotsTxt",
161: "routes.SitemapXml",
162: "routes.OpenSearchXml",
163: "routes.BadRoute",
164: "routes.HTTPSRedirect",
98: "routes.AccountEditPrivacy",
99: "routes.AccountEditPrivacySubmit",
100: "routes.AccountEditMFA",
101: "routes.AccountEditMFASetup",
102: "routes.AccountEditMFASetupSubmit",
103: "routes.AccountEditMFADisableSubmit",
104: "routes.AccountEditEmail",
105: "routes.AccountEditEmailTokenSubmit",
106: "routes.AccountLogins",
107: "routes.AccountBlocked",
108: "routes.LevelList",
109: "routes.Convos",
110: "routes.ConvosCreate",
111: "routes.Convo",
112: "routes.ConvosCreateSubmit",
113: "routes.ConvosCreateReplySubmit",
114: "routes.ConvosDeleteReplySubmit",
115: "routes.ConvosEditReplySubmit",
116: "routes.RelationsBlockCreate",
117: "routes.RelationsBlockCreateSubmit",
118: "routes.RelationsBlockRemove",
119: "routes.RelationsBlockRemoveSubmit",
120: "routes.ViewProfile",
121: "routes.BanUserSubmit",
122: "routes.UnbanUser",
123: "routes.ActivateUser",
124: "routes.IPSearch",
125: "routes.CreateTopicSubmit",
126: "routes.EditTopicSubmit",
127: "routes.DeleteTopicSubmit",
128: "routes.StickTopicSubmit",
129: "routes.UnstickTopicSubmit",
130: "routes.LockTopicSubmit",
131: "routes.UnlockTopicSubmit",
132: "routes.MoveTopicSubmit",
133: "routes.LikeTopicSubmit",
134: "routes.AddAttachToTopicSubmit",
135: "routes.RemoveAttachFromTopicSubmit",
136: "routes.ViewTopic",
137: "routes.CreateReplySubmit",
138: "routes.ReplyEditSubmit",
139: "routes.ReplyDeleteSubmit",
140: "routes.ReplyLikeSubmit",
141: "routes.AddAttachToReplySubmit",
142: "routes.RemoveAttachFromReplySubmit",
143: "routes.ProfileReplyCreateSubmit",
144: "routes.ProfileReplyEditSubmit",
145: "routes.ProfileReplyDeleteSubmit",
146: "routes.PollVote",
147: "routes.PollResults",
148: "routes.AccountLogin",
149: "routes.AccountRegister",
150: "routes.AccountLogout",
151: "routes.AccountLoginSubmit",
152: "routes.AccountLoginMFAVerify",
153: "routes.AccountLoginMFAVerifySubmit",
154: "routes.AccountRegisterSubmit",
155: "routes.AccountPasswordReset",
156: "routes.AccountPasswordResetSubmit",
157: "routes.AccountPasswordResetToken",
158: "routes.AccountPasswordResetTokenSubmit",
159: "routes.DynamicRoute",
160: "routes.UploadedFile",
161: "routes.StaticFile",
162: "routes.RobotsTxt",
163: "routes.SitemapXml",
164: "routes.OpenSearchXml",
165: "routes.BadRoute",
166: "routes.HTTPSRedirect",
}
var osMapEnum = map[string]int{
"unknown": 0,
@ -681,7 +687,7 @@ type HTTPSRedirect struct {}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
co.RouteViewCounter.Bump(164)
co.RouteViewCounter.Bump(166)
dest := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
}
@ -889,7 +895,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
co.GlobalViewCounter.Bump()
if prefix == "/s" { //old prefix: /static
co.RouteViewCounter.Bump(159)
co.RouteViewCounter.Bump(161)
req.URL.Path += extraData
routes.StaticFile(w, req)
return
@ -1800,7 +1806,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
co.RouteViewCounter.Bump(97)
err = routes.AccountEditUsernameSubmit(w,req,user)
case "/user/edit/mfa/":
case "/user/edit/privacy/":
err = c.MemberOnly(w,req,user)
if err != nil {
return err
@ -1808,6 +1814,31 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
co.RouteViewCounter.Bump(98)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
err = routes.AccountEditPrivacy(w,req,user,head)
case "/user/edit/privacy/submit/":
err = c.NoSessionMismatch(w,req,user)
if err != nil {
return err
}
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
co.RouteViewCounter.Bump(99)
err = routes.AccountEditPrivacySubmit(w,req,user)
case "/user/edit/mfa/":
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
co.RouteViewCounter.Bump(100)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
@ -1818,7 +1849,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(99)
co.RouteViewCounter.Bump(101)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -1835,7 +1866,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(100)
co.RouteViewCounter.Bump(102)
err = routes.AccountEditMFASetupSubmit(w,req,user)
case "/user/edit/mfa/disable/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -1848,7 +1879,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(101)
co.RouteViewCounter.Bump(103)
err = routes.AccountEditMFADisableSubmit(w,req,user)
case "/user/edit/email/":
err = c.MemberOnly(w,req,user)
@ -1856,14 +1887,14 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(102)
co.RouteViewCounter.Bump(104)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
err = routes.AccountEditEmail(w,req,user,head)
case "/user/edit/token/":
co.RouteViewCounter.Bump(103)
co.RouteViewCounter.Bump(105)
err = routes.AccountEditEmailTokenSubmit(w,req,user,extraData)
case "/user/edit/logins/":
err = c.MemberOnly(w,req,user)
@ -1871,7 +1902,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(104)
co.RouteViewCounter.Bump(106)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -1883,7 +1914,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(105)
co.RouteViewCounter.Bump(107)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -1895,7 +1926,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(106)
co.RouteViewCounter.Bump(108)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -1907,7 +1938,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(107)
co.RouteViewCounter.Bump(109)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -1919,7 +1950,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(108)
co.RouteViewCounter.Bump(110)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -1931,7 +1962,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(109)
co.RouteViewCounter.Bump(111)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -1948,7 +1979,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(110)
co.RouteViewCounter.Bump(112)
err = routes.ConvosCreateSubmit(w,req,user)
case "/user/convo/create/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -1961,7 +1992,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(111)
co.RouteViewCounter.Bump(113)
err = routes.ConvosCreateReplySubmit(w,req,user,extraData)
case "/user/convo/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -1974,7 +2005,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(112)
co.RouteViewCounter.Bump(114)
err = routes.ConvosDeleteReplySubmit(w,req,user,extraData)
case "/user/convo/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -1987,7 +2018,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(113)
co.RouteViewCounter.Bump(115)
err = routes.ConvosEditReplySubmit(w,req,user,extraData)
case "/user/block/create/":
err = c.MemberOnly(w,req,user)
@ -1995,7 +2026,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(114)
co.RouteViewCounter.Bump(116)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2012,7 +2043,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(115)
co.RouteViewCounter.Bump(117)
err = routes.RelationsBlockCreateSubmit(w,req,user,extraData)
case "/user/block/remove/":
err = c.MemberOnly(w,req,user)
@ -2020,7 +2051,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(116)
co.RouteViewCounter.Bump(118)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2037,11 +2068,11 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(117)
co.RouteViewCounter.Bump(119)
err = routes.RelationsBlockRemoveSubmit(w,req,user,extraData)
default:
req.URL.Path += extraData
co.RouteViewCounter.Bump(118)
co.RouteViewCounter.Bump(120)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2061,7 +2092,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(119)
co.RouteViewCounter.Bump(121)
err = routes.BanUserSubmit(w,req,user,extraData)
case "/users/unban/":
err = c.NoSessionMismatch(w,req,user)
@ -2074,7 +2105,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(120)
co.RouteViewCounter.Bump(122)
err = routes.UnbanUser(w,req,user,extraData)
case "/users/activate/":
err = c.NoSessionMismatch(w,req,user)
@ -2087,7 +2118,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(121)
co.RouteViewCounter.Bump(123)
err = routes.ActivateUser(w,req,user,extraData)
case "/users/ips/":
err = c.MemberOnly(w,req,user)
@ -2095,7 +2126,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(122)
co.RouteViewCounter.Bump(124)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2119,7 +2150,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(123)
co.RouteViewCounter.Bump(125)
err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2132,7 +2163,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(124)
co.RouteViewCounter.Bump(126)
err = routes.EditTopicSubmit(w,req,user,extraData)
case "/topic/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2146,7 +2177,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
}
req.URL.Path += extraData
co.RouteViewCounter.Bump(125)
co.RouteViewCounter.Bump(127)
err = routes.DeleteTopicSubmit(w,req,user)
case "/topic/stick/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2159,7 +2190,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(126)
co.RouteViewCounter.Bump(128)
err = routes.StickTopicSubmit(w,req,user,extraData)
case "/topic/unstick/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2172,7 +2203,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(127)
co.RouteViewCounter.Bump(129)
err = routes.UnstickTopicSubmit(w,req,user,extraData)
case "/topic/lock/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2186,7 +2217,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
}
req.URL.Path += extraData
co.RouteViewCounter.Bump(128)
co.RouteViewCounter.Bump(130)
err = routes.LockTopicSubmit(w,req,user)
case "/topic/unlock/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2199,7 +2230,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(129)
co.RouteViewCounter.Bump(131)
err = routes.UnlockTopicSubmit(w,req,user,extraData)
case "/topic/move/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2212,7 +2243,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(130)
co.RouteViewCounter.Bump(132)
err = routes.MoveTopicSubmit(w,req,user,extraData)
case "/topic/like/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2225,7 +2256,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(131)
co.RouteViewCounter.Bump(133)
err = routes.LikeTopicSubmit(w,req,user,extraData)
case "/topic/attach/add/submit/":
err = c.MemberOnly(w,req,user)
@ -2242,7 +2273,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(132)
co.RouteViewCounter.Bump(134)
err = routes.AddAttachToTopicSubmit(w,req,user,extraData)
case "/topic/attach/remove/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2255,10 +2286,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(133)
co.RouteViewCounter.Bump(135)
err = routes.RemoveAttachFromTopicSubmit(w,req,user,extraData)
default:
co.RouteViewCounter.Bump(134)
co.RouteViewCounter.Bump(136)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2282,7 +2313,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(135)
co.RouteViewCounter.Bump(137)
err = routes.CreateReplySubmit(w,req,user)
case "/reply/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2295,7 +2326,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(136)
co.RouteViewCounter.Bump(138)
err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2308,7 +2339,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(137)
co.RouteViewCounter.Bump(139)
err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2321,7 +2352,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(138)
co.RouteViewCounter.Bump(140)
err = routes.ReplyLikeSubmit(w,req,user,extraData)
case "/reply/attach/add/submit/":
err = c.MemberOnly(w,req,user)
@ -2338,7 +2369,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(139)
co.RouteViewCounter.Bump(141)
err = routes.AddAttachToReplySubmit(w,req,user,extraData)
case "/reply/attach/remove/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2351,7 +2382,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(140)
co.RouteViewCounter.Bump(142)
err = routes.RemoveAttachFromReplySubmit(w,req,user,extraData)
}
case "/profile":
@ -2367,7 +2398,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(141)
co.RouteViewCounter.Bump(143)
err = routes.ProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2380,7 +2411,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(142)
co.RouteViewCounter.Bump(144)
err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2393,7 +2424,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(143)
co.RouteViewCounter.Bump(145)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
}
case "/poll":
@ -2409,23 +2440,23 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(144)
co.RouteViewCounter.Bump(146)
err = routes.PollVote(w,req,user,extraData)
case "/poll/results/":
co.RouteViewCounter.Bump(145)
co.RouteViewCounter.Bump(147)
err = routes.PollResults(w,req,user,extraData)
}
case "/accounts":
switch(req.URL.Path) {
case "/accounts/login/":
co.RouteViewCounter.Bump(146)
co.RouteViewCounter.Bump(148)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
err = routes.AccountLogin(w,req,user,head)
case "/accounts/create/":
co.RouteViewCounter.Bump(147)
co.RouteViewCounter.Bump(149)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2442,7 +2473,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(148)
co.RouteViewCounter.Bump(150)
err = routes.AccountLogout(w,req,user)
case "/accounts/login/submit/":
err = c.ParseForm(w,req,user)
@ -2450,10 +2481,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(149)
co.RouteViewCounter.Bump(151)
err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/mfa_verify/":
co.RouteViewCounter.Bump(150)
co.RouteViewCounter.Bump(152)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2465,7 +2496,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(151)
co.RouteViewCounter.Bump(153)
err = routes.AccountLoginMFAVerifySubmit(w,req,user)
case "/accounts/create/submit/":
err = c.ParseForm(w,req,user)
@ -2473,10 +2504,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(152)
co.RouteViewCounter.Bump(154)
err = routes.AccountRegisterSubmit(w,req,user)
case "/accounts/password-reset/":
co.RouteViewCounter.Bump(153)
co.RouteViewCounter.Bump(155)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2488,10 +2519,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(154)
co.RouteViewCounter.Bump(156)
err = routes.AccountPasswordResetSubmit(w,req,user)
case "/accounts/password-reset/token/":
co.RouteViewCounter.Bump(155)
co.RouteViewCounter.Bump(157)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2503,7 +2534,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(156)
co.RouteViewCounter.Bump(158)
err = routes.AccountPasswordResetTokenSubmit(w,req,user)
}
/*case "/sitemaps": // TODO: Count these views
@ -2520,7 +2551,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
h.Del("Content-Type")
h.Del("Content-Encoding")
}
co.RouteViewCounter.Bump(158)
co.RouteViewCounter.Bump(160)
req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this?
r.UploadHandler(w,req) // TODO: Count these views
@ -2530,7 +2561,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
// TODO: Add support for favicons and robots.txt files
switch(extraData) {
case "robots.txt":
co.RouteViewCounter.Bump(160)
co.RouteViewCounter.Bump(162)
return routes.RobotsTxt(w,req)
case "favicon.ico":
gzw, ok := w.(c.GzipResponseWriter)
@ -2544,10 +2575,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
routes.StaticFile(w,req)
return nil
case "opensearch.xml":
co.RouteViewCounter.Bump(162)
co.RouteViewCounter.Bump(164)
return routes.OpenSearchXml(w,req)
/*case "sitemap.xml":
co.RouteViewCounter.Bump(161)
co.RouteViewCounter.Bump(163)
return routes.SitemapXml(w,req)*/
}
return c.NotFound(w,req,nil)
@ -2558,7 +2589,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
r.RUnlock()
if ok {
co.RouteViewCounter.Bump(157) // TODO: Be more specific about *which* dynamic route it is
co.RouteViewCounter.Bump(159) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData
return handle(w,req,user)
}
@ -2569,7 +2600,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
} else {
r.DumpRequest(req,"Bad Route")
}
co.RouteViewCounter.Bump(163)
co.RouteViewCounter.Bump(165)
return c.NotFound(w,req,nil)
}
return err

View File

@ -791,7 +791,7 @@ func BenchmarkQueryTopicParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var tu c.TopicUser
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.views, 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.IP, &tu.ViewCount, &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.views, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, 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.IP, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.Level)
if err == ErrNoRows {
log.Fatal("No rows found!")
return
@ -812,14 +812,14 @@ func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var tu c.TopicUser
getTopicUser, err := qgen.Builder.SimpleLeftJoin("topics", "users", "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", "topics.createdBy = users.uid", "tid = ?", "", "")
getTopicUser, err := qgen.Builder.SimpleLeftJoin("topics", "users", "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.level", "topics.createdBy = users.uid", "tid = ?", "", "")
if err != nil {
b.Fatal(err)
}
defer getTopicUser.Close()
for pb.Next() {
err := getTopicUser.QueryRow(1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
err := getTopicUser.QueryRow(1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.Level)
if err == ErrNoRows {
b.Fatal("No rows found!")
return
@ -873,7 +873,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
var tu c.TopicUser
b.Run("topic", func(b *testing.B) {
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.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &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.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.IP, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.Level)
if err == ErrNoRows {
b.Fatal("No rows found!")
return
@ -885,7 +885,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
})
b.Run("topic_replies", func(b *testing.B) {
for i := 0; i < b.N; i++ {
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress from replies left join users ON replies.createdBy = users.uid where tid = ?", 1)
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group, users.level, replies.ipaddress from replies left join users ON replies.createdBy = users.uid where tid = ?", 1)
if err != nil {
b.Fatal(err)
return
@ -907,13 +907,13 @@ func BenchmarkQueriesSerial(b *testing.B) {
var group int
b.Run("topic_replies_scan", func(b *testing.B) {
for i := 0; i < b.N; i++ {
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress from replies left join users ON replies.createdBy = users.uid where tid = ?", 1)
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group, users.level, replies.ipaddress from replies left join users ON replies.createdBy = users.uid where tid = ?", 1)
if err != nil {
b.Fatal(err)
return
}
for rows.Next() {
err := rows.Scan(&r.ID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.Avatar, &r.CreatedByName, &isSuperAdmin, &group, &r.URLPrefix, &r.URLName, &r.Level, &r.IP)
err := rows.Scan(&r.ID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.Avatar, &r.CreatedByName, &isSuperAdmin, &group, &r.Level, &r.IP)
if err != nil {
b.Fatal(err)
return
@ -933,36 +933,19 @@ func BenchmarkQueriesSerial(b *testing.B) {
// TODO: Take the attachment system into account in these parser benches
func BenchmarkParserSerial(b *testing.B) {
b.ReportAllocs()
b.Run("empty_post", func(b *testing.B) {
f := func(name, msg string) func(b *testing.B) {
return func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = c.ParseMessage("", 0, "")
_ = c.ParseMessage(msg, 0, "", nil)
}
})
b.Run("short_post", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = c.ParseMessage("Hey everyone, how's it going?", 0, "")
}
})
b.Run("one_smily", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = c.ParseMessage("Hey everyone, how's it going? :)", 0, "")
}
})
b.Run("five_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = c.ParseMessage("Hey everyone, how's it going? :):):):):)", 0, "")
}
})
b.Run("ten_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = c.ParseMessage("Hey everyone, how's it going? :):):):):):):):):):)", 0, "")
}
})
b.Run("twenty_smilies", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = c.ParseMessage("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)", 0, "")
}
})
f("empty_post","")
f("short_post","Hey everyone, how's it going?")
f("one_smily","Hey everyone, how's it going? :)")
f("five_smilies","Hey everyone, how's it going? :):):):):)")
f("ten_smilies","Hey everyone, how's it going? :):):):):):):):):):)")
f("twenty_smilies","Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)")
}
func BenchmarkBBCodePluginWithRegexpSerial(b *testing.B) {

View File

@ -293,7 +293,7 @@ func TestParser(t *testing.T) {
// TODO: Fix this hack and make the results a bit more reproducible, push the tests further in the process.
for _, item := range l.Items {
if res := c.ParseMessage(item.Msg, 1, "forums"); res != item.Expects {
if res := c.ParseMessage(item.Msg, 1, "forums", nil); res != item.Expects {
if item.Name != "" {
t.Error("Name: ", item.Name)
}
@ -314,7 +314,7 @@ func TestParser(t *testing.T) {
l.Add("//"+c.Site.URL+"\n", "<a href='https://"+c.Site.URL+"'>"+c.Site.URL+"</a><br>")
l.Add("//"+c.Site.URL+"\n//"+c.Site.URL, "<a href='https://"+c.Site.URL+"'>"+c.Site.URL+"</a><br><a href='https://"+c.Site.URL+"'>"+c.Site.URL+"</a>")
for _, item := range l.Items {
if res := c.ParseMessage(item.Msg, 1, "forums"); res != item.Expects {
if res := c.ParseMessage(item.Msg, 1, "forums", nil); res != item.Expects {
if item.Name != "" {
t.Error("Name: ", item.Name)
}
@ -338,7 +338,7 @@ func TestParser(t *testing.T) {
}
c.WriteURL(sb, c.BuildTopicURL("", tid), "#nnid-"+strconv.Itoa(tid))
})
res := c.ParseMessage("#nnid-1", 1, "forums")
res := c.ParseMessage("#nnid-1", 1, "forums", nil)
expect := "<a href='/topic/1'>#nnid-1</a>"
if res != expect {
t.Error("Bad output:", "'"+res+"'")
@ -356,7 +356,7 @@ func TestParser(t *testing.T) {
}
c.WriteURL(sb, c.BuildTopicURL("", tid), "#longidnameneedtooverflowhack-"+strconv.Itoa(tid))
})
res = c.ParseMessage("#longidnameneedtooverflowhack-1", 1, "forums")
res = c.ParseMessage("#longidnameneedtooverflowhack-1", 1, "forums", nil)
expect = "<a href='/topic/1'>#longidnameneedtooverflowhack-1</a>"
if res != expect {
t.Error("Bad output:", "'"+res+"'")

View File

@ -42,6 +42,7 @@ func init() {
addPatch(25, patch25)
addPatch(26, patch26)
addPatch(27, patch27)
addPatch(28, patch28)
}
func patch0(scanner *bufio.Scanner) (err error) {
@ -745,3 +746,7 @@ func patch27(scanner *bufio.Scanner) error {
}
return execStmt(qgen.Builder.AddColumn("administration_logs", tC{"extra", "text", 0, false, false, ""}, nil))
}
func patch28(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.AddColumn("users", tC{"enable_embeds", "int", 0, false, false, "-1"}, nil))
}

View File

@ -56,6 +56,8 @@ func userRoutes() *RouteGroup {
UploadAction("AvatarSubmit", "/avatar/submit/").MaxSizeVar("int(c.Config.MaxRequestSize)"),
Action("RevokeAvatarSubmit", "/avatar/revoke/submit/"),
Action("UsernameSubmit", "/username/submit/"), // TODO: Full test this
MView("Privacy", "/privacy/"),
Action("PrivacySubmit", "/privacy/submit/"),
MView("MFA", "/mfa/"),
MView("MFASetup", "/mfa/setup/"),
Action("MFASetupSubmit", "/mfa/setup/submit/"),

View File

@ -14,7 +14,7 @@ import (
c "github.com/Azareal/Gosora/common"
p "github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
)
// A blank list to fill out that parameter in Page for routes which don't use it
@ -572,6 +572,37 @@ func AccountEditMFADisableSubmit(w http.ResponseWriter, r *http.Request, user c.
return nil
}
func AccountEditPrivacy(w http.ResponseWriter, r *http.Request, user c.User, h *c.Header) c.RouteError {
accountEditHead("account_privacy", w, r, &user, h)
profileComments := false
receiveConvos := false
enableEmbeds := !c.DefaultParseSettings.NoEmbed
if user.ParseSettings != nil {
enableEmbeds = !user.ParseSettings.NoEmbed
}
pi := c.Account{h, "privacy", "account_own_edit_privacy", c.AccountPrivacyPage{h, profileComments, receiveConvos, enableEmbeds}}
return renderTemplate("account", w, r, h, pi)
}
func AccountEditPrivacySubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
//headerLite, _ := c.SimpleUserCheck(w, r, &user)
sEnableEmbeds := r.FormValue("enable_embeds")
enableEmbeds, err := strconv.Atoi(sEnableEmbeds)
if err != nil {
return c.LocalError("enable_embeds must be 0 or 1", w, r, user)
}
if sEnableEmbeds != r.FormValue("o_enable_embeds") {
err = (&user).UpdatePrivacy(enableEmbeds)
if err != nil {
return c.InternalError(err, w, r)
}
}
http.Redirect(w, r, "/user/edit/privacy/?updated=1", http.StatusSeeOther)
return nil
}
func AccountEditEmail(w http.ResponseWriter, r *http.Request, user c.User, h *c.Header) c.RouteError {
accountEditHead("account_email", w, r, &user, h)
emails, err := c.Emails.GetEmailsByUser(&user)
@ -598,7 +629,6 @@ func AccountEditEmail(w http.ResponseWriter, r *http.Request, user c.User, h *c.
func AccountEditEmailAddSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
email := r.PostFormValue("email")
_, err := c.Emails.Get(&user, email)
if err == nil {
return c.LocalError("You have already added this email.", w, r, user)
@ -735,7 +765,7 @@ func AccountBlocked(w http.ResponseWriter, r *http.Request, user c.User, h *c.He
}
pageList := c.Paginate(page, lastPage, 5)
pi := c.Account{h, "logins", "account_blocked", c.AccountBlocksPage{h, blocks, c.Paginator{pageList, page, lastPage}}}
pi := c.Account{h, "blocked", "account_blocked", c.AccountBlocksPage{h, blocks, c.Paginator{pageList, page, lastPage}}}
return renderTemplate("account", w, r, h, pi)
}

View File

@ -78,7 +78,7 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user c.User, header *c.
replyLiked := false
replyLikeCount := 0
ru := &c.ReplyUser{Reply: c.Reply{rid, puser.ID, replyContent, replyCreatedBy, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, 0, "", replyLiked, replyLikeCount, 0, ""}, ContentHtml: c.ParseMessage(replyContent, 0, ""), CreatedByName: replyCreatedByName, Avatar: replyAvatar, Level: 0}
ru := &c.ReplyUser{Reply: c.Reply{rid, puser.ID, replyContent, replyCreatedBy, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, 0, "", replyLiked, replyLikeCount, 0, ""}, ContentHtml: c.ParseMessage(replyContent, 0, "", user.ParseSettings), CreatedByName: replyCreatedByName, Avatar: replyAvatar, Level: 0}
ru.Init()
group, err := c.Groups.Get(ru.Group)

View File

@ -194,7 +194,7 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
prid, _ := strconv.Atoi(r.FormValue("prid"))
if js && (prid == 0 || rids[0] == prid) {
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums")})
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums", user.ParseSettings)})
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
@ -267,7 +267,7 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid s
if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(topic.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
} else {
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums")})
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums", user.ParseSettings)})
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}

View File

@ -9,17 +9,17 @@ import (
"io"
//"fmt"
"golang.org/x/image/tiff"
"image"
"image/gif"
"image/jpeg"
"image/png"
"log"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"image"
"image/gif"
"image/jpeg"
"image/png"
"golang.org/x/image/tiff"
c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/counters"
@ -72,16 +72,17 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.He
header.Path = c.BuildTopicURL(c.NameToSlug(topic.Title), topic.ID)
// TODO: Cache ContentHTML when possible?
topic.ContentHTML = c.ParseMessage(topic.Content, topic.ParentID, "forums")
topic.ContentHTML = c.ParseMessage(topic.Content, topic.ParentID, "forums", user.ParseSettings)
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
if topic.ContentHTML == topic.Content {
topic.ContentHTML = topic.Content
}
topic.ContentLines = strings.Count(topic.Content, "\n")
if len(topic.Content) > 200 {
header.OGDesc = topic.Content[:197] + "..."
} else {
header.OGDesc = topic.Content
if len(header.OGDesc) > 200 {
header.OGDesc = header.OGDesc[:197] + "..."
}
postGroup, err := c.Groups.Get(topic.Group)
@ -141,7 +142,6 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.He
if strings.HasPrefix(r.URL.Fragment, "post-") {
pFrag, _ = strconv.Atoi(strings.TrimPrefix(r.URL.Fragment, "post-"))
}
rlist, ogdesc, err := topic.Replies(offset, pFrag, &user)
if err == sql.ErrNoRows {
return c.LocalError("Bad Page. Some of the posts may have been deleted or you got here by directly typing in the page number.", w, r, user)
@ -603,7 +603,7 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else {
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(topic.Content, topic.ParentID, "forums")})
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(topic.Content, topic.ParentID, "forums", user.ParseSettings)})
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}

View File

@ -10,6 +10,7 @@ CREATE TABLE [users] (
[lastActiveAt] datetime not null,
[session] nvarchar (200) DEFAULT '' not null,
[last_ip] nvarchar (200) DEFAULT '0.0.0.0.0' not null,
[enable_embeds] int DEFAULT -1 not null,
[email] nvarchar (200) DEFAULT '' not null,
[avatar] nvarchar (100) DEFAULT '' not null,
[message] nvarchar (MAX) DEFAULT '' not null,

View File

@ -10,6 +10,7 @@ CREATE TABLE `users` (
`lastActiveAt` datetime not null,
`session` varchar(200) DEFAULT '' not null,
`last_ip` varchar(200) DEFAULT '0.0.0.0.0' not null,
`enable_embeds` int DEFAULT -1 not null,
`email` varchar(200) DEFAULT '' not null,
`avatar` varchar(100) DEFAULT '' not null,
`message` text not null,

View File

@ -10,6 +10,7 @@ CREATE TABLE "users" (
`lastActiveAt` timestamp not null,
`session` varchar (200) DEFAULT '' not null,
`last_ip` varchar (200) DEFAULT '0.0.0.0.0' not null,
`enable_embeds` int DEFAULT -1 not null,
`email` varchar (200) DEFAULT '' not null,
`avatar` varchar (100) DEFAULT '' not null,
`message` text DEFAULT '' not null,

View File

@ -7,6 +7,7 @@
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/user/edit/password/">{{lang "account_menu_password"}}</a></div>
<div class="rowitem passive"><a href="/user/edit/email/">{{lang "account_menu_email"}}</a></div>
<div class="rowitem passive"><a href="/user/edit/privacy/">{{lang "account_menu_privacy"}}</a></div>
<!--<div class="rowitem passive"><a href="/user/edit/notifications/">{{lang "account_menu_notifications"}}</a> <span class="account_soon">Coming Soon</span></div>-->
<div class="rowitem passive"><a href="/user/edit/logins/">{{lang "account_menu_logins"}}</a></div>
<div class="rowitem passive"><a href="/user/edit/blocked/">{{lang "account_menu_blocked"}}</a></div>

View File

@ -0,0 +1,34 @@
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>{{lang "account_privacy_head"}}</h1></div>
</div>
<div class="colstack_item the_form">
<form action="/user/edit/privacy/submit/?s={{.CurrentUser.Session}}" method="post">
<!--<input name="o_profile_comments" value="{{if .ProfileComments}}1{{else}}0{{end}}" type="hidden" />
<input name="o_receive_convos" value="{{if .ReceiveConvos}}1{{else}}0{{end}}" type="hidden" />-->
<input name="o_enable_embeds" value="{{if .EnableEmbeds}}1{{else}}0{{end}}" type="hidden" />
<!--<div class="formrow real_first_child">
<div class="formitem formlabel">{{lang "account_privacy_profile_comments"}}</div>
<div class="formitem"><select name="profile_comments">
<option{{if .ProfileComments}} selected{{end}} value=1>{{lang "option_yes"}}</option>
<option{{if not .ProfileComments}} selected{{end}} value=0>{{lang "option_no"}}</option>
</select></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Receive Conversations</a></div>
<div class="formitem"><select name="receive_convos">
<option{{if .ReceiveConvos}} selected{{end}} value=1>{{lang "option_yes"}}</option>
<option{{if not .ReceiveConvos}} selected{{end}} value=0>{{lang "option_no"}}</option>
</select></div>
</div>-->
<div class="formrow">
<div class="formitem formlabel"><a>Enable Embeds</a></div>
<div class="formitem"><select name="enable_embeds">
<option{{if .EnableEmbeds}} selected{{end}} value=1>{{lang "option_yes"}}</option>
<option{{if not .EnableEmbeds}} selected{{end}} value=0>{{lang "option_no"}}</option>
</select></div>
</div>
<div class="formrow">
<div class="formitem"><button name="account-button" class="formbutton form_middle_button">{{lang "account_privacy_button"}}</button></div>
</div>
</form>
</div>