Block requests without known hosts for security reasons.

We now track stats for Uptimebot.
We now track stats for malformed requests.
Added json as a valid column type.

Continued work on poll posts.
Added the polls table.
Added the polls_voters table.
Added the poll column to the topics table.
Changed the type of the poll column on the replies table from boolean to int.
This commit is contained in:
Azareal 2018-01-25 04:57:33 +00:00
parent 0ce7f8a9d7
commit 59754b489d
29 changed files with 508 additions and 27 deletions

202
common/poll_cache.go Normal file
View File

@ -0,0 +1,202 @@
package common
import (
"sync"
"sync/atomic"
)
type PollCache interface {
Get(id int) (*Poll, error)
GetUnsafe(id int) (*Poll, error)
BulkGet(ids []int) (list []*Poll)
Set(item *Poll) error
Add(item *Poll) error
AddUnsafe(item *Poll) error
Remove(id int) error
RemoveUnsafe(id int) error
Flush()
Length() int
SetCapacity(capacity int)
GetCapacity() int
}
type MemoryPollCache struct {
items map[int]*Poll
length int64
capacity int
sync.RWMutex
}
// NewMemoryPollCache gives you a new instance of MemoryPollCache
func NewMemoryPollCache(capacity int) *MemoryPollCache {
return &MemoryPollCache{
items: make(map[int]*Poll),
capacity: capacity,
}
}
func (mus *MemoryPollCache) Get(id int) (*Poll, error) {
mus.RLock()
item, ok := mus.items[id]
mus.RUnlock()
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mus *MemoryPollCache) BulkGet(ids []int) (list []*Poll) {
list = make([]*Poll, len(ids))
mus.RLock()
for i, id := range ids {
list[i] = mus.items[id]
}
mus.RUnlock()
return list
}
func (mus *MemoryPollCache) GetUnsafe(id int) (*Poll, error) {
item, ok := mus.items[id]
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mus *MemoryPollCache) Set(item *Poll) error {
mus.Lock()
user, ok := mus.items[item.ID]
if ok {
mus.Unlock()
*user = *item
} else if int(mus.length) >= mus.capacity {
mus.Unlock()
return ErrStoreCapacityOverflow
} else {
mus.items[item.ID] = item
mus.Unlock()
atomic.AddInt64(&mus.length, 1)
}
return nil
}
func (mus *MemoryPollCache) Add(item *Poll) error {
if int(mus.length) >= mus.capacity {
return ErrStoreCapacityOverflow
}
mus.Lock()
mus.items[item.ID] = item
mus.length = int64(len(mus.items))
mus.Unlock()
return nil
}
func (mus *MemoryPollCache) AddUnsafe(item *Poll) error {
if int(mus.length) >= mus.capacity {
return ErrStoreCapacityOverflow
}
mus.items[item.ID] = item
mus.length = int64(len(mus.items))
return nil
}
func (mus *MemoryPollCache) Remove(id int) error {
mus.Lock()
_, ok := mus.items[id]
if !ok {
mus.Unlock()
return ErrNoRows
}
delete(mus.items, id)
mus.Unlock()
atomic.AddInt64(&mus.length, -1)
return nil
}
func (mus *MemoryPollCache) RemoveUnsafe(id int) error {
_, ok := mus.items[id]
if !ok {
return ErrNoRows
}
delete(mus.items, id)
atomic.AddInt64(&mus.length, -1)
return nil
}
func (mus *MemoryPollCache) Flush() {
mus.Lock()
mus.items = make(map[int]*Poll)
mus.length = 0
mus.Unlock()
}
// ! Is this concurrent?
// Length returns the number of users in the memory cache
func (mus *MemoryPollCache) Length() int {
return int(mus.length)
}
func (mus *MemoryPollCache) SetCapacity(capacity int) {
mus.capacity = capacity
}
func (mus *MemoryPollCache) GetCapacity() int {
return mus.capacity
}
type NullPollCache struct {
}
// NewNullPollCache gives you a new instance of NullPollCache
func NewNullPollCache() *NullPollCache {
return &NullPollCache{}
}
func (mus *NullPollCache) Get(id int) (*Poll, error) {
return nil, ErrNoRows
}
func (mus *NullPollCache) BulkGet(ids []int) (list []*Poll) {
return list
}
func (mus *NullPollCache) GetUnsafe(id int) (*Poll, error) {
return nil, ErrNoRows
}
func (mus *NullPollCache) Set(_ *Poll) error {
return nil
}
func (mus *NullPollCache) Add(item *Poll) error {
_ = item
return nil
}
func (mus *NullPollCache) AddUnsafe(item *Poll) error {
_ = item
return nil
}
func (mus *NullPollCache) Remove(id int) error {
return nil
}
func (mus *NullPollCache) RemoveUnsafe(id int) error {
return nil
}
func (mus *NullPollCache) Flush() {
}
func (mus *NullPollCache) Length() int {
return 0
}
func (mus *NullPollCache) SetCapacity(_ int) {
}
func (mus *NullPollCache) GetCapacity() int {
return 0
}

