Updated the codebeat and codeclimate config files.

Renamed the pre_render_view_forum hook to pre_render_forum.
Renamed the pre_render_ips hook to pre_render_ip_search.
Renamed routeTopicCreate to CreateTopic and moved it into /routes/topic.go
Moved three routes into /routes/user.go
Renamed routeIps to IPSearch and moved it into /routes/moderate.go
Moved the IP Search logic out of the route and into the DefaultIPSearcher.
Added InQ() to the select builders and replaced three hand-rolled queries with it.
De-duplicated some pagination logic.
This commit is contained in:
Azareal 2018-01-21 11:17:43 +00:00
parent 2997135e80
commit 8252c481df
25 changed files with 354 additions and 367 deletions

View File

@ -1,3 +1,4 @@
/public/chartist/**
/public/trumbowyg/**
/public/jquery-emojiarea/**
/public/font-awesome-4.7.0/**
@ -17,3 +18,4 @@ template_profile.go
gen_mysql.go
gen_mssql.go
gen_pgsql.go
gen_router.go

17
.codeclimate.yml Normal file
View File

@ -0,0 +1,17 @@
exclude_patterns:
- "gen_*"
- "schema/*"
- "public/chartist/*"
- "public/trumbowyg/*"
- "public/jquery-emojiarea/*"
- "public/font-awesome-4.7.0/*"
- "public/jquery-3.1.1.min.js"
- "public/EQCSS.min.js"
- "public/EQCSS.js"
- "template_list.go"
- "template_forum.go"
- "template_forums.go"
- "template_topic.go"
- "template_topic_alt.go"
- "template_topics.go"
- "template_profile.go"

View File

@ -75,7 +75,7 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User
"pre_render": nil,
"pre_render_forum_list": nil,
"pre_render_view_forum": nil,
"pre_render_forum": nil,
"pre_render_topic_list": nil,
"pre_render_view_topic": nil,
"pre_render_profile": nil,
@ -90,7 +90,7 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User
"pre_render_login": nil,
"pre_render_register": nil,
"pre_render_ban": nil,
"pre_render_ips": nil,
"pre_render_ip_search": nil,
"pre_render_panel_dashboard": nil,
"pre_render_panel_forums": nil,

76
common/ip_search.go Normal file
View File

@ -0,0 +1,76 @@
package common
import (
"database/sql"
"../query_gen/lib"
)
var IPSearch IPSearcher
type IPSearcher interface {
Lookup(ip string) (uids []int, err error)
}
type DefaultIPSearcher struct {
searchUsers *sql.Stmt
searchReplies *sql.Stmt
searchTopics *sql.Stmt
}
// NewDefaultIPSearcher gives you a new instance of DefaultIPSearcher
func NewDefaultIPSearcher() (*DefaultIPSearcher, error) {
acc := qgen.Builder.Accumulator()
return &DefaultIPSearcher{
searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(),
searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(),
searchReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(),
}, acc.FirstError()
}
func (searcher *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) {
var uid int
var reqUserList = make(map[int]bool)
var runQuery = func(stmt *sql.Stmt) error {
rows, err := stmt.Query(ip)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&uid)
if err != nil {
return err
}
reqUserList[uid] = true
}
return rows.Err()
}
err = runQuery(searcher.searchUsers)
if err != nil {
return uids, err
}
err = runQuery(searcher.searchTopics)
if err != nil {
return uids, err
}
err = runQuery(searcher.searchReplies)
if err != nil {
return uids, err
}
// Convert the user ID map to a slice, then bulk load the users
uids = make([]int, len(reqUserList))
var i int
for userID := range reqUserList {
uids[i] = userID
i++
}
return uids, nil
}

View File

@ -64,9 +64,6 @@ type Stmts struct {
todaysTopicCount *sql.Stmt
todaysReportCount *sql.Stmt
todaysNewUserCount *sql.Stmt
findUsersByIPUsers *sql.Stmt
findUsersByIPTopics *sql.Stmt
findUsersByIPReplies *sql.Stmt
Mocks bool
}

View File

@ -66,9 +66,6 @@ type Stmts struct {
todaysTopicCount *sql.Stmt
todaysReportCount *sql.Stmt
todaysNewUserCount *sql.Stmt
findUsersByIPUsers *sql.Stmt
findUsersByIPTopics *sql.Stmt
findUsersByIPReplies *sql.Stmt
Mocks bool
}

View File

@ -28,9 +28,6 @@ type Stmts struct {
todaysTopicCount *sql.Stmt
todaysReportCount *sql.Stmt
todaysNewUserCount *sql.Stmt
findUsersByIPUsers *sql.Stmt
findUsersByIPTopics *sql.Stmt
findUsersByIPReplies *sql.Stmt
Mocks bool
}

View File

@ -25,7 +25,7 @@ var RouteMap = map[string]interface{}{
"routeShowAttachment": routeShowAttachment,
"routeWebsockets": routeWebsockets,
"routeReportSubmit": routeReportSubmit,
"routeTopicCreate": routeTopicCreate,
"routes.CreateTopic": routes.CreateTopic,
"routeTopics": routeTopics,
"routePanelForums": routePanelForums,
"routePanelForumsCreateSubmit": routePanelForumsCreateSubmit,
@ -79,11 +79,11 @@ var RouteMap = map[string]interface{}{
"routeAccountEditEmail": routeAccountEditEmail,
"routeAccountEditEmailTokenSubmit": routeAccountEditEmailTokenSubmit,
"routeProfile": routeProfile,
"routeBanSubmit": routeBanSubmit,
"routeUnban": routeUnban,
"routeActivate": routeActivate,
"routeIps": routeIps,
"routeTopicCreateSubmit": routeTopicCreateSubmit,
"routes.BanUserSubmit": routes.BanUserSubmit,
"routes.UnbanUser": routes.UnbanUser,
"routes.ActivateUser": routes.ActivateUser,
"routes.IPSearch": routes.IPSearch,
"routeCreateTopicSubmit": routeCreateTopicSubmit,
"routes.EditTopicSubmit": routes.EditTopicSubmit,
"routes.DeleteTopicSubmit": routes.DeleteTopicSubmit,
"routes.StickTopicSubmit": routes.StickTopicSubmit,
@ -120,7 +120,7 @@ var routeMapEnum = map[string]int{
"routeShowAttachment": 6,
"routeWebsockets": 7,
"routeReportSubmit": 8,
"routeTopicCreate": 9,
"routes.CreateTopic": 9,
"routeTopics": 10,
"routePanelForums": 11,
"routePanelForumsCreateSubmit": 12,
@ -174,11 +174,11 @@ var routeMapEnum = map[string]int{
"routeAccountEditEmail": 60,
"routeAccountEditEmailTokenSubmit": 61,
"routeProfile": 62,
"routeBanSubmit": 63,
"routeUnban": 64,
"routeActivate": 65,
"routeIps": 66,
"routeTopicCreateSubmit": 67,
"routes.BanUserSubmit": 63,
"routes.UnbanUser": 64,
"routes.ActivateUser": 65,
"routes.IPSearch": 66,
"routeCreateTopicSubmit": 67,
"routes.EditTopicSubmit": 68,
"routes.DeleteTopicSubmit": 69,
"routes.StickTopicSubmit": 70,
@ -213,7 +213,7 @@ var reverseRouteMapEnum = map[int]string{
6: "routeShowAttachment",
7: "routeWebsockets",
8: "routeReportSubmit",
9: "routeTopicCreate",
9: "routes.CreateTopic",
10: "routeTopics",
11: "routePanelForums",
12: "routePanelForumsCreateSubmit",
@ -267,11 +267,11 @@ var reverseRouteMapEnum = map[int]string{
60: "routeAccountEditEmail",
61: "routeAccountEditEmailTokenSubmit",
62: "routeProfile",
63: "routeBanSubmit",
64: "routeUnban",
65: "routeActivate",
66: "routeIps",
67: "routeTopicCreateSubmit",
63: "routes.BanUserSubmit",
64: "routes.UnbanUser",
65: "routes.ActivateUser",
66: "routes.IPSearch",
67: "routeCreateTopicSubmit",
68: "routes.EditTopicSubmit",
69: "routes.DeleteTopicSubmit",
70: "routes.StickTopicSubmit",
@ -657,7 +657,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
common.RouteViewCounter.Bump(9)
err = routeTopicCreate(w,req,user,extraData)
err = routes.CreateTopic(w,req,user,extraData)
default:
common.RouteViewCounter.Bump(10)
err = routeTopics(w,req,user)
@ -1083,7 +1083,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
common.RouteViewCounter.Bump(63)
err = routeBanSubmit(w,req,user,extraData)
err = routes.BanUserSubmit(w,req,user,extraData)
case "/users/unban/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -1098,7 +1098,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
common.RouteViewCounter.Bump(64)
err = routeUnban(w,req,user,extraData)
err = routes.UnbanUser(w,req,user,extraData)
case "/users/activate/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -1113,7 +1113,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
common.RouteViewCounter.Bump(65)
err = routeActivate(w,req,user,extraData)
err = routes.ActivateUser(w,req,user,extraData)
case "/users/ips/":
err = common.MemberOnly(w,req,user)
if err != nil {
@ -1122,7 +1122,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
common.RouteViewCounter.Bump(66)
err = routeIps(w,req,user)
err = routes.IPSearch(w,req,user)
}
if err != nil {
router.handleError(err,w,req,user)
@ -1143,7 +1143,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
common.RouteViewCounter.Bump(67)
err = routeTopicCreateSubmit(w,req,user)
err = routeCreateTopicSubmit(w,req,user)
case "/topic/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {

View File

@ -80,6 +80,11 @@ func afterDBInit() (err error) {
if err != nil {
return err
}
common.IPSearch, err = common.NewDefaultIPSearcher()
if err != nil {
return err
}
common.GlobalViewCounter, err = common.NewGlobalViewCounter()
if err != nil {
return err

View File

@ -16,110 +16,7 @@ import (
"./common"
)
// Experimenting
/*func memberRenderTemplate(tmplName string, themeName string, w http.ResponseWriter, r *http.Request, user common.User, pi interface{}) common.RouteError {
if common.PreRenderHooks["pre_render_"+tmplName] != nil {
if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &user, pi) {
return nil
}
}
err := common.RunThemeTemplate(themeName, tmplName, pi, w)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}*/
// ? - Should we add a new permission or permission zone (like per-forum permissions) specifically for profile comment creation
// ? - Should we allow banned users to make reports? How should we handle report abuse?
// TODO: Add a permission to stop certain users from using custom avatars
// ? - Log username changes and put restrictions on this?
func routeTopicCreate(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
var fid int
var err error
if sfid != "" {
fid, err = strconv.Atoi(sfid)
if err != nil {
return common.LocalError("You didn't provide a valid number for the forum ID.", w, r, user)
}
}
if fid == 0 {
fid = common.Config.DefaultForum
}
headerVars, ferr := common.ForumUserCheck(w, r, &user, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateTopic {
return common.NoPermissions(w, r, user)
}
headerVars.Zone = "create_topic"
// Lock this to the forum being linked?
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)
var strictmode bool
if common.Vhooks["topic_create_pre_loop"] != nil {
common.RunVhook("topic_create_pre_loop", w, r, fid, &headerVars, &user, &strictmode)
}
// TODO: Re-add support for plugin_guilds
var forumList []common.Forum
var canSee []int
if user.IsSuperAdmin {
canSee, err = common.Forums.GetAllVisibleIDs()
if err != nil {
return common.InternalError(err, w, r)
}
} else {
group, err := common.Groups.Get(user.Group)
if err != nil {
// TODO: Refactor this
common.LocalError("Something weird happened behind the scenes", w, r, user)
log.Printf("Group #%d doesn't exist, but it's set on common.User #%d", user.Group, user.ID)
return nil
}
canSee = group.CanSee
}
// TODO: plugin_superadmin needs to be able to override this loop. Skip flag on topic_create_pre_loop?
for _, ffid := range canSee {
// TODO: Surely, there's a better way of doing this. I've added it in for now to support plugin_guilds, but we really need to clean this up
if strictmode && ffid != fid {
continue
}
// Do a bulk forum fetch, just in case it's the SqlForumStore?
forum := common.Forums.DirtyGet(ffid)
if forum.Name != "" && forum.Active {
fcopy := forum.Copy()
if common.Hooks["topic_create_frow_assign"] != nil {
// TODO: Add the skip feature to all the other row based hooks?
if common.RunHook("topic_create_frow_assign", &fcopy).(bool) {
continue
}
}
forumList = append(forumList, fcopy)
}
}
ctpage := common.CreateTopicPage{"Create Topic", user, headerVars, forumList, fid}
if common.PreRenderHooks["pre_render_create_topic"] != nil {
if common.RunPreRenderHook("pre_render_create_topic", w, r, &user, &ctpage) {
return nil
}
}
err = common.RunThemeTemplate(headerVars.Theme.Name, "create_topic", ctpage, w)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}
// POST functions. Authorised users only.
func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
func routeCreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Reduce this to 1MB for attachments for each file?
if r.ContentLength > int64(common.Config.MaxRequestSize) {
size, unit := common.ConvertByteUnit(float64(common.Config.MaxRequestSize))

View File

@ -75,56 +75,41 @@ func initMSSQL() (err error) {
}
// TODO: Is there a less noisy way of doing this for tests?
log.Print("Preparing get_activity_feed_by_watcher statement.")
log.Print("Preparing getActivityFeedByWatcher statement.")
getActivityFeedByWatcherStmt, err = db.Prepare("SELECT activity_stream_matches.asid, activity_stream.actor, activity_stream.targetUser, activity_stream.event, activity_stream.elementType, activity_stream.elementID FROM [activity_stream_matches] INNER JOIN [activity_stream] ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE [watcher] = ? ORDER BY activity_stream.asid ASC OFFSET 0 ROWS FETCH NEXT 8 ROWS ONLY")
if err != nil {
return err
}
log.Print("Preparing get_activity_count_by_watcher statement.")
log.Print("Preparing getActivityCountByWatcher statement.")
getActivityCountByWatcherStmt, err = db.Prepare("SELECT count(*) FROM [activity_stream_matches] INNER JOIN [activity_stream] ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE [watcher] = ?")
if err != nil {
return err
}
log.Print("Preparing todays_post_count statement.")
log.Print("Preparing todaysPostCount statement.")
todaysPostCountStmt, err = db.Prepare("select count(*) from replies where createdAt >= DATEADD(DAY, -1, GETUTCDATE())")
if err != nil {
return err
}
log.Print("Preparing todays_topic_count statement.")
log.Print("Preparing todaysTopicCount statement.")
todaysTopicCountStmt, err = db.Prepare("select count(*) from topics where createdAt >= DATEADD(DAY, -1, GETUTCDATE())")
if err != nil {
return err
}
log.Print("Preparing todays_report_count statement.")
log.Print("Preparing todaysReportCount statement.")
todaysReportCountStmt, err = db.Prepare("select count(*) from topics where createdAt >= DATEADD(DAY, -1, GETUTCDATE()) and parentID = 1")
if err != nil {
return err
}
log.Print("Preparing todays_newuser_count statement.")
log.Print("Preparing todaysNewUserCount statement.")
todaysNewUserCountStmt, err = db.Prepare("select count(*) from users where createdAt >= DATEADD(DAY, -1, GETUTCDATE())")
if err != nil {
return err
}
// ? - Why is this a custom query? Are we planning a union or something?
log.Print("Preparing find_users_by_ip_users statement.")
findUsersByIPUsersStmt, err = db.Prepare("select uid from users where last_ip = ?")
if err != nil {
return err
}
log.Print("Preparing find_users_by_ip_topics statement.")
findUsersByIPTopicsStmt, err = db.Prepare("select uid from users where uid in(select createdBy from topics where ipaddress = ?)")
if err != nil {
return err
}
log.Print("Preparing find_users_by_ip_replies statement.")
findUsersByIPRepliesStmt, err = db.Prepare("select uid from users where uid in(select createdBy from replies where ipaddress = ?)")
return err
return nil
}

