diff --git a/common/pages.go b/common/pages.go index c72ee81e..11d96dd1 100644 --- a/common/pages.go +++ b/common/pages.go @@ -56,6 +56,7 @@ type TopicPage struct { Header *HeaderVars ItemList []ReplyUser Topic TopicUser + Poll Poll Page int LastPage int } diff --git a/common/poll_store.go b/common/poll_store.go index df99f71c..25c048f5 100644 --- a/common/poll_store.go +++ b/common/poll_store.go @@ -1,7 +1,11 @@ package common -import "database/sql" -import "../query_gen/lib" +import ( + "database/sql" + "encoding/json" + + "../query_gen/lib" +) var Polls PollStore @@ -11,9 +15,19 @@ type Poll struct { //AntiCheat bool // Apply various mitigations for cheating // GroupPower map[gid]points // The number of points a group can spend in this poll, defaults to 1 - Options []string - Results map[int]int // map[optionIndex]points - VoteCount int + Options map[int]string + Results map[int]int // map[optionIndex]points + QuickOptions []PollOption // TODO: Fix up the template transpiler so we don't need to use this hack anymore + VoteCount int +} + +func (poll *Poll) Copy() Poll { + return *poll +} + +type PollOption struct { + ID int + Value string } type Pollable interface { @@ -23,7 +37,7 @@ type Pollable interface { type PollStore interface { Get(id int) (*Poll, error) Exists(id int) bool - Create(parent Pollable, pollType int, pollOptions []string) (int, error) + Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error) Reload(id int) error //GlobalCount() int @@ -72,8 +86,14 @@ func (store *DefaultPollStore) Get(id int) (*Poll, error) { poll = &Poll{ID: id} var optionTxt []byte - err = store.get.QueryRow(id).Scan(&poll.Type, optionTxt, &poll.VoteCount) + err = store.get.QueryRow(id).Scan(&poll.Type, &optionTxt, &poll.VoteCount) + if err != nil { + return nil, err + } + + err = json.Unmarshal(optionTxt, &poll.Options) if err == nil { + poll.QuickOptions = store.unpackOptionsMap(poll.Options) store.cache.Set(poll) } return poll, err @@ -82,17 +102,38 @@ func (store *DefaultPollStore) Get(id int) (*Poll, error) { func (store *DefaultPollStore) Reload(id int) error { poll := &Poll{ID: id} var optionTxt []byte - err := store.get.QueryRow(id).Scan(&poll.Type, optionTxt, &poll.VoteCount) + err := store.get.QueryRow(id).Scan(&poll.Type, &optionTxt, &poll.VoteCount) if err != nil { store.cache.Remove(id) return err } + + err = json.Unmarshal(optionTxt, &poll.Options) + if err != nil { + store.cache.Remove(id) + return err + } + + poll.QuickOptions = store.unpackOptionsMap(poll.Options) _ = store.cache.Set(poll) return nil } -func (store *DefaultPollStore) Create(parent Pollable, pollType int, pollOptions []string) (id int, err error) { - res, err := store.create.Exec(pollType, pollOptions) +func (store *DefaultPollStore) unpackOptionsMap(rawOptions map[int]string) []PollOption { + options := make([]PollOption, len(rawOptions)) + for id, option := range rawOptions { + options[id] = PollOption{id, option} + } + return options +} + +func (store *DefaultPollStore) Create(parent Pollable, pollType int, pollOptions map[int]string) (id int, err error) { + pollOptionsTxt, err := json.Marshal(pollOptions) + if err != nil { + return 0, err + } + + res, err := store.create.Exec(pollType, pollOptionsTxt) //pollOptionsTxt if err != nil { return 0, err } diff --git a/common/template_init.go b/common/template_init.go index b39443d8..46aa4340 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -108,12 +108,16 @@ func compileTemplates() error { log.Print("Compiling the templates") var now = time.Now() - topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, RelativeTime(now), now, RelativeTime(now), 0, "", "127.0.0.1", 0, 1, "classname", "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, "", 0, "", "", "", "", "", 58, false} + poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{ + PollOption{0, "Nothing"}, + PollOption{1, "Something"}, + }, VoteCount: 7} + topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, RelativeTime(now), now, RelativeTime(now), 0, "", "127.0.0.1", 0, 1, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, "", 0, "", "", "", "", "", 58, false} var replyList []ReplyUser replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, RelativeTime(now), 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""}) var varList = make(map[string]tmpl.VarItem) - tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1} + tpage := TopicPage{"Title", user, headerVars, replyList, topic, poll, 1, 1} topicIDTmpl, err := c.Compile("topic.html", "templates/", "common.TopicPage", tpage, varList) if err != nil { return err diff --git a/common/topic.go b/common/topic.go index fa0a43bc..cc62120c 100644 --- a/common/topic.go +++ b/common/topic.go @@ -41,6 +41,7 @@ type Topic struct { PostCount int LikeCount int ClassName string // CSS Class Name + Poll int Data string // Used for report metadata } @@ -63,6 +64,7 @@ type TopicUser struct { PostCount int LikeCount int ClassName string + Poll int Data string // Used for report metadata UserLink string @@ -146,8 +148,8 @@ func init() { setPoll: acc.Update("topics").Set("content = '', parsed_content = '', poll = ?").Where("tid = ? AND poll = 0").Prepare(), createActionReply: acc.Insert("replies").Columns("tid, actionType, ipaddress, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(), - getTopicUser: acc.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 = ?", "", ""), - getByReplyID: acc.SimpleLeftJoin("replies", "topics", "topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, topics.data", "replies.tid = topics.tid", "rid = ?", "", ""), + getTopicUser: acc.SimpleLeftJoin("topics", "users", "topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, topics.poll, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level", "topics.createdBy = users.uid", "tid = ?", "", ""), + getByReplyID: acc.SimpleLeftJoin("replies", "topics", "topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, topics.poll, topics.data", "replies.tid = topics.tid", "rid = ?", "", ""), } return acc.FirstError() }) @@ -283,7 +285,7 @@ func (topic *Topic) Copy() Topic { func TopicByReplyID(rid int) (*Topic, error) { topic := Topic{ID: 0} - err := topicStmts.getByReplyID.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) + err := topicStmts.getByReplyID.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data) topic.Link = BuildTopicURL(NameToSlug(topic.Title), topic.ID) return &topic, err } @@ -316,13 +318,13 @@ func GetTopicUser(tid int) (TopicUser, error) { } tu := TopicUser{ID: tid} - err := topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level) + err := topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level) tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID) tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy) tu.Tag = Groups.DirtyGet(tu.Group).Tag if tcache != nil { - theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, ParentID: tu.ParentID, IPAddress: tu.IPAddress, PostCount: tu.PostCount, LikeCount: tu.LikeCount} + theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, ParentID: tu.ParentID, IPAddress: tu.IPAddress, PostCount: tu.PostCount, LikeCount: tu.LikeCount, Poll: tu.Poll} //log.Printf("theTopic: %+v\n", theTopic) _ = tcache.Add(&theTopic) } @@ -351,6 +353,7 @@ func copyTopicToTopicUser(topic *Topic, user *User) (tu TopicUser) { tu.IPAddress = topic.IPAddress tu.PostCount = topic.PostCount tu.LikeCount = topic.LikeCount + tu.Poll = topic.Poll tu.Data = topic.Data return tu diff --git a/common/topic_store.go b/common/topic_store.go index 8c7628cb..128f5101 100644 --- a/common/topic_store.go +++ b/common/topic_store.go @@ -56,7 +56,7 @@ func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) { } return &DefaultTopicStore{ cache: cache, - get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data").Where("tid = ?").Prepare(), + get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, poll, data").Where("tid = ?").Prepare(), exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(), topicCount: acc.Count("topics").Prepare(), create: acc.Insert("topics").Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ipaddress, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(), @@ -70,7 +70,7 @@ func (mts *DefaultTopicStore) DirtyGet(id int) *Topic { } topic = &Topic{ID: id} - err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) + err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data) if err == nil { topic.Link = BuildTopicURL(NameToSlug(topic.Title), id) _ = mts.cache.Add(topic) @@ -87,7 +87,7 @@ func (mts *DefaultTopicStore) Get(id int) (topic *Topic, err error) { } topic = &Topic{ID: id} - err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) + err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data) if err == nil { topic.Link = BuildTopicURL(NameToSlug(topic.Title), id) _ = mts.cache.Add(topic) @@ -98,14 +98,14 @@ func (mts *DefaultTopicStore) Get(id int) (topic *Topic, err error) { // BypassGet will always bypass the cache and pull the topic directly from the database func (mts *DefaultTopicStore) BypassGet(id int) (*Topic, error) { topic := &Topic{ID: id} - err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) + err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data) topic.Link = BuildTopicURL(NameToSlug(topic.Title), id) return topic, err } func (mts *DefaultTopicStore) Reload(id int) error { topic := &Topic{ID: id} - err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) + err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data) if err == nil { topic.Link = BuildTopicURL(NameToSlug(topic.Title), id) _ = mts.cache.Set(topic) diff --git a/gen_router.go b/gen_router.go index 3ee916d2..f717ee21 100644 --- a/gen_router.go +++ b/gen_router.go @@ -107,6 +107,7 @@ var RouteMap = map[string]interface{}{ "routeRegisterSubmit": routeRegisterSubmit, "routeDynamic": routeDynamic, "routeUploads": routeUploads, + "BadRoute": BadRoute, } // ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS @@ -202,6 +203,7 @@ var routeMapEnum = map[string]int{ "routeRegisterSubmit": 88, "routeDynamic": 89, "routeUploads": 90, + "BadRoute": 91, } var reverseRouteMapEnum = map[int]string{ 0: "routeAPI", @@ -295,6 +297,7 @@ var reverseRouteMapEnum = map[int]string{ 88: "routeRegisterSubmit", 89: "routeDynamic", 90: "routeUploads", + 91: "BadRoute", } var agentMapEnum = map[string]int{ "unknown": 0, @@ -315,6 +318,7 @@ var agentMapEnum = map[string]int{ "lynx": 15, "blank": 16, "malformed": 17, + "suspicious": 18, } var reverseAgentMapEnum = map[int]string{ 0: "unknown", @@ -335,6 +339,7 @@ var reverseAgentMapEnum = map[int]string{ 15: "lynx", 16: "blank", 17: "malformed", + 18: "suspicious", } // TODO: Stop spilling these into the package scope? @@ -397,7 +402,7 @@ func (router *GenRouter) RemoveFunc(pattern string) error { func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { - w.WriteHeader(405) + w.WriteHeader(200) // 405 w.Write([]byte("")) log.Print("Malformed Request") log.Print("UA: ", req.UserAgent()) @@ -432,10 +437,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) + common.AgentViewCounter.Bump(18) break } } - if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") { + // TODO: Flag any requests which has a dot with anything but a number after that + if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") || strings.Contains(req.URL.Path,".php") || strings.Contains(req.URL.Path,".asp") || strings.Contains(req.URL.Path,".cgi") { log.Print("Suspicious UA: ", req.UserAgent()) log.Print("Method: ", req.Method) for key, value := range req.Header { @@ -448,6 +455,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) + common.AgentViewCounter.Bump(18) } } @@ -1522,6 +1530,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } return } - common.NotFound(w,req) // TODO: Collect all the error view counts so we can add a replacement for GlobalViewCounter by adding up the view counts of every route? Complex and may be inaccurate, collecting it globally and locally would at-least help find places we aren't capturing views + + common.RouteViewCounter.Bump(91) + common.NotFound(w,req) } } diff --git a/public/global.js b/public/global.js index 0a4275a7..82a91569 100644 --- a/public/global.js +++ b/public/global.js @@ -591,8 +591,25 @@ $(document).ready(function(){ if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click(); }; + function addPollInput() { + console.log("clicked on pollinputinput"); + let dataPollInput = $(this).parent().attr("data-pollinput"); + console.log("dataPollInput: ", dataPollInput); + if(dataPollInput == undefined) return; + if(dataPollInput != (pollInputIndex-1)) return; + + $(".poll_content_row .formitem").append("
"); + pollInputIndex++; + console.log("new pollInputIndex: ", pollInputIndex); + $(".pollinputinput").off("click"); + $(".pollinputinput").click(addPollInput); + } + + var pollInputIndex = 1; $("#add_poll_button").click(function(event){ event.preventDefault(); $(".poll_content_row").removeClass("auto_hide"); + $("#has_poll_input").val("1"); + $(".pollinputinput").click(addPollInput); }); }); diff --git a/query_gen/tables.go b/query_gen/tables.go index 8d0ef734..62252473 100644 --- a/query_gen/tables.go +++ b/query_gen/tables.go @@ -230,11 +230,13 @@ func createTables(adapter qgen.Adapter) error { }, ) - qgen.Install.CreateTable("polls_voters", "utf8mb4", "utf8mb4_general_ci", + qgen.Install.CreateTable("polls_votes", "utf8mb4", "utf8mb4_general_ci", []qgen.DBTableColumn{ qgen.DBTableColumn{"pollID", "int", 0, false, false, ""}, qgen.DBTableColumn{"uid", "int", 0, false, false, ""}, qgen.DBTableColumn{"option", "int", 0, false, false, "0"}, + qgen.DBTableColumn{"castAt", "createdAt", 0, false, false, ""}, + qgen.DBTableColumn{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"}, }, []qgen.DBTableKey{}, ) diff --git a/router_gen/main.go b/router_gen/main.go index 04f75c64..7b37b2ae 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -155,6 +155,7 @@ func main() { // Stubs for us to refer to these routes through mapIt("routeDynamic") mapIt("routeUploads") + mapIt("BadRoute") tmplVars.AllRouteNames = allRouteNames tmplVars.AllRouteMap = allRouteMap tmplVars.AllAgentNames = []string{ @@ -177,6 +178,7 @@ func main() { "lynx", "blank", "malformed", + "suspicious", } tmplVars.AllAgentMap = make(map[string]int) @@ -279,7 +281,7 @@ func (router *GenRouter) RemoveFunc(pattern string) error { func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { - w.WriteHeader(405) + w.WriteHeader(200) // 405 w.Write([]byte("")) log.Print("Malformed Request") log.Print("UA: ", req.UserAgent()) @@ -314,10 +316,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) + common.AgentViewCounter.Bump({{.AllAgentMap.suspicious}}) break } } - if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") { + // TODO: Flag any requests which has a dot with anything but a number after that + if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") || strings.Contains(req.URL.Path,".php") || strings.Contains(req.URL.Path,".asp") || strings.Contains(req.URL.Path,".cgi") { log.Print("Suspicious UA: ", req.UserAgent()) log.Print("Method: ", req.Method) for key, value := range req.Header { @@ -330,6 +334,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) + common.AgentViewCounter.Bump({{.AllAgentMap.suspicious}}) } } @@ -518,7 +523,9 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } return } - common.NotFound(w,req) // TODO: Collect all the error view counts so we can add a replacement for GlobalViewCounter by adding up the view counts of every route? Complex and may be inaccurate, collecting it globally and locally would at-least help find places we aren't capturing views + + common.RouteViewCounter.Bump({{.AllRouteMap.BadRoute}}) + common.NotFound(w,req) } } ` diff --git a/routes.go b/routes.go index 9ad89a8d..09e33a7f 100644 --- a/routes.go +++ b/routes.go @@ -45,6 +45,8 @@ func routeDynamic() { } func routeUploads() { } +func BadRoute() { +} // GET functions func routeStatic(w http.ResponseWriter, r *http.Request) { @@ -484,9 +486,18 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User, urlB topic.Avatar = strings.Replace(common.Config.Noavatar, "{id}", strconv.Itoa(topic.CreatedBy), 1) } + var poll *common.Poll + if topic.Poll != 0 { + poll, err = common.Polls.Get(topic.Poll) + if err != nil { + log.Print("Couldn't find the attached poll for topic " + strconv.Itoa(topic.ID)) + return common.InternalError(err, w, r) + } + } + // Calculate the offset offset, page, lastPage := common.PageOffset(topic.PostCount, page, common.Config.ItemsPerPage) - tpage := common.TopicPage{topic.Title, user, headerVars, replyList, topic, page, lastPage} + tpage := common.TopicPage{topic.Title, user, headerVars, replyList, topic, poll.Copy(), page, lastPage} // Get the replies.. rows, err := stmts.getTopicRepliesOffset.Query(topic.ID, offset, common.Config.ItemsPerPage) diff --git a/routes/topic.go b/routes/topic.go index 4a4fe46d..a981caf2 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -133,8 +133,54 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) return common.LocalError("This topic doesn't have a title", w, r, user) case common.ErrNoBody: return common.LocalError("This topic doesn't have a body", w, r, user) - default: - return common.InternalError(err, w, r) + } + return common.InternalError(err, w, r) + } + + topic, err := common.Topics.Get(tid) + if err != nil { + return common.LocalError("Unable to load the topic", w, r, user) + } + if r.PostFormValue("has_poll") == "1" { + var maxPollOptions = 10 + var pollInputItems = make(map[int]string) + var pollInputCount = 0 + for key, values := range r.Form { + //if common.Dev.SuperDebug { + log.Print("key: ", key) + log.Printf("values: %+v\n", values) + //} + for _, value := range values { + if strings.HasPrefix(key, "pollinputitem[") { + halves := strings.Split(key, "[") + if len(halves) != 2 { + return common.LocalError("Malformed pollinputitem", w, r, user) + } + halves[1] = strings.TrimSuffix(halves[1], "]") + + index, err := strconv.Atoi(halves[1]) + if err != nil { + return common.LocalError("Malformed pollinputitem", w, r, user) + } + + // If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack + _, exists := pollInputItems[index] + if !exists { + pollInputCount++ + pollInputItems[index] = html.EscapeString(value) + + if len(pollInputItems) >= maxPollOptions { + break + } + } + } + } + } + + pollType := 0 // Basic single choice + _, err := common.Polls.Create(topic, pollType, pollInputItems) + if err != nil { + return common.LocalError("Failed to add poll to topic", w, r, user) // TODO: Might need to be an internal error as it could leave phantom polls? } } diff --git a/schema/mssql/query_polls_votes.sql b/schema/mssql/query_polls_votes.sql new file mode 100644 index 00000000..567fbc32 --- /dev/null +++ b/schema/mssql/query_polls_votes.sql @@ -0,0 +1,7 @@ +CREATE TABLE [polls_votes] ( + [pollID] int not null, + [uid] int not null, + [option] int DEFAULT 0 not null, + [castAt] datetime not null, + [ipaddress] nvarchar (200) DEFAULT '0.0.0.0.0' not null +); \ No newline at end of file diff --git a/schema/mysql/query_polls_votes.sql b/schema/mysql/query_polls_votes.sql new file mode 100644 index 00000000..364940af --- /dev/null +++ b/schema/mysql/query_polls_votes.sql @@ -0,0 +1,7 @@ +CREATE TABLE `polls_votes` ( + `pollID` int not null, + `uid` int not null, + `option` int DEFAULT 0 not null, + `castAt` datetime not null, + `ipaddress` varchar(200) DEFAULT '0.0.0.0.0' not null +) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/pgsql/query_polls_votes.sql b/schema/pgsql/query_polls_votes.sql new file mode 100644 index 00000000..6344e232 --- /dev/null +++ b/schema/pgsql/query_polls_votes.sql @@ -0,0 +1,7 @@ +CREATE TABLE `polls_votes` ( + `pollID` int not null, + `uid` int not null, + `option` int DEFAULT 0 not null, + `castAt` timestamp not null, + `ipaddress` varchar (200) DEFAULT '0.0.0.0.0' not null +); \ No newline at end of file diff --git a/template_list.go b/template_list.go index a7f498b1..fffac260 100644 --- a/template_list.go +++ b/template_list.go @@ -350,171 +350,209 @@ var topic_alt_20 = []byte(`