118
common/poll_store.go Normal file
View File

@ -0,0 +1,118 @@
package common
import "database/sql"
import "../query_gen/lib"
var Polls PollStore
type Poll struct {
ID int
Type int // 0: Single choice, 1: Multiple choice, 2: Multiple choice w/ points
//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
}
type Pollable interface {
SetPoll(pollID int) error
}
type PollStore interface {
Get(id int) (*Poll, error)
Exists(id int) bool
Create(parent Pollable, pollType int, pollOptions []string) (int, error)
Reload(id int) error
//GlobalCount() int
SetCache(cache PollCache)
GetCache() PollCache
}
type DefaultPollStore struct {
cache PollCache
get *sql.Stmt
exists *sql.Stmt
create *sql.Stmt
delete *sql.Stmt
//pollCount *sql.Stmt
}
func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
acc := qgen.Builder.Accumulator()
if cache == nil {
cache = NewNullPollCache()
}
// TODO: Add an admin version of registerStmt with more flexibility?
return &DefaultPollStore{
cache: cache,
get: acc.Select("polls").Columns("type, options, votes").Where("pollID = ?").Prepare(),
exists: acc.Select("polls").Columns("pollID").Where("pollID = ?").Prepare(),
create: acc.Insert("polls").Columns("type, options").Fields("?,?").Prepare(),
//pollCount: acc.SimpleCount("polls", "", ""),
}, acc.FirstError()
}
func (store *DefaultPollStore) Exists(id int) bool {
err := store.exists.QueryRow(id).Scan(&id)
if err != nil && err != ErrNoRows {
LogError(err)
}
return err != ErrNoRows
}
func (store *DefaultPollStore) Get(id int) (*Poll, error) {
poll, err := store.cache.Get(id)
if err == nil {
return poll, nil
}
poll = &Poll{ID: id}
var optionTxt []byte
err = store.get.QueryRow(id).Scan(&poll.Type, optionTxt, &poll.VoteCount)
if err == nil {
store.cache.Set(poll)
}
return poll, err
}
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)
if err != nil {
store.cache.Remove(id)
return err
}
_ = 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)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
if err != nil {
return 0, err
}
return int(lastID), parent.SetPoll(int(lastID)) // TODO: Delete the poll if SetPoll fails
}
func (store *DefaultPollStore) SetCache(cache PollCache) {
store.cache = cache
}
// TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it.
func (store *DefaultPollStore) GetCache() PollCache {
_, ok := store.cache.(*NullPollCache)
if ok {
return nil
}
return store.cache
}

View File