View File

@ -69,55 +69,41 @@ func initMySQL() (err error) {
}
// TODO: Is there a less noisy way of doing this for tests?
log.Print("Preparing get_activity_feed_by_watcher statement.")
log.Print("Preparing getActivityFeedByWatcher statement.")
stmts.getActivityFeedByWatcher, err = db.Prepare("SELECT activity_stream_matches.asid, activity_stream.actor, activity_stream.targetUser, activity_stream.event, activity_stream.elementType, activity_stream.elementID FROM `activity_stream_matches` INNER JOIN `activity_stream` ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE `watcher` = ? ORDER BY activity_stream.asid ASC LIMIT 8")
if err != nil {
return err
}
log.Print("Preparing get_activity_count_by_watcher statement.")
log.Print("Preparing getActivityCountByWatcher statement.")
stmts.getActivityCountByWatcher, err = db.Prepare("SELECT count(*) FROM `activity_stream_matches` INNER JOIN `activity_stream` ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE `watcher` = ?")
if err != nil {
return err
}
log.Print("Preparing todays_post_count statement.")
log.Print("Preparing todaysPostCount statement.")
stmts.todaysPostCount, err = db.Prepare("select count(*) from replies where createdAt BETWEEN (utc_timestamp() - interval 1 day) and utc_timestamp()")
if err != nil {
return err
}
log.Print("Preparing todays_topic_count statement.")
log.Print("Preparing todaysTopicCount statement.")
stmts.todaysTopicCount, err = db.Prepare("select count(*) from topics where createdAt BETWEEN (utc_timestamp() - interval 1 day) and utc_timestamp()")
if err != nil {
return err
}
log.Print("Preparing todays_report_count statement.")
log.Print("Preparing todaysReportCount statement.")
stmts.todaysReportCount, err = db.Prepare("select count(*) from topics where createdAt BETWEEN (utc_timestamp() - interval 1 day) and utc_timestamp() and parentID = 1")
if err != nil {
return err
}
log.Print("Preparing todays_newuser_count statement.")
log.Print("Preparing todaysNewUserCount statement.")
stmts.todaysNewUserCount, err = db.Prepare("select count(*) from users where createdAt BETWEEN (utc_timestamp() - interval 1 day) and utc_timestamp()")
if err != nil {
return err
}
log.Print("Preparing find_users_by_ip_users statement.")
stmts.findUsersByIPUsers, err = db.Prepare("select uid from users where last_ip = ?")
if err != nil {
return err
}
log.Print("Preparing find_users_by_ip_topics statement.")
stmts.findUsersByIPTopics, err = db.Prepare("select uid from users where uid in(select createdBy from topics where ipaddress = ?)")
if err != nil {
return err
}
log.Print("Preparing find_users_by_ip_replies statement.")
stmts.findUsersByIPReplies, err = db.Prepare("select uid from users where uid in(select createdBy from replies where ipaddress = ?)")
return err
return nil
}

