Add posts as a criteria for group promotions.

Load the posts column when loading users.
Make DefaultUserStore.Reload a wrapper around DefaultUserStore.BypassGet.
Use the OO style query builder more in common/user.go
GroupPromotions is now bound to an interface rather than the concrete type.
Fix some issues in the GroupPromotionStore interface.
Improve error handling for table creation and reduce boilerplate.
Shorten some more things.

Add panel_group_promotions_posts phrase.

You will need to run the patcher / updater for this commit.
This commit is contained in:
Azareal 2019-10-07 08:20:37 +10:00
parent a5f77c376b
commit 2e28ae39f3
17 changed files with 230 additions and 205 deletions

View File

@ -9,8 +9,14 @@ type tblColumn = qgen.DBTableColumn
type tC = tblColumn type tC = tblColumn
type tblKey = qgen.DBTableKey type tblKey = qgen.DBTableKey
func createTables(adapter qgen.Adapter) error { func createTables(adapter qgen.Adapter) (err error) {
qgen.Install.CreateTable("users", mysqlPre, mysqlCol, createTable := func(table string, charset string, collation string, columns []qgen.DBTableColumn, keys []qgen.DBTableKey) {
if err != nil {
return
}
err = qgen.Install.CreateTable(table, charset, collation, columns, keys)
}
createTable("users", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"uid", "int", 0, false, true, ""}, tC{"uid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""}, tC{"name", "varchar", 100, false, false, ""},
@ -51,7 +57,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("users_groups", mysqlPre, mysqlCol, createTable("users_groups", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"gid", "int", 0, false, true, ""}, tC{"gid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""}, tC{"name", "varchar", 100, false, false, ""},
@ -69,7 +75,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("users_groups_promotions", mysqlPre, mysqlCol, createTable("users_groups_promotions", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"pid", "int", 0, false, true, ""}, tC{"pid", "int", 0, false, true, ""},
tC{"from_gid", "int", 0, false, false, ""}, tC{"from_gid", "int", 0, false, false, ""},
@ -78,6 +84,7 @@ func createTables(adapter qgen.Adapter) error {
// Requirements // Requirements
tC{"level", "int", 0, false, false, ""}, tC{"level", "int", 0, false, false, ""},
tC{"posts", "int", 0, false, false, "0"},
tC{"minTime", "int", 0, false, false, ""}, // How long someone needs to have been in their current group before being promoted tC{"minTime", "int", 0, false, false, ""}, // How long someone needs to have been in their current group before being promoted
}, },
[]tblKey{ []tblKey{
@ -85,7 +92,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("users_2fa_keys", mysqlPre, mysqlCol, createTable("users_2fa_keys", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tC{"secret", "varchar", 100, false, false, ""}, tC{"secret", "varchar", 100, false, false, ""},
@ -110,7 +117,7 @@ func createTables(adapter qgen.Adapter) error {
// TODO: Add a mod-queue and other basic auto-mod features. This is needed for awaiting activation and the mod_queue penalty flag // TODO: Add a mod-queue and other basic auto-mod features. This is needed for awaiting activation and the mod_queue penalty flag
// TODO: Add a penalty type where a user is stopped from creating plugin_guilds social groups // TODO: Add a penalty type where a user is stopped from creating plugin_guilds social groups
// TODO: Shadow bans. We will probably have a CanShadowBan permission for this, as we *really* don't want people using this lightly. // TODO: Shadow bans. We will probably have a CanShadowBan permission for this, as we *really* don't want people using this lightly.
/*qgen.Install.CreateTable("users_penalties","","", /*createTable("users_penalties","","",
[]tC{ []tC{
tC{"uid","int",0,false,false,""}, tC{"uid","int",0,false,false,""},
tC{"element_id","int",0,false,false,""}, tC{"element_id","int",0,false,false,""},
@ -134,7 +141,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
)*/ )*/
qgen.Install.CreateTable("users_groups_scheduler", "", "", createTable("users_groups_scheduler", "", "",
[]tC{ []tC{
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tC{"set_group", "int", 0, false, false, ""}, tC{"set_group", "int", 0, false, false, ""},
@ -150,7 +157,7 @@ func createTables(adapter qgen.Adapter) error {
) )
// TODO: Can we use a piece of software dedicated to persistent queues for this rather than relying on the database for it? // TODO: Can we use a piece of software dedicated to persistent queues for this rather than relying on the database for it?
qgen.Install.CreateTable("users_avatar_queue", "", "", createTable("users_avatar_queue", "", "",
[]tC{ []tC{
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
}, },
@ -160,7 +167,7 @@ func createTables(adapter qgen.Adapter) error {
) )
// TODO: Should we add a users prefix to this table to fit the "unofficial convention"? // TODO: Should we add a users prefix to this table to fit the "unofficial convention"?
qgen.Install.CreateTable("emails", "", "", createTable("emails", "", "",
[]tC{ []tC{
tC{"email", "varchar", 200, false, false, ""}, tC{"email", "varchar", 200, false, false, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -171,7 +178,7 @@ func createTables(adapter qgen.Adapter) error {
// TODO: Allow for patterns in domains, if the bots try to shake things up there? // TODO: Allow for patterns in domains, if the bots try to shake things up there?
/* /*
qgen.Install.CreateTable("email_domain_blacklist", "", "", createTable("email_domain_blacklist", "", "",
[]tC{ []tC{
tC{"domain", "varchar", 200, false, false, ""}, tC{"domain", "varchar", 200, false, false, ""},
tC{"gtld", "boolean", 0, false, false, "0"}, tC{"gtld", "boolean", 0, false, false, "0"},
@ -183,7 +190,7 @@ func createTables(adapter qgen.Adapter) error {
*/ */
// TODO: Implement password resets // TODO: Implement password resets
qgen.Install.CreateTable("password_resets", "", "", createTable("password_resets", "", "",
[]tC{ []tC{
tC{"email", "varchar", 200, false, false, ""}, tC{"email", "varchar", 200, false, false, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -193,7 +200,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("forums", mysqlPre, mysqlCol, createTable("forums", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"fid", "int", 0, false, true, ""}, tC{"fid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""}, tC{"name", "varchar", 100, false, false, ""},
@ -213,7 +220,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("forums_permissions", "", "", createTable("forums_permissions", "", "",
[]tC{ []tC{
tC{"fid", "int", 0, false, false, ""}, tC{"fid", "int", 0, false, false, ""},
tC{"gid", "int", 0, false, false, ""}, tC{"gid", "int", 0, false, false, ""},
@ -226,7 +233,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("topics", mysqlPre, mysqlCol, createTable("topics", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"tid", "int", 0, false, true, ""}, tC{"tid", "int", 0, false, true, ""},
tC{"title", "varchar", 100, false, false, ""}, // TODO: Increase the max length to 200? tC{"title", "varchar", 100, false, false, ""}, // TODO: Increase the max length to 200?
@ -263,7 +270,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("replies", mysqlPre, mysqlCol, createTable("replies", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"rid", "int", 0, false, true, ""}, // TODO: Rename to replyID? tC{"rid", "int", 0, false, true, ""}, // TODO: Rename to replyID?
tC{"tid", "int", 0, false, false, ""}, // TODO: Rename to topicID? tC{"tid", "int", 0, false, false, ""}, // TODO: Rename to topicID?
@ -287,7 +294,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("attachments", mysqlPre, mysqlCol, createTable("attachments", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"attachID", "int", 0, false, true, ""}, tC{"attachID", "int", 0, false, true, ""},
tC{"sectionID", "int", 0, false, false, "0"}, tC{"sectionID", "int", 0, false, false, "0"},
@ -303,7 +310,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("revisions", mysqlPre, mysqlCol, createTable("revisions", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"reviseID", "int", 0, false, true, ""}, tC{"reviseID", "int", 0, false, true, ""},
tC{"content", "text", 0, false, false, ""}, tC{"content", "text", 0, false, false, ""},
@ -317,7 +324,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("polls", mysqlPre, mysqlCol, createTable("polls", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"pollID", "int", 0, false, true, ""}, tC{"pollID", "int", 0, false, true, ""},
tC{"parentID", "int", 0, false, false, "0"}, tC{"parentID", "int", 0, false, false, "0"},
@ -331,7 +338,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("polls_options", "", "", createTable("polls_options", "", "",
[]tC{ []tC{
tC{"pollID", "int", 0, false, false, ""}, tC{"pollID", "int", 0, false, false, ""},
tC{"option", "int", 0, false, false, "0"}, tC{"option", "int", 0, false, false, "0"},
@ -339,7 +346,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("polls_votes", mysqlPre, mysqlCol, createTable("polls_votes", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"pollID", "int", 0, false, false, ""}, tC{"pollID", "int", 0, false, false, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -349,7 +356,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("users_replies", mysqlPre, mysqlCol, createTable("users_replies", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"rid", "int", 0, false, true, ""}, tC{"rid", "int", 0, false, true, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -366,7 +373,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("likes", "", "", createTable("likes", "", "",
[]tC{ []tC{
tC{"weight", "tinyint", 0, false, false, "1"}, tC{"weight", "tinyint", 0, false, false, "1"},
tC{"targetItem", "int", 0, false, false, ""}, tC{"targetItem", "int", 0, false, false, ""},
@ -378,7 +385,7 @@ func createTables(adapter qgen.Adapter) error {
) )
//columns("participants, createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid = ?") //columns("participants, createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid = ?")
qgen.Install.CreateTable("conversations", "", "", createTable("conversations", "", "",
[]tC{ []tC{
tC{"cid", "int", 0, false, true, ""}, tC{"cid", "int", 0, false, true, ""},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -391,7 +398,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("conversations_posts", "", "", createTable("conversations_posts", "", "",
[]tC{ []tC{
tC{"pid", "int", 0, false, true, ""}, tC{"pid", "int", 0, false, true, ""},
tC{"cid", "int", 0, false, false, ""}, tC{"cid", "int", 0, false, false, ""},
@ -404,14 +411,14 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("conversations_participants", "", "", createTable("conversations_participants", "", "",
[]tC{ []tC{
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tC{"cid", "int", 0, false, false, ""}, tC{"cid", "int", 0, false, false, ""},
}, nil, }, nil,
) )
qgen.Install.CreateTable("activity_stream_matches", "", "", createTable("activity_stream_matches", "", "",
[]tC{ []tC{
tC{"watcher", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"watcher", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"asid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"asid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -421,7 +428,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("activity_stream", "", "", createTable("activity_stream", "", "",
[]tC{ []tC{
tC{"asid", "int", 0, false, true, ""}, tC{"asid", "int", 0, false, true, ""},
tC{"actor", "int", 0, false, false, ""}, /* the one doing the act */ // TODO: Make this a foreign key tC{"actor", "int", 0, false, false, ""}, /* the one doing the act */ // TODO: Make this a foreign key
@ -436,7 +443,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("activity_subscriptions", "", "", createTable("activity_subscriptions", "", "",
[]tC{ []tC{
tC{"user", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"user", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"targetID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */ tC{"targetID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
@ -446,7 +453,7 @@ func createTables(adapter qgen.Adapter) error {
) )
/* Due to MySQL's design, we have to drop the unique keys for table settings, plugins, and themes down from 200 to 180 or it will error */ /* Due to MySQL's design, we have to drop the unique keys for table settings, plugins, and themes down from 200 to 180 or it will error */
qgen.Install.CreateTable("settings", "", "", createTable("settings", "", "",
[]tC{ []tC{
tC{"name", "varchar", 180, false, false, ""}, tC{"name", "varchar", 180, false, false, ""},
tC{"content", "varchar", 250, false, false, ""}, tC{"content", "varchar", 250, false, false, ""},
@ -458,7 +465,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("word_filters", "", "", createTable("word_filters", "", "",
[]tC{ []tC{
tC{"wfid", "int", 0, false, true, ""}, tC{"wfid", "int", 0, false, true, ""},
tC{"find", "varchar", 200, false, false, ""}, tC{"find", "varchar", 200, false, false, ""},
@ -469,7 +476,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("plugins", "", "", createTable("plugins", "", "",
[]tC{ []tC{
tC{"uname", "varchar", 180, false, false, ""}, tC{"uname", "varchar", 180, false, false, ""},
tC{"active", "boolean", 0, false, false, "0"}, tC{"active", "boolean", 0, false, false, "0"},
@ -480,7 +487,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("themes", "", "", createTable("themes", "", "",
[]tC{ []tC{
tC{"uname", "varchar", 180, false, false, ""}, tC{"uname", "varchar", 180, false, false, ""},
tC{"default", "boolean", 0, false, false, "0"}, tC{"default", "boolean", 0, false, false, "0"},
@ -491,7 +498,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("widgets", "", "", createTable("widgets", "", "",
[]tC{ []tC{
tC{"wid", "int", 0, false, true, ""}, tC{"wid", "int", 0, false, true, ""},
tC{"position", "int", 0, false, false, ""}, tC{"position", "int", 0, false, false, ""},
@ -506,7 +513,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("menus", "", "", createTable("menus", "", "",
[]tC{ []tC{
tC{"mid", "int", 0, false, true, ""}, tC{"mid", "int", 0, false, true, ""},
}, },
@ -515,7 +522,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("menu_items", "", "", createTable("menu_items", "", "",
[]tC{ []tC{
tC{"miid", "int", 0, false, true, ""}, tC{"miid", "int", 0, false, true, ""},
tC{"mid", "int", 0, false, false, ""}, tC{"mid", "int", 0, false, false, ""},
@ -539,7 +546,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("pages", mysqlPre, mysqlCol, createTable("pages", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"pid", "int", 0, false, true, ""}, tC{"pid", "int", 0, false, true, ""},
//tC{"path", "varchar", 200, false, false, ""}, //tC{"path", "varchar", 200, false, false, ""},
@ -555,7 +562,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("registration_logs", "", "", createTable("registration_logs", "", "",
[]tC{ []tC{
tC{"rlid", "int", 0, false, true, ""}, tC{"rlid", "int", 0, false, true, ""},
tC{"username", "varchar", 100, false, false, ""}, tC{"username", "varchar", 100, false, false, ""},
@ -570,7 +577,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("login_logs", "", "", createTable("login_logs", "", "",
[]tC{ []tC{
tC{"lid", "int", 0, false, true, ""}, tC{"lid", "int", 0, false, true, ""},
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
@ -583,7 +590,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
) )
qgen.Install.CreateTable("moderation_logs", "", "", createTable("moderation_logs", "", "",
[]tC{ []tC{
tC{"action", "varchar", 100, false, false, ""}, tC{"action", "varchar", 100, false, false, ""},
tC{"elementID", "int", 0, false, false, ""}, tC{"elementID", "int", 0, false, false, ""},
@ -594,7 +601,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("administration_logs", "", "", createTable("administration_logs", "", "",
[]tC{ []tC{
tC{"action", "varchar", 100, false, false, ""}, tC{"action", "varchar", 100, false, false, ""},
tC{"elementID", "int", 0, false, false, ""}, tC{"elementID", "int", 0, false, false, ""},
@ -605,7 +612,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("viewchunks", "", "", createTable("viewchunks", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
@ -613,7 +620,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("viewchunks_agents", "", "", createTable("viewchunks_agents", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
@ -622,7 +629,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("viewchunks_systems", "", "", createTable("viewchunks_systems", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
@ -630,7 +637,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("viewchunks_langs", "", "", createTable("viewchunks_langs", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
@ -638,7 +645,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("viewchunks_referrers", "", "", createTable("viewchunks_referrers", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
@ -646,7 +653,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("viewchunks_forums", "", "", createTable("viewchunks_forums", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
@ -654,7 +661,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("topicchunks", "", "", createTable("topicchunks", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
@ -662,7 +669,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("postchunks", "", "", createTable("postchunks", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
@ -670,7 +677,7 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("memchunks", "", "", createTable("memchunks", "", "",
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"stack", "int", 0, false, false, "0"}, tC{"stack", "int", 0, false, false, "0"},
@ -679,24 +686,24 @@ func createTables(adapter qgen.Adapter) error {
}, nil, }, nil,
) )
qgen.Install.CreateTable("sync", "", "", createTable("sync", "", "",
[]tC{ []tC{
tC{"last_update", "datetime", 0, false, false, ""}, tC{"last_update", "datetime", 0, false, false, ""},
}, nil, }, nil,
) )
qgen.Install.CreateTable("updates", "", "", createTable("updates", "", "",
[]tC{ []tC{
tC{"dbVersion", "int", 0, false, false, "0"}, tC{"dbVersion", "int", 0, false, false, "0"},
}, nil, }, nil,
) )
qgen.Install.CreateTable("meta", "", "", createTable("meta", "", "",
[]tC{ []tC{
tC{"name", "varchar", 200, false, false, ""}, tC{"name", "varchar", 200, false, false, ""},
tC{"value", "varchar", 200, false, false, ""}, tC{"value", "varchar", 200, false, false, ""},
}, nil, }, nil,
) )
return nil return err
} }

View File

@ -6,7 +6,7 @@ import (
qgen "github.com/Azareal/Gosora/query_gen" qgen "github.com/Azareal/Gosora/query_gen"
) )
var GroupPromotions *DefaultGroupPromotionStore var GroupPromotions GroupPromotionStore
type GroupPromotion struct { type GroupPromotion struct {
ID int ID int
@ -15,15 +15,16 @@ type GroupPromotion struct {
TwoWay bool TwoWay bool
Level int Level int
Posts int
MinTime int MinTime int
} }
type GroupPromotionStore interface { type GroupPromotionStore interface {
GetByGroup() ([]*GroupPromotion, error) GetByGroup(gid int) (gps []*GroupPromotion, err error)
Get(id int) (*GroupPromotion, error) Get(id int) (*GroupPromotion, error)
PromoteIfEligible(u *User, level int) error PromoteIfEligible(u *User, level int, posts int) error
Delete(id int) error Delete(id int) error
Create(from int, to int, twoWay bool, level int) (int, error) Create(from int, to int, twoWay bool, level int, posts int) (int, error)
} }
type DefaultGroupPromotionStore struct { type DefaultGroupPromotionStore struct {
@ -39,13 +40,13 @@ type DefaultGroupPromotionStore struct {
func NewDefaultGroupPromotionStore(acc *qgen.Accumulator) (*DefaultGroupPromotionStore, error) { func NewDefaultGroupPromotionStore(acc *qgen.Accumulator) (*DefaultGroupPromotionStore, error) {
ugp := "users_groups_promotions" ugp := "users_groups_promotions"
return &DefaultGroupPromotionStore{ return &DefaultGroupPromotionStore{
getByGroup: acc.Select(ugp).Columns("pid, from_gid, to_gid, two_way, level, minTime").Where("from_gid=? OR to_gid=?").Prepare(), getByGroup: acc.Select(ugp).Columns("pid, from_gid, to_gid, two_way, level, posts, minTime").Where("from_gid=? OR to_gid=?").Prepare(),
get: acc.Select(ugp).Columns("from_gid, to_gid, two_way, level, minTime").Where("pid = ?").Prepare(), get: acc.Select(ugp).Columns("from_gid, to_gid, two_way, level, posts, minTime").Where("pid = ?").Prepare(),
delete: acc.Delete(ugp).Where("pid = ?").Prepare(), delete: acc.Delete(ugp).Where("pid = ?").Prepare(),
create: acc.Insert(ugp).Columns("from_gid, to_gid, two_way, level, minTime").Fields("?,?,?,?,?").Prepare(), create: acc.Insert(ugp).Columns("from_gid, to_gid, two_way, level, posts, minTime").Fields("?,?,?,?,?,?").Prepare(),
getByUser: acc.Select(ugp).Columns("pid, to_gid, two_way, level, minTime").Where("from_gid=? AND level>=?").Orderby("level DESC").Limit("1").Prepare(), getByUser: acc.Select(ugp).Columns("pid, to_gid, two_way, level, posts, minTime").Where("from_gid=? AND level>=? AND posts>=?").Orderby("level DESC").Limit("1").Prepare(),
updateUser: acc.Update("users").Set("group = ?").Where("level >= ?").Prepare(), updateUser: acc.Update("users").Set("group = ?").Where("level >= ? AND posts >= ?").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
@ -58,7 +59,7 @@ func (s *DefaultGroupPromotionStore) GetByGroup(gid int) (gps []*GroupPromotion,
for rows.Next() { for rows.Next() {
g := &GroupPromotion{} g := &GroupPromotion{}
err := rows.Scan(&g.ID, &g.From, &g.To, &g.TwoWay, &g.Level, &g.MinTime) err := rows.Scan(&g.ID, &g.From, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -75,22 +76,22 @@ func (s *DefaultGroupPromotionStore) Get(id int) (*GroupPromotion, error) {
}*/ }*/
g := &GroupPromotion{ID: id} g := &GroupPromotion{ID: id}
err := s.get.QueryRow(id).Scan(&g.From, &g.To, &g.TwoWay, &g.Level, &g.MinTime) err := s.get.QueryRow(id).Scan(&g.From, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime)
if err == nil { if err == nil {
//s.cache.Set(u) //s.cache.Set(u)
} }
return g, err return g, err
} }
func (s *DefaultGroupPromotionStore) PromoteIfEligible(u *User, level int) error { func (s *DefaultGroupPromotionStore) PromoteIfEligible(u *User, level int, posts int) error {
g := &GroupPromotion{From: u.Group} g := &GroupPromotion{From: u.Group}
err := s.getByUser.QueryRow(u.Group, level).Scan(&g.ID, &g.To, &g.TwoWay, &g.Level, &g.MinTime) err := s.getByUser.QueryRow(u.Group, level, posts).Scan(&g.ID, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil return nil
} else if err != nil { } else if err != nil {
return err return err
} }
_, err = s.updateUser.Exec(g.To, g.Level) _, err = s.updateUser.Exec(g.To, g.Level, g.Posts)
return err return err
} }
@ -99,8 +100,8 @@ func (s *DefaultGroupPromotionStore) Delete(id int) error {
return err return err
} }
func (s *DefaultGroupPromotionStore) Create(from int, to int, twoWay bool, level int) (int, error) { func (s *DefaultGroupPromotionStore) Create(from int, to int, twoWay bool, level int, posts int) (int, error) {
res, err := s.create.Exec(from, to, twoWay, level, 0) res, err := s.create.Exec(from, to, twoWay, level, posts, 0)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -113,7 +113,7 @@ func (r *Reply) Like(uid int) (err error) {
if err != nil { if err != nil {
return err return err
} }
_, err = userStmts.incrementLiked.Exec(1, uid) _, err = userStmts.incLiked.Exec(1, uid)
_ = Rstore.GetCache().Remove(r.ID) _ = Rstore.GetCache().Remove(r.ID)
return err return err
} }

View File

@ -91,14 +91,14 @@ var Template_account_handle = genIntTmpl("account")
func tmplInitUsers() (User, User, User) { func tmplInitUsers() (User, User, User) {
avatar, microAvatar := BuildAvatar(62, "") avatar, microAvatar := BuildAvatar(62, "")
user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", "", "", 0, 0, 0, "0.0.0.0.0", "", 0} user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", "", "", 0, 0, 0, 0,"0.0.0.0.0", "", 0}
// TODO: Do a more accurate level calculation for this? // TODO: Do a more accurate level calculation for this?
avatar, microAvatar = BuildAvatar(1, "") avatar, microAvatar = BuildAvatar(1, "")
user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 58, 1000, 0, "127.0.0.1", "", 0} user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 58, 1000, 0, 1000, "127.0.0.1", "", 0}
avatar, microAvatar = BuildAvatar(2, "") avatar, microAvatar = BuildAvatar(2, "")
user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 42, 900, 0, "::1", "", 0} user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 42, 900, 0, 900, "::1", "", 0}
return user, user2, user3 return user, user2, user3
} }

View File

@ -318,7 +318,7 @@ func (t *Topic) Like(score int, uid int) (err error) {
if err != nil { if err != nil {
return err return err
} }
_, err = userStmts.incrementLiked.Exec(1, uid) _, err = userStmts.incLiked.Exec(1, uid)
t.cacheRemove() t.cacheRemove()
return err return err
} }

View File

@ -13,7 +13,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/Azareal/Gosora/query_gen" qgen "github.com/Azareal/Gosora/query_gen"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
) )
@ -51,6 +51,7 @@ type User struct {
Tag string Tag string
Level int Level int
Score int Score int
Posts int
Liked int Liked int
LastIP string // ! This part of the UserCache data might fall out of date LastIP string // ! This part of the UserCache data might fall out of date
LastAgent string // ! Temporary hack, don't use LastAgent string // ! Temporary hack, don't use
@ -104,7 +105,7 @@ type MeUser struct {
//Perms Perms //Perms Perms
//PluginPerms map[string]bool //PluginPerms map[string]bool
S string // Session S string // Session
Avatar string Avatar string
MicroAvatar string MicroAvatar string
Tag string Tag string
@ -114,24 +115,24 @@ type MeUser struct {
} }
type UserStmts struct { type UserStmts struct {
activate *sql.Stmt activate *sql.Stmt
changeGroup *sql.Stmt changeGroup *sql.Stmt
delete *sql.Stmt delete *sql.Stmt
setAvatar *sql.Stmt setAvatar *sql.Stmt
setUsername *sql.Stmt setUsername *sql.Stmt
incrementTopics *sql.Stmt incTopics *sql.Stmt
updateLevel *sql.Stmt updateLevel *sql.Stmt
update *sql.Stmt update *sql.Stmt
// TODO: Split these into a sub-struct // TODO: Split these into a sub-struct
incrementScore *sql.Stmt incScore *sql.Stmt
incrementPosts *sql.Stmt incPosts *sql.Stmt
incrementBigposts *sql.Stmt incBigposts *sql.Stmt
incrementMegaposts *sql.Stmt incMegaposts *sql.Stmt
incrementLiked *sql.Stmt incLiked *sql.Stmt
decrementLiked *sql.Stmt decLiked *sql.Stmt
updateLastIP *sql.Stmt updateLastIP *sql.Stmt
setPassword *sql.Stmt setPassword *sql.Stmt
@ -142,23 +143,23 @@ var userStmts UserStmts
func init() { func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { DbInits.Add(func(acc *qgen.Accumulator) error {
var w = "uid = ?" w := "uid = ?"
userStmts = UserStmts{ userStmts = UserStmts{
activate: acc.SimpleUpdate("users", "active = 1", w), activate: acc.SimpleUpdate("users", "active = 1", w),
changeGroup: acc.SimpleUpdate("users", "group = ?", w), // TODO: Implement user_count for users_groups here changeGroup: acc.SimpleUpdate("users", "group = ?", w), // TODO: Implement user_count for users_groups here
delete: acc.SimpleDelete("users", w), delete: acc.SimpleDelete("users", w),
setAvatar: acc.Update("users").Set("avatar = ?").Where(w).Prepare(), setAvatar: acc.Update("users").Set("avatar = ?").Where(w).Prepare(),
setUsername: acc.Update("users").Set("name = ?").Where(w).Prepare(), setUsername: acc.Update("users").Set("name = ?").Where(w).Prepare(),
incrementTopics: acc.SimpleUpdate("users", "topics = topics + ?", w), incTopics: acc.SimpleUpdate("users", "topics = topics + ?", w),
updateLevel: acc.SimpleUpdate("users", "level = ?", w), updateLevel: acc.SimpleUpdate("users", "level = ?", w),
update: acc.Update("users").Set("name = ?, email = ?, group = ?").Where(w).Prepare(), // TODO: Implement user_count for users_groups on things which use this update: acc.Update("users").Set("name = ?, email = ?, group = ?").Where(w).Prepare(), // TODO: Implement user_count for users_groups on things which use this
incrementScore: acc.SimpleUpdate("users", "score = score + ?", w), incScore: acc.Update("users").Set("score = score + ?").Where(w).Prepare(),
incrementPosts: acc.SimpleUpdate("users", "posts = posts + ?", w), incPosts: acc.Update("users").Set("posts = posts + ?").Where(w).Prepare(),
incrementBigposts: acc.SimpleUpdate("users", "posts = posts + ?, bigposts = bigposts + ?", w), incBigposts: acc.Update("users").Set("posts = posts + ?, bigposts = bigposts + ?").Where(w).Prepare(),
incrementMegaposts: acc.SimpleUpdate("users", "posts = posts + ?, bigposts = bigposts + ?, megaposts = megaposts + ?", w), incMegaposts: acc.Update("users").Set("posts = posts + ?, bigposts = bigposts + ?, megaposts = megaposts + ?").Where(w).Prepare(),
incrementLiked: acc.SimpleUpdate("users", "liked = liked + ?, lastLiked = UTC_TIMESTAMP()", w), incLiked: acc.Update("users").Set("liked = liked + ?, lastLiked = UTC_TIMESTAMP()").Where(w).Prepare(),
decrementLiked: acc.SimpleUpdate("users", "liked = liked - ?", w), decLiked: acc.Update("users").Set("liked = liked - ?").Where(w).Prepare(),
//recalcLastLiked: acc... //recalcLastLiked: acc...
updateLastIP: acc.SimpleUpdate("users", "last_ip = ?", w), updateLastIP: acc.SimpleUpdate("users", "last_ip = ?", w),
@ -342,15 +343,15 @@ func (u *User) UpdateIP(host string) error {
return err return err
} }
func (u *User) Update(newname string, newemail string, newgroup int) (err error) { func (u *User) Update(name string, email string, group int) (err error) {
return u.bindStmt(userStmts.update, newname, newemail, newgroup) return u.bindStmt(userStmts.update, name, email, group)
} }
func (u *User) IncreasePostStats(wcount int, topic bool) (err error) { func (u *User) IncreasePostStats(wcount int, topic bool) (err error) {
var mod int var mod int
baseScore := 1 baseScore := 1
if topic { if topic {
_, err = userStmts.incrementTopics.Exec(1, u.ID) _, err = userStmts.incTopics.Exec(1, u.ID)
if err != nil { if err != nil {
return err return err
} }
@ -359,31 +360,31 @@ func (u *User) IncreasePostStats(wcount int, topic bool) (err error) {
settings := SettingBox.Load().(SettingMap) settings := SettingBox.Load().(SettingMap)
if wcount >= settings["megapost_min_words"].(int) { if wcount >= settings["megapost_min_words"].(int) {
_, err = userStmts.incrementMegaposts.Exec(1, 1, 1, u.ID) _, err = userStmts.incMegaposts.Exec(1, 1, 1, u.ID)
mod = 4 mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) { } else if wcount >= settings["bigpost_min_words"].(int) {
_, err = userStmts.incrementBigposts.Exec(1, 1, u.ID) _, err = userStmts.incBigposts.Exec(1, 1, u.ID)
mod = 1 mod = 1
} else { } else {
_, err = userStmts.incrementPosts.Exec(1, u.ID) _, err = userStmts.incPosts.Exec(1, u.ID)
} }
if err != nil { if err != nil {
return err return err
} }
_, err = userStmts.incrementScore.Exec(baseScore+mod, u.ID) _, err = userStmts.incScore.Exec(baseScore+mod, u.ID)
if err != nil { if err != nil {
return err return err
} }
//log.Print(u.Score + baseScore + mod) //log.Print(u.Score + baseScore + mod)
// TODO: Use a transaction to prevent level desyncs? // TODO: Use a transaction to prevent level desyncs?
level := GetLevel(u.Score+baseScore+mod) level := GetLevel(u.Score + baseScore + mod)
//log.Print(level) //log.Print(level)
_, err = userStmts.updateLevel.Exec(level, u.ID) _, err = userStmts.updateLevel.Exec(level, u.ID)
if err != nil { if err != nil {
return err return err
} }
err = GroupPromotions.PromoteIfEligible(u,level) err = GroupPromotions.PromoteIfEligible(u, level, u.Posts+1)
u.CacheRemove() u.CacheRemove()
return err return err
} }
@ -392,7 +393,7 @@ func (u *User) DecreasePostStats(wcount int, topic bool) (err error) {
var mod int var mod int
baseScore := -1 baseScore := -1
if topic { if topic {
_, err = userStmts.incrementTopics.Exec(-1, u.ID) _, err = userStmts.incTopics.Exec(-1, u.ID)
if err != nil { if err != nil {
return err return err
} }
@ -401,19 +402,19 @@ func (u *User) DecreasePostStats(wcount int, topic bool) (err error) {
settings := SettingBox.Load().(SettingMap) settings := SettingBox.Load().(SettingMap)
if wcount >= settings["megapost_min_words"].(int) { if wcount >= settings["megapost_min_words"].(int) {
_, err = userStmts.incrementMegaposts.Exec(-1, -1, -1, u.ID) _, err = userStmts.incMegaposts.Exec(-1, -1, -1, u.ID)
mod = 4 mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) { } else if wcount >= settings["bigpost_min_words"].(int) {
_, err = userStmts.incrementBigposts.Exec(-1, -1, u.ID) _, err = userStmts.incBigposts.Exec(-1, -1, u.ID)
mod = 1 mod = 1
} else { } else {
_, err = userStmts.incrementPosts.Exec(-1, u.ID) _, err = userStmts.incPosts.Exec(-1, u.ID)
} }
if err != nil { if err != nil {
return err return err
} }
_, err = userStmts.incrementScore.Exec(baseScore-mod, u.ID) _, err = userStmts.incScore.Exec(baseScore-mod, u.ID)
if err != nil { if err != nil {
return err return err
} }
@ -479,7 +480,7 @@ func buildNoavatar(uid int, width int) string {
} }
} }
if !Config.DisableDefaultNoavatar && uid < 5 { if !Config.DisableDefaultNoavatar && uid < 5 {
return "/s/n"+strconv.Itoa(uid)+"-"+strconv.Itoa(width)+".png?i=0" return "/s/n" + strconv.Itoa(uid) + "-" + strconv.Itoa(width) + ".png?i=0"
} }
return strings.Replace(strings.Replace(Config.Noavatar, "{id}", strconv.Itoa(uid), 1), "{width}", strconv.Itoa(width), 1) return strings.Replace(strings.Replace(Config.Noavatar, "{id}", strconv.Itoa(uid), 1), "{width}", strconv.Itoa(width), 1)
} }

View File

@ -53,9 +53,9 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
// TODO: Add an admin version of registerStmt with more flexibility? // TODO: Add an admin version of registerStmt with more flexibility?
return &DefaultUserStore{ return &DefaultUserStore{
cache: cache, cache: cache,
get: acc.SimpleSelect("users", "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group", "uid = ?", "", ""), get: acc.SimpleSelect("users", "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, posts, liked, last_ip, temp_group", "uid = ?", "", ""),
getByName: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Where("name = ?").Prepare(), getByName: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, posts, liked, last_ip, temp_group").Where("name = ?").Prepare(),
getOffset: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Orderby("uid ASC").Limit("?,?").Prepare(), getOffset: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, posts, liked, last_ip, temp_group").Orderby("uid ASC").Limit("?,?").Prepare(),
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""), exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
register: acc.Insert("users").Columns("name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt").Fields("?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), // TODO: Implement user_count on users_groups here register: acc.Insert("users").Columns("name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt").Fields("?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), // TODO: Implement user_count on users_groups here
usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""), usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""),
@ -86,7 +86,7 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
//log.Print("uncached user") //log.Print("uncached user")
u = &User{ID: id, Loggedin: true} u = &User{ID: id, Loggedin: true}
err = s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score,&u.Liked, &u.LastIP, &u.TempGroup) err = s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts,&u.Liked, &u.LastIP, &u.TempGroup)
if err == nil { if err == nil {
u.Init() u.Init()
s.cache.Set(u) s.cache.Set(u)
@ -98,7 +98,7 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
// ! This bypasses the cache, use frugally // ! This bypasses the cache, use frugally
func (s *DefaultUserStore) GetByName(name string) (*User, error) { func (s *DefaultUserStore) GetByName(name string) (*User, error) {
u := &User{Loggedin: true} u := &User{Loggedin: true}
err := s.getByName.QueryRow(name).Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Liked, &u.LastIP, &u.TempGroup) err := s.getByName.QueryRow(name).Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts,&u.Liked, &u.LastIP, &u.TempGroup)
if err == nil { if err == nil {
u.Init() u.Init()
s.cache.Set(u) s.cache.Set(u)
@ -117,7 +117,7 @@ func (s *DefaultUserStore) GetOffset(offset int, perPage int) (users []*User, er
for rows.Next() { for rows.Next() {
u := &User{Loggedin: true} u := &User{Loggedin: true}
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Liked, &u.LastIP, &u.TempGroup) err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -171,7 +171,7 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
} }
q = q[0 : len(q)-1] q = q[0 : len(q)-1]
rows, err := qgen.NewAcc().Select("users").Columns("uid,name,group,active,is_super_admin,session,email,avatar,message,url_prefix,url_name,level,score,liked,last_ip,temp_group").Where("uid IN(" + q + ")").Query(idList...) rows, err := qgen.NewAcc().Select("users").Columns("uid,name,group,active,is_super_admin,session,email,avatar,message,url_prefix,url_name,level,score,posts,liked,last_ip,temp_group").Where("uid IN(" + q + ")").Query(idList...)
if err != nil { if err != nil {
return list, err return list, err
} }
@ -179,7 +179,7 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
for rows.Next() { for rows.Next() {
u := &User{Loggedin: true} u := &User{Loggedin: true}
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Liked, &u.LastIP, &u.TempGroup) err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup)
if err != nil { if err != nil {
return list, err return list, err
} }
@ -212,19 +212,19 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
func (s *DefaultUserStore) BypassGet(id int) (*User, error) { func (s *DefaultUserStore) BypassGet(id int) (*User, error) {
u := &User{ID: id, Loggedin: true} u := &User{ID: id, Loggedin: true}
err := s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Liked, &u.LastIP, &u.TempGroup) err := s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup)
u.Init() if err == nil {
u.Init()
}
return u, err return u, err
} }
func (s *DefaultUserStore) Reload(id int) error { func (s *DefaultUserStore) Reload(id int) error {
u := &User{ID: id, Loggedin: true} u, err := s.BypassGet(id)
err := s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.URLPrefix, &u.URLName, &u.Level, &u.Score, &u.Liked, &u.LastIP, &u.TempGroup)
if err != nil { if err != nil {
s.cache.Remove(id) s.cache.Remove(id)
return err return err
} }
u.Init()
_ = s.cache.Set(u) _ = s.cache.Set(u)
TopicListThaw.Thaw() TopicListThaw.Thaw()
return nil return nil

View File

@ -106,8 +106,7 @@ func gloinit() (err error) {
} }
func init() { func init() {
err := gloinit() if err := gloinit(); err != nil {
if err != nil {
log.Print("Something bad happened") log.Print("Something bad happened")
//debug.PrintStack() //debug.PrintStack()
log.Fatalf("%+v\n", err) log.Fatalf("%+v\n", err)

View File

@ -889,6 +889,7 @@
"panel_group_promotions_to":"To", "panel_group_promotions_to":"To",
"panel_group_promotions_two_way":"Two Way", "panel_group_promotions_two_way":"Two Way",
"panel_group_promotions_level":"Level", "panel_group_promotions_level":"Level",
"panel_group_promotions_posts":"Posts",
"panel_group_promotions_create_button":"Add Promotion", "panel_group_promotions_create_button":"Add Promotion",
"panel_word_filters_head":"Word Filters", "panel_word_filters_head":"Word Filters",

View File

@ -38,6 +38,7 @@ func init() {
addPatch(22, patch22) addPatch(22, patch22)
addPatch(23, patch23) addPatch(23, patch23)
addPatch(24, patch24) addPatch(24, patch24)
addPatch(25, patch25)
} }
func patch0(scanner *bufio.Scanner) (err error) { func patch0(scanner *bufio.Scanner) (err error) {
@ -734,4 +735,8 @@ func patch24(scanner *bufio.Scanner) error {
tblKey{"pid", "primary","",false}, tblKey{"pid", "primary","",false},
}, },
)) ))
}
func patch25(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.AddColumn("users_groups_promotions", tC{"posts", "int", 0, false, false, "0"}, nil))
} }

View File

@ -30,81 +30,81 @@ type installer struct {
plugins []QueryPlugin plugins []QueryPlugin
} }
func (install *installer) SetAdapter(name string) error { func (ins *installer) SetAdapter(name string) error {
adap, err := GetAdapter(name) adap, err := GetAdapter(name)
if err != nil { if err != nil {
return err return err
} }
install.SetAdapterInstance(adap) ins.SetAdapterInstance(adap)
return nil return nil
} }
func (install *installer) SetAdapterInstance(adapter Adapter) { func (ins *installer) SetAdapterInstance(adapter Adapter) {
install.adapter = adapter ins.adapter = adapter
install.instructions = []DBInstallInstruction{} ins.instructions = []DBInstallInstruction{}
} }
func (install *installer) AddPlugins(plugins ...QueryPlugin) { func (ins *installer) AddPlugins(plugins ...QueryPlugin) {
install.plugins = append(install.plugins, plugins...) ins.plugins = append(ins.plugins, plugins...)
} }
func (install *installer) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) error { func (ins *installer) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) error {
tableStruct := &DBInstallTable{table, charset, collation, columns, keys} tableStruct := &DBInstallTable{table, charset, collation, columns, keys}
err := install.RunHook("CreateTableStart", tableStruct) err := ins.RunHook("CreateTableStart", tableStruct)
if err != nil { if err != nil {
return err return err
} }
res, err := install.adapter.CreateTable("", table, charset, collation, columns, keys) res, err := ins.adapter.CreateTable("", table, charset, collation, columns, keys)
if err != nil { if err != nil {
return err return err
} }
err = install.RunHook("CreateTableAfter", tableStruct) err = ins.RunHook("CreateTableAfter", tableStruct)
if err != nil { if err != nil {
return err return err
} }
install.instructions = append(install.instructions, DBInstallInstruction{table, res, "create-table"}) ins.instructions = append(ins.instructions, DBInstallInstruction{table, res, "create-table"})
install.tables = append(install.tables, tableStruct) ins.tables = append(ins.tables, tableStruct)
return nil return nil
} }
// TODO: Let plugins manipulate the parameters like in CreateTable // TODO: Let plugins manipulate the parameters like in CreateTable
func (install *installer) AddIndex(table string, iname string, colname string) error { func (ins *installer) AddIndex(table string, iname string, colname string) error {
err := install.RunHook("AddIndexStart", table, iname, colname) err := ins.RunHook("AddIndexStart", table, iname, colname)
if err != nil { if err != nil {
return err return err
} }
res, err := install.adapter.AddIndex("", table, iname, colname) res, err := ins.adapter.AddIndex("", table, iname, colname)
if err != nil { if err != nil {
return err return err
} }
err = install.RunHook("AddIndexAfter", table, iname, colname) err = ins.RunHook("AddIndexAfter", table, iname, colname)
if err != nil { if err != nil {
return err return err
} }
install.instructions = append(install.instructions, DBInstallInstruction{table, res, "index"}) ins.instructions = append(ins.instructions, DBInstallInstruction{table, res, "index"})
return nil return nil
} }
// TODO: Let plugins manipulate the parameters like in CreateTable // TODO: Let plugins manipulate the parameters like in CreateTable
func (install *installer) SimpleInsert(table string, columns string, fields string) error { func (ins *installer) SimpleInsert(table string, columns string, fields string) error {
err := install.RunHook("SimpleInsertStart", table, columns, fields) err := ins.RunHook("SimpleInsertStart", table, columns, fields)
if err != nil { if err != nil {
return err return err
} }
res, err := install.adapter.SimpleInsert("", table, columns, fields) res, err := ins.adapter.SimpleInsert("", table, columns, fields)
if err != nil { if err != nil {
return err return err
} }
err = install.RunHook("SimpleInsertAfter", table, columns, fields, res) err = ins.RunHook("SimpleInsertAfter", table, columns, fields, res)
if err != nil { if err != nil {
return err return err
} }
install.instructions = append(install.instructions, DBInstallInstruction{table, res, "insert"}) ins.instructions = append(ins.instructions, DBInstallInstruction{table, res, "insert"})
return nil return nil
} }
func (install *installer) RunHook(name string, args ...interface{}) error { func (ins *installer) RunHook(name string, args ...interface{}) error {
for _, plugin := range install.plugins { for _, plugin := range ins.plugins {
err := plugin.Hook(name, args...) err := plugin.Hook(name, args...)
if err != nil { if err != nil {
return err return err
@ -113,12 +113,12 @@ func (install *installer) RunHook(name string, args ...interface{}) error {
return nil return nil
} }
func (install *installer) Write() error { func (ins *installer) Write() error {
var inserts string var inserts string
// We can't escape backticks, so we have to dump it out a file at a time // We can't escape backticks, so we have to dump it out a file at a time
for _, instr := range install.instructions { for _, instr := range ins.instructions {
if instr.Type == "create-table" { if instr.Type == "create-table" {
err := writeFile("./schema/"+install.adapter.GetName()+"/query_"+instr.Table+".sql", instr.Contents) err := writeFile("./schema/"+ins.adapter.GetName()+"/query_"+instr.Table+".sql", instr.Contents)
if err != nil { if err != nil {
return err return err
} }
@ -127,12 +127,12 @@ func (install *installer) Write() error {
} }
} }
err := writeFile("./schema/"+install.adapter.GetName()+"/inserts.sql", inserts) err := writeFile("./schema/"+ins.adapter.GetName()+"/inserts.sql", inserts)
if err != nil { if err != nil {
return err return err
} }
for _, plugin := range install.plugins { for _, plugin := range ins.plugins {
err := plugin.Write() err := plugin.Write()
if err != nil { if err != nil {
return err return err

View File

@ -137,7 +137,7 @@ func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := (r.PostFormValue("js") == "1") js := r.PostFormValue("js") == "1"
if !user.Perms.ManageForums { if !user.Perms.ManageForums {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, user, js)
} }
@ -218,7 +218,7 @@ func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sfid
if !user.Perms.ManageForums { if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, user)
} }
js := (r.PostFormValue("js") == "1") js := r.PostFormValue("js") == "1"
fid, err := strconv.Atoi(sfid) fid, err := strconv.Atoi(sfid)
if err != nil { if err != nil {
@ -260,7 +260,7 @@ func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if !user.Perms.ManageForums { if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, user)
} }
js := (r.PostFormValue("js") == "1") js := r.PostFormValue("js") == "1"
fid, err := strconv.Atoi(sfid) fid, err := strconv.Atoi(sfid)
if err != nil { if err != nil {

View File

@ -208,6 +208,10 @@ func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
if err != nil { if err != nil {
return c.LocalError("level must be integer", w, r, user) return c.LocalError("level must be integer", w, r, user)
} }
posts, err := strconv.Atoi(r.FormValue("posts"))
if err != nil {
return c.LocalError("posts must be integer", w, r, user)
}
g, err := c.Groups.Get(from) g, err := c.Groups.Get(from)
ferr := groupCheck(w, r, user, g, err) ferr := groupCheck(w, r, user, g, err)
@ -219,7 +223,7 @@ func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
if err != nil { if err != nil {
return ferr return ferr
} }
_, err = c.GroupPromotions.Create(from, to, twoWay, level) _, err = c.GroupPromotions.Create(from, to, twoWay, level, posts)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -300,50 +304,50 @@ func GroupsEditPerms(w http.ResponseWriter, r *http.Request, user c.User, sgid s
// TODO: Load the phrases in bulk for efficiency? // TODO: Load the phrases in bulk for efficiency?
var localPerms []c.NameLangToggle var localPerms []c.NameLangToggle
addLocalPerm := func(permStr string, perm bool) { addPerm := func(permStr string, perm bool) {
localPerms = append(localPerms, c.NameLangToggle{permStr, p.GetPermPhrase(permStr), perm}) localPerms = append(localPerms, c.NameLangToggle{permStr, p.GetPermPhrase(permStr), perm})
} }
addLocalPerm("ViewTopic", g.Perms.ViewTopic) addPerm("ViewTopic", g.Perms.ViewTopic)
addLocalPerm("LikeItem", g.Perms.LikeItem) addPerm("LikeItem", g.Perms.LikeItem)
addLocalPerm("CreateTopic", g.Perms.CreateTopic) addPerm("CreateTopic", g.Perms.CreateTopic)
//<-- //<--
addLocalPerm("EditTopic", g.Perms.EditTopic) addPerm("EditTopic", g.Perms.EditTopic)
addLocalPerm("DeleteTopic", g.Perms.DeleteTopic) addPerm("DeleteTopic", g.Perms.DeleteTopic)
addLocalPerm("CreateReply", g.Perms.CreateReply) addPerm("CreateReply", g.Perms.CreateReply)
addLocalPerm("EditReply", g.Perms.EditReply) addPerm("EditReply", g.Perms.EditReply)
addLocalPerm("DeleteReply", g.Perms.DeleteReply) addPerm("DeleteReply", g.Perms.DeleteReply)
addLocalPerm("PinTopic", g.Perms.PinTopic) addPerm("PinTopic", g.Perms.PinTopic)
addLocalPerm("CloseTopic", g.Perms.CloseTopic) addPerm("CloseTopic", g.Perms.CloseTopic)
addLocalPerm("MoveTopic", g.Perms.MoveTopic) addPerm("MoveTopic", g.Perms.MoveTopic)
var globalPerms []c.NameLangToggle var globalPerms []c.NameLangToggle
addGlobalPerm := func(permStr string, perm bool) { addPerm = func(permStr string, perm bool) {
globalPerms = append(globalPerms, c.NameLangToggle{permStr, p.GetPermPhrase(permStr), perm}) globalPerms = append(globalPerms, c.NameLangToggle{permStr, p.GetPermPhrase(permStr), perm})
} }
addGlobalPerm("BanUsers", g.Perms.BanUsers) addPerm("BanUsers", g.Perms.BanUsers)
addGlobalPerm("ActivateUsers", g.Perms.ActivateUsers) addPerm("ActivateUsers", g.Perms.ActivateUsers)
addGlobalPerm("EditUser", g.Perms.EditUser) addPerm("EditUser", g.Perms.EditUser)
addGlobalPerm("EditUserEmail", g.Perms.EditUserEmail) addPerm("EditUserEmail", g.Perms.EditUserEmail)
addGlobalPerm("EditUserPassword", g.Perms.EditUserPassword) addPerm("EditUserPassword", g.Perms.EditUserPassword)
addGlobalPerm("EditUserGroup", g.Perms.EditUserGroup) addPerm("EditUserGroup", g.Perms.EditUserGroup)
addGlobalPerm("EditUserGroupSuperMod", g.Perms.EditUserGroupSuperMod) addPerm("EditUserGroupSuperMod", g.Perms.EditUserGroupSuperMod)
addGlobalPerm("EditUserGroupAdmin", g.Perms.EditUserGroupAdmin) addPerm("EditUserGroupAdmin", g.Perms.EditUserGroupAdmin)
addGlobalPerm("EditGroup", g.Perms.EditGroup) addPerm("EditGroup", g.Perms.EditGroup)
addGlobalPerm("EditGroupLocalPerms", g.Perms.EditGroupLocalPerms) addPerm("EditGroupLocalPerms", g.Perms.EditGroupLocalPerms)
addGlobalPerm("EditGroupGlobalPerms", g.Perms.EditGroupGlobalPerms) addPerm("EditGroupGlobalPerms", g.Perms.EditGroupGlobalPerms)
addGlobalPerm("EditGroupSuperMod", g.Perms.EditGroupSuperMod) addPerm("EditGroupSuperMod", g.Perms.EditGroupSuperMod)
addGlobalPerm("EditGroupAdmin", g.Perms.EditGroupAdmin) addPerm("EditGroupAdmin", g.Perms.EditGroupAdmin)
addGlobalPerm("ManageForums", g.Perms.ManageForums) addPerm("ManageForums", g.Perms.ManageForums)
addGlobalPerm("EditSettings", g.Perms.EditSettings) addPerm("EditSettings", g.Perms.EditSettings)
addGlobalPerm("ManageThemes", g.Perms.ManageThemes) addPerm("ManageThemes", g.Perms.ManageThemes)
addGlobalPerm("ManagePlugins", g.Perms.ManagePlugins) addPerm("ManagePlugins", g.Perms.ManagePlugins)
addGlobalPerm("ViewAdminLogs", g.Perms.ViewAdminLogs) addPerm("ViewAdminLogs", g.Perms.ViewAdminLogs)
addGlobalPerm("ViewIPs", g.Perms.ViewIPs) addPerm("ViewIPs", g.Perms.ViewIPs)
addGlobalPerm("UploadFiles", g.Perms.UploadFiles) addPerm("UploadFiles", g.Perms.UploadFiles)
addGlobalPerm("UploadAvatars", g.Perms.UploadAvatars) addPerm("UploadAvatars", g.Perms.UploadAvatars)
addGlobalPerm("UseConvos", g.Perms.UseConvos) addPerm("UseConvos", g.Perms.UseConvos)
pi := c.PanelEditGroupPermsPage{basePage, g.ID, g.Name, localPerms, globalPerms} pi := c.PanelEditGroupPermsPage{basePage, g.ID, g.Name, localPerms, globalPerms}
return renderTemplate("panel_group_edit_perms", w, r, basePage.Header, pi) return renderTemplate("panel_group_edit_perms", w, r, basePage.Header, pi)

View File

@ -4,6 +4,7 @@ CREATE TABLE [users_groups_promotions] (
[to_gid] int not null, [to_gid] int not null,
[two_way] bit DEFAULT 0 not null, [two_way] bit DEFAULT 0 not null,
[level] int not null, [level] int not null,
[posts] int DEFAULT 0 not null,
[minTime] int not null, [minTime] int not null,
primary key([pid]) primary key([pid])
); );

View File

@ -4,6 +4,7 @@ CREATE TABLE `users_groups_promotions` (
`to_gid` int not null, `to_gid` int not null,
`two_way` boolean DEFAULT 0 not null, `two_way` boolean DEFAULT 0 not null,
`level` int not null, `level` int not null,
`posts` int DEFAULT 0 not null,
`minTime` int not null, `minTime` int not null,
primary key(`pid`) primary key(`pid`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -4,6 +4,7 @@ CREATE TABLE "users_groups_promotions" (
`to_gid` int not null, `to_gid` int not null,
`two_way` boolean DEFAULT 0 not null, `two_way` boolean DEFAULT 0 not null,
`level` int not null, `level` int not null,
`posts` int DEFAULT 0 not null,
`minTime` int not null, `minTime` int not null,
primary key(`pid`) primary key(`pid`)
); );

View File

@ -57,6 +57,10 @@
<div class="formitem formlabel"><a>{{lang "panel_group_promotions_level"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_group_promotions_level"}}</a></div>
<div class="formitem"><input name="level" type="number" value=0 /></div> <div class="formitem"><input name="level" type="number" value=0 /></div>
</div> </div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_group_promotions_posts"}}</a></div>
<div class="formitem"><input name="posts" type="number" value=0 /></div>
</div>
<div class="formrow form_button_row"> <div class="formrow form_button_row">
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_group_promotions_create_button"}}</button></div> <div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_group_promotions_create_button"}}</button></div>
</div> </div>