@ -66,6 +66,7 @@ type ReplyStmts struct {
isLiked *sql.Stmt
createLike *sql.Stmt
edit *sql.Stmt
setPoll *sql.Stmt
delete *sql.Stmt
addLikesToReply *sql.Stmt
removeRepliesFromTopic *sql.Stmt
@ -76,7 +77,8 @@ func init() {
replyStmts = ReplyStmts{
isLiked: acc.Select("likes").Columns("targetItem").Where("sentBy = ? and targetItem = ? and targetType = 'replies'").Prepare(),
createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy").Fields("?,?,?,?").Prepare(),
edit: acc.Update("replies").Set("content = ?, parsed_content = ?, poll = ?").Where("rid = ?").Prepare(),
edit: acc.Update("replies").Set("content = ?, parsed_content = ?").Where("rid = ? AND poll = 0").Prepare(),
setPoll: acc.Update("replies").Set("content = '', parsed_content = '', poll = ?").Where("rid = ? AND poll = 0").Prepare(),
delete: acc.Delete("replies").Where("rid = ?").Prepare(),
addLikesToReply: acc.Update("replies").Set("likeCount = likeCount + ?").Where("rid = ?").Prepare(),
removeRepliesFromTopic: acc.Update("topics").Set("postCount = postCount - ?").Where("tid = ?").Prepare(),
@ -127,12 +129,13 @@ func (reply *Reply) SetPost(content string) error {
}
content = PreparseMessage(html.UnescapeString(content))
parsedContent := ParseMessage(content, topic.ParentID, "forums")
_, err = replyStmts.edit.Exec(content, parsedContent, 0, reply.ID)
_, err = replyStmts.edit.Exec(content, parsedContent, reply.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
return err
}
func (reply *Reply) SetPoll(content string) error {
return nil
func (reply *Reply) SetPoll(pollID int) error {
_, err := replyStmts.setPoll.Exec(pollID, reply.ID) // TODO: Sniff if this changed anything to see if we hit a poll
return err
}
func (reply *Reply) Topic() (*Topic, error) {

View File

@ -22,6 +22,7 @@ type site struct {
Name string
Email string
URL string
Host string
Port string
EnableSsl bool
EnableEmails bool
@ -82,6 +83,7 @@ type devConfig struct {
func ProcessConfig() error {
Config.Noavatar = strings.Replace(Config.Noavatar, "{site_url}", Site.URL, -1)
Site.Host = Site.URL
if Site.Port != "80" && Site.Port != "443" {
Site.URL = strings.TrimSuffix(Site.URL, "/")
Site.URL = strings.TrimSuffix(Site.URL, "\\")

View File

@ -120,6 +120,7 @@ type TopicStmts struct {
addLikesToTopic *sql.Stmt
delete *sql.Stmt
edit *sql.Stmt
setPoll *sql.Stmt
createActionReply *sql.Stmt
getTopicUser *sql.Stmt // TODO: Can we get rid of this?
@ -141,7 +142,8 @@ func init() {
createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy").Fields("?,?,?,?").Prepare(),
addLikesToTopic: acc.Update("topics").Set("likeCount = likeCount + ?").Where("tid = ?").Prepare(),
delete: acc.Delete("topics").Where("tid = ?").Prepare(),
edit: acc.Update("topics").Set("title = ?, content = ?, parsed_content = ?").Where("tid = ?").Prepare(),
edit: acc.Update("topics").Set("title = ?, content = ?, parsed_content = ?").Where("tid = ?").Prepare(), // TODO: Only run the content update bits on non-polls, does this matter?
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 = ?", "", ""),
@ -257,6 +259,11 @@ func (topic *Topic) Update(name string, content string) error {
return err
}
func (topic *Topic) SetPoll(pollID int) error {
_, err := topicStmts.setPoll.Exec(pollID, topic.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
return err
}
// TODO: Have this go through the ReplyStore?
func (topic *Topic) CreateActionReply(action string, ipaddress string, user User) (err error) {
_, err = topicStmts.createActionReply.Exec(topic.ID, action, ipaddress, user.ID)

View File

@ -311,8 +311,10 @@ var agentMapEnum = map[string]int{
"duckduckgo": 11,
"discord": 12,
"cloudflarealwayson": 13,
"lynx": 14,
"blank": 15,
"uptimebot": 14,
"lynx": 15,
"blank": 16,
"malformed": 17,
}
var reverseAgentMapEnum = map[int]string{
0: "unknown",
@ -329,8 +331,10 @@ var reverseAgentMapEnum = map[int]string{
11: "duckduckgo",
12: "discord",
13: "cloudflarealwayson",
14: "lynx",
15: "blank",
14: "uptimebot",
15: "lynx",
16: "blank",
17: "malformed",
}
// TODO: Stop spilling these into the package scope?
@ -392,20 +396,23 @@ func (router *GenRouter) RemoveFunc(pattern string) error {
// TODO: GetDefaultRoute
func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host {
w.WriteHeader(405)
w.Write([]byte(""))
log.Print("Suspicious UA: ", req.UserAgent())
log.Print("Malformed Request")
log.Print("UA: ", req.UserAgent())
log.Print("Method: ", req.Method)
for key, value := range req.Header {
for _, vvalue := range value {
log.Print("Header '" + key + "': " + vvalue + "!!")
}
}
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr)
common.AgentViewCounter.Bump(17)
return
}
@ -420,6 +427,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("Header '" + key + "': " + vvalue + "!!")
}
}
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("req.Referer(): ", req.Referer())
@ -435,6 +443,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("Header '" + key + "': " + vvalue + "!!")
}
}
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("req.Referer(): ", req.Referer())
@ -458,6 +467,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
log.Print("prefix: ", prefix)
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData)
@ -507,11 +517,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case strings.Contains(ua,"Discordbot"):
common.AgentViewCounter.Bump(12)
case strings.Contains(ua,"Lynx"):
common.AgentViewCounter.Bump(14)
common.AgentViewCounter.Bump(15)
case strings.Contains(ua,"CloudFlare-AlwaysOnline"):
common.AgentViewCounter.Bump(13)
case strings.Contains(ua,"Uptimebot"):
common.AgentViewCounter.Bump(14)
case ua == "":
common.AgentViewCounter.Bump(15)
common.AgentViewCounter.Bump(16)
if common.Dev.DebugMode {
log.Print("Blank UA: ", req.UserAgent())
log.Print("Method: ", req.Method)
@ -522,6 +534,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
log.Print("prefix: ", prefix)
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData)
@ -539,6 +552,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
log.Print("prefix: ", prefix)
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData)

View File

@ -2,14 +2,15 @@
package main
var dbTablePrimaryKeys = map[string]string{
"replies":"rid",
"attachments":"attachID",
"users_replies":"rid",
"activity_stream":"asid",
"word_filters":"wfid",
"forums":"fid",
"users_groups":"gid",
"users_groups_scheduler":"uid",
"topics":"tid",
"replies":"rid",
"word_filters":"wfid",
"users_replies":"rid",
"activity_stream":"asid",
"users":"uid",
"forums":"fid",
"topics":"tid",
"attachments":"attachID",
"polls":"pollID",
}

View File

@ -92,6 +92,10 @@ func afterDBInit() (err error) {
if err != nil {
return err
}
common.Polls, err = common.NewDefaultPollStore(common.NewMemoryPollCache(100)) // TODO: Max number of polls held in cache, make this a config item
if err != nil {
return err
}
common.GlobalViewCounter, err = common.NewGlobalViewCounter()
if err != nil {

View File

@ -591,5 +591,8 @@ $(document).ready(function(){
if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click();
};
$("#add_poll_button").click(function(event){
event.preventDefault();
$(".poll_content_row").removeClass("auto_hide");
});
});

View File

@ -60,6 +60,9 @@ func (adapter *MssqlAdapter) CreateTable(name string, table string, charset stri
case "text":
column.Type = "nvarchar"
max = true
case "json":
column.Type = "nvarchar"
max = true
case "boolean":
column.Type = "bit"
}

View File

@ -49,6 +49,8 @@ func (adapter *MysqlAdapter) CreateTable(name string, table string, charset stri
// Make it easier to support Cassandra in the future
if column.Type == "createdAt" {
column.Type = "datetime"
} else if column.Type == "json" {
column.Type = "text"
}
var size string

View File

@ -162,6 +162,7 @@ func createTables(adapter qgen.Adapter) error {
qgen.DBTableColumn{"words", "int", 0, false, false, "0"},
qgen.DBTableColumn{"views", "int", 0, false, false, "0"},
qgen.DBTableColumn{"css_class", "varchar", 100, false, false, "''"},
qgen.DBTableColumn{"poll", "int", 0, false, false, "0"},
qgen.DBTableColumn{"data", "varchar", 200, false, false, "''"},
},
[]qgen.DBTableKey{
@ -184,7 +185,7 @@ func createTables(adapter qgen.Adapter) error {
qgen.DBTableColumn{"likeCount", "int", 0, false, false, "0"},
qgen.DBTableColumn{"words", "int", 0, false, false, "1"}, // ? - replies has a default of 1 and topics has 0? why?
qgen.DBTableColumn{"actionType", "varchar", 20, false, false, "''"},
qgen.DBTableColumn{"poll", "boolean", 0, false, false, "0"},
qgen.DBTableColumn{"poll", "int", 0, false, false, "0"},
},
[]qgen.DBTableKey{
qgen.DBTableKey{"rid", "primary"},
@ -217,6 +218,27 @@ func createTables(adapter qgen.Adapter) error {
[]qgen.DBTableKey{},
)
qgen.Install.CreateTable("polls", "utf8mb4", "utf8mb4_general_ci",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"pollID", "int", 0, false, true, ""},
qgen.DBTableColumn{"type", "int", 0, false, false, "0"},
qgen.DBTableColumn{"options", "json", 0, false, false, ""},
qgen.DBTableColumn{"votes", "int", 0, false, false, "0"},
},
[]qgen.DBTableKey{
qgen.DBTableKey{"pollID", "primary"},
},
)
qgen.Install.CreateTable("polls_voters", "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.DBTableKey{},
)
qgen.Install.CreateTable("users_replies", "utf8mb4", "utf8mb4_general_ci",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"rid", "int", 0, false, true, ""},

View File

@ -173,8 +173,10 @@ func main() {
"duckduckgo",
"discord",
"cloudflarealwayson",
"uptimebot",
"lynx",
"blank",
"malformed",
}
tmplVars.AllAgentMap = make(map[string]int)
@ -276,20 +278,23 @@ func (router *GenRouter) RemoveFunc(pattern string) error {
// TODO: GetDefaultRoute
func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host {
w.WriteHeader(405)
w.Write([]byte(""))
log.Print("Suspicious UA: ", req.UserAgent())
log.Print("Malformed Request")
log.Print("UA: ", req.UserAgent())
log.Print("Method: ", req.Method)
for key, value := range req.Header {
for _, vvalue := range value {
log.Print("Header '" + key + "': " + vvalue + "!!")
}
}
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("req.Referer(): ", req.Referer())
log.Print("req.RemoteAddr: ", req.RemoteAddr)
common.AgentViewCounter.Bump({{.AllAgentMap.malformed}})
return
}
@ -304,6 +309,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("Header '" + key + "': " + vvalue + "!!")
}
}
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("req.Referer(): ", req.Referer())
@ -319,6 +325,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("Header '" + key + "': " + vvalue + "!!")
}
}
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("req.Referer(): ", req.Referer())
@ -342,6 +349,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
log.Print("prefix: ", prefix)
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData)
@ -394,6 +402,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.AgentViewCounter.Bump({{.AllAgentMap.lynx}})
case strings.Contains(ua,"CloudFlare-AlwaysOnline"):
common.AgentViewCounter.Bump({{.AllAgentMap.cloudflarealwayson}})
case strings.Contains(ua,"Uptimebot"):
common.AgentViewCounter.Bump({{.AllAgentMap.uptimebot}})
case ua == "":
common.AgentViewCounter.Bump({{.AllAgentMap.blank}})
if common.Dev.DebugMode {
@ -406,6 +416,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
log.Print("prefix: ", prefix)
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData)
@ -423,6 +434,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
log.Print("prefix: ", prefix)
log.Print("req.Host: ", req.Host)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("req.URL.RawQuery: ", req.URL.RawQuery)
log.Print("extraData: ", extraData)

View File

@ -0,0 +1,7 @@
CREATE TABLE [polls] (
[pollID] int not null IDENTITY,
[type] int DEFAULT 0 not null,
[options] nvarchar (MAX) not null,
[votes] int DEFAULT 0 not null,
primary key([pollID])
);

View File

@ -0,0 +1,5 @@
CREATE TABLE [polls_voters] (
[pollID] int not null,
[uid] int not null,
[option] int DEFAULT 0 not null
);

View File

@ -12,6 +12,6 @@ CREATE TABLE [replies] (
[likeCount] int DEFAULT 0 not null,
[words] int DEFAULT 1 not null,
[actionType] nvarchar (20) DEFAULT '' not null,
[poll] bit DEFAULT 0 not null,
[poll] int DEFAULT 0 not null,
primary key([rid])
);

View File

@ -16,6 +16,7 @@ CREATE TABLE [topics] (
[words] int DEFAULT 0 not null,
[views] int DEFAULT 0 not null,
[css_class] nvarchar (100) DEFAULT '' not null,
[poll] int DEFAULT 0 not null,
[data] nvarchar (200) DEFAULT '' not null,
primary key([tid])
);

View File

@ -0,0 +1,7 @@
CREATE TABLE `polls` (
`pollID` int not null AUTO_INCREMENT,
`type` int DEFAULT 0 not null,
`options` text not null,
`votes` int DEFAULT 0 not null,
primary key(`pollID`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -0,0 +1,5 @@
CREATE TABLE `polls_voters` (
`pollID` int not null,
`uid` int not null,
`option` int DEFAULT 0 not null
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -12,6 +12,6 @@ CREATE TABLE `replies` (
`likeCount` int DEFAULT 0 not null,
`words` int DEFAULT 1 not null,
`actionType` varchar(20) DEFAULT '' not null,
`poll` boolean DEFAULT 0 not null,
`poll` int DEFAULT 0 not null,
primary key(`rid`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -16,6 +16,7 @@ CREATE TABLE `topics` (
`words` int DEFAULT 0 not null,
`views` int DEFAULT 0 not null,
`css_class` varchar(100) DEFAULT '' not null,
`poll` int DEFAULT 0 not null,
`data` varchar(200) DEFAULT '' not null,
primary key(`tid`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -0,0 +1,7 @@
CREATE TABLE `polls` (
`pollID` serial not null,
`type` int DEFAULT 0 not null,
`options` json not null,
`votes` int DEFAULT 0 not null,
primary key(`pollID`)
);

View File

@ -0,0 +1,5 @@
CREATE TABLE `polls_voters` (
`pollID` int not null,
`uid` int not null,
`option` int DEFAULT 0 not null
);

View File

@ -12,6 +12,6 @@ CREATE TABLE `replies` (
`likeCount` int DEFAULT 0 not null,
`words` int DEFAULT 1 not null,
`actionType` varchar (20) DEFAULT '' not null,
`poll` boolean DEFAULT 0 not null,
`poll` int DEFAULT 0 not null,
primary key(`rid`)
);

View File

@ -16,6 +16,7 @@ CREATE TABLE `topics` (
`words` int DEFAULT 0 not null,
`views` int DEFAULT 0 not null,
`css_class` varchar (100) DEFAULT '' not null,
`poll` int DEFAULT 0 not null,
`data` varchar (200) DEFAULT '' not null,
primary key(`tid`)
);

View File

@ -900,9 +900,19 @@ var topics_23 = []byte(`
<textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
</div>
</div>
<div class="formrow poll_content_row auto_hide">
<div class="formitem">
<div class="pollinput">
<input type="checkbox" disabled />
<label class="pollinputlabel"></label>
<input type="text" placeholder="Add new poll option" />
</div>
</div>
</div>
<div class="formrow quick_button_row">
<div class="formitem">
<button form="topic_create_form_form" class="formbutton">Create Topic</button>
<button form="topic_create_form_form" class="formbutton" id="add_poll_button">Add Poll</button>
`)
var topics_24 = []byte(`
<input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
@ -1071,9 +1081,15 @@ var forum_21 = []byte(`" type="hidden">
<textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
</div>
</div>
<div class="formrow poll_content_row auto_hide">
<div class="formitem">
Poll stuff
</div>
</div>
<div class="formrow quick_button_row">
<div class="formitem">
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button>
<button form="topic_create_form_form" class="formbutton" id="add_poll_button">Add Poll</button>
`)
var forum_22 = []byte(`
<input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />

View File

@ -55,9 +55,15 @@
<textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
</div>
</div>
<div class="formrow poll_content_row auto_hide">
<div class="formitem">
Poll stuff
</div>
</div>
<div class="formrow quick_button_row">
<div class="formitem">
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button>
<button form="topic_create_form_form" class="formbutton" id="add_poll_button">Add Poll</button>
{{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>

View File

@ -74,9 +74,19 @@
<textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
</div>
</div>
<div class="formrow poll_content_row auto_hide">
<div class="formitem">
<div class="pollinput">
<input type="checkbox" disabled />
<label class="pollinputlabel"></label>
<input type="text" placeholder="Add new poll option" />
</div>
</div>
</div>
<div class="formrow quick_button_row">
<div class="formitem">
<button form="topic_create_form_form" class="formbutton">Create Topic</button>
<button form="topic_create_form_form" class="formbutton" id="add_poll_button">Add Poll</button>
{{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>

View File

@ -502,6 +502,20 @@ input, select {
width: 100%;
}
input[type=checkbox] {
display: none;
}
input[type=checkbox] + label {
width: 10px;
border: black;
background-color: var(--element-background-color);
}
.poll_content_row {
padding-left: 20px;
padding-top: 4px;
padding-bottom: 2px;
}
.formbutton {
margin-top: 12px;
margin-left: auto;
@ -534,6 +548,9 @@ input, select {
margin-left: 10px;
margin-top: 8px;
}
.quick_button_row #add_poll_button {
background: hsl(209, 47%, 56%);
}
.quick_button_row .add_file_button {
background: hsl(129, 57%, 56%);
}