View File

@ -22,7 +22,7 @@ func initGuilds() (err error) {
common.Plugins["guilds"].AddHook("intercept_build_widgets", guilds.Widgets)
common.Plugins["guilds"].AddHook("trow_assign", guilds.TrowAssign)
common.Plugins["guilds"].AddHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
common.Plugins["guilds"].AddHook("pre_render_view_forum", guilds.PreRenderViewForum)
common.Plugins["guilds"].AddHook("pre_render_forum", guilds.PreRenderViewForum)
common.Plugins["guilds"].AddHook("simple_forum_check_pre_perms", guilds.ForumCheck)
common.Plugins["guilds"].AddHook("forum_check_pre_perms", guilds.ForumCheck)
// TODO: Auto-grant this perm to admins upon installation?
@ -61,7 +61,7 @@ func deactivateGuilds() {
common.Plugins["guilds"].RemoveHook("intercept_build_widgets", guilds.Widgets)
common.Plugins["guilds"].RemoveHook("trow_assign", guilds.TrowAssign)
common.Plugins["guilds"].RemoveHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
common.Plugins["guilds"].RemoveHook("pre_render_view_forum", guilds.PreRenderViewForum)
common.Plugins["guilds"].RemoveHook("pre_render_forum", guilds.PreRenderViewForum)
common.Plugins["guilds"].RemoveHook("simple_forum_check_pre_perms", guilds.ForumCheck)
common.Plugins["guilds"].RemoveHook("forum_check_pre_perms", guilds.ForumCheck)
common.DeregisterPluginPerm("CreateGuild")

View File

@ -47,6 +47,8 @@ type accSelectBuilder struct {
orderby string
limit string
dateCutoff *dateCutoff // We might want to do this in a slightly less hacky way
inChain *accSelectBuilder
inColumn string
build *Accumulator
}
@ -61,6 +63,12 @@ func (selectItem *accSelectBuilder) Where(where string) *accSelectBuilder {
return selectItem
}
func (selectItem *accSelectBuilder) InQ(column string, subBuilder *accSelectBuilder) *accSelectBuilder {
selectItem.inChain = subBuilder
selectItem.inColumn = column
return selectItem
}
func (selectItem *accSelectBuilder) DateCutoff(column string, quantity int, unit string) *accSelectBuilder {
selectItem.dateCutoff = &dateCutoff{column, quantity, unit}
return selectItem
@ -78,7 +86,7 @@ func (selectItem *accSelectBuilder) Limit(limit string) *accSelectBuilder {
func (selectItem *accSelectBuilder) Prepare() *sql.Stmt {
// TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL.
if selectItem.dateCutoff != nil {
if selectItem.dateCutoff != nil || selectItem.inChain != nil {
selectBuilder := selectItem.build.GetAdapter().Builder().Select().FromAcc(selectItem)
return selectItem.build.prepare(selectItem.build.GetAdapter().ComplexSelect(selectBuilder))
}

View File

@ -198,7 +198,7 @@ func (build *Accumulator) Update(table string) *accUpdateBuilder {
}
func (build *Accumulator) Select(table string) *accSelectBuilder {
return &accSelectBuilder{table, "", "", "", "", nil, build}
return &accSelectBuilder{table, "", "", "", "", nil, nil, "", build}
}
func (build *Accumulator) Insert(table string) *accInsertBuilder {

View File

@ -12,7 +12,7 @@ type prebuilder struct {
func (build *prebuilder) Select(nlist ...string) *selectPrebuilder {
name := optString(nlist, "_builder")
return &selectPrebuilder{name, "", "", "", "", "", nil, build.adapter}
return &selectPrebuilder{name, "", "", "", "", "", nil, nil, "", build.adapter}
}
func (build *prebuilder) Insert(nlist ...string) *insertPrebuilder {
@ -96,6 +96,8 @@ type selectPrebuilder struct {
orderby string
limit string
dateCutoff *dateCutoff
inChain *selectPrebuilder
inColumn string // for inChain
build Adapter
}
@ -115,6 +117,11 @@ func (selectItem *selectPrebuilder) Where(where string) *selectPrebuilder {
return selectItem
}
func (selectItem *selectPrebuilder) InQ(subBuilder *selectPrebuilder) *selectPrebuilder {
selectItem.inChain = subBuilder
return selectItem
}
func (selectItem *selectPrebuilder) Orderby(orderby string) *selectPrebuilder {
selectItem.orderby = orderby
return selectItem
@ -130,9 +137,14 @@ func (selectItem *selectPrebuilder) FromAcc(accBuilder *accSelectBuilder) *selec
selectItem.table = accBuilder.table
selectItem.columns = accBuilder.columns
selectItem.where = accBuilder.where
selectItem.dateCutoff = accBuilder.dateCutoff
selectItem.orderby = accBuilder.orderby
selectItem.limit = accBuilder.limit
selectItem.dateCutoff = accBuilder.dateCutoff
if accBuilder.inChain != nil {
selectItem.inChain = &selectPrebuilder{"__builder", accBuilder.inChain.table, accBuilder.inChain.columns, accBuilder.inChain.where, accBuilder.inChain.orderby, accBuilder.inChain.limit, accBuilder.inChain.dateCutoff, nil, "", selectItem.build}
selectItem.inColumn = accBuilder.inColumn
}
return selectItem
}

View File

@ -1114,9 +1114,6 @@ type Stmts struct {
todaysTopicCount *sql.Stmt
todaysReportCount *sql.Stmt
todaysNewUserCount *sql.Stmt
findUsersByIPUsers *sql.Stmt
findUsersByIPTopics *sql.Stmt
findUsersByIPReplies *sql.Stmt
Mocks bool
}

View File

@ -2,9 +2,11 @@
package qgen
//import "fmt"
import "strings"
import "strconv"
import "errors"
import (
"errors"
"strconv"
"strings"
)
func init() {
Registry = append(Registry,
@ -399,7 +401,7 @@ func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns str
return querystr, nil
}
func (adapter *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (string, error) {
func (adapter *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out string, err error) {
if preBuilder.name == "" {
return "", errors.New("You need a name for this statement")
}
@ -418,9 +420,19 @@ func (adapter *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (string
}
querystr = querystr[0 : len(querystr)-1]
whereStr, err := adapter.buildFlexiWhere(preBuilder.where, preBuilder.dateCutoff)
if err != nil {
return querystr, err
var whereStr string
// TODO: Let callers have a Where() and a InQ()
if preBuilder.inChain != nil {
whereStr, err = adapter.ComplexSelect(preBuilder.inChain)
if err != nil {
return querystr, err
}
whereStr = " WHERE `" + preBuilder.inColumn + "` IN(" + whereStr + ")"
} else {
whereStr, err = adapter.buildFlexiWhere(preBuilder.where, preBuilder.dateCutoff)
if err != nil {
return querystr, err
}
}
querystr += " FROM `" + preBuilder.table + "`" + whereStr + adapter.buildOrderby(preBuilder.orderby) + adapter.buildLimit(preBuilder.limit)
@ -664,9 +676,6 @@ type Stmts struct {
todaysTopicCount *sql.Stmt
todaysReportCount *sql.Stmt
todaysNewUserCount *sql.Stmt
findUsersByIPUsers *sql.Stmt
findUsersByIPTopics *sql.Stmt
findUsersByIPReplies *sql.Stmt
Mocks bool
}

View File

@ -375,9 +375,6 @@ type Stmts struct {
todaysTopicCount *sql.Stmt
todaysReportCount *sql.Stmt
todaysNewUserCount *sql.Stmt
findUsersByIPUsers *sql.Stmt
findUsersByIPTopics *sql.Stmt
findUsersByIPReplies *sql.Stmt
Mocks bool
}

View File

@ -20,7 +20,7 @@ func routes() {
topicGroup := newRouteGroup("/topics/",
View("routeTopics", "/topics/"),
MemberView("routeTopicCreate", "/topics/create/", "extraData"),
MemberView("routes.CreateTopic", "/topics/create/", "extraData"),
)
addRouteGroup(topicGroup)
@ -53,10 +53,10 @@ func buildUserRoutes() {
// TODO: Auto test and manual test these routes
userGroup = newRouteGroup("/users/")
userGroup.Routes(
Action("routeBanSubmit", "/users/ban/submit/", "extraData"),
Action("routeUnban", "/users/unban/", "extraData"),
Action("routeActivate", "/users/activate/", "extraData"),
MemberView("routeIps", "/users/ips/"), // TODO: .Perms("ViewIPs")?
Action("routes.BanUserSubmit", "/users/ban/submit/", "extraData"),
Action("routes.UnbanUser", "/users/unban/", "extraData"),
Action("routes.ActivateUser", "/users/activate/", "extraData"),
MemberView("routes.IPSearch", "/users/ips/"), // TODO: .Perms("ViewIPs")?
)
addRouteGroup(userGroup)
}
@ -65,7 +65,7 @@ func buildTopicRoutes() {
topicGroup := newRouteGroup("/topic/")
topicGroup.Routes(
View("routeTopicID", "/topic/", "extraData"),
Action("routeTopicCreateSubmit", "/topic/create/submit/"),
Action("routeCreateTopicSubmit", "/topic/create/submit/"),
Action("routes.EditTopicSubmit", "/topic/edit/submit/", "extraData"),
Action("routes.DeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"),
Action("routes.StickTopicSubmit", "/topic/stick/submit/", "extraData"),

View File

@ -291,18 +291,8 @@ func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid s
}
headerVars.Zone = "view_forum"
// Calculate the offset
var offset int
// TODO: Does forum.TopicCount take the deleted items into consideration for guests?
lastPage := (forum.TopicCount / common.Config.ItemsPerPage) + 1
if page > 1 {
offset = (common.Config.ItemsPerPage * page) - common.Config.ItemsPerPage
} else if page == -1 {
page = lastPage
offset = (common.Config.ItemsPerPage * page) - common.Config.ItemsPerPage
} else {
page = 1
}
// TODO: Does forum.TopicCount take the deleted items into consideration for guests? We don't have soft-delete yet, only hard-delete
offset, page, lastPage := common.PageOffset(forum.TopicCount, page, common.Config.ItemsPerPage)
// TODO: Move this to *Forum
rows, err := stmts.getForumTopicsOffset.Query(fid, offset, common.Config.ItemsPerPage)
@ -358,8 +348,8 @@ func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid s
}
pi := common.ForumPage{forum.Name, user, headerVars, topicList, forum, page, lastPage}
if common.PreRenderHooks["pre_render_view_forum"] != nil {
if common.RunPreRenderHook("pre_render_view_forum", w, r, &user, &pi) {
if common.PreRenderHooks["pre_render_forum"] != nil {
if common.RunPreRenderHook("pre_render_forum", w, r, &user, &pi) {
return nil
}
}
@ -430,10 +420,9 @@ func routeForums(w http.ResponseWriter, r *http.Request, user common.User) commo
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
page, _ = strconv.Atoi(r.FormValue("page"))
page, _ := strconv.Atoi(r.FormValue("page"))
// SEO URLs...
// TODO: Make a shared function for this
@ -496,16 +485,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User, urlB
}
// Calculate the offset
lastPage := (topic.PostCount / common.Config.ItemsPerPage) + 1
if page > 1 {
offset = (common.Config.ItemsPerPage * page) - common.Config.ItemsPerPage
} else if page == -1 {
page = lastPage
offset = (common.Config.ItemsPerPage * page) - common.Config.ItemsPerPage
} else {
page = 1
}
offset, page, lastPage := common.PageOffset(topic.PostCount, page, common.Config.ItemsPerPage)
tpage := common.TopicPage{topic.Title, user, headerVars, replyList, topic, page, lastPage}
// Get the replies..
@ -918,7 +898,6 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
if err != nil {
return common.PreErrorJS("Invalid asid", w, r)
}
_, err = stmts.deleteActivityStreamMatch.Exec(user.ID, asid)
if err != nil {
return common.InternalError(err, w, r)
@ -957,7 +936,6 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
}
msglist += res + ","
}
err = rows.Err()
if err != nil {
return common.InternalErrorJS(err, w, r)

44
routes/moderate.go Normal file
View File

@ -0,0 +1,44 @@
package routes
import (
"html"
"net/http"
"../common"
)
func IPSearch(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, ferr := common.UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
// TODO: How should we handle the permissions if we extend this into an alt detector of sorts?
if !user.Perms.ViewIPs {
return common.NoPermissions(w, r, user)
}
// TODO: Reject IP Addresses with illegal characters
var ip = html.EscapeString(r.FormValue("ip"))
uids, err := common.IPSearch.Lookup(ip)
if err != nil {
return common.InternalError(err, w, r)
}
// TODO: What if a user is deleted via the Control Panel? We'll cross that bridge when we come to it, although we might lean towards blanking the account and removing the related data rather than purging it
userList, err := common.Users.BulkGetMap(uids)
if err != nil {
return common.InternalError(err, w, r)
}
pi := common.IPSearchPage{common.GetTitlePhrase("ip-search"), user, headerVars, userList, ip}
if common.PreRenderHooks["pre_render_ip_search"] != nil {
if common.RunPreRenderHook("pre_render_ip_search", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "ip-search.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}

1
routes/panel/filler.txt Normal file
View File

@ -0,0 +1 @@
This file is here so that Git will include this folder in the repository.

View File

@ -12,6 +12,93 @@ import (
var successJSONBytes = []byte(`{"success":"1"}`)
// ? - Should we add a new permission or permission zone (like per-forum permissions) specifically for profile comment creation
// ? - Should we allow banned users to make reports? How should we handle report abuse?
// TODO: Add a permission to stop certain users from using custom avatars
// ? - Log username changes and put restrictions on this?
func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
var fid int
var err error
if sfid != "" {
fid, err = strconv.Atoi(sfid)
if err != nil {
return common.LocalError("You didn't provide a valid number for the forum ID.", w, r, user)
}
}
if fid == 0 {
fid = common.Config.DefaultForum
}
headerVars, ferr := common.ForumUserCheck(w, r, &user, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateTopic {
return common.NoPermissions(w, r, user)
}
headerVars.Zone = "create_topic"
// Lock this to the forum being linked?
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)
var strictmode bool
if common.Vhooks["topic_create_pre_loop"] != nil {
common.RunVhook("topic_create_pre_loop", w, r, fid, &headerVars, &user, &strictmode)
}
// TODO: Re-add support for plugin_guilds
var forumList []common.Forum
var canSee []int
if user.IsSuperAdmin {
canSee, err = common.Forums.GetAllVisibleIDs()
if err != nil {
return common.InternalError(err, w, r)
}
} else {
group, err := common.Groups.Get(user.Group)
if err != nil {
// TODO: Refactor this
common.LocalError("Something weird happened behind the scenes", w, r, user)
log.Printf("Group #%d doesn't exist, but it's set on common.User #%d", user.Group, user.ID)
return nil
}
canSee = group.CanSee
}
// TODO: plugin_superadmin needs to be able to override this loop. Skip flag on topic_create_pre_loop?
for _, ffid := range canSee {
// TODO: Surely, there's a better way of doing this. I've added it in for now to support plugin_guilds, but we really need to clean this up
if strictmode && ffid != fid {
continue
}
// Do a bulk forum fetch, just in case it's the SqlForumStore?
forum := common.Forums.DirtyGet(ffid)
if forum.Name != "" && forum.Active {
fcopy := forum.Copy()
if common.Hooks["topic_create_frow_assign"] != nil {
// TODO: Add the skip feature to all the other row based hooks?
if common.RunHook("topic_create_frow_assign", &fcopy).(bool) {
continue
}
}
forumList = append(forumList, fcopy)
}
}
ctpage := common.CreateTopicPage{"Create Topic", user, headerVars, forumList, fid}
if common.PreRenderHooks["pre_render_create_topic"] != nil {
if common.RunPreRenderHook("pre_render_create_topic", w, r, &user, &ctpage) {
return nil
}
}
err = common.RunThemeTemplate(headerVars.Theme.Name, "create_topic", ctpage, w)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}
// 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
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
@ -149,11 +236,7 @@ func StickTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User,
return common.InternalError(err, w, r)
}
err = common.ModLogs.Create("stick", tid, "topic", user.LastIP, user.ID)
if err != nil {
return common.InternalError(err, w, r)
}
err = topic.CreateActionReply("stick", user.LastIP, user)
err = addTopicAction("stick", topic, user)
if err != nil {
return common.InternalError(err, w, r)
}
@ -188,11 +271,7 @@ func UnstickTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User
return common.InternalError(err, w, r)
}
err = common.ModLogs.Create("unstick", tid, "topic", user.LastIP, user.ID)
if err != nil {
return common.InternalError(err, w, r)
}
err = topic.CreateActionReply("unstick", user.LastIP, user)
err = addTopicAction("unstick", topic, user)
if err != nil {
return common.InternalError(err, w, r)
}
@ -247,11 +326,7 @@ func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) c
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = common.ModLogs.Create("lock", tid, "topic", user.LastIP, user.ID)
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = topic.CreateActionReply("lock", user.LastIP, user)
err = addTopicAction("lock", topic, user)
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
@ -290,11 +365,7 @@ func UnlockTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User,
return common.InternalError(err, w, r)
}
err = common.ModLogs.Create("unlock", tid, "topic", user.LastIP, user.ID)
if err != nil {
return common.InternalError(err, w, r)
}
err = topic.CreateActionReply("unlock", user.LastIP, user)
err = addTopicAction("unlock", topic, user)
if err != nil {
return common.InternalError(err, w, r)
}
@ -354,11 +425,7 @@ func MoveTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, s
}
// TODO: Log more data so we can list the destination forum in the action post?
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)
err = addTopicAction("move", topic, user)
if err != nil {
return common.InternalErrorJS(err, w, r)
}
@ -369,3 +436,11 @@ func MoveTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, s
}
return nil
}
func addTopicAction(action string, topic *common.Topic, user common.User) error {
err := common.ModLogs.Create(action, topic.ID, "topic", user.LastIP, user.ID)
if err != nil {
return err
}
return topic.CreateActionReply(action, user.LastIP, user)
}

View File

@ -1,108 +1,15 @@
package main
package routes
import (
"database/sql"
"net/http"
"strconv"
"time"
"./common"
"../common"
)
func routeIps(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, ferr := common.UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ViewIPs {
return common.NoPermissions(w, r, user)
}
var ip = r.FormValue("ip")
var uid int
var reqUserList = make(map[int]bool)
rows, err := stmts.findUsersByIPUsers.Query(ip)
if err != nil {
return common.InternalError(err, w, r)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&uid)
if err != nil {
return common.InternalError(err, w, r)
}
reqUserList[uid] = true
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
}
rows2, err := stmts.findUsersByIPTopics.Query(ip)
if err != nil {
return common.InternalError(err, w, r)
}
defer rows2.Close()
for rows2.Next() {
err := rows2.Scan(&uid)
if err != nil {
return common.InternalError(err, w, r)
}
reqUserList[uid] = true
}
err = rows2.Err()
if err != nil {
return common.InternalError(err, w, r)
}
rows3, err := stmts.findUsersByIPReplies.Query(ip)
if err != nil {
return common.InternalError(err, w, r)
}
defer rows3.Close()
for rows3.Next() {
err := rows3.Scan(&uid)
if err != nil {
return common.InternalError(err, w, r)
}
reqUserList[uid] = true
}
err = rows3.Err()
if err != nil {
return common.InternalError(err, w, r)
}
// Convert the user ID map to a slice, then bulk load the users
var idSlice = make([]int, len(reqUserList))
var i int
for userID := range reqUserList {
idSlice[i] = userID
i++
}
// TODO: What if a user is deleted via the Control Panel?
userList, err := common.Users.BulkGetMap(idSlice)
if err != nil {
return common.InternalError(err, w, r)
}
pi := common.IPSearchPage{common.GetTitlePhrase("ip-search"), user, headerVars, userList, ip}
if common.PreRenderHooks["pre_render_ips"] != nil {
if common.RunPreRenderHook("pre_render_ips", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "ip-search.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}
func routeBanSubmit(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {
func BanUserSubmit(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {
if !user.Perms.BanUsers {
return common.NoPermissions(w, r, user)
}
@ -116,7 +23,7 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user common.User, su
}
targetUser, err := common.Users.Get(uid)
if err == ErrNoRows {
if err == sql.ErrNoRows {
return common.LocalError("The user you're trying to ban no longer exists.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
@ -160,7 +67,7 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user common.User, su
}
err = targetUser.Ban(duration, user.ID)
if err == ErrNoRows {
if err == sql.ErrNoRows {
return common.LocalError("The user you're trying to ban no longer exists.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
@ -175,7 +82,7 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user common.User, su
return nil
}
func routeUnban(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {
func UnbanUser(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {
if !user.Perms.BanUsers {
return common.NoPermissions(w, r, user)
}
@ -186,7 +93,7 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user common.User, suid s
}
targetUser, err := common.Users.Get(uid)
if err == ErrNoRows {
if err == sql.ErrNoRows {
return common.LocalError("The user you're trying to unban no longer exists.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
@ -199,7 +106,7 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user common.User, suid s
err = targetUser.Unban()
if err == common.ErrNoTempGroup {
return common.LocalError("The user you're trying to unban is not banned", w, r, user)
} else if err == ErrNoRows {
} else if err == sql.ErrNoRows {
return common.LocalError("The user you're trying to unban no longer exists.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
@ -214,7 +121,7 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user common.User, suid s
return nil
}
func routeActivate(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {
func ActivateUser(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {
if !user.Perms.ActivateUsers {
return common.NoPermissions(w, r, user)
}
@ -225,7 +132,7 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user common.User, sui
}
targetUser, err := common.Users.Get(uid)
if err == ErrNoRows {
if err == sql.ErrNoRows {
return common.LocalError("The account you're trying to activate no longer exists.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)