diff --git a/common/counters.go b/common/counters.go
index 0c9e0745..3870e40f 100644
--- a/common/counters.go
+++ b/common/counters.go
@@ -8,21 +8,25 @@ import (
"../query_gen/lib"
)
-var GlobalViewCounter *ChunkedViewCounter
+// Global counters
+var GlobalViewCounter *DefaultViewCounter
var AgentViewCounter *DefaultAgentViewCounter
var RouteViewCounter *DefaultRouteViewCounter
+var PostCounter *DefaultPostCounter
+
+// Local counters
var TopicViewCounter *DefaultTopicViewCounter
-type ChunkedViewCounter struct {
+type DefaultViewCounter struct {
buckets [2]int64
currentBucket int64
insert *sql.Stmt
}
-func NewChunkedViewCounter() (*ChunkedViewCounter, error) {
+func NewGlobalViewCounter() (*DefaultViewCounter, error) {
acc := qgen.Builder.Accumulator()
- counter := &ChunkedViewCounter{
+ counter := &DefaultViewCounter{
currentBucket: 0,
insert: acc.Insert("viewchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),
}
@@ -32,7 +36,7 @@ func NewChunkedViewCounter() (*ChunkedViewCounter, error) {
return counter, acc.FirstError()
}
-func (counter *ChunkedViewCounter) Tick() (err error) {
+func (counter *DefaultViewCounter) Tick() (err error) {
var oldBucket = counter.currentBucket
var nextBucket int64 // 0
if counter.currentBucket == 0 {
@@ -47,11 +51,11 @@ func (counter *ChunkedViewCounter) Tick() (err error) {
return counter.insertChunk(previousViewChunk)
}
-func (counter *ChunkedViewCounter) Bump() {
+func (counter *DefaultViewCounter) Bump() {
atomic.AddInt64(&counter.buckets[counter.currentBucket], 1)
}
-func (counter *ChunkedViewCounter) insertChunk(count int64) error {
+func (counter *DefaultViewCounter) insertChunk(count int64) error {
if count == 0 {
return nil
}
@@ -60,6 +64,53 @@ func (counter *ChunkedViewCounter) insertChunk(count int64) error {
return err
}
+type DefaultPostCounter struct {
+ buckets [2]int64
+ currentBucket int64
+
+ insert *sql.Stmt
+}
+
+func NewPostCounter() (*DefaultPostCounter, error) {
+ acc := qgen.Builder.Accumulator()
+ counter := &DefaultPostCounter{
+ currentBucket: 0,
+ insert: acc.Insert("postchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),
+ }
+ AddScheduledFifteenMinuteTask(counter.Tick)
+ //AddScheduledSecondTask(counter.Tick)
+ AddShutdownTask(counter.Tick)
+ return counter, acc.FirstError()
+}
+
+func (counter *DefaultPostCounter) Tick() (err error) {
+ var oldBucket = counter.currentBucket
+ var nextBucket int64 // 0
+ if counter.currentBucket == 0 {
+ nextBucket = 1
+ }
+ atomic.AddInt64(&counter.buckets[oldBucket], counter.buckets[nextBucket])
+ atomic.StoreInt64(&counter.buckets[nextBucket], 0)
+ atomic.StoreInt64(&counter.currentBucket, nextBucket)
+
+ var previousViewChunk = counter.buckets[oldBucket]
+ atomic.AddInt64(&counter.buckets[oldBucket], -previousViewChunk)
+ return counter.insertChunk(previousViewChunk)
+}
+
+func (counter *DefaultPostCounter) Bump() {
+ atomic.AddInt64(&counter.buckets[counter.currentBucket], 1)
+}
+
+func (counter *DefaultPostCounter) insertChunk(count int64) error {
+ if count == 0 {
+ return nil
+ }
+ debugLogf("Inserting a postchunk with a count of %d", count)
+ _, err := counter.insert.Exec(count)
+ return err
+}
+
type RWMutexCounterBucket struct {
counter int
sync.RWMutex
@@ -114,7 +165,7 @@ func (counter *DefaultAgentViewCounter) insertChunk(count int, agent int) error
func (counter *DefaultAgentViewCounter) Bump(agent int) {
// TODO: Test this check
- debugLog("counter.agentBuckets[", agent, "]: ", counter.agentBuckets[agent])
+ debugDetail("counter.agentBuckets[", agent, "]: ", counter.agentBuckets[agent])
if len(counter.agentBuckets) <= agent || agent < 0 {
return
}
diff --git a/common/extend.go b/common/extend.go
index ae2f0428..99fb2441 100644
--- a/common/extend.go
+++ b/common/extend.go
@@ -96,7 +96,7 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User
"pre_render_panel_forums": nil,
"pre_render_panel_delete_forum": nil,
"pre_render_panel_edit_forum": nil,
- "pre_render_panel_analytics": nil,
+ "pre_render_panel_analytics_views": nil,
"pre_render_panel_analytics_routes": nil,
"pre_render_panel_analytics_agents": nil,
"pre_render_panel_analytics_route_views": nil,
diff --git a/common/pages.go b/common/pages.go
index 58c74e04..8a55499a 100644
--- a/common/pages.go
+++ b/common/pages.go
@@ -206,6 +206,7 @@ type PanelAnalyticsRoutePage struct {
Zone string
Route string
PrimaryGraph PanelTimeGraph
+ ViewItems []PanelAnalyticsItem
TimeRange string
}
diff --git a/common/permissions.go b/common/permissions.go
index 613d249b..8b3ec5bc 100644
--- a/common/permissions.go
+++ b/common/permissions.go
@@ -78,8 +78,9 @@ type Perms struct {
EditReply bool
//EditOwnReply bool
DeleteReply bool
- PinTopic bool
- CloseTopic bool
+ //DeleteOwnReply bool
+ PinTopic bool
+ CloseTopic bool
//CloseOwnTopic bool
//ExtData map[string]bool
diff --git a/common/routes_common.go b/common/routes_common.go
index 3d127a0c..fce992a3 100644
--- a/common/routes_common.go
+++ b/common/routes_common.go
@@ -5,6 +5,7 @@ import (
"log"
"net"
"net/http"
+ "strconv"
"strings"
)
@@ -346,3 +347,25 @@ func NoSessionMismatch(w http.ResponseWriter, r *http.Request, user User) RouteE
func ReqIsJson(r *http.Request) bool {
return r.Header.Get("Content-type") == "application/json"
}
+
+func HandleUploadRoute(w http.ResponseWriter, r *http.Request, user User, maxFileSize int) RouteError {
+ // TODO: Reuse this code more
+ if r.ContentLength > int64(maxFileSize) {
+ size, unit := ConvertByteUnit(float64(maxFileSize))
+ return CustomError("Your upload is too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user)
+ }
+ r.Body = http.MaxBytesReader(w, r.Body, int64(maxFileSize))
+
+ err := r.ParseMultipartForm(int64(Megabyte))
+ if err != nil {
+ return LocalError("Bad Form", w, r, user)
+ }
+ return nil
+}
+
+func NoUploadSessionMismatch(w http.ResponseWriter, r *http.Request, user User) RouteError {
+ if r.FormValue("session") != user.Session {
+ return SecurityError(w, r, user)
+ }
+ return nil
+}
diff --git a/common/topic.go b/common/topic.go
index 6b69692e..64cca91e 100644
--- a/common/topic.go
+++ b/common/topic.go
@@ -112,6 +112,7 @@ type TopicStmts struct {
addRepliesToTopic *sql.Stmt
lock *sql.Stmt
unlock *sql.Stmt
+ moveTo *sql.Stmt
stick *sql.Stmt
unstick *sql.Stmt
hasLikedTopic *sql.Stmt
@@ -132,6 +133,7 @@ func init() {
addRepliesToTopic: acc.Update("topics").Set("postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()").Where("tid = ?").Prepare(),
lock: acc.Update("topics").Set("is_closed = 1").Where("tid = ?").Prepare(),
unlock: acc.Update("topics").Set("is_closed = 0").Where("tid = ?").Prepare(),
+ moveTo: acc.Update("topics").Set("parentID = ?").Where("tid = ?").Prepare(),
stick: acc.Update("topics").Set("sticky = 1").Where("tid = ?").Prepare(),
unstick: acc.Update("topics").Set("sticky = 0").Where("tid = ?").Prepare(),
hasLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy = ? and targetItem = ? and targetType = 'topics'").Prepare(),
@@ -175,6 +177,12 @@ func (topic *Topic) Unlock() (err error) {
return err
}
+func (topic *Topic) MoveTo(destForum int) (err error) {
+ _, err = topicStmts.moveTo.Exec(destForum, topic.ID)
+ topic.cacheRemove()
+ return err
+}
+
// TODO: We might want more consistent terminology rather than using stick in some places and pin in others. If you don't understand the difference, there is none, they are one and the same.
func (topic *Topic) Stick() (err error) {
_, err = topicStmts.stick.Exec(topic.ID)
diff --git a/gen_router.go b/gen_router.go
index d81dfcb0..b3692442 100644
--- a/gen_router.go
+++ b/gen_router.go
@@ -56,6 +56,7 @@ var RouteMap = map[string]interface{}{
"routePanelAnalyticsAgents": routePanelAnalyticsAgents,
"routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews,
"routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews,
+ "routePanelAnalyticsPosts": routePanelAnalyticsPosts,
"routePanelGroups": routePanelGroups,
"routePanelGroupsEdit": routePanelGroupsEdit,
"routePanelGroupsEditPerms": routePanelGroupsEditPerms,
@@ -79,6 +80,20 @@ var RouteMap = map[string]interface{}{
"routeUnban": routeUnban,
"routeActivate": routeActivate,
"routeIps": routeIps,
+ "routeTopicCreateSubmit": routeTopicCreateSubmit,
+ "routeEditTopicSubmit": routeEditTopicSubmit,
+ "routeDeleteTopicSubmit": routeDeleteTopicSubmit,
+ "routeStickTopicSubmit": routeStickTopicSubmit,
+ "routeUnstickTopicSubmit": routeUnstickTopicSubmit,
+ "routeLockTopicSubmit": routeLockTopicSubmit,
+ "routeUnlockTopicSubmit": routeUnlockTopicSubmit,
+ "routeMoveTopicSubmit": routeMoveTopicSubmit,
+ "routeLikeTopicSubmit": routeLikeTopicSubmit,
+ "routeTopicID": routeTopicID,
+ "routeCreateReplySubmit": routeCreateReplySubmit,
+ "routeReplyEditSubmit": routeReplyEditSubmit,
+ "routeReplyDeleteSubmit": routeReplyDeleteSubmit,
+ "routeReplyLikeSubmit": routeReplyLikeSubmit,
"routeDynamic": routeDynamic,
"routeUploads": routeUploads,
}
@@ -126,31 +141,46 @@ var routeMapEnum = map[string]int{
"routePanelAnalyticsAgents": 38,
"routePanelAnalyticsRouteViews": 39,
"routePanelAnalyticsAgentViews": 40,
- "routePanelGroups": 41,
- "routePanelGroupsEdit": 42,
- "routePanelGroupsEditPerms": 43,
- "routePanelGroupsEditSubmit": 44,
- "routePanelGroupsEditPermsSubmit": 45,
- "routePanelGroupsCreateSubmit": 46,
- "routePanelBackups": 47,
- "routePanelLogsMod": 48,
- "routePanelDebug": 49,
- "routePanel": 50,
- "routeAccountEditCritical": 51,
- "routeAccountEditCriticalSubmit": 52,
- "routeAccountEditAvatar": 53,
- "routeAccountEditAvatarSubmit": 54,
- "routeAccountEditUsername": 55,
- "routeAccountEditUsernameSubmit": 56,
- "routeAccountEditEmail": 57,
- "routeAccountEditEmailTokenSubmit": 58,
- "routeProfile": 59,
- "routeBanSubmit": 60,
- "routeUnban": 61,
- "routeActivate": 62,
- "routeIps": 63,
- "routeDynamic": 64,
- "routeUploads": 65,
+ "routePanelAnalyticsPosts": 41,
+ "routePanelGroups": 42,
+ "routePanelGroupsEdit": 43,
+ "routePanelGroupsEditPerms": 44,
+ "routePanelGroupsEditSubmit": 45,
+ "routePanelGroupsEditPermsSubmit": 46,
+ "routePanelGroupsCreateSubmit": 47,
+ "routePanelBackups": 48,
+ "routePanelLogsMod": 49,
+ "routePanelDebug": 50,
+ "routePanel": 51,
+ "routeAccountEditCritical": 52,
+ "routeAccountEditCriticalSubmit": 53,
+ "routeAccountEditAvatar": 54,
+ "routeAccountEditAvatarSubmit": 55,
+ "routeAccountEditUsername": 56,
+ "routeAccountEditUsernameSubmit": 57,
+ "routeAccountEditEmail": 58,
+ "routeAccountEditEmailTokenSubmit": 59,
+ "routeProfile": 60,
+ "routeBanSubmit": 61,
+ "routeUnban": 62,
+ "routeActivate": 63,
+ "routeIps": 64,
+ "routeTopicCreateSubmit": 65,
+ "routeEditTopicSubmit": 66,
+ "routeDeleteTopicSubmit": 67,
+ "routeStickTopicSubmit": 68,
+ "routeUnstickTopicSubmit": 69,
+ "routeLockTopicSubmit": 70,
+ "routeUnlockTopicSubmit": 71,
+ "routeMoveTopicSubmit": 72,
+ "routeLikeTopicSubmit": 73,
+ "routeTopicID": 74,
+ "routeCreateReplySubmit": 75,
+ "routeReplyEditSubmit": 76,
+ "routeReplyDeleteSubmit": 77,
+ "routeReplyLikeSubmit": 78,
+ "routeDynamic": 79,
+ "routeUploads": 80,
}
var reverseRouteMapEnum = map[int]string{
0: "routeAPI",
@@ -194,31 +224,46 @@ var reverseRouteMapEnum = map[int]string{
38: "routePanelAnalyticsAgents",
39: "routePanelAnalyticsRouteViews",
40: "routePanelAnalyticsAgentViews",
- 41: "routePanelGroups",
- 42: "routePanelGroupsEdit",
- 43: "routePanelGroupsEditPerms",
- 44: "routePanelGroupsEditSubmit",
- 45: "routePanelGroupsEditPermsSubmit",
- 46: "routePanelGroupsCreateSubmit",
- 47: "routePanelBackups",
- 48: "routePanelLogsMod",
- 49: "routePanelDebug",
- 50: "routePanel",
- 51: "routeAccountEditCritical",
- 52: "routeAccountEditCriticalSubmit",
- 53: "routeAccountEditAvatar",
- 54: "routeAccountEditAvatarSubmit",
- 55: "routeAccountEditUsername",
- 56: "routeAccountEditUsernameSubmit",
- 57: "routeAccountEditEmail",
- 58: "routeAccountEditEmailTokenSubmit",
- 59: "routeProfile",
- 60: "routeBanSubmit",
- 61: "routeUnban",
- 62: "routeActivate",
- 63: "routeIps",
- 64: "routeDynamic",
- 65: "routeUploads",
+ 41: "routePanelAnalyticsPosts",
+ 42: "routePanelGroups",
+ 43: "routePanelGroupsEdit",
+ 44: "routePanelGroupsEditPerms",
+ 45: "routePanelGroupsEditSubmit",
+ 46: "routePanelGroupsEditPermsSubmit",
+ 47: "routePanelGroupsCreateSubmit",
+ 48: "routePanelBackups",
+ 49: "routePanelLogsMod",
+ 50: "routePanelDebug",
+ 51: "routePanel",
+ 52: "routeAccountEditCritical",
+ 53: "routeAccountEditCriticalSubmit",
+ 54: "routeAccountEditAvatar",
+ 55: "routeAccountEditAvatarSubmit",
+ 56: "routeAccountEditUsername",
+ 57: "routeAccountEditUsernameSubmit",
+ 58: "routeAccountEditEmail",
+ 59: "routeAccountEditEmailTokenSubmit",
+ 60: "routeProfile",
+ 61: "routeBanSubmit",
+ 62: "routeUnban",
+ 63: "routeActivate",
+ 64: "routeIps",
+ 65: "routeTopicCreateSubmit",
+ 66: "routeEditTopicSubmit",
+ 67: "routeDeleteTopicSubmit",
+ 68: "routeStickTopicSubmit",
+ 69: "routeUnstickTopicSubmit",
+ 70: "routeLockTopicSubmit",
+ 71: "routeUnlockTopicSubmit",
+ 72: "routeMoveTopicSubmit",
+ 73: "routeLikeTopicSubmit",
+ 74: "routeTopicID",
+ 75: "routeCreateReplySubmit",
+ 76: "routeReplyEditSubmit",
+ 77: "routeReplyDeleteSubmit",
+ 78: "routeReplyLikeSubmit",
+ 79: "routeDynamic",
+ 80: "routeUploads",
}
var agentMapEnum = map[string]int{
"unknown": 0,
@@ -339,6 +384,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer())
+ log.Print("req.RemoteAddr: ", req.RemoteAddr)
}
if prefix == "/static" {
@@ -389,6 +435,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if common.Dev.DebugMode {
log.Print("Blank UA: ", req.UserAgent())
log.Print("Method: ", req.Method)
+
for key, value := range req.Header {
for _, vvalue := range value {
log.Print("Header '" + key + "': " + vvalue + "!!")
@@ -398,6 +445,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer())
+ log.Print("req.RemoteAddr: ", req.RemoteAddr)
}
default:
common.AgentViewCounter.Bump(0)
@@ -413,6 +461,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer())
+ log.Print("req.RemoteAddr: ", req.RemoteAddr)
}
}
@@ -724,14 +773,23 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/panel/analytics/agent/":
common.RouteViewCounter.Bump(40)
err = routePanelAnalyticsAgentViews(w,req,user,extraData)
- case "/panel/groups/":
+ case "/panel/analytics/posts/":
+ err = common.ParseForm(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
common.RouteViewCounter.Bump(41)
+ err = routePanelAnalyticsPosts(w,req,user)
+ case "/panel/groups/":
+ common.RouteViewCounter.Bump(42)
err = routePanelGroups(w,req,user)
case "/panel/groups/edit/":
- common.RouteViewCounter.Bump(42)
+ common.RouteViewCounter.Bump(43)
err = routePanelGroupsEdit(w,req,user,extraData)
case "/panel/groups/edit/perms/":
- common.RouteViewCounter.Bump(43)
+ common.RouteViewCounter.Bump(44)
err = routePanelGroupsEditPerms(w,req,user,extraData)
case "/panel/groups/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
@@ -740,7 +798,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(44)
+ common.RouteViewCounter.Bump(45)
err = routePanelGroupsEditSubmit(w,req,user,extraData)
case "/panel/groups/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user)
@@ -749,7 +807,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(45)
+ common.RouteViewCounter.Bump(46)
err = routePanelGroupsEditPermsSubmit(w,req,user,extraData)
case "/panel/groups/create/":
err = common.NoSessionMismatch(w,req,user)
@@ -758,7 +816,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(46)
+ common.RouteViewCounter.Bump(47)
err = routePanelGroupsCreateSubmit(w,req,user)
case "/panel/backups/":
err = common.SuperAdminOnly(w,req,user)
@@ -767,10 +825,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(47)
+ common.RouteViewCounter.Bump(48)
err = routePanelBackups(w,req,user,extraData)
case "/panel/logs/mod/":
- common.RouteViewCounter.Bump(48)
+ common.RouteViewCounter.Bump(49)
err = routePanelLogsMod(w,req,user)
case "/panel/debug/":
err = common.AdminOnly(w,req,user)
@@ -779,10 +837,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(49)
+ common.RouteViewCounter.Bump(50)
err = routePanelDebug(w,req,user)
default:
- common.RouteViewCounter.Bump(50)
+ common.RouteViewCounter.Bump(51)
err = routePanel(w,req,user)
}
if err != nil {
@@ -797,7 +855,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(51)
+ common.RouteViewCounter.Bump(52)
err = routeAccountEditCritical(w,req,user)
case "/user/edit/critical/submit/":
err = common.NoSessionMismatch(w,req,user)
@@ -812,7 +870,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(52)
+ common.RouteViewCounter.Bump(53)
err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/":
err = common.MemberOnly(w,req,user)
@@ -821,7 +879,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(53)
+ common.RouteViewCounter.Bump(54)
err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/":
err = common.MemberOnly(w,req,user)
@@ -830,7 +888,18 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(54)
+ err = common.HandleUploadRoute(w,req,user,common.Config.MaxRequestSize)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+ err = common.NoUploadSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(55)
err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/":
err = common.MemberOnly(w,req,user)
@@ -839,7 +908,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(55)
+ common.RouteViewCounter.Bump(56)
err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/":
err = common.NoSessionMismatch(w,req,user)
@@ -854,7 +923,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(56)
+ common.RouteViewCounter.Bump(57)
err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/":
err = common.MemberOnly(w,req,user)
@@ -863,7 +932,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(57)
+ common.RouteViewCounter.Bump(58)
err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/":
err = common.NoSessionMismatch(w,req,user)
@@ -878,11 +947,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(58)
+ common.RouteViewCounter.Bump(59)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default:
req.URL.Path += extraData
- common.RouteViewCounter.Bump(59)
+ common.RouteViewCounter.Bump(60)
err = routeProfile(w,req,user)
}
if err != nil {
@@ -903,7 +972,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(60)
+ common.RouteViewCounter.Bump(61)
err = routeBanSubmit(w,req,user,extraData)
case "/users/unban/":
err = common.NoSessionMismatch(w,req,user)
@@ -918,7 +987,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(61)
+ common.RouteViewCounter.Bump(62)
err = routeUnban(w,req,user,extraData)
case "/users/activate/":
err = common.NoSessionMismatch(w,req,user)
@@ -933,7 +1002,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(62)
+ common.RouteViewCounter.Bump(63)
err = routeActivate(w,req,user,extraData)
case "/users/ips/":
err = common.MemberOnly(w,req,user)
@@ -942,12 +1011,229 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
- common.RouteViewCounter.Bump(63)
+ common.RouteViewCounter.Bump(64)
err = routeIps(w,req,user)
}
if err != nil {
router.handleError(err,w,req,user)
}
+ case "/topic":
+ switch(req.URL.Path) {
+ case "/topic/create/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(65)
+ err = routeTopicCreateSubmit(w,req,user)
+ case "/topic/edit/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(66)
+ err = routeEditTopicSubmit(w,req,user,extraData)
+ case "/topic/delete/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ req.URL.Path += extraData
+ common.RouteViewCounter.Bump(67)
+ err = routeDeleteTopicSubmit(w,req,user)
+ case "/topic/stick/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(68)
+ err = routeStickTopicSubmit(w,req,user,extraData)
+ case "/topic/unstick/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(69)
+ err = routeUnstickTopicSubmit(w,req,user,extraData)
+ case "/topic/lock/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ req.URL.Path += extraData
+ common.RouteViewCounter.Bump(70)
+ err = routeLockTopicSubmit(w,req,user)
+ case "/topic/unlock/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(71)
+ err = routeUnlockTopicSubmit(w,req,user,extraData)
+ case "/topic/move/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(72)
+ err = routeMoveTopicSubmit(w,req,user,extraData)
+ case "/topic/like/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(73)
+ err = routeLikeTopicSubmit(w,req,user,extraData)
+ default:
+ common.RouteViewCounter.Bump(74)
+ err = routeTopicID(w,req,user, extraData)
+ }
+ if err != nil {
+ router.handleError(err,w,req,user)
+ }
+ case "/reply":
+ switch(req.URL.Path) {
+ case "/reply/create/":
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.HandleUploadRoute(w,req,user,common.Config.MaxRequestSize)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+ err = common.NoUploadSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(75)
+ err = routeCreateReplySubmit(w,req,user)
+ case "/reply/edit/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(76)
+ err = routeReplyEditSubmit(w,req,user,extraData)
+ case "/reply/delete/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(77)
+ err = routeReplyDeleteSubmit(w,req,user,extraData)
+ case "/reply/like/submit/":
+ err = common.NoSessionMismatch(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ err = common.MemberOnly(w,req,user)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }
+
+ common.RouteViewCounter.Bump(78)
+ err = routeReplyLikeSubmit(w,req,user,extraData)
+ }
+ if err != nil {
+ router.handleError(err,w,req,user)
+ }
/*case "/sitemaps": // TODO: Count these views
req.URL.Path += extraData
err = sitemapSwitch(w,req)
@@ -959,7 +1245,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.NotFound(w,req)
return
}
- common.RouteViewCounter.Bump(65)
+ common.RouteViewCounter.Bump(80)
req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) // TODO: Count these views
@@ -1003,7 +1289,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock()
if ok {
- common.RouteViewCounter.Bump(64) // TODO: Be more specific about *which* dynamic route it is
+ common.RouteViewCounter.Bump(79) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData
err = handle(w,req,user)
if err != nil {
diff --git a/main.go b/main.go
index 1cccba3d..7c23fefa 100644
--- a/main.go
+++ b/main.go
@@ -80,7 +80,7 @@ func afterDBInit() (err error) {
if err != nil {
return err
}
- common.GlobalViewCounter, err = common.NewChunkedViewCounter()
+ common.GlobalViewCounter, err = common.NewGlobalViewCounter()
if err != nil {
return err
}
@@ -92,6 +92,10 @@ func afterDBInit() (err error) {
if err != nil {
return err
}
+ common.PostCounter, err = common.NewPostCounter()
+ if err != nil {
+ return err
+ }
common.TopicViewCounter, err = common.NewDefaultTopicViewCounter()
if err != nil {
return err
@@ -299,22 +303,6 @@ func main() {
// TODO: Move these routes into the new routes list
log.Print("Initialising the router")
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
- router.HandleFunc("/topic/create/submit/", routeTopicCreateSubmit)
- router.HandleFunc("/topic/", routeTopicID)
- router.HandleFunc("/reply/create/", routeCreateReply)
- //router.HandleFunc("/reply/edit/", routeReplyEdit) // No js fallback
- //router.HandleFunc("/reply/delete/", routeReplyDelete) // No js confirmation page? We could have a confirmation modal for the JS case
- router.HandleFunc("/reply/edit/submit/", routeReplyEditSubmit)
- router.HandleFunc("/reply/delete/submit/", routeReplyDeleteSubmit)
- router.HandleFunc("/reply/like/submit/", routeReplyLikeSubmit)
- router.HandleFunc("/topic/edit/submit/", routeEditTopic)
- router.HandleFunc("/topic/delete/submit/", routeDeleteTopic)
- router.HandleFunc("/topic/stick/submit/", routeStickTopic)
- router.HandleFunc("/topic/unstick/submit/", routeUnstickTopic)
- router.HandleFunc("/topic/lock/submit/", routeLockTopic)
- router.HandleFunc("/topic/unlock/submit/", routeUnlockTopic)
- router.HandleFunc("/topic/move/submit/", routeMoveTopic)
- router.HandleFunc("/topic/like/submit/", routeLikeTopic)
// Accounts
router.HandleFunc("/accounts/login/", routeLogin)
diff --git a/member_routes.go b/member_routes.go
index d710c9f1..2886fc2f 100644
--- a/member_routes.go
+++ b/member_routes.go
@@ -228,24 +228,12 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user common.
}
}
+ common.PostCounter.Bump()
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
return nil
}
-func routeCreateReply(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- // TODO: Reduce this to 1MB for attachments for each file?
- // TODO: Reuse this code more
- if r.ContentLength > int64(common.Config.MaxRequestSize) {
- size, unit := common.ConvertByteUnit(float64(common.Config.MaxRequestSize))
- return common.CustomError("Your attachments are too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user)
- }
- r.Body = http.MaxBytesReader(w, r.Body, int64(common.Config.MaxRequestSize))
-
- err := r.ParseMultipartForm(int64(common.Megabyte))
- if err != nil {
- return common.LocalError("Unable to parse the form", w, r, user)
- }
-
+func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
tid, err := strconv.Atoi(r.PostFormValue("tid"))
if err != nil {
return common.PreError("Failed to convert the Topic ID", w, r)
@@ -372,17 +360,14 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user common.User)
if err != nil {
return common.InternalError(err, w, r)
}
+
+ common.PostCounter.Bump()
return nil
}
// TODO: Refactor this
-func routeLikeTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- err := r.ParseForm()
- if err != nil {
- return common.PreError("Bad Form", w, r)
- }
-
- tid, err := strconv.Atoi(r.URL.Path[len("/topic/like/submit/"):])
+func routeLikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
+ tid, err := strconv.Atoi(stid)
if err != nil {
return common.PreError("Topic IDs can only ever be numbers.", w, r)
}
@@ -442,13 +427,8 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user common.User) co
return nil
}
-func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- err := r.ParseForm()
- if err != nil {
- return common.PreError("Bad Form", w, r)
- }
-
- rid, err := strconv.Atoi(r.URL.Path[len("/reply/like/submit/"):])
+func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError {
+ rid, err := strconv.Atoi(srid)
if err != nil {
return common.PreError("The provided Reply ID is not a valid number.", w, r)
}
@@ -524,10 +504,14 @@ func routeProfileReplyCreate(w http.ResponseWriter, r *http.Request, user common
if err != nil {
return common.LocalError("Bad Form", w, r, user)
}
+
uid, err := strconv.Atoi(r.PostFormValue("uid"))
if err != nil {
return common.LocalError("Invalid UID", w, r, user)
}
+ if !common.Users.Exists(uid) {
+ return common.LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
+ }
content := common.PreparseMessage(r.PostFormValue("reply-content"))
// TODO: Fully parse the post and store it in the parsed column
@@ -536,10 +520,7 @@ func routeProfileReplyCreate(w http.ResponseWriter, r *http.Request, user common
return common.InternalError(err, w, r)
}
- if !common.Users.Exists(uid) {
- return common.LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
- }
-
+ common.PostCounter.Bump()
http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther)
return nil
}
@@ -629,6 +610,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user common.User,
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
+ common.PostCounter.Bump()
http.Redirect(w, r, "/topic/"+strconv.FormatInt(lastID, 10), http.StatusSeeOther)
return nil
@@ -719,20 +701,10 @@ func routeAccountEditAvatar(w http.ResponseWriter, r *http.Request, user common.
}
func routeAccountEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- if r.ContentLength > int64(common.Config.MaxRequestSize) {
- size, unit := common.ConvertByteUnit(float64(common.Config.MaxRequestSize))
- return common.CustomError("Your avatar's too big. Avatars must be smaller than "+strconv.Itoa(int(size))+unit, http.StatusExpectationFailed, "Error", w, r, user)
- }
- r.Body = http.MaxBytesReader(w, r.Body, int64(common.Config.MaxRequestSize))
-
headerVars, ferr := common.UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
- err := r.ParseMultipartForm(int64(common.Megabyte))
- if err != nil {
- return common.LocalError("Upload failed", w, r, user)
- }
var filename, ext string
for _, fheaders := range r.MultipartForm.File {
@@ -783,7 +755,7 @@ func routeAccountEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user c
}
}
- err = user.ChangeAvatar("." + ext)
+ err := user.ChangeAvatar("." + ext)
if err != nil {
return common.InternalError(err, w, r)
}
diff --git a/mod_routes.go b/mod_routes.go
index e163099a..7299d748 100644
--- a/mod_routes.go
+++ b/mod_routes.go
@@ -15,15 +15,10 @@ import (
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
// TODO: Disable stat updates in posts handled by plugin_guilds
-// TODO: Make sure this route is member only
-func routeEditTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- err := r.ParseForm()
- if err != nil {
- return common.PreError("Bad Form", w, r)
- }
+func routeEditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
isJs := (r.PostFormValue("js") == "1")
- tid, err := strconv.Atoi(r.URL.Path[len("/topic/edit/submit/"):])
+ tid, err := strconv.Atoi(stid)
if err != nil {
return common.PreErrorJSQ("The provided TopicID is not a valid number.", w, r, isJs)
}
@@ -64,8 +59,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user common.User) co
// TODO: Add support for soft-deletion and add a permission for hard delete in addition to the usual
// TODO: Disable stat updates in posts handled by plugin_guilds
-// TODO: Make sure this route is member only
-func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
+func routeDeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Move this to some sort of middleware
var tids []int
var isJs = false
@@ -131,8 +125,8 @@ func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user common.User)
return nil
}
-func routeStickTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- tid, err := strconv.Atoi(r.URL.Path[len("/topic/stick/submit/"):])
+func routeStickTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
+ tid, err := strconv.Atoi(stid)
if err != nil {
return common.PreError("The provided TopicID is not a valid number.", w, r)
}
@@ -170,8 +164,8 @@ func routeStickTopic(w http.ResponseWriter, r *http.Request, user common.User) c
return nil
}
-func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- tid, err := strconv.Atoi(r.URL.Path[len("/topic/unstick/submit/"):])
+func routeUnstickTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
+ tid, err := strconv.Atoi(stid)
if err != nil {
return common.PreError("The provided TopicID is not a valid number.", w, r)
}
@@ -210,7 +204,7 @@ func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user common.User)
return nil
}
-func routeLockTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
+func routeLockTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Move this to some sort of middleware
var tids []int
var isJs = false
@@ -272,8 +266,8 @@ func routeLockTopic(w http.ResponseWriter, r *http.Request, user common.User) co
return nil
}
-func routeUnlockTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- tid, err := strconv.Atoi(r.URL.Path[len("/topic/unlock/submit/"):])
+func routeUnlockTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
+ tid, err := strconv.Atoi(stid)
if err != nil {
return common.PreError("The provided TopicID is not a valid number.", w, r)
}
@@ -312,58 +306,70 @@ func routeUnlockTopic(w http.ResponseWriter, r *http.Request, user common.User)
return nil
}
-func routeMoveTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- return common.NoPermissions(w, r, user)
+// ! JS only route
+// TODO: Figure a way to get this route to work without JS
+func routeMoveTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
+ // Not fully implemented
+ return common.NoPermissionsJS(w, r, user)
- tid, err := strconv.Atoi(r.URL.Path[len("/topic/move/submit/"):])
+ // TODO: Move this to some sort of middleware
+ var tids []int
+ if r.Body == nil {
+ return common.PreErrorJS("No request body", w, r)
+ }
+ err := json.NewDecoder(r.Body).Decode(&tids)
if err != nil {
- return common.PreError("The provided TopicID is not a valid number.", w, r)
+ return common.PreErrorJS("We weren't able to parse your data", w, r)
+ }
+ if len(tids) == 0 {
+ return common.LocalErrorJS("You haven't provided any IDs", w, r)
+ }
+ fid := 0
+
+ for _, tid := range tids {
+ topic, err := common.Topics.Get(tid)
+ if err == ErrNoRows {
+ return common.PreErrorJS("The topic you tried to move doesn't exist.", w, r)
+ } else if err != nil {
+ return common.InternalErrorJS(err, w, r)
+ }
+
+ // TODO: Add hooks to make use of headerLite
+ _, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
+ if ferr != nil {
+ return ferr
+ }
+ if !user.Perms.ViewTopic || !user.IsSuperMod { // TODO: Add a MoveTo permission
+ return common.NoPermissionsJS(w, r, user)
+ }
+
+ err = topic.MoveTo(fid)
+ if err != nil {
+ return common.InternalErrorJS(err, w, r)
+ }
+
+ err = common.ModLogs.Create("move", tid, "topic", user.LastIP, user.ID)
+ if err != nil {
+ return common.InternalErrorJS(err, w, r)
+ }
+ err = topic.CreateActionReply("move", user.LastIP, user)
+ if err != nil {
+ return common.InternalErrorJS(err, w, r)
+ }
}
- topic, err := common.Topics.Get(tid)
- if err == ErrNoRows {
- return common.PreError("The topic you tried to move doesn't exist.", w, r)
- } else if err != nil {
- return common.InternalError(err, w, r)
+ if len(tids) == 1 {
+ http.Redirect(w, r, "/topic/"+strconv.Itoa(tids[0]), http.StatusSeeOther)
}
-
- // TODO: Add hooks to make use of headerLite
- _, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
- if ferr != nil {
- return ferr
- }
- if !user.Perms.ViewTopic { // TODO: MoveTopic permission?
- return common.NoPermissions(w, r, user)
- }
-
- err = topic.Unlock()
- if err != nil {
- return common.InternalError(err, w, r)
- }
-
- err = common.ModLogs.Create("move", tid, "topic", user.LastIP, user.ID)
- if err != nil {
- return common.InternalError(err, w, r)
- }
- err = topic.CreateActionReply("move", user.LastIP, user)
- if err != nil {
- return common.InternalError(err, w, r)
- }
-
- http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
return nil
}
// TODO: Disable stat updates in posts handled by plugin_guilds
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
-func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- err := r.ParseForm()
- if err != nil {
- return common.PreError("Bad Form", w, r)
- }
+func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError {
isJs := (r.PostFormValue("js") == "1")
- rid, err := strconv.Atoi(r.URL.Path[len("/reply/edit/submit/"):])
+ rid, err := strconv.Atoi(srid)
if err != nil {
return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs)
}
@@ -408,14 +414,10 @@ func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.Us
// TODO: Refactor this
// TODO: Disable stat updates in posts handled by plugin_guilds
-func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
- err := r.ParseForm()
- if err != nil {
- return common.PreError("Bad Form", w, r)
- }
+func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError {
isJs := (r.PostFormValue("isJs") == "1")
- rid, err := strconv.Atoi(r.URL.Path[len("/reply/delete/submit/"):])
+ rid, err := strconv.Atoi(srid)
if err != nil {
return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs)
}
diff --git a/panel_routes.go b/panel_routes.go
index 4abf9b93..ba110f3a 100644
--- a/panel_routes.go
+++ b/panel_routes.go
@@ -559,6 +559,52 @@ func routePanelForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Reque
return panelSuccessRedirect("/panel/forums/edit/perms/"+strconv.Itoa(fid)+"-"+strconv.Itoa(gid), w, r, isJs)
}
+type AnalyticsTimeRange struct {
+ Quantity int
+ Unit string
+ Slices int
+ SliceWidth int
+ Range string
+}
+
+func panelAnalyticsTimeRange(rawTimeRange string) (timeRange AnalyticsTimeRange, err error) {
+ timeRange.Quantity = 6
+ timeRange.Unit = "hour"
+ timeRange.Slices = 12
+ timeRange.SliceWidth = 60 * 30
+ timeRange.Range = "six-hours"
+
+ switch rawTimeRange {
+ case "one-month":
+ timeRange.Quantity = 30
+ timeRange.Unit = "day"
+ timeRange.Slices = 30
+ timeRange.SliceWidth = 60 * 60 * 24
+ timeRange.Range = "one-month"
+ case "two-days": // Two days is experimental
+ timeRange.Quantity = 2
+ timeRange.Unit = "day"
+ timeRange.Slices = 24
+ timeRange.SliceWidth = 60 * 60 * 2
+ timeRange.Range = "two-days"
+ case "one-day":
+ timeRange.Quantity = 1
+ timeRange.Unit = "day"
+ timeRange.Slices = 24
+ timeRange.SliceWidth = 60 * 60
+ timeRange.Range = "one-day"
+ case "twelve-hours":
+ timeRange.Quantity = 12
+ timeRange.Slices = 24
+ timeRange.Range = "twelve-hours"
+ case "six-hours", "":
+ timeRange.Range = "six-hours"
+ default:
+ return timeRange, errors.New("Unknown time range")
+ }
+ return timeRange, nil
+}
+
func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
@@ -567,6 +613,225 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
+ timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
+ if err != nil {
+ return common.LocalError(err.Error(), w, r, user)
+ }
+
+ var revLabelList []int64
+ var labelList []int64
+ var viewMap = make(map[int64]int64)
+ var currentTime = time.Now().Unix()
+
+ for i := 1; i <= timeRange.Slices; i++ {
+ var label = currentTime - int64(i*timeRange.SliceWidth)
+ revLabelList = append(revLabelList, label)
+ viewMap[label] = 0
+ }
+ for _, value := range revLabelList {
+ labelList = append(labelList, value)
+ }
+
+ var viewList []int64
+ log.Print("in routePanelAnalyticsViews")
+
+ acc := qgen.Builder.Accumulator()
+ rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
+ if err != nil && err != ErrNoRows {
+ return common.InternalError(err, w, r)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var count int64
+ var createdAt time.Time
+ err := rows.Scan(&count, &createdAt)
+ if err != nil {
+ return common.InternalError(err, w, r)
+ }
+ log.Print("count: ", count)
+ log.Print("createdAt: ", createdAt)
+
+ var unixCreatedAt = createdAt.Unix()
+ log.Print("unixCreatedAt: ", unixCreatedAt)
+ for _, value := range labelList {
+ if unixCreatedAt > value {
+ viewMap[value] += count
+ break
+ }
+ }
+ }
+ err = rows.Err()
+ if err != nil {
+ return common.InternalError(err, w, r)
+ }
+
+ var viewItems []common.PanelAnalyticsItem
+ for _, value := range revLabelList {
+ viewList = append(viewList, viewMap[value])
+ viewItems = append(viewItems, common.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
+ }
+ graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
+ log.Printf("graph: %+v\n", graph)
+
+ pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", graph, viewItems, timeRange.Range}
+ return panelRenderTemplate("panel_analytics_views", w, r, user, &pi)
+}
+
+func routePanelAnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.User, route string) common.RouteError {
+ headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
+ if ferr != nil {
+ return ferr
+ }
+ headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
+ headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
+
+ timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
+ if err != nil {
+ return common.LocalError(err.Error(), w, r, user)
+ }
+
+ var revLabelList []int64
+ var labelList []int64
+ var viewMap = make(map[int64]int64)
+ var currentTime = time.Now().Unix()
+
+ for i := 1; i <= timeRange.Slices; i++ {
+ var label = currentTime - int64(i*timeRange.SliceWidth)
+ revLabelList = append(revLabelList, label)
+ viewMap[label] = 0
+ }
+ for _, value := range revLabelList {
+ labelList = append(labelList, value)
+ }
+
+ var viewList []int64
+ log.Print("in routePanelAnalyticsRouteViews")
+
+ acc := qgen.Builder.Accumulator()
+ // TODO: Validate the route is valid
+ rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(route)
+ if err != nil && err != ErrNoRows {
+ return common.InternalError(err, w, r)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var count int64
+ var createdAt time.Time
+ err := rows.Scan(&count, &createdAt)
+ if err != nil {
+ return common.InternalError(err, w, r)
+ }
+ log.Print("count: ", count)
+ log.Print("createdAt: ", createdAt)
+
+ var unixCreatedAt = createdAt.Unix()
+ log.Print("unixCreatedAt: ", unixCreatedAt)
+ for _, value := range labelList {
+ if unixCreatedAt > value {
+ viewMap[value] += count
+ break
+ }
+ }
+ }
+ err = rows.Err()
+ if err != nil {
+ return common.InternalError(err, w, r)
+ }
+
+ var viewItems []common.PanelAnalyticsItem
+ for _, value := range revLabelList {
+ viewList = append(viewList, viewMap[value])
+ viewItems = append(viewItems, common.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
+ }
+ graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
+ log.Printf("graph: %+v\n", graph)
+
+ pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(route), graph, viewItems, timeRange.Range}
+ return panelRenderTemplate("panel_analytics_route_views", w, r, user, &pi)
+}
+
+func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.User, agent string) common.RouteError {
+ headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
+ if ferr != nil {
+ return ferr
+ }
+ headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
+ headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
+
+ timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
+ if err != nil {
+ return common.LocalError(err.Error(), w, r, user)
+ }
+
+ var revLabelList []int64
+ var labelList []int64
+ var viewMap = make(map[int64]int64)
+ var currentTime = time.Now().Unix()
+
+ for i := 1; i <= timeRange.Slices; i++ {
+ var label = currentTime - int64(i*timeRange.SliceWidth)
+ revLabelList = append(revLabelList, label)
+ viewMap[label] = 0
+ }
+ for _, value := range revLabelList {
+ labelList = append(labelList, value)
+ }
+
+ var viewList []int64
+ log.Print("in routePanelAnalyticsAgentViews")
+
+ acc := qgen.Builder.Accumulator()
+ // TODO: Verify the agent is valid
+ rows, err := acc.Select("viewchunks_agents").Columns("count, createdAt").Where("browser = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(agent)
+ if err != nil && err != ErrNoRows {
+ return common.InternalError(err, w, r)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var count int64
+ var createdAt time.Time
+ err := rows.Scan(&count, &createdAt)
+ if err != nil {
+ return common.InternalError(err, w, r)
+ }
+ log.Print("count: ", count)
+ log.Print("createdAt: ", createdAt)
+
+ var unixCreatedAt = createdAt.Unix()
+ log.Print("unixCreatedAt: ", unixCreatedAt)
+ for _, value := range labelList {
+ if unixCreatedAt > value {
+ viewMap[value] += count
+ break
+ }
+ }
+ }
+ err = rows.Err()
+ if err != nil {
+ return common.InternalError(err, w, r)
+ }
+
+ for _, value := range revLabelList {
+ viewList = append(viewList, viewMap[value])
+ }
+ graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
+ log.Printf("graph: %+v\n", graph)
+
+ pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(agent), graph, timeRange.Range}
+ return panelRenderTemplate("panel_analytics_agent_views", w, r, user, &pi)
+}
+
+func routePanelAnalyticsPosts(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
+ headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
+ if ferr != nil {
+ return ferr
+ }
+ headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
+ headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
+
var timeQuantity = 6
var timeUnit = "hour"
var timeSlices = 12
@@ -617,10 +882,10 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
}
var viewList []int64
- log.Print("in routePanelAnalyticsViews")
+ log.Print("in routePanelAnalyticsPosts")
acc := qgen.Builder.Accumulator()
- rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeQuantity, timeUnit).Query()
+ rows, err := acc.Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeQuantity, timeUnit).Query()
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
@@ -659,220 +924,7 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
log.Printf("graph: %+v\n", graph)
pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", graph, viewItems, timeRange}
- if common.PreRenderHooks["pre_render_panel_analytics"] != nil {
- if common.RunPreRenderHook("pre_render_panel_analytics", w, r, &user, &pi) {
- return nil
- }
- }
- err = common.Templates.ExecuteTemplate(w, "panel-analytics-views.html", pi)
- if err != nil {
- return common.InternalError(err, w, r)
- }
- return nil
-}
-
-func routePanelAnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.User, route string) common.RouteError {
- headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
- if ferr != nil {
- return ferr
- }
- headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
- headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
-
- var timeQuantity = 6
- var timeUnit = "hour"
- var timeSlices = 12
- var sliceWidth = 60 * 30
- var timeRange = "six-hours"
-
- switch r.FormValue("timeRange") {
- case "one-month":
- timeQuantity = 30
- timeUnit = "day"
- timeSlices = 30
- sliceWidth = 60 * 60 * 24
- timeRange = "one-month"
- case "two-days": // Two days is experimental
- timeQuantity = 2
- timeUnit = "day"
- timeSlices = 24
- sliceWidth = 60 * 60 * 2
- timeRange = "two-days"
- case "one-day":
- timeQuantity = 1
- timeUnit = "day"
- timeSlices = 24
- sliceWidth = 60 * 60
- timeRange = "one-day"
- case "twelve-hours":
- timeQuantity = 12
- timeSlices = 24
- timeRange = "twelve-hours"
- case "six-hours", "":
- timeRange = "six-hours"
- default:
- return common.LocalError("Unknown time range", w, r, user)
- }
-
- var revLabelList []int64
- var labelList []int64
- var viewMap = make(map[int64]int64)
- var currentTime = time.Now().Unix()
-
- for i := 1; i <= timeSlices; i++ {
- var label = currentTime - int64(i*sliceWidth)
- revLabelList = append(revLabelList, label)
- viewMap[label] = 0
- }
- for _, value := range revLabelList {
- labelList = append(labelList, value)
- }
-
- var viewList []int64
- log.Print("in routePanelAnalyticsRouteViews")
-
- acc := qgen.Builder.Accumulator()
- // TODO: Validate the route is valid
- rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", timeQuantity, timeUnit).Query(route)
- if err != nil && err != ErrNoRows {
- return common.InternalError(err, w, r)
- }
- defer rows.Close()
-
- for rows.Next() {
- var count int64
- var createdAt time.Time
- err := rows.Scan(&count, &createdAt)
- if err != nil {
- return common.InternalError(err, w, r)
- }
- log.Print("count: ", count)
- log.Print("createdAt: ", createdAt)
-
- var unixCreatedAt = createdAt.Unix()
- log.Print("unixCreatedAt: ", unixCreatedAt)
- for _, value := range labelList {
- if unixCreatedAt > value {
- viewMap[value] += count
- break
- }
- }
- }
- err = rows.Err()
- if err != nil {
- return common.InternalError(err, w, r)
- }
-
- for _, value := range revLabelList {
- viewList = append(viewList, viewMap[value])
- }
- graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
- log.Printf("graph: %+v\n", graph)
-
- pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(route), graph, timeRange}
- return panelRenderTemplate("panel_analytics_route_views", w, r, user, &pi)
-}
-
-func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.User, agent string) common.RouteError {
- headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
- if ferr != nil {
- return ferr
- }
- headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
- headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
-
- var timeQuantity = 6
- var timeUnit = "hour"
- var timeSlices = 12
- var sliceWidth = 60 * 30
- var timeRange = "six-hours"
-
- switch r.FormValue("timeRange") {
- case "one-month":
- timeQuantity = 30
- timeUnit = "day"
- timeSlices = 30
- sliceWidth = 60 * 60 * 24
- timeRange = "one-month"
- case "two-days": // Two days is experimental
- timeQuantity = 2
- timeUnit = "day"
- timeSlices = 24
- sliceWidth = 60 * 60 * 2
- timeRange = "two-days"
- case "one-day":
- timeQuantity = 1
- timeUnit = "day"
- timeSlices = 24
- sliceWidth = 60 * 60
- timeRange = "one-day"
- case "twelve-hours":
- timeQuantity = 12
- timeSlices = 24
- timeRange = "twelve-hours"
- case "six-hours", "":
- timeRange = "six-hours"
- default:
- return common.LocalError("Unknown time range", w, r, user)
- }
-
- var revLabelList []int64
- var labelList []int64
- var viewMap = make(map[int64]int64)
- var currentTime = time.Now().Unix()
-
- for i := 1; i <= timeSlices; i++ {
- var label = currentTime - int64(i*sliceWidth)
- revLabelList = append(revLabelList, label)
- viewMap[label] = 0
- }
- for _, value := range revLabelList {
- labelList = append(labelList, value)
- }
-
- var viewList []int64
- log.Print("in routePanelAnalyticsAgentViews")
-
- acc := qgen.Builder.Accumulator()
- // TODO: Verify the agent is valid
- rows, err := acc.Select("viewchunks_agents").Columns("count, createdAt").Where("browser = ?").DateCutoff("createdAt", timeQuantity, timeUnit).Query(agent)
- if err != nil && err != ErrNoRows {
- return common.InternalError(err, w, r)
- }
- defer rows.Close()
-
- for rows.Next() {
- var count int64
- var createdAt time.Time
- err := rows.Scan(&count, &createdAt)
- if err != nil {
- return common.InternalError(err, w, r)
- }
- log.Print("count: ", count)
- log.Print("createdAt: ", createdAt)
-
- var unixCreatedAt = createdAt.Unix()
- log.Print("unixCreatedAt: ", unixCreatedAt)
- for _, value := range labelList {
- if unixCreatedAt > value {
- viewMap[value] += count
- break
- }
- }
- }
- err = rows.Err()
- if err != nil {
- return common.InternalError(err, w, r)
- }
-
- for _, value := range revLabelList {
- viewList = append(viewList, viewMap[value])
- }
- graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
- log.Printf("graph: %+v\n", graph)
-
- pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(agent), graph, timeRange}
- return panelRenderTemplate("panel_analytics_agent_views", w, r, user, &pi)
+ return panelRenderTemplate("panel_analytics_posts", w, r, user, &pi)
}
func routePanelAnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
diff --git a/public/global.js b/public/global.js
index 90f061ec..77e07a3b 100644
--- a/public/global.js
+++ b/public/global.js
@@ -6,6 +6,9 @@ var conn;
var selectedTopics = [];
var attachItemCallback = function(){}
+// Topic move
+var forumToMoveTo = 0;
+
// TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts
function ajaxError(xhr,status,errstr) {
console.log("The AJAX request failed");
@@ -500,6 +503,20 @@ $(document).ready(function(){
$(".mod_floater").removeClass("auto_hide");
});
});
+
+ let bulkActionSender = function(action, selectedTopics) {
+ let url = "/topic/"+action+"/submit/?session=" + session;
+ $.ajax({
+ url: url,
+ type: "POST",
+ data: JSON.stringify(selectedTopics),
+ contentType: "application/json",
+ error: ajaxError,
+ success: function() {
+ window.location.reload();
+ }
+ });
+ };
$(".mod_floater_submit").click(function(event){
event.preventDefault();
let selectNode = this.form.querySelector(".mod_floater_options");
@@ -511,22 +528,24 @@ $(document).ready(function(){
switch(action) {
case "move":
console.log("move action");
+ let modTopicMover = $("#mod_topic_mover");
$("#mod_topic_mover").removeClass("auto_hide");
+ $("#mod_topic_mover .pane_row").click(function(){
+ modTopicMover.find(".pane_row").removeClass("pane_selected");
+ let fid = this.getAttribute("data-fid");
+ if (fid == null) {
+ return;
+ }
+ this.classList.add("pane_selected");
+ console.log("fid: " + fid);
+ let moverFid = document.getElementById("#mover_fid");
+ console.log("moverFid: ", moverFid);
+ moverFid.value = fid;
+ });
return;
}
- let url = "/topic/"+action+"/submit/";
- //console.log("JSON.stringify(selectedTopics) ", JSON.stringify(selectedTopics));
- $.ajax({
- url: url,
- type: "POST",
- data: JSON.stringify(selectedTopics),
- contentType: "application/json",
- error: ajaxError,
- success: function() {
- window.location.reload();
- }
- });
+ bulkActionSender(action,selectedTopics);
});
});
diff --git a/query_gen/tables.go b/query_gen/tables.go
index 587a14f6..1d188dc4 100644
--- a/query_gen/tables.go
+++ b/query_gen/tables.go
@@ -386,6 +386,15 @@ func createTables(adapter qgen.Adapter) error {
)
*/
+ qgen.Install.CreateTable("postchunks", "", "",
+ []qgen.DBTableColumn{
+ qgen.DBTableColumn{"count", "int", 0, false, false, "0"},
+ qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""},
+ // TODO: Add a column for the parent topic / profile?
+ },
+ []qgen.DBTableKey{},
+ )
+
qgen.Install.CreateTable("sync", "", "",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"last_update", "datetime", 0, false, false, ""},
diff --git a/router_gen/main.go b/router_gen/main.go
index f0b64faa..e5c2f362 100644
--- a/router_gen/main.go
+++ b/router_gen/main.go
@@ -299,6 +299,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer())
+ log.Print("req.RemoteAddr: ", req.RemoteAddr)
}
if prefix == "/static" {
@@ -349,6 +350,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if common.Dev.DebugMode {
log.Print("Blank UA: ", req.UserAgent())
log.Print("Method: ", req.Method)
+
for key, value := range req.Header {
for _, vvalue := range value {
log.Print("Header '" + key + "': " + vvalue + "!!")
@@ -358,6 +360,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer())
+ log.Print("req.RemoteAddr: ", req.RemoteAddr)
}
default:
common.AgentViewCounter.Bump({{.AllAgentMap.unknown}})
@@ -373,6 +376,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer())
+ log.Print("req.RemoteAddr: ", req.RemoteAddr)
}
}
diff --git a/router_gen/route_impl.go b/router_gen/route_impl.go
index d89f5d28..64281f7b 100644
--- a/router_gen/route_impl.go
+++ b/router_gen/route_impl.go
@@ -95,10 +95,25 @@ func AnonAction(fname string, path string, args ...string) *RouteImpl {
return route(fname, path, args...).Before("ParseForm")
}
-func UploadAction(fname string, path string, args ...string) *RouteImpl {
+// Make this it's own type to force the user to manipulate methods on it to set parameters
+type uploadAction struct {
+ Route *RouteImpl
+}
+
+func UploadAction(fname string, path string, args ...string) *uploadAction {
route := route(fname, path, args...)
if !route.hasBefore("SuperModOnly", "AdminOnly") {
route.Before("MemberOnly")
}
- return route
+ return &uploadAction{route}
+}
+
+func (action *uploadAction) MaxSizeVar(varName string) *RouteImpl {
+ action.Route.LitBefore(`err = common.HandleUploadRoute(w,req,user,` + varName + `)
+ if err != nil {
+ router.handleError(err,w,req,user)
+ return
+ }`)
+ action.Route.Before("NoUploadSessionMismatch")
+ return action.Route
}
diff --git a/router_gen/routes.go b/router_gen/routes.go
index 222a1e68..30973509 100644
--- a/router_gen/routes.go
+++ b/router_gen/routes.go
@@ -26,6 +26,8 @@ func routes() {
buildPanelRoutes()
buildUserRoutes()
+ buildTopicRoutes()
+ buildReplyRoutes()
}
// TODO: Test the email token route
@@ -36,7 +38,7 @@ func buildUserRoutes() {
MemberView("routeAccountEditCritical", "/user/edit/critical/"),
Action("routeAccountEditCriticalSubmit", "/user/edit/critical/submit/"), // TODO: Full test this
MemberView("routeAccountEditAvatar", "/user/edit/avatar/"),
- UploadAction("routeAccountEditAvatarSubmit", "/user/edit/avatar/submit/"),
+ UploadAction("routeAccountEditAvatarSubmit", "/user/edit/avatar/submit/").MaxSizeVar("common.Config.MaxRequestSize"),
MemberView("routeAccountEditUsername", "/user/edit/username/"),
Action("routeAccountEditUsernameSubmit", "/user/edit/username/submit/"), // TODO: Full test this
MemberView("routeAccountEditEmail", "/user/edit/email/"),
@@ -55,6 +57,37 @@ func buildUserRoutes() {
addRouteGroup(userGroup)
}
+func buildTopicRoutes() {
+ topicGroup := newRouteGroup("/topic/")
+ topicGroup.Routes(
+ View("routeTopicID", "/topic/", "extraData"),
+ Action("routeTopicCreateSubmit", "/topic/create/submit/"),
+ Action("routeEditTopicSubmit", "/topic/edit/submit/", "extraData"),
+ Action("routeDeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"),
+ Action("routeStickTopicSubmit", "/topic/stick/submit/", "extraData"),
+ Action("routeUnstickTopicSubmit", "/topic/unstick/submit/", "extraData"),
+ Action("routeLockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"),
+ Action("routeUnlockTopicSubmit", "/topic/unlock/submit/", "extraData"),
+ Action("routeMoveTopicSubmit", "/topic/move/submit/", "extraData"),
+ Action("routeLikeTopicSubmit", "/topic/like/submit/", "extraData"),
+ )
+ addRouteGroup(topicGroup)
+}
+
+func buildReplyRoutes() {
+ //router.HandleFunc("/reply/edit/", routeReplyEdit) // No js fallback
+ //router.HandleFunc("/reply/delete/", routeReplyDelete) // No js confirmation page? We could have a confirmation modal for the JS case
+ replyGroup := newRouteGroup("/reply/")
+ replyGroup.Routes(
+ // TODO: Reduce this to 1MB for attachments for each file?
+ UploadAction("routeCreateReplySubmit", "/reply/create/").MaxSizeVar("common.Config.MaxRequestSize"), // TODO: Rename the route so it's /reply/create/submit/
+ Action("routeReplyEditSubmit", "/reply/edit/submit/", "extraData"),
+ Action("routeReplyDeleteSubmit", "/reply/delete/submit/", "extraData"),
+ Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData"),
+ )
+ addRouteGroup(replyGroup)
+}
+
func buildPanelRoutes() {
panelGroup := newRouteGroup("/panel/").Before("SuperModOnly")
panelGroup.Routes(
@@ -96,6 +129,7 @@ func buildPanelRoutes() {
View("routePanelAnalyticsAgents", "/panel/analytics/agents/"),
View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"),
View("routePanelAnalyticsAgentViews", "/panel/analytics/agent/", "extraData"),
+ View("routePanelAnalyticsPosts", "/panel/analytics/posts/").Before("ParseForm"),
View("routePanelGroups", "/panel/groups/"),
View("routePanelGroupsEdit", "/panel/groups/edit/", "extraData"),
diff --git a/routes.go b/routes.go
index a2c5bb1a..9586ccb9 100644
--- a/routes.go
+++ b/routes.go
@@ -438,7 +438,7 @@ func routeForums(w http.ResponseWriter, r *http.Request, user common.User) commo
return nil
}
-func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
+func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User, urlBit string) common.RouteError {
var err error
var page, offset int
var replyList []common.ReplyUser
@@ -447,7 +447,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) comm
// SEO URLs...
// TODO: Make a shared function for this
- halves := strings.Split(r.URL.Path[len("/topic/"):], ".")
+ halves := strings.Split(urlBit, ".")
if len(halves) < 2 {
halves = append(halves, halves[0])
}
diff --git a/schema/mssql/query_postchunks.sql b/schema/mssql/query_postchunks.sql
new file mode 100644
index 00000000..68bf37ff
--- /dev/null
+++ b/schema/mssql/query_postchunks.sql
@@ -0,0 +1,4 @@
+CREATE TABLE [postchunks] (
+ [count] int DEFAULT 0 not null,
+ [createdAt] datetime not null
+);
\ No newline at end of file
diff --git a/schema/mysql/query_postchunks.sql b/schema/mysql/query_postchunks.sql
new file mode 100644
index 00000000..b0d3b4be
--- /dev/null
+++ b/schema/mysql/query_postchunks.sql
@@ -0,0 +1,4 @@
+CREATE TABLE `postchunks` (
+ `count` int DEFAULT 0 not null,
+ `createdAt` datetime not null
+);
\ No newline at end of file
diff --git a/schema/pgsql/query_postchunks.sql b/schema/pgsql/query_postchunks.sql
new file mode 100644
index 00000000..b6ee2dd3
--- /dev/null
+++ b/schema/pgsql/query_postchunks.sql
@@ -0,0 +1,4 @@
+CREATE TABLE `postchunks` (
+ `count` int DEFAULT 0 not null,
+ `createdAt` timestamp not null
+);
\ No newline at end of file
diff --git a/template_list.go b/template_list.go
index 2f5e1c5c..8059405e 100644
--- a/template_list.go
+++ b/template_list.go
@@ -87,165 +87,176 @@ var header_21 = []byte(``)
var topic_0 = []byte(`
+var topic_1 = []byte(`?session=`)
+var topic_2 = []byte(`' method="post">
`)
-var topic_2 = []byte(`
+var topic_3 = []byte(`
`)
-var topic_7 = []byte(`
+var topic_6 = []byte(`?page=`)
+var topic_7 = []byte(`"><`)
+var topic_8 = []byte(`
`)
-var topic_12 = []byte(`
+var topic_13 = []byte(`
+var topic_22 = []byte(`" style="background-image: url(`)
+var topic_23 = []byte(`), url(/static/`)
+var topic_24 = []byte(`/post-avatar-bg.jpg);background-position: 0px `)
+var topic_25 = []byte(`-1`)
+var topic_26 = []byte(`0px;background-repeat:no-repeat, repeat-y;">
`)
-var topic_26 = []byte(`
+var topic_27 = []byte(`
+var topic_28 = []byte(`
`)
-var topic_29 = []byte(`
+var topic_29 = []byte(`" class="username real_username" rel="author">`)
+var topic_30 = []byte(`
`)
-var topic_30 = []byte(`
+var topic_31 = []byte(`
`)
-var topic_37 = []byte(``)
-var topic_39 = []byte(``)
-var topic_41 = []byte(``)
-var topic_43 = []byte(``)
-var topic_45 = []byte(``)
-var topic_47 = []byte(``)
-var topic_49 = []byte(``)
-var topic_52 = []byte(`
+var topic_37 = []byte(` style="background-color:#D6FFD6;"`)
+var topic_38 = []byte(`>`)
+var topic_39 = []byte(``)
+var topic_41 = []byte(``)
+var topic_44 = []byte(``)
+var topic_47 = []byte(``)
+var topic_50 = []byte(``)
+var topic_53 = []byte(``)
+var topic_56 = []byte(``)
+var topic_59 = []byte(`
+var topic_60 = []byte(`?session=`)
+var topic_61 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag this topic" aria-label="Flag this topic" rel="nofollow">
`)
-var topic_55 = []byte(``)
-var topic_56 = []byte(``)
-var topic_57 = []byte(``)
-var topic_58 = []byte(``)
-var topic_59 = []byte(``)
-var topic_60 = []byte(``)
-var topic_61 = []byte(`
+var topic_62 = []byte(``)
+var topic_63 = []byte(``)
+var topic_64 = []byte(``)
+var topic_65 = []byte(``)
+var topic_66 = []byte(``)
+var topic_67 = []byte(``)
+var topic_68 = []byte(`
`)
-var topic_62 = []byte(`
+var topic_69 = []byte(`
`)
-var topic_63 = []byte(`
+var topic_70 = []byte(`
`)
-var topic_64 = []byte(`
+var topic_71 = []byte(`
`)
-var topic_65 = []byte(`
+var topic_72 = []byte(`
+var topic_73 = []byte(`" style="background-image: url(`)
+var topic_74 = []byte(`), url(/static/`)
+var topic_75 = []byte(`/post-avatar-bg.jpg);background-position: 0px `)
+var topic_76 = []byte(`-1`)
+var topic_77 = []byte(`0px;background-repeat:no-repeat, repeat-y;">
`)
-var topic_71 = []byte(`
+var topic_78 = []byte(`
`)
-var topic_72 = []byte(`
+var topic_79 = []byte(`
`)
-var topic_74 = []byte(`
+var topic_80 = []byte(`" class="username real_username" rel="author">`)
+var topic_81 = []byte(`
`)
-var topic_75 = []byte(``)
-var topic_79 = []byte(``)
-var topic_81 = []byte(``)
-var topic_83 = []byte(``)
-var topic_85 = []byte(`
+var topic_82 = []byte(``)
+var topic_87 = []byte(``)
+var topic_90 = []byte(``)
+var topic_93 = []byte(``)
+var topic_95 = []byte(`
+var topic_96 = []byte(`?session=`)
+var topic_97 = []byte(`&type=reply" class="mod_button report_item" title="Flag this reply" aria-label="Flag this reply" rel="nofollow">
`)
-var topic_88 = []byte(``)
-var topic_89 = []byte(``)
-var topic_90 = []byte(``)
-var topic_91 = []byte(``)
-var topic_92 = []byte(``)
-var topic_93 = []byte(``)
-var topic_94 = []byte(`
+var topic_98 = []byte(``)
+var topic_99 = []byte(``)
+var topic_100 = []byte(``)
+var topic_101 = []byte(``)
+var topic_102 = []byte(``)
+var topic_103 = []byte(``)
+var topic_104 = []byte(`
`)
-var topic_95 = []byte(`
+var topic_105 = []byte(`
`)
-var topic_96 = []byte(`
+var topic_106 = []byte(`
`)
-var topic_alt_28 = []byte(`
+var topic_alt_29 = []byte(`
+var topic_alt_30 = []byte(`
`)
-var topic_alt_30 = []byte(`
`)
-var topic_alt_32 = []byte(`
`)
-var topic_alt_34 = []byte(`
`)
-var topic_alt_36 = []byte(`
`)
-var topic_alt_38 = []byte(`
`)
-var topic_alt_40 = []byte(`
`)
-var topic_alt_42 = []byte(`
`)
-var topic_alt_44 = []byte(`
`)
-var topic_alt_46 = []byte(`
+var topic_alt_31 = []byte(`
`)
+var topic_alt_34 = []byte(`
`)
+var topic_alt_36 = []byte(`
`)
+var topic_alt_39 = []byte(`
`)
+var topic_alt_42 = []byte(`
`)
+var topic_alt_45 = []byte(`
`)
+var topic_alt_48 = []byte(`
`)
+var topic_alt_51 = []byte(`
`)
+var topic_alt_53 = []byte(`
+var topic_alt_54 = []byte(`?session=`)
+var topic_alt_55 = []byte(`&type=topic" class="action_button report_item" aria-label="Report this post" data-action="report">
`)
-var topic_alt_49 = []byte(`
+var topic_alt_56 = []byte(`
`)
-var topic_alt_60 = []byte(`
+var topic_alt_67 = []byte(`
+var topic_alt_68 = []byte(`action_item`)
+var topic_alt_69 = []byte(`">
+var topic_alt_70 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">
`)
-var topic_alt_65 = []byte(`
+var topic_alt_71 = []byte(`" class="the_name" rel="author">`)
+var topic_alt_72 = []byte(`
`)
-var topic_alt_66 = []byte(``)
-var topic_alt_67 = []byte(`
`)
-var topic_alt_68 = []byte(`Level `)
-var topic_alt_69 = []byte(`
`)
-var topic_alt_70 = []byte(`
+var topic_alt_73 = []byte(``)
+var topic_alt_74 = []byte(`
`)
+var topic_alt_75 = []byte(`Level `)
+var topic_alt_76 = []byte(`
`)
+var topic_alt_77 = []byte(`
+var topic_alt_78 = []byte(`style="margin-left: 0px;"`)
+var topic_alt_79 = []byte(`>
`)
-var topic_alt_73 = []byte(`
+var topic_alt_80 = []byte(`
`)
-var topic_alt_74 = []byte(`
+var topic_alt_81 = []byte(`
`)
-var topic_alt_75 = []byte(`
+var topic_alt_82 = []byte(`
`)
-var topic_alt_76 = []byte(`
+var topic_alt_83 = []byte(`
`)
-var topic_alt_77 = []byte(`
+var topic_alt_84 = []byte(`
`)
-var topic_alt_101 = []byte(`
+var topic_alt_111 = []byte(`
`)
-var topic_alt_102 = []byte(`
+var topic_alt_112 = []byte(`
+var topic_alt_113 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">
`)
-var topic_alt_105 = []byte(`
+var topic_alt_114 = []byte(`" class="the_name" rel="author">`)
+var topic_alt_115 = []byte(`
`)
-var topic_alt_106 = []byte(`
`)
-var topic_alt_107 = []byte(`
`)
-var topic_alt_108 = []byte(`
Level `)
-var topic_alt_109 = []byte(`
`)
-var topic_alt_110 = []byte(`
+var topic_alt_116 = []byte(`
`)
+var topic_alt_117 = []byte(`
`)
+var topic_alt_118 = []byte(`
Level `)
+var topic_alt_119 = []byte(`
`)
+var topic_alt_120 = []byte(`
`)
-var topic_alt_114 = []byte(`
+var topic_alt_125 = []byte(`
@@ -821,18 +843,20 @@ var topics_8 = []byte(`
`)
var topics_9 = []byte(`