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:
parent
0ce7f8a9d7
commit
59754b489d
202
common/poll_cache.go
Normal file
202
common/poll_cache.go
Normal 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
118
common/poll_store.go
Normal 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
|
||||
}
|
@ -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) {
|
||||
|
@ -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, "\\")
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
}
|
||||
|
4
main.go
4
main.go
@ -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 {
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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, ""},
|
||||
|
@ -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)
|
||||
|
7
schema/mssql/query_polls.sql
Normal file
7
schema/mssql/query_polls.sql
Normal 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])
|
||||
);
|
5
schema/mssql/query_polls_voters.sql
Normal file
5
schema/mssql/query_polls_voters.sql
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE [polls_voters] (
|
||||
[pollID] int not null,
|
||||
[uid] int not null,
|
||||
[option] int DEFAULT 0 not null
|
||||
);
|
@ -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])
|
||||
);
|
@ -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])
|
||||
);
|
7
schema/mysql/query_polls.sql
Normal file
7
schema/mysql/query_polls.sql
Normal 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;
|
5
schema/mysql/query_polls_voters.sql
Normal file
5
schema/mysql/query_polls_voters.sql
Normal 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;
|
@ -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;
|
@ -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;
|
7
schema/pgsql/query_polls.sql
Normal file
7
schema/pgsql/query_polls.sql
Normal 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`)
|
||||
);
|
5
schema/pgsql/query_polls_voters.sql
Normal file
5
schema/pgsql/query_polls_voters.sql
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE `polls_voters` (
|
||||
`pollID` int not null,
|
||||
`uid` int not null,
|
||||
`option` int DEFAULT 0 not null
|
||||
);
|
@ -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`)
|
||||
);
|
@ -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`)
|
||||
);
|
@ -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;" />
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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%);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user