diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..9e4c6c42 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.9 + - master +before_install: + - chmod 755 ./install-linux + - chmod 755 ./run-linux +script: go test +addons: + mariadb: '10.0' \ No newline at end of file diff --git a/auth.go b/auth.go index 20e8115e..9a5ecfac 100644 --- a/auth.go +++ b/auth.go @@ -20,8 +20,12 @@ var auth Auth // ErrMismatchedHashAndPassword is thrown whenever a hash doesn't match it's unhashed password var ErrMismatchedHashAndPassword = bcrypt.ErrMismatchedHashAndPassword +// nolint // ErrPasswordTooLong is silly, but we don't want bcrypt to bork on us var ErrPasswordTooLong = errors.New("The password you selected is too long") +var ErrWrongPassword = errors.New("That's not the correct password.") +var ErrSecretError = errors.New("There was a glitch in the system. Please contact your local administrator.") +var ErrNoUserByName = errors.New("We couldn't find an account with that username.") // Auth is the main authentication interface. type Auth interface { @@ -61,24 +65,24 @@ func (auth *DefaultAuth) Authenticate(username string, password string) (uid int var realPassword, salt string err = auth.login.QueryRow(username).Scan(&uid, &realPassword, &salt) if err == ErrNoRows { - return 0, errors.New("We couldn't find an account with that username.") // nolint + return 0, ErrNoUserByName } else if err != nil { LogError(err) - return 0, errors.New("There was a glitch in the system. Please contact your local administrator.") // nolint + return 0, ErrSecretError } if salt == "" { // Send an email to admin for this? LogError(errors.New("Missing salt for user #" + strconv.Itoa(uid) + ". Potential security breach.")) - return 0, errors.New("There was a glitch in the system. Please contact your local administrator") + return 0, ErrSecretError } err = CheckPassword(realPassword, password, salt) if err == ErrMismatchedHashAndPassword { - return 0, errors.New("That's not the correct password.") + return 0, ErrWrongPassword } else if err != nil { LogError(err) - return 0, errors.New("There was a glitch in the system. Please contact your local administrator.") + return 0, ErrSecretError } return uid, nil @@ -89,7 +93,7 @@ func (auth *DefaultAuth) ForceLogout(uid int) error { _, err := auth.logout.Exec(uid) if err != nil { LogError(err) - return errors.New("There was a glitch in the system. Please contact your local administrator.") + return ErrSecretError } // Flush the user out of the cache @@ -110,6 +114,7 @@ func (auth *DefaultAuth) Logout(w http.ResponseWriter, _ int) { } // TODO: Set the cookie domain +// SetCookies sets the two cookies required for the current user to be recognised as a specific user in future requests func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session string) { cookie := http.Cookie{Name: "uid", Value: strconv.Itoa(uid), Path: "/", MaxAge: year} http.SetCookie(w, &cookie) diff --git a/cache.go b/cache.go index 34dd65c4..2ea4bc71 100644 --- a/cache.go +++ b/cache.go @@ -12,8 +12,8 @@ const CACHE_SQL int = 2 // ErrCacheDesync is thrown whenever a piece of data, for instance, a user is out of sync with the database. Currently unused. var ErrCacheDesync = errors.New("The cache is out of sync with the database.") // TODO: A cross-server synchronisation mechanism -// ErrStoreCapacityOverflow is thrown whenever a datastore reaches it's maximum hard capacity. I'm not sure if this error is actually used. -var ErrStoreCapacityOverflow = errors.New("This datastore has reached it's maximum capacity.") +// ErrStoreCapacityOverflow is thrown whenever a datastore reaches it's maximum hard capacity. I'm not sure if this error is actually used. It might be, we should check +var ErrStoreCapacityOverflow = errors.New("This datastore has reached it's maximum capacity.") // nolint // nolint type DataStore interface { diff --git a/errors.go b/errors.go index becb9d5b..05eca1d6 100644 --- a/errors.go +++ b/errors.go @@ -14,14 +14,18 @@ var errorBuffer []error //var notfoundCountPerSecond int //var nopermsCountPerSecond int -// LogError logs internal handler errors which can't be handled with InternalError() as a wrapper for log.Fatal(), we might do more with it in the future +// LogError logs internal handler errors which can't be handled with InternalError() as a wrapper for log.Fatal(), we might do more with it in the future. func LogError(err error) { + LogWarning(err) + log.Fatal("") +} + +func LogWarning(err error) { log.Print(err) debug.PrintStack() errorBufferMutex.Lock() defer errorBufferMutex.Unlock() errorBuffer = append(errorBuffer, err) - log.Fatal("") } // InternalError is the main function for handling internal errors, while simultaneously printing out a page for the end-user to let them know that *something* has gone wrong diff --git a/forum_store.go b/forum_store.go index fbac8d6c..f244cd0f 100644 --- a/forum_store.go +++ b/forum_store.go @@ -405,3 +405,5 @@ func (mfs *MemoryForumStore) GlobalCount() (fcount int) { } // TODO: Work on SqlForumStore + +// TODO: Work on the NullForumStore diff --git a/gen_mssql.go b/gen_mssql.go index 5488a676..eed4a3d5 100644 --- a/gen_mssql.go +++ b/gen_mssql.go @@ -65,13 +65,10 @@ var createForumStmt *sql.Stmt var addForumPermsToForumStmt *sql.Stmt var addPluginStmt *sql.Stmt var addThemeStmt *sql.Stmt -var createGroupStmt *sql.Stmt var addModlogEntryStmt *sql.Stmt var addAdminlogEntryStmt *sql.Stmt var addAttachmentStmt *sql.Stmt var createWordFilterStmt *sql.Stmt -var addForumPermsToGroupStmt *sql.Stmt -var replaceScheduleGroupStmt *sql.Stmt var addRepliesToTopicStmt *sql.Stmt var removeRepliesFromTopicStmt *sql.Stmt var addTopicsToForumStmt *sql.Stmt @@ -105,6 +102,7 @@ var updatePluginStmt *sql.Stmt var updatePluginInstallStmt *sql.Stmt var updateThemeStmt *sql.Stmt var updateUserStmt *sql.Stmt +var updateUserGroupStmt *sql.Stmt var updateGroupPermsStmt *sql.Stmt var updateGroupRankStmt *sql.Stmt var updateGroupStmt *sql.Stmt @@ -116,15 +114,11 @@ var bumpSyncStmt *sql.Stmt var deleteUserStmt *sql.Stmt var deleteReplyStmt *sql.Stmt var deleteProfileReplyStmt *sql.Stmt -var deleteForumPermsByForumStmt *sql.Stmt var deleteActivityStreamMatchStmt *sql.Stmt var deleteWordFilterStmt *sql.Stmt var reportExistsStmt *sql.Stmt var groupCountStmt *sql.Stmt var modlogCountStmt *sql.Stmt -var addForumPermsToForumAdminsStmt *sql.Stmt -var addForumPermsToForumStaffStmt *sql.Stmt -var addForumPermsToForumMembersStmt *sql.Stmt var notifyWatchersStmt *sql.Stmt // nolint @@ -365,9 +359,9 @@ func _gen_mssql() (err error) { } log.Print("Preparing getExpiredScheduledGroups statement.") - getExpiredScheduledGroupsStmt, err = db.Prepare("SELECT [uid] FROM [users_groups_scheduler] WHERE GETUTCDATE() > [revert_at] AND [temporary] = 1") + getExpiredScheduledGroupsStmt, err = db.Prepare("SELECT [uid] FROM [users_groups_scheduler] WHERE GETDATE() > [revert_at] AND [temporary] = 1") if err != nil { - log.Print("Bad Query: ","SELECT [uid] FROM [users_groups_scheduler] WHERE GETUTCDATE() > [revert_at] AND [temporary] = 1") + log.Print("Bad Query: ","SELECT [uid] FROM [users_groups_scheduler] WHERE GETDATE() > [revert_at] AND [temporary] = 1") return err } @@ -539,13 +533,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing createGroup statement.") - createGroupStmt, err = db.Prepare("INSERT INTO [users_groups] ([name],[tag],[is_admin],[is_mod],[is_banned],[permissions]) VALUES (?,?,?,?,?,?)") - if err != nil { - log.Print("Bad Query: ","INSERT INTO [users_groups] ([name],[tag],[is_admin],[is_mod],[is_banned],[permissions]) VALUES (?,?,?,?,?,?)") - return err - } - log.Print("Preparing addModlogEntry statement.") addModlogEntryStmt, err = db.Prepare("INSERT INTO [moderation_logs] ([action],[elementID],[elementType],[ipaddress],[actorID],[doneAt]) VALUES (?,?,?,?,?,GETUTCDATE())") if err != nil { @@ -574,20 +561,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing addForumPermsToGroup statement.") - addForumPermsToGroupStmt, err = db.Prepare("MERGE [forums_permissions] WITH(HOLDLOCK) as t1 USING (VALUES(?,?,?,?)) AS updates (f0,f1,f2,f3) ON [gid] = ? [fid] = ? WHEN MATCHED THEN UPDATE SET [gid] = f0,[fid] = f1,[preset] = f2,[permissions] = f3 WHEN NOT MATCHED THEN INSERT([gid],[fid],[preset],[permissions]) VALUES (f0,f1,f2,f3);") - if err != nil { - log.Print("Bad Query: ","MERGE [forums_permissions] WITH(HOLDLOCK) as t1 USING (VALUES(?,?,?,?)) AS updates (f0,f1,f2,f3) ON [gid] = ? [fid] = ? WHEN MATCHED THEN UPDATE SET [gid] = f0,[fid] = f1,[preset] = f2,[permissions] = f3 WHEN NOT MATCHED THEN INSERT([gid],[fid],[preset],[permissions]) VALUES (f0,f1,f2,f3);") - return err - } - - log.Print("Preparing replaceScheduleGroup statement.") - replaceScheduleGroupStmt, err = db.Prepare("MERGE [users_groups_scheduler] WITH(HOLDLOCK) as t1 USING (VALUES(?,?,?,GETUTCDATE(),?,?)) AS updates (f0,f1,f2,f3,f4,f5) ON [uid] = ? WHEN MATCHED THEN UPDATE SET [uid] = f0,[set_group] = f1,[issued_by] = f2,[issued_at] = f3,[revert_at] = f4,[temporary] = f5 WHEN NOT MATCHED THEN INSERT([uid],[set_group],[issued_by],[issued_at],[revert_at],[temporary]) VALUES (f0,f1,f2,f3,f4,f5);") - if err != nil { - log.Print("Bad Query: ","MERGE [users_groups_scheduler] WITH(HOLDLOCK) as t1 USING (VALUES(?,?,?,GETUTCDATE(),?,?)) AS updates (f0,f1,f2,f3,f4,f5) ON [uid] = ? WHEN MATCHED THEN UPDATE SET [uid] = f0,[set_group] = f1,[issued_by] = f2,[issued_at] = f3,[revert_at] = f4,[temporary] = f5 WHEN NOT MATCHED THEN INSERT([uid],[set_group],[issued_by],[issued_at],[revert_at],[temporary]) VALUES (f0,f1,f2,f3,f4,f5);") - return err - } - log.Print("Preparing addRepliesToTopic statement.") addRepliesToTopicStmt, err = db.Prepare("UPDATE [topics] SET [postCount] = [postCount] + ?,[lastReplyBy] = ?,[lastReplyAt] = GETUTCDATE() WHERE [tid] = ?") if err != nil { @@ -819,6 +792,13 @@ func _gen_mssql() (err error) { return err } + log.Print("Preparing updateUserGroup statement.") + updateUserGroupStmt, err = db.Prepare("UPDATE [users] SET [group] = ? WHERE [uid] = ?") + if err != nil { + log.Print("Bad Query: ","UPDATE [users] SET [group] = ? WHERE [uid] = ?") + return err + } + log.Print("Preparing updateGroupPerms statement.") updateGroupPermsStmt, err = db.Prepare("UPDATE [users_groups] SET [permissions] = ? WHERE [gid] = ?") if err != nil { @@ -896,13 +876,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing deleteForumPermsByForum statement.") - deleteForumPermsByForumStmt, err = db.Prepare("DELETE FROM [forums_permissions] WHERE [fid] = ?") - if err != nil { - log.Print("Bad Query: ","DELETE FROM [forums_permissions] WHERE [fid] = ?") - return err - } - log.Print("Preparing deleteActivityStreamMatch statement.") deleteActivityStreamMatchStmt, err = db.Prepare("DELETE FROM [activity_stream_matches] WHERE [watcher] = ? AND [asid] = ?") if err != nil { @@ -938,27 +911,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing addForumPermsToForumAdmins statement.") - addForumPermsToForumAdminsStmt, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) SELECT [gid],[? AS fid],[? AS preset],[? AS permissions] FROM [users_groups] WHERE [is_admin] = 1") - if err != nil { - log.Print("Bad Query: ","INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) SELECT [gid],[? AS fid],[? AS preset],[? AS permissions] FROM [users_groups] WHERE [is_admin] = 1") - return err - } - - log.Print("Preparing addForumPermsToForumStaff statement.") - addForumPermsToForumStaffStmt, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) SELECT [gid],[? AS fid],[? AS preset],[? AS permissions] FROM [users_groups] WHERE [is_admin] = 0 AND [is_mod] = 1") - if err != nil { - log.Print("Bad Query: ","INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) SELECT [gid],[? AS fid],[? AS preset],[? AS permissions] FROM [users_groups] WHERE [is_admin] = 0 AND [is_mod] = 1") - return err - } - - log.Print("Preparing addForumPermsToForumMembers statement.") - addForumPermsToForumMembersStmt, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) SELECT [gid],[? AS fid],[? AS preset],[? AS permissions] FROM [users_groups] WHERE [is_admin] = 0 AND [is_mod] = 0 AND [is_banned] = 0") - if err != nil { - log.Print("Bad Query: ","INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) SELECT [gid],[? AS fid],[? AS preset],[? AS permissions] FROM [users_groups] WHERE [is_admin] = 0 AND [is_mod] = 0 AND [is_banned] = 0") - return err - } - log.Print("Preparing notifyWatchers statement.") notifyWatchersStmt, err = db.Prepare("INSERT INTO [activity_stream_matches] ([watcher],[asid]) SELECT [activity_subscriptions].[user],[activity_stream].[asid] FROM [activity_stream] INNER JOIN [activity_subscriptions] ON [activity_subscriptions].[targetType] = [activity_stream].[elementType] AND [activity_subscriptions].[targetID] = [activity_stream].[elementID] AND [activity_subscriptions].[user] != [activity_stream].[actor] WHERE [asid] = ?1") if err != nil { diff --git a/gen_mysql.go b/gen_mysql.go index 78bff293..811e6c74 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -6,7 +6,7 @@ package main import "log" import "database/sql" -import "./query_gen/lib" +//import "./query_gen/lib" // nolint var getUserStmt *sql.Stmt @@ -67,13 +67,10 @@ var createForumStmt *sql.Stmt var addForumPermsToForumStmt *sql.Stmt var addPluginStmt *sql.Stmt var addThemeStmt *sql.Stmt -var createGroupStmt *sql.Stmt var addModlogEntryStmt *sql.Stmt var addAdminlogEntryStmt *sql.Stmt var addAttachmentStmt *sql.Stmt var createWordFilterStmt *sql.Stmt -var addForumPermsToGroupStmt *qgen.MySQLUpsertCallback -var replaceScheduleGroupStmt *qgen.MySQLUpsertCallback var addRepliesToTopicStmt *sql.Stmt var removeRepliesFromTopicStmt *sql.Stmt var addTopicsToForumStmt *sql.Stmt @@ -107,6 +104,7 @@ var updatePluginStmt *sql.Stmt var updatePluginInstallStmt *sql.Stmt var updateThemeStmt *sql.Stmt var updateUserStmt *sql.Stmt +var updateUserGroupStmt *sql.Stmt var updateGroupPermsStmt *sql.Stmt var updateGroupRankStmt *sql.Stmt var updateGroupStmt *sql.Stmt @@ -118,15 +116,11 @@ var bumpSyncStmt *sql.Stmt var deleteUserStmt *sql.Stmt var deleteReplyStmt *sql.Stmt var deleteProfileReplyStmt *sql.Stmt -var deleteForumPermsByForumStmt *sql.Stmt var deleteActivityStreamMatchStmt *sql.Stmt var deleteWordFilterStmt *sql.Stmt var reportExistsStmt *sql.Stmt var groupCountStmt *sql.Stmt var modlogCountStmt *sql.Stmt -var addForumPermsToForumAdminsStmt *sql.Stmt -var addForumPermsToForumStaffStmt *sql.Stmt -var addForumPermsToForumMembersStmt *sql.Stmt var notifyWatchersStmt *sql.Stmt // nolint @@ -483,12 +477,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing createGroup statement.") - createGroupStmt, err = db.Prepare("INSERT INTO `users_groups`(`name`,`tag`,`is_admin`,`is_mod`,`is_banned`,`permissions`) VALUES (?,?,?,?,?,?)") - if err != nil { - return err - } - log.Print("Preparing addModlogEntry statement.") addModlogEntryStmt, err = db.Prepare("INSERT INTO `moderation_logs`(`action`,`elementID`,`elementType`,`ipaddress`,`actorID`,`doneAt`) VALUES (?,?,?,?,?,UTC_TIMESTAMP())") if err != nil { @@ -513,18 +501,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing addForumPermsToGroup statement.") - addForumPermsToGroupStmt, err = qgen.PrepareMySQLUpsertCallback(db, "INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE `gid` = ? AND `fid` = ? AND `preset` = ? AND `permissions` = ?") - if err != nil { - return err - } - - log.Print("Preparing replaceScheduleGroup statement.") - replaceScheduleGroupStmt, err = qgen.PrepareMySQLUpsertCallback(db, "INSERT INTO `users_groups_scheduler`(`uid`,`set_group`,`issued_by`,`issued_at`,`revert_at`,`temporary`) VALUES (?,?,?,UTC_TIMESTAMP(),?,?) ON DUPLICATE KEY UPDATE `uid` = ? AND `set_group` = ? AND `issued_by` = ? AND `issued_at` = UTC_TIMESTAMP() AND `revert_at` = ? AND `temporary` = ?") - if err != nil { - return err - } - log.Print("Preparing addRepliesToTopic statement.") addRepliesToTopicStmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyBy` = ?,`lastReplyAt` = UTC_TIMESTAMP() WHERE `tid` = ?") if err != nil { @@ -723,6 +699,12 @@ func _gen_mysql() (err error) { return err } + log.Print("Preparing updateUserGroup statement.") + updateUserGroupStmt, err = db.Prepare("UPDATE `users` SET `group` = ? WHERE `uid` = ?") + if err != nil { + return err + } + log.Print("Preparing updateGroupPerms statement.") updateGroupPermsStmt, err = db.Prepare("UPDATE `users_groups` SET `permissions` = ? WHERE `gid` = ?") if err != nil { @@ -789,12 +771,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing deleteForumPermsByForum statement.") - deleteForumPermsByForumStmt, err = db.Prepare("DELETE FROM `forums_permissions` WHERE `fid` = ?") - if err != nil { - return err - } - log.Print("Preparing deleteActivityStreamMatch statement.") deleteActivityStreamMatchStmt, err = db.Prepare("DELETE FROM `activity_stream_matches` WHERE `watcher` = ? AND `asid` = ?") if err != nil { @@ -825,24 +801,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing addForumPermsToForumAdmins statement.") - addForumPermsToForumAdminsStmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 1") - if err != nil { - return err - } - - log.Print("Preparing addForumPermsToForumStaff statement.") - addForumPermsToForumStaffStmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 0 AND `is_mod` = 1") - if err != nil { - return err - } - - log.Print("Preparing addForumPermsToForumMembers statement.") - addForumPermsToForumMembersStmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 0 AND `is_mod` = 0 AND `is_banned` = 0") - if err != nil { - return err - } - log.Print("Preparing notifyWatchers statement.") notifyWatchersStmt, err = db.Prepare("INSERT INTO `activity_stream_matches`(`watcher`,`asid`) SELECT `activity_subscriptions`.`user`, `activity_stream`.`asid` FROM `activity_stream` INNER JOIN `activity_subscriptions` ON `activity_subscriptions`.`targetType` = `activity_stream`.`elementType` AND `activity_subscriptions`.`targetID` = `activity_stream`.`elementID` AND `activity_subscriptions`.`user` != `activity_stream`.`actor` WHERE `asid` = ?") if err != nil { diff --git a/gen_pgsql.go b/gen_pgsql.go index eaae16c0..b4b33281 100644 --- a/gen_pgsql.go +++ b/gen_pgsql.go @@ -40,6 +40,7 @@ var updatePluginStmt *sql.Stmt var updatePluginInstallStmt *sql.Stmt var updateThemeStmt *sql.Stmt var updateUserStmt *sql.Stmt +var updateUserGroupStmt *sql.Stmt var updateGroupPermsStmt *sql.Stmt var updateGroupRankStmt *sql.Stmt var updateGroupStmt *sql.Stmt @@ -253,6 +254,12 @@ func _gen_pgsql() (err error) { return err } + log.Print("Preparing updateUserGroup statement.") + updateUserGroupStmt, err = db.Prepare("UPDATE `users` SET `group` = ? WHERE `uid` = ?") + if err != nil { + return err + } + log.Print("Preparing updateGroupPerms statement.") updateGroupPermsStmt, err = db.Prepare("UPDATE `users_groups` SET `permissions` = ? WHERE `gid` = ?") if err != nil { diff --git a/gen_tables.go b/gen_tables.go index e8fc1617..9af297c0 100644 --- a/gen_tables.go +++ b/gen_tables.go @@ -2,14 +2,14 @@ package main var dbTablePrimaryKeys = map[string]string{ - "forums":"fid", - "topics":"tid", - "attachments":"attachID", - "users_replies":"rid", - "word_filters":"wfid", - "users":"uid", "users_groups":"gid", "users_groups_scheduler":"uid", - "replies":"rid", + "topics":"tid", + "users_replies":"rid", "activity_stream":"asid", + "word_filters":"wfid", + "users":"uid", + "forums":"fid", + "replies":"rid", + "attachments":"attachID", } diff --git a/group.go b/group.go index 640f9264..23ac043a 100644 --- a/group.go +++ b/group.go @@ -27,6 +27,24 @@ type Group struct { CanSee []int // The IDs of the forums this group can see } +// TODO: Reload the group from the database rather than modifying it via it's pointer +func (group *Group) ChangeRank(isAdmin bool, isMod bool, isBanned bool) (err error) { + _, err = updateGroupRankStmt.Exec(isAdmin, isMod, isBanned, group.ID) + if err != nil { + return err + } + + group.IsAdmin = isAdmin + group.IsMod = isMod + if isAdmin || isMod { + group.IsBanned = false + } else { + group.IsBanned = isBanned + } + + return nil +} + // ! Ahem, don't listen to the comment below. It's not concurrency safe right now. // Copy gives you a non-pointer concurrency safe copy of the group func (group *Group) Copy() Group { diff --git a/group_store.go b/group_store.go index ad95ff74..7ad8d8a3 100644 --- a/group_store.go +++ b/group_store.go @@ -6,6 +6,8 @@ import ( "errors" "log" "sync" + + "./query_gen/lib" ) var groupCreateMutex sync.Mutex @@ -19,7 +21,7 @@ type GroupStore interface { Get(id int) (*Group, error) GetCopy(id int) (Group, error) Exists(id int) bool - Create(groupName string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) + Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) GetAll() ([]*Group, error) GetRange(lower int, higher int) ([]*Group, error) } @@ -71,8 +73,11 @@ func (mgs *MemoryGroupStore) LoadGroups() error { log.Print(group.Name + ": ") log.Printf("%+v\n", group.PluginPerms) } - //group.Perms.ExtData = make(map[string]bool) + // TODO: Can we optimise the bit where this cascades down to the user now? + if group.IsAdmin || group.IsMod { + group.IsBanned = false + } mgs.groups = append(mgs.groups, &group) } err = rows.Err() @@ -110,15 +115,26 @@ func (mgs *MemoryGroupStore) GetCopy(gid int) (Group, error) { } func (mgs *MemoryGroupStore) Exists(gid int) bool { - return (gid <= mgs.groupCapCount) && (gid > -1) && mgs.groups[gid].Name != "" + return (gid <= mgs.groupCapCount) && (gid >= 0) && mgs.groups[gid].Name != "" } -func (mgs *MemoryGroupStore) Create(groupName string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) { +// ? Allow two groups with the same name? +func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) { groupCreateMutex.Lock() defer groupCreateMutex.Unlock() var permstr = "{}" - res, err := createGroupStmt.Exec(groupName, tag, isAdmin, isMod, isBanned, permstr) + tx, err := db.Begin() + if err != nil { + return 0, err + } + defer tx.Rollback() + + insertTx, err := qgen.Builder.SimpleInsertTx(tx, "users_groups", "name, tag, is_admin, is_mod, is_banned, permissions", "?,?,?,?,?,?") + if err != nil { + return 0, err + } + res, err := insertTx.Exec(name, tag, isAdmin, isMod, isBanned, permstr) if err != nil { return 0, err } @@ -138,14 +154,14 @@ func (mgs *MemoryGroupStore) Create(groupName string, tag string, isAdmin bool, runVhook("create_group_preappend", &pluginPerms, &pluginPermsBytes) } - mgs.groups = append(mgs.groups, &Group{gid, groupName, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList}) - // Generate the forum permissions based on the presets... fdata, err := fstore.GetAll() if err != nil { return 0, err } + var presetSet = make(map[int]string) + var permSet = make(map[int]ForumPerms) permUpdateMutex.Lock() defer permUpdateMutex.Unlock() for _, forum := range fdata { @@ -161,23 +177,38 @@ func (mgs *MemoryGroupStore) Create(groupName string, tag string, isAdmin bool, } permmap := presetToPermmap(forum.Preset) - permitem := permmap[thePreset] - permitem.Overrides = true - permstr, err := json.Marshal(permitem) - if err != nil { - return gid, err - } - perms := string(permstr) - _, err = addForumPermsToGroupStmt.Exec(gid, forum.ID, forum.Preset, perms) - if err != nil { - return gid, err - } + permItem := permmap[thePreset] + permItem.Overrides = true + permSet[forum.ID] = permItem + presetSet[forum.ID] = forum.Preset + } + + err = replaceForumPermsForGroupTx(tx, gid, presetSet, permSet) + if err != nil { + return 0, err + } + + err = tx.Commit() + if err != nil { + return 0, err + } + + // TODO: Can we optimise the bit where this cascades down to the user now? + if isAdmin || isMod { + isBanned = false + } + + mgs.groups = append(mgs.groups, &Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList}) + mgs.groupCapCount++ + + for _, forum := range fdata { err = rebuildForumPermissions(forum.ID) if err != nil { return gid, err } } + return gid, nil } diff --git a/install/install/mysql.go b/install/install/mysql.go index f34f3834..d194581e 100644 --- a/install/install/mysql.go +++ b/install/install/mysql.go @@ -99,7 +99,7 @@ func (ins *MysqlInstaller) InitDatabase() (err error) { return nil } -func (ins *MysqlInstaller) TableDefs() error { +func (ins *MysqlInstaller) TableDefs() (err error) { //fmt.Println("Creating the tables") files, _ := ioutil.ReadDir("./schema/mysql/") for _, f := range files { @@ -115,6 +115,13 @@ func (ins *MysqlInstaller) TableDefs() error { } table = strings.TrimSuffix(table, ext) + // ? - This is mainly here for tests, although it might allow the installer to overwrite a production database, so we might want to proceed with caution + _, err = ins.db.Exec("DROP TABLE IF EXISTS `" + table + "`;") + if err != nil { + fmt.Println("Failed query:", "DROP TABLE IF EXISTS `"+table+"`;") + return err + } + fmt.Println("Creating table '" + table + "'") data, err := ioutil.ReadFile("./schema/mysql/" + f.Name()) if err != nil { diff --git a/main.go b/main.go index 3f67117b..d73124f4 100644 --- a/main.go +++ b/main.go @@ -55,6 +55,12 @@ var allowedFileExts = StringList{ var imageFileExts = StringList{ "png", "jpg", "jpeg", "svg", "bmp", "gif", "tif", "webp", "apng", } +var archiveFileExts = StringList{ + "bz2", "zip", "gz", "7z", "tar", "cab", +} +var executableFileExts = StringList{ + "exe", "jar", "phar", "shar", "iso", +} // TODO: Write a test for this func (slice StringList) Contains(needle string) bool { @@ -70,6 +76,16 @@ var staticFiles = make(map[string]SFile) var logWriter = io.MultiWriter(os.Stderr) func main() { + // TODO: Recover from panics + /*defer func() { + r := recover() + if r != nil { + log.Print(r) + debug.PrintStack() + return + } + }()*/ + // TODO: Have a file for each run with the time/date the server started as the file name? // TODO: Log panics with recover() f, err := os.OpenFile("./operations.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) @@ -178,7 +194,6 @@ func main() { // TODO: Automatically lock topics, if they're really old, and the associated setting is enabled. // TODO: Publish scheduled posts. - // TODO: Delete the empty users_groups_scheduler entries // TODO: Add a plugin hook here } diff --git a/misc_test.go b/misc_test.go index 596e9e3b..3a8660f8 100644 --- a/misc_test.go +++ b/misc_test.go @@ -1,24 +1,26 @@ package main import ( + "bytes" "fmt" + "net/http/httptest" "runtime/debug" "strconv" "testing" "time" ) -func recordMustExist(t *testing.T, err error, errmsg string) { +func recordMustExist(t *testing.T, err error, errmsg string, args ...interface{}) { if err == ErrNoRows { - t.Error(errmsg) + t.Errorf(errmsg, args...) } else if err != nil { t.Fatal(err) } } -func recordMustNotExist(t *testing.T, err error, errmsg string) { +func recordMustNotExist(t *testing.T, err error, errmsg string, args ...interface{}) { if err == nil { - t.Error(errmsg) + t.Errorf(errmsg, args...) } else if err != ErrNoRows { t.Fatal(err) } @@ -42,50 +44,42 @@ func TestUserStore(t *testing.T) { userStoreTest(t, 3) } func userStoreTest(t *testing.T, newUserID int) { - var user *User - var err error - ucache, hasCache := users.(UserCache) - if hasCache && ucache.Length() != 0 { - t.Error("Initial ucache length isn't zero") + // Go doesn't have short-circuiting, so this'll allow us to do one liner tests + if !hasCache { + ucache = &NullUserStore{} } + expect(t, (!hasCache || ucache.Length() == 0), fmt.Sprintf("The initial ucache length should be zero, not %d", ucache.Length())) - _, err = users.Get(-1) + _, err := users.Get(-1) recordMustNotExist(t, err, "UID #-1 shouldn't exist") - - if hasCache && ucache.Length() != 0 { - t.Error("There shouldn't be anything in the user cache") - } + expect(t, !hasCache || ucache.Length() == 0, fmt.Sprintf("We found %d items in the user cache and it's supposed to be empty", ucache.Length())) _, err = users.Get(0) recordMustNotExist(t, err, "UID #0 shouldn't exist") + expect(t, !hasCache || ucache.Length() == 0, fmt.Sprintf("We found %d items in the user cache and it's supposed to be empty", ucache.Length())) - if hasCache && ucache.Length() != 0 { - t.Error("There shouldn't be anything in the user cache") - } - - user, err = users.Get(1) + user, err := users.Get(1) recordMustExist(t, err, "Couldn't find UID #1") - if user.ID != 1 { - t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.") - } - if user.Name != "Admin" { - t.Error("user.Name should be 'Admin', not '" + user.Name + "'") - } - if user.Group != 1 { - t.Error("Admin should be in group 1") - } + expect(t, user.ID == 1, fmt.Sprintf("user.ID should be 1. Got '%d' instead.", user.ID)) + expect(t, user.Name == "Admin", fmt.Sprintf("user.Name should be 'Admin', not '%s'", user.Name)) + expect(t, user.Group == 1, "Admin should be in group 1") + expect(t, user.IsSuperAdmin, "Admin should be a super admin") + expect(t, user.IsAdmin, "Admin should be an admin") + expect(t, user.IsSuperMod, "Admin should be a super mod") + expect(t, user.IsMod, "Admin should be a mod") + expect(t, !user.IsBanned, "Admin should not be banned") - user, err = users.Get(newUserID) + _, err = users.Get(newUserID) recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't exist", newUserID)) if hasCache { expectIntToBeX(t, ucache.Length(), 1, "User cache length should be 1, not %d") - user, err = ucache.CacheGet(-1) + _, err = ucache.CacheGet(-1) recordMustNotExist(t, err, "UID #-1 shouldn't exist, even in the cache") - user, err = ucache.CacheGet(0) + _, err = ucache.CacheGet(0) recordMustNotExist(t, err, "UID #0 shouldn't exist, even in the cache") user, err = ucache.CacheGet(1) recordMustExist(t, err, "Couldn't find UID #1 in the cache") @@ -97,8 +91,8 @@ func userStoreTest(t *testing.T, newUserID int) { t.Error("user.Name should be 'Admin', not '" + user.Name + "'") } - user, err = ucache.CacheGet(newUserID) - recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't exist, even in the cache", newUserID)) + _, err = ucache.CacheGet(newUserID) + recordMustNotExist(t, err, "UID #%d shouldn't exist, even in the cache", newUserID) ucache.Flush() expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") @@ -147,9 +141,8 @@ func userStoreTest(t *testing.T, newUserID int) { recordMustExist(t, err, "Couldn't find UID #1 in the cache") if user.ID != 1 { - t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.") + t.Errorf("user.ID does not match the requested UID. Got '%d' instead.", user.ID) } - ucache.Flush() } @@ -158,120 +151,281 @@ func userStoreTest(t *testing.T, newUserID int) { expect(t, users.Exists(1), "UID #1 should exist") expect(t, !users.Exists(newUserID), fmt.Sprintf("UID #%d shouldn't exist", newUserID)) - if hasCache { - expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") - } + expect(t, !hasCache || ucache.Length() == 0, fmt.Sprintf("User cache length should be 0, not %d", ucache.Length())) expectIntToBeX(t, users.GlobalCount(), 1, "The number of users should be one, not %d") var awaitingActivation = 5 - uid, err := users.Create("Sam", "ReallyBadPassword", "sam@localhost.loc", awaitingActivation, 0) - if err != nil { - t.Error(err) - } - if uid != newUserID { - t.Errorf("The UID of the new user should be %d", newUserID) - } - if !users.Exists(newUserID) { - t.Errorf("UID #%d should exist", newUserID) - } + uid, err := users.Create("Sam", "ReallyBadPassword", "sam@localhost.loc", awaitingActivation, false) + expectNilErr(t, err) + expect(t, uid == newUserID, fmt.Sprintf("The UID of the new user should be %d", newUserID)) + expect(t, users.Exists(newUserID), fmt.Sprintf("UID #%d should exist", newUserID)) user, err = users.Get(newUserID) - recordMustExist(t, err, fmt.Sprintf("Couldn't find UID #%d", newUserID)) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) if user.ID != newUserID { t.Errorf("The UID of the user record should be %d", newUserID) } - if user.Name != "Sam" { - t.Error("The user should be named Sam") - } + + expect(t, user.Name == "Sam", "The user should be named Sam") + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, !user.IsSuperMod, "Sam should not be a super mod") + expect(t, !user.IsMod, "Sam should not be a mod") + expect(t, !user.IsBanned, "Sam should not be banned") expectIntToBeX(t, user.Group, 5, "Sam should be in group 5") if hasCache { expectIntToBeX(t, ucache.Length(), 1, "User cache length should be 1, not %d") user, err = ucache.CacheGet(newUserID) - recordMustExist(t, err, fmt.Sprintf("Couldn't find UID #%d in the cache", newUserID)) - if user.ID != newUserID { - t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.") - } + recordMustExist(t, err, "Couldn't find UID #%d in the cache", newUserID) + expect(t, user.ID == newUserID, fmt.Sprintf("user.ID does not match the requested UID. Got '%d' instead.", user.ID)) } err = user.Activate() - if err != nil { - t.Error(err) - } + expectNilErr(t, err) expectIntToBeX(t, user.Group, 5, "Sam should still be in group 5 in this copy") // ? - What if we change the caching mechanism so it isn't hard purged and reloaded? We'll deal with that when we come to it, but for now, this is a sign of a cache bug if hasCache { expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") _, err = ucache.CacheGet(newUserID) - recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't be in the cache", newUserID)) + recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID) } user, err = users.Get(newUserID) - recordMustExist(t, err, fmt.Sprintf("Couldn't find UID #%d", newUserID)) - if user.ID != newUserID { - t.Errorf("The UID of the user record should be %d", newUserID) - } - expectIntToBeX(t, user.Group, config.DefaultGroup, "Sam should be in group "+strconv.Itoa(config.DefaultGroup)) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) + + expect(t, user.ID == newUserID, fmt.Sprintf("The UID of the user record should be %d, not %d", newUserID, user.ID)) + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, !user.IsSuperMod, "Sam should not be a super mod") + expect(t, !user.IsMod, "Sam should not be a mod") + expect(t, !user.IsBanned, "Sam should not be banned") + + expect(t, user.Group == config.DefaultGroup, fmt.Sprintf("Sam should be in group %d, not %d", config.DefaultGroup, user.Group)) // Permanent ban duration, _ := time.ParseDuration("0") // TODO: Attempt a double ban, double activation, and double unban err = user.Ban(duration, 1) - if err != nil { - t.Error(err) - } - expectIntToBeX(t, user.Group, config.DefaultGroup, "Sam should still be in the default group in this copy") + expectNilErr(t, err) + expect(t, user.Group == config.DefaultGroup, fmt.Sprintf("Sam should be in group %d, not %d", config.DefaultGroup, user.Group)) if hasCache { expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") _, err = ucache.CacheGet(2) - recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't be in the cache", newUserID)) + recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID) } user, err = users.Get(newUserID) - recordMustExist(t, err, fmt.Sprintf("Couldn't find UID #%d", newUserID)) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) if user.ID != newUserID { t.Errorf("The UID of the user record should be %d", newUserID) } - expectIntToBeX(t, user.Group, banGroup, "Sam should be in group "+strconv.Itoa(banGroup)) + + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, !user.IsSuperMod, "Sam should not be a super mod") + expect(t, !user.IsMod, "Sam should not be a mod") + expect(t, user.IsBanned, "Sam should be banned") + + expectIntToBeX(t, user.Group, banGroup, "Sam should be in group %d") // TODO: Do tests against the scheduled updates table and the task system to make sure the ban exists there and gets revoked when it should err = user.Unban() - if err != nil { - t.Error(err) - } + expectNilErr(t, err) expectIntToBeX(t, user.Group, banGroup, "Sam should still be in the ban group in this copy") if hasCache { expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") _, err = ucache.CacheGet(newUserID) - recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't be in the cache", newUserID)) + recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID) } user, err = users.Get(newUserID) - recordMustExist(t, err, fmt.Sprintf("Couldn't find UID #%d", newUserID)) - if user.ID != newUserID { - t.Errorf("The UID of the user record should be %d", newUserID) - } - expectIntToBeX(t, user.Group, config.DefaultGroup, "Sam should be back in group "+strconv.Itoa(config.DefaultGroup)) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) + expectIntToBeX(t, user.ID, newUserID, "The UID of the user record should be %d") + + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, !user.IsSuperMod, "Sam should not be a super mod") + expect(t, !user.IsMod, "Sam should not be a mod") + expect(t, !user.IsBanned, "Sam should not be banned") + + expectIntToBeX(t, user.Group, config.DefaultGroup, "Sam should be back in group %d") + + var reportsForumID = 1 + var generalForumID = 2 + dummyResponseRecorder := httptest.NewRecorder() + bytesBuffer := bytes.NewBuffer([]byte("")) + dummyRequest1 := httptest.NewRequest("", "/forum/1", bytesBuffer) + dummyRequest2 := httptest.NewRequest("", "/forum/2", bytesBuffer) + + err = user.ChangeGroup(1) + expectNilErr(t, err) + expect(t, user.Group == config.DefaultGroup, "Someone's mutated this pointer elsewhere") + + user, err = users.Get(newUserID) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) + expectIntToBeX(t, user.ID, newUserID, "The UID of the user record should be %d") + var user2 *User = getDummyUser() + *user2 = *user + + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, user.IsAdmin, "Sam should be an admin") + expect(t, user.IsSuperMod, "Sam should be a super mod") + expect(t, user.IsMod, "Sam should be a mod") + expect(t, !user.IsBanned, "Sam should not be banned") + + _, success := forumUserCheck(dummyResponseRecorder, dummyRequest1, user, reportsForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, user.Perms.ViewTopic, "Admins should be able to access the reports forum") + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest2, user2, generalForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, user2.Perms.ViewTopic, "Sam should be able to access the general forum") + + err = user.ChangeGroup(2) + expectNilErr(t, err) + expect(t, user.Group == 1, "Someone's mutated this pointer elsewhere") + + user, err = users.Get(newUserID) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) + expectIntToBeX(t, user.ID, newUserID, "The UID of the user record should be %d") + user2 = getDummyUser() + *user2 = *user + + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, user.IsSuperMod, "Sam should be a super mod") + expect(t, user.IsMod, "Sam should be a mod") + expect(t, !user.IsBanned, "Sam should not be banned") + + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest1, user, reportsForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, user.Perms.ViewTopic, "Mods should be able to access the reports forum") + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest2, user2, generalForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, user2.Perms.ViewTopic, "Sam should be able to access the general forum") + + err = user.ChangeGroup(3) + expectNilErr(t, err) + expect(t, user.Group == 2, "Someone's mutated this pointer elsewhere") + + user, err = users.Get(newUserID) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) + expectIntToBeX(t, user.ID, newUserID, "The UID of the user record should be %d") + user2 = getDummyUser() + *user2 = *user + + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, !user.IsSuperMod, "Sam should not be a super mod") + expect(t, !user.IsMod, "Sam should not be a mod") + expect(t, !user.IsBanned, "Sam should not be banned") + + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest1, user, reportsForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, !user.Perms.ViewTopic, "Members shouldn't be able to access the reports forum") + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest2, user2, generalForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, user2.Perms.ViewTopic, "Sam should be able to access the general forum") + expect(t, user.Perms.ViewTopic != user2.Perms.ViewTopic, "user.Perms.ViewTopic and user2.Perms.ViewTopic should never match") + + err = user.ChangeGroup(4) + expectNilErr(t, err) + expect(t, user.Group == 3, "Someone's mutated this pointer elsewhere") + + user, err = users.Get(newUserID) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) + expectIntToBeX(t, user.ID, newUserID, "The UID of the user record should be %d") + user2 = getDummyUser() + *user2 = *user + + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, !user.IsSuperMod, "Sam should not be a super mod") + expect(t, !user.IsMod, "Sam should not be a mod") + expect(t, user.IsBanned, "Sam should be banned") + + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest1, user, reportsForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, !user.Perms.ViewTopic, "Members shouldn't be able to access the reports forum") + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest2, user2, generalForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, user2.Perms.ViewTopic, "Sam should be able to access the general forum") + + err = user.ChangeGroup(5) + expectNilErr(t, err) + expect(t, user.Group == 4, "Someone's mutated this pointer elsewhere") + + user, err = users.Get(newUserID) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) + expectIntToBeX(t, user.ID, newUserID, "The UID of the user record should be %d") + user2 = getDummyUser() + *user2 = *user + + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, !user.IsSuperMod, "Sam should not be a super mod") + expect(t, !user.IsMod, "Sam should not be a mod") + expect(t, !user.IsBanned, "Sam should not be banned") + + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest1, user, reportsForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, !user.Perms.ViewTopic, "Members shouldn't be able to access the reports forum") + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest2, user2, generalForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, user2.Perms.ViewTopic, "Sam should be able to access the general forum") + + err = user.ChangeGroup(6) + expectNilErr(t, err) + expect(t, user.Group == 5, "Someone's mutated this pointer elsewhere") + + user, err = users.Get(newUserID) + recordMustExist(t, err, "Couldn't find UID #%d", newUserID) + expectIntToBeX(t, user.ID, newUserID, "The UID of the user record should be %d") + user2 = getDummyUser() + *user2 = *user + + expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") + expect(t, !user.IsAdmin, "Sam should not be an admin") + expect(t, !user.IsSuperMod, "Sam should not be a super mod") + expect(t, !user.IsMod, "Sam should not be a mod") + expect(t, !user.IsBanned, "Sam should not be banned") + + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest1, user, reportsForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, !user.Perms.ViewTopic, "Members shouldn't be able to access the reports forum") + _, success = forumUserCheck(dummyResponseRecorder, dummyRequest2, user2, generalForumID) + expect(t, success, "There shouldn't be any errors in forumUserCheck") + expect(t, user2.Perms.ViewTopic, "Sam should be able to access the general forum") + + err = user.ChangeGroup(config.DefaultGroup) + expectNilErr(t, err) + expect(t, user.Group == 6, "Someone's mutated this pointer elsewhere") err = user.Delete() - if err != nil { - t.Error(err) - } - expect(t, !users.Exists(newUserID), fmt.Sprintf("UID #%d should not longer exist", newUserID)) + expectNilErr(t, err) + expect(t, !users.Exists(newUserID), fmt.Sprintf("UID #%d should no longer exist", newUserID)) if hasCache { expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") _, err = ucache.CacheGet(newUserID) - recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't be in the cache", newUserID)) + recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID) } - // TODO: Works for now but might cause a data race with the task system - //ResetTables() + _, err = users.Get(newUserID) + recordMustNotExist(t, err, "UID #%d shouldn't exist", newUserID) +} + +// TODO: Add an error message to this? +func expectNilErr(t *testing.T, item error) { + if item != nil { + debug.PrintStack() + t.Fatal(item) + } } func expectIntToBeX(t *testing.T, item int, expect int, errmsg string) { @@ -284,7 +438,7 @@ func expectIntToBeX(t *testing.T, item int, expect int, errmsg string) { func expect(t *testing.T, item bool, errmsg string) { if !item { debug.PrintStack() - t.Fatalf(errmsg) + t.Fatal(errmsg) } } @@ -309,18 +463,10 @@ func topicStoreTest(t *testing.T) { var err error _, err = topics.Get(-1) - if err == nil { - t.Error("TID #-1 shouldn't exist") - } else if err != ErrNoRows { - t.Fatal(err) - } + recordMustNotExist(t, err, "TID #-1 shouldn't exist") _, err = topics.Get(0) - if err == nil { - t.Error("TID #0 shouldn't exist") - } else if err != ErrNoRows { - t.Fatal(err) - } + recordMustNotExist(t, err, "TID #0 shouldn't exist") topic, err = topics.Get(1) recordMustExist(t, err, "Couldn't find TID #1") @@ -361,24 +507,13 @@ func TestForumStore(t *testing.T) { initPlugins() } - var forum *Forum - var err error + _, err := fstore.Get(-1) + recordMustNotExist(t, err, "FID #-1 shouldn't exist") - _, err = fstore.Get(-1) - if err == nil { - t.Error("FID #-1 shouldn't exist") - } else if err != ErrNoRows { - t.Fatal(err) - } + _, err = fstore.Get(0) + recordMustNotExist(t, err, "FID #0 shouldn't exist") - forum, err = fstore.Get(0) - if err == nil { - t.Error("FID #0 shouldn't exist") - } else if err != ErrNoRows { - t.Fatal(err) - } - - forum, err = fstore.Get(1) + forum, err := fstore.Get(1) recordMustExist(t, err, "Couldn't find FID #1") if forum.ID != 1 { @@ -417,50 +552,95 @@ func TestGroupStore(t *testing.T) { initPlugins() } - var group *Group - var err error - - _, err = gstore.Get(-1) - if err == nil { - t.Error("GID #-1 shouldn't exist") - } else if err != ErrNoRows { - t.Fatal(err) - } + _, err := gstore.Get(-1) + recordMustNotExist(t, err, "GID #-1 shouldn't exist") // TODO: Refactor the group store to remove GID #0 - group, err = gstore.Get(0) + group, err := gstore.Get(0) recordMustExist(t, err, "Couldn't find GID #0") if group.ID != 0 { - t.Error("group.ID doesn't not match the requested GID. Got '" + strconv.Itoa(group.ID) + "' instead.") - } - if group.Name != "Unknown" { - t.Error("GID #0 is named '" + group.Name + "' and not 'Unknown'") + t.Errorf("group.ID doesn't not match the requested GID. Got '%d' instead.", group.ID) } + expect(t, group.Name == "Unknown", fmt.Sprintf("GID #0 is named '%s' and not 'Unknown'", group.Name)) group, err = gstore.Get(1) recordMustExist(t, err, "Couldn't find GID #1") if group.ID != 1 { - t.Error("group.ID doesn't not match the requested GID. Got '" + strconv.Itoa(group.ID) + "' instead.'") + t.Errorf("group.ID doesn't not match the requested GID. Got '%d' instead.'", group.ID) } - _ = group - ok := gstore.Exists(-1) - if ok { - t.Error("GID #-1 shouldn't exist") - } + expect(t, !ok, "GID #-1 shouldn't exist") + // 0 aka Unknown, for system posts and other oddities ok = gstore.Exists(0) - if !ok { - t.Error("GID #0 should exist") - } + expect(t, ok, "GID #0 should exist") ok = gstore.Exists(1) - if !ok { - t.Error("GID #1 should exist") - } + expect(t, ok, "GID #1 should exist") + + var isAdmin = true + var isMod = true + var isBanned = false + gid, err := gstore.Create("Testing", "Test", isAdmin, isMod, isBanned) + expectNilErr(t, err) + expect(t, gstore.Exists(gid), "The group we just made doesn't exist") + + group, err = gstore.Get(gid) + expectNilErr(t, err) + expect(t, group.ID == gid, "The group ID should match the requested ID") + expect(t, group.IsAdmin, "This should be an admin group") + expect(t, group.IsMod, "This should be a mod group") + expect(t, !group.IsBanned, "This shouldn't be a ban group") + + isAdmin = false + isMod = true + isBanned = true + gid, err = gstore.Create("Testing 2", "Test", isAdmin, isMod, isBanned) + expectNilErr(t, err) + expect(t, gstore.Exists(gid), "The group we just made doesn't exist") + + group, err = gstore.Get(gid) + expectNilErr(t, err) + expect(t, group.ID == gid, "The group ID should match the requested ID") + expect(t, !group.IsAdmin, "This should not be an admin group") + expect(t, group.IsMod, "This should be a mod group") + expect(t, !group.IsBanned, "This shouldn't be a ban group") + + // TODO: Make sure this pointer doesn't change once we refactor the group store to stop updating the pointer + err = group.ChangeRank(false, false, true) + expectNilErr(t, err) + + group, err = gstore.Get(gid) + expectNilErr(t, err) + expect(t, group.ID == gid, "The group ID should match the requested ID") + expect(t, !group.IsAdmin, "This shouldn't be an admin group") + expect(t, !group.IsMod, "This shouldn't be a mod group") + expect(t, group.IsBanned, "This should be a ban group") + + err = group.ChangeRank(true, true, true) + expectNilErr(t, err) + + group, err = gstore.Get(gid) + expectNilErr(t, err) + expect(t, group.ID == gid, "The group ID should match the requested ID") + expect(t, group.IsAdmin, "This should be an admin group") + expect(t, group.IsMod, "This should be a mod group") + expect(t, !group.IsBanned, "This shouldn't be a ban group") + + err = group.ChangeRank(false, true, true) + expectNilErr(t, err) + + group, err = gstore.Get(gid) + expectNilErr(t, err) + expect(t, group.ID == gid, "The group ID should match the requested ID") + expect(t, !group.IsAdmin, "This shouldn't be an admin group") + expect(t, group.IsMod, "This should be a mod group") + expect(t, !group.IsBanned, "This shouldn't be a ban group") + + // TODO: Test group deletion } func TestReplyStore(t *testing.T) { @@ -471,28 +651,23 @@ func TestReplyStore(t *testing.T) { initPlugins() } - reply, err := rstore.Get(-1) - if err == nil { - t.Error("RID #-1 shouldn't exist") - } + _, err := rstore.Get(-1) + recordMustNotExist(t, err, "RID #-1 shouldn't exist") - reply, err = rstore.Get(0) - if err == nil { - t.Error("RID #0 shouldn't exist") - } + _, err = rstore.Get(0) + recordMustNotExist(t, err, "RID #0 shouldn't exist") + + reply, err := rstore.Get(1) + expectNilErr(t, err) - reply, err = rstore.Get(1) - if err != nil { - t.Fatal(err) - } if reply.ID != 1 { - t.Error("RID #1 has the wrong ID. It should be 1 not " + strconv.Itoa(reply.ID)) + t.Errorf("RID #1 has the wrong ID. It should be 1 not %d", reply.ID) } if reply.ParentID != 1 { - t.Error("The parent topic of RID #1 should be 1 not " + strconv.Itoa(reply.ParentID)) + t.Errorf("The parent topic of RID #1 should be 1 not %d", reply.ParentID) } if reply.CreatedBy != 1 { - t.Error("The creator of RID #1 should be 1 not " + strconv.Itoa(reply.CreatedBy)) + t.Errorf("The creator of RID #1 should be 1 not %d", reply.CreatedBy) } } @@ -505,14 +680,10 @@ func TestProfileReplyStore(t *testing.T) { } _, err := prstore.Get(-1) - if err == nil { - t.Error("RID #-1 shouldn't exist") - } + recordMustNotExist(t, err, "RID #-1 shouldn't exist") _, err = prstore.Get(0) - if err == nil { - t.Error("RID #0 shouldn't exist") - } + recordMustNotExist(t, err, "RID #0 shouldn't exist") } func TestSlugs(t *testing.T) { @@ -558,7 +729,7 @@ func TestAuth(t *testing.T) { /* No extra salt tests, we might not need this extra salt, as bcrypt has it's own? */ realPassword = "Madame Cassandra's Mystic Orb" - t.Log("Set real_password to '" + realPassword + "'") + t.Log("Set realPassword to '" + realPassword + "'") t.Log("Hashing the real password") hashedPassword, err = BcryptGeneratePasswordNoSalt(realPassword) if err != nil { diff --git a/mod_routes.go b/mod_routes.go index 21a5ad27..ea655b22 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -819,7 +819,10 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user User) { } err = targetUser.Unban() - if err == ErrNoRows { + if err == ErrNoTempGroup { + LocalError("The user you're trying to unban is not banned", w, r, user) + return + } else if err == ErrNoRows { LocalError("The user you're trying to unban no longer exists.", w, r, user) return } else if err != nil { diff --git a/pages.go b/pages.go index 525a3987..91c19adb 100644 --- a/pages.go +++ b/pages.go @@ -456,6 +456,7 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) msg = strings.Replace(msg, ":O", "😲", -1) msg = strings.Replace(msg, ":p", "😛", -1) msg = strings.Replace(msg, ":o", "😲", -1) + msg = strings.Replace(msg, ";)", "😉", -1) //msg = url_reg.ReplaceAllString(msg,"$2$3//$4") // Word filter list. E.g. Swear words and other things the admins don't like @@ -473,13 +474,12 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) var lastItem = 0 var i = 0 for ; len(msgbytes) > (i + 1); i++ { - //log.Print("Index:",i) + //log.Print("Index: ",i) //log.Print("Index Item: ",msgbytes[i]) //log.Print("string(msgbytes[i]): ",string(msgbytes[i])) //log.Print("End Index") if (i == 0 && (msgbytes[0] > 32)) || ((msgbytes[i] < 33) && (msgbytes[i+1] > 32)) { - //log.Print("IN") - //log.Print(msgbytes[i]) + //log.Print("IN ",msgbytes[i]) if (i != 0) || msgbytes[i] < 33 { i++ } @@ -509,12 +509,12 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) outbytes = append(outbytes, urlClose...) lastItem = i - //log.Print("string(msgbytes) ",string(msgbytes)) - //log.Print("msgbytes ",msgbytes) - //log.Print(msgbytes[lastItem - 1]) - //log.Print(lastItem - 1) - //log.Print(msgbytes[lastItem]) - //log.Print("lastItem ",lastItem) + //log.Print("string(msgbytes): ",string(msgbytes)) + //log.Print("msgbytes: ",msgbytes) + //log.Print("msgbytes[lastItem - 1]: ",msgbytes[lastItem - 1]) + //log.Print("lastItem - 1: ",lastItem - 1) + //log.Print("msgbytes[lastItem]: ",msgbytes[lastItem]) + //log.Print("lastItem: ",lastItem) } else if bytes.Equal(msgbytes[i+1:i+5], []byte("rid-")) { outbytes = append(outbytes, msgbytes[lastItem:i]...) i += 5 @@ -589,10 +589,10 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) //log.Print(string(msgbytes)) //log.Print(msgbytes) - //log.Print(msgbytes[lastItem - 1]) - //log.Print("lastItem - 1",lastItem - 1) - //log.Print("msgbytes[lastItem]",msgbytes[lastItem]) - //log.Print("lastItem",lastItem) + //log.Print("msgbytes[lastItem - 1]: ", msgbytes[lastItem - 1]) + //log.Print("lastItem - 1: ", lastItem - 1) + //log.Print("msgbytes[lastItem]: ", msgbytes[lastItem]) + //log.Print("lastItem: ", lastItem) } else if msgbytes[i] == 'h' || msgbytes[i] == 'f' || msgbytes[i] == 'g' { //log.Print("IN hfg") if msgbytes[i+1] == 't' && msgbytes[i+2] == 't' && msgbytes[i+3] == 'p' { @@ -616,10 +616,10 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) urlLen := partialURLBytesLen(msgbytes[i:]) if msgbytes[i+urlLen] > 32 { // space and invisibles //log.Print("INVALID URL") - //log.Print("msgbytes[i+urlLen]", msgbytes[i+urlLen]) - //log.Print("string(msgbytes[i+urlLen])", string(msgbytes[i+urlLen])) - //log.Print("msgbytes[i:i+urlLen]", msgbytes[i:i+urlLen]) - //log.Print("string(msgbytes[i:i+urlLen])", string(msgbytes[i:i+urlLen])) + //log.Print("msgbytes[i+urlLen]: ", msgbytes[i+urlLen]) + //log.Print("string(msgbytes[i+urlLen]): ", string(msgbytes[i+urlLen])) + //log.Print("msgbytes[i:i+urlLen]: ", msgbytes[i:i+urlLen]) + //log.Print("string(msgbytes[i:i+urlLen]): ", string(msgbytes[i:i+urlLen])) outbytes = append(outbytes, invalidURL...) i += urlLen continue @@ -673,18 +673,18 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) urlLen := partialURLBytesLen(msgbytes[i:]) if msgbytes[i+urlLen] > 32 { // space and invisibles //log.Print("INVALID URL") - //log.Print("msgbytes[i+urlLen]", msgbytes[i+urlLen]) - //log.Print("string(msgbytes[i+urlLen])", string(msgbytes[i+urlLen])) - //log.Print("msgbytes[i:i+urlLen]", msgbytes[i:i+urlLen]) - //log.Print("string(msgbytes[i:i+urlLen])", string(msgbytes[i:i+urlLen])) + //log.Print("msgbytes[i+urlLen]: ", msgbytes[i+urlLen]) + //log.Print("string(msgbytes[i+urlLen]): ", string(msgbytes[i+urlLen])) + //log.Print("msgbytes[i:i+urlLen]: ", msgbytes[i:i+urlLen]) + //log.Print("string(msgbytes[i:i+urlLen]): ", string(msgbytes[i:i+urlLen])) outbytes = append(outbytes, invalidURL...) i += urlLen continue } //log.Print("VALID URL") - //log.Print("msgbytes[i:i+urlLen]", msgbytes[i:i+urlLen]) - //log.Print("string(msgbytes[i:i+urlLen])", string(msgbytes[i:i+urlLen])) + //log.Print("msgbytes[i:i+urlLen]: ", msgbytes[i:i+urlLen]) + //log.Print("string(msgbytes[i:i+urlLen]): ", string(msgbytes[i:i+urlLen])) media, ok := parseMediaBytes(msgbytes[i : i+urlLen]) if !ok { outbytes = append(outbytes, invalidURL...) @@ -733,10 +733,10 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) } if lastItem != i && len(outbytes) != 0 { - //log.Print("lastItem: ",msgbytes[lastItem]) - //log.Print("lastItem index: ",lastItem) - //log.Print("i: ",i) - //log.Print("lastItem to end: ",msgbytes[lastItem:]) + //log.Print("lastItem: ", msgbytes[lastItem]) + //log.Print("lastItem index: ", lastItem) + //log.Print("i: ", i) + //log.Print("lastItem to end: ", msgbytes[lastItem:]) //log.Print("-----") calclen := len(msgbytes) - 10 if calclen <= lastItem { @@ -756,7 +756,7 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) } // TODO: Write a test for this -func regexParseMessage(msg string) string { +/*func regexParseMessage(msg string) string { msg = strings.Replace(msg, ":)", "😀", -1) msg = strings.Replace(msg, ":D", "😃", -1) msg = strings.Replace(msg, ":P", "😛", -1) @@ -766,7 +766,7 @@ func regexParseMessage(msg string) string { msg = runSshook("parse_assign", msg) } return msg -} +}*/ // 6, 7, 8, 6, 2, 7 // ftp://, http://, https:// git://, //, mailto: (not a URL, just here for length comparison purposes) diff --git a/panel_routes.go b/panel_routes.go index d9488c19..ae89d5d7 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -451,7 +451,6 @@ func routePanelForumsEditSubmit(w http.ResponseWriter, r *http.Request, user Use } } -// ! This probably misses the forumView cache func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user User, sfid string) { _, ok := SimplePanelUserCheck(w, r, &user) if !ok { @@ -509,24 +508,24 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use } group.Forums[fid] = fperms - perms, err := json.Marshal(fperms) - if err != nil { - InternalErrorJSQ(err, w, r, isJs) - return - } - - _, err = addForumPermsToGroupStmt.Exec(gid, fid, permPreset, perms) + err = replaceForumPermsForGroup(gid, map[int]string{fid: permPreset}, map[int]ForumPerms{fid: fperms}) if err != nil { InternalErrorJSQ(err, w, r, isJs) return } + // TODO: Add this and replaceForumPermsForGroup into a transaction? _, err = updateForumStmt.Exec(forum.Name, forum.Desc, forum.Active, "", fid) if err != nil { InternalErrorJSQ(err, w, r, isJs) return } - forum.Preset = "" + err = fstore.Reload(fid) + if err != nil { + // Log this? -- Another admin might have deleted it + LocalErrorJSQ("Unable to reload forum", w, r, user, isJs) + return + } } if !isJs { @@ -1347,7 +1346,7 @@ func routePanelGroups(w http.ResponseWriter, r *http.Request, user User) { perPage := 9 offset, page, lastPage := pageOffset(stats.Groups, page, perPage) - // Skip the System group + // Skip the 'Unknown' group offset++ var count int @@ -1436,15 +1435,16 @@ func routePanelGroupsEdit(w http.ResponseWriter, r *http.Request, user User, sgi } var rank string - if group.IsAdmin { + switch { + case group.IsAdmin: rank = "Admin" - } else if group.IsMod { + case group.IsMod: rank = "Mod" - } else if group.IsBanned { + case group.IsBanned: rank = "Banned" - } else if group.ID == 6 { + case group.ID == 6: rank = "Guest" - } else { + default: rank = "Member" } @@ -1619,54 +1619,28 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use LocalError("You need the EditGroupAdmin permission to designate this group as an admin group.", w, r, user) return } - - _, err = updateGroupRankStmt.Exec(1, 1, 0, gid) - if err != nil { - InternalError(err, w) - return - } - group.IsAdmin = true - group.IsMod = true - group.IsBanned = false + err = group.ChangeRank(true, true, false) case "Mod": if !user.Perms.EditGroupSuperMod { LocalError("You need the EditGroupSuperMod permission to designate this group as a super-mod group.", w, r, user) return } - - _, err = updateGroupRankStmt.Exec(0, 1, 0, gid) - if err != nil { - InternalError(err, w) - return - } - group.IsAdmin = false - group.IsMod = true - group.IsBanned = false + err = group.ChangeRank(false, true, false) case "Banned": - _, err = updateGroupRankStmt.Exec(0, 0, 1, gid) - if err != nil { - InternalError(err, w) - return - } - group.IsAdmin = false - group.IsMod = false - group.IsBanned = true + err = group.ChangeRank(false, false, true) case "Guest": LocalError("You can't designate a group as a guest group.", w, r, user) return case "Member": - _, err = updateGroupRankStmt.Exec(0, 0, 0, gid) - if err != nil { - InternalError(err, w) - return - } - group.IsAdmin = false - group.IsMod = false - group.IsBanned = false + err = group.ChangeRank(false, false, false) default: LocalError("Invalid group type.", w, r, user) return } + if err != nil { + InternalError(err, w) + return + } } _, err = updateGroupStmt.Exec(gname, gtag, gid) diff --git a/permissions.go b/permissions.go index 6a42ec6c..cb28c117 100644 --- a/permissions.go +++ b/permissions.go @@ -1,9 +1,16 @@ package main -import "log" -import "sync" -import "strconv" -import "encoding/json" +import ( + "database/sql" + "encoding/json" + "log" + "strconv" + "sync" + + "./query_gen/lib" +) + +// TODO: Refactor the perms system var permUpdateMutex sync.Mutex var BlankPerms Perms @@ -261,10 +268,18 @@ func presetToPermmap(preset string) (out map[string]ForumPerms) { } func permmapToQuery(permmap map[string]ForumPerms, fid int) error { - permUpdateMutex.Lock() - defer permUpdateMutex.Unlock() + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() - _, err := deleteForumPermsByForumStmt.Exec(fid) + deleteForumPermsByForumTx, err := qgen.Builder.SimpleDeleteTx(tx, "forums_permissions", "fid = ?") + if err != nil { + return err + } + + _, err = deleteForumPermsByForumTx.Exec(fid) if err != nil { return err } @@ -273,7 +288,16 @@ func permmapToQuery(permmap map[string]ForumPerms, fid int) error { if err != nil { return err } - _, err = addForumPermsToForumAdminsStmt.Exec(fid, "", perms) + + addForumPermsToForumAdminsTx, err := qgen.Builder.SimpleInsertSelectTx(tx, + qgen.DB_Insert{"forums_permissions", "gid, fid, preset, permissions", ""}, + qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 1", "", ""}, + ) + if err != nil { + return err + } + + _, err = addForumPermsToForumAdminsTx.Exec(fid, "", perms) if err != nil { return err } @@ -282,7 +306,15 @@ func permmapToQuery(permmap map[string]ForumPerms, fid int) error { if err != nil { return err } - _, err = addForumPermsToForumStaffStmt.Exec(fid, "", perms) + + addForumPermsToForumStaffTx, err := qgen.Builder.SimpleInsertSelectTx(tx, + qgen.DB_Insert{"forums_permissions", "gid, fid, preset, permissions", ""}, + qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 0 AND is_mod = 1", "", ""}, + ) + if err != nil { + return err + } + _, err = addForumPermsToForumStaffTx.Exec(fid, "", perms) if err != nil { return err } @@ -291,23 +323,77 @@ func permmapToQuery(permmap map[string]ForumPerms, fid int) error { if err != nil { return err } - _, err = addForumPermsToForumMembersStmt.Exec(fid, "", perms) + + addForumPermsToForumMembersTx, err := qgen.Builder.SimpleInsertSelectTx(tx, + qgen.DB_Insert{"forums_permissions", "gid, fid, preset, permissions", ""}, + qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 0 AND is_mod = 0 AND is_banned = 0", "", ""}, + ) + if err != nil { + return err + } + _, err = addForumPermsToForumMembersTx.Exec(fid, "", perms) if err != nil { return err } - perms, err = json.Marshal(permmap["guests"]) - if err != nil { - return err - } - _, err = addForumPermsToGroupStmt.Exec(6, fid, "", perms) + // 6 is the ID of the Not Loggedin Group + // TODO: Use a shared variable rather than a literal for the group ID + err = replaceForumPermsForGroupTx(tx, 6, map[int]string{fid: ""}, map[int]ForumPerms{fid: permmap["guests"]}) if err != nil { return err } + err = tx.Commit() + if err != nil { + return err + } + + permUpdateMutex.Lock() + defer permUpdateMutex.Unlock() return rebuildForumPermissions(fid) } +func replaceForumPermsForGroup(gid int, presetSet map[int]string, permSets map[int]ForumPerms) error { + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + err = replaceForumPermsForGroupTx(tx, gid, presetSet, permSets) + if err != nil { + return err + } + return tx.Commit() +} + +func replaceForumPermsForGroupTx(tx *sql.Tx, gid int, presetSets map[int]string, permSets map[int]ForumPerms) error { + deleteForumPermsForGroupTx, err := qgen.Builder.SimpleDeleteTx(tx, "forums_permissions", "gid = ? AND fid = ?") + if err != nil { + return err + } + + addForumPermsToGroupTx, err := qgen.Builder.SimpleInsertTx(tx, "forums_permissions", "gid, fid, preset, permissions", "?,?,?,?") + if err != nil { + return err + } + for fid, permSet := range permSets { + permstr, err := json.Marshal(permSet) + if err != nil { + return err + } + _, err = deleteForumPermsForGroupTx.Exec(gid, fid) + if err != nil { + return err + } + _, err = addForumPermsToGroupTx.Exec(gid, fid, presetSets[fid], string(permstr)) + if err != nil { + return err + } + } + + return nil +} + // TODO: Need a more thread-safe way of doing this. Possibly with sync.Map? func rebuildForumPermissions(fid int) error { if dev.DebugMode { diff --git a/plugin_markdown.go b/plugin_markdown.go index 95603b09..71ce1aaa 100644 --- a/plugin_markdown.go +++ b/plugin_markdown.go @@ -2,30 +2,27 @@ package main //import "fmt" import ( - "regexp" + "log" "strings" ) var markdownMaxDepth = 25 // How deep the parser will go when parsing Markdown strings var markdownUnclosedElement []byte -var markdownBoldTagOpen, markdownBoldTagClose []byte -var markdownItalicTagOpen, markdownItalicTagClose []byte -var markdownUnderlineTagOpen, markdownUnderlineTagClose []byte -var markdownStrikeTagOpen, markdownStrikeTagClose []byte - -var markdownBoldItalic *regexp.Regexp -var markdownBold *regexp.Regexp -var markdownItalic *regexp.Regexp -var markdownStrike *regexp.Regexp -var markdownUnderline *regexp.Regexp +var markdownBoldTagOpen []byte +var markdownBoldTagClose []byte +var markdownItalicTagOpen []byte +var markdownItalicTagClose []byte +var markdownUnderlineTagOpen []byte +var markdownUnderlineTagClose []byte +var markdownStrikeTagOpen []byte +var markdownStrikeTagClose []byte func init() { plugins["markdown"] = NewPlugin("markdown", "Markdown", "Azareal", "http://github.com/Azareal", "", "", "", initMarkdown, nil, deactivateMarkdown, nil, nil) } func initMarkdown() error { - //plugins["markdown"].AddHook("parse_assign", markdownRegexParse) plugins["markdown"].AddHook("parse_assign", markdownParse) markdownUnclosedElement = []byte("[Unclosed Element]") @@ -38,35 +35,19 @@ func initMarkdown() error { markdownUnderlineTagClose = []byte("") markdownStrikeTagOpen = []byte("") markdownStrikeTagClose = []byte("") - - markdownBoldItalic = regexp.MustCompile(`\*\*\*(.*)\*\*\*`) - markdownBold = regexp.MustCompile(`\*\*(.*)\*\*`) - markdownItalic = regexp.MustCompile(`\*(.*)\*`) - //markdownStrike = regexp.MustCompile(`\~\~(.*)\~\~`) - markdownStrike = regexp.MustCompile(`\~(.*)\~`) - //markdown_underline = regexp.MustCompile(`\_\_(.*)\_\_`) - markdownUnderline = regexp.MustCompile(`\_(.*)\_`) return nil } func deactivateMarkdown() { - //plugins["markdown"].RemoveHook("parse_assign", markdownRegexParse) plugins["markdown"].RemoveHook("parse_assign", markdownParse) } -func markdownRegexParse(msg string) string { - msg = markdownBoldItalic.ReplaceAllString(msg, "$1") - msg = markdownBold.ReplaceAllString(msg, "$1") - msg = markdownItalic.ReplaceAllString(msg, "$1") - msg = markdownStrike.ReplaceAllString(msg, "$1") - msg = markdownUnderline.ReplaceAllString(msg, "$1") - return msg -} - // An adapter for the parser, so that the parser can call itself recursively. // This is less for the simple Markdown elements like bold and italics and more for the really complicated ones I plan on adding at some point. func markdownParse(msg string) string { - return strings.TrimSpace(_markdownParse(msg+" ", 0)) + msg = strings.TrimSpace(_markdownParse(msg+" ", 0)) + log.Print("final msg: ", msg) + return msg } // Under Construction! @@ -77,16 +58,14 @@ func _markdownParse(msg string, n int) string { var outbytes []byte var lastElement int - //log.Print("enter message loop") - //log.Print("Message: %v\n",strings.Replace(msg,"\r","\\r",-1)) + log.Printf("Initial Message: %+v\n", strings.Replace(msg, "\r", "\\r", -1)) for index := 0; index < len(msg); index++ { - /*//log.Print("--OUTER MARKDOWN LOOP START--") - //log.Print("index",index) - //log.Print("msg[index]",msg[index]) - //log.Print("string(msg[index])",string(msg[index])) - //log.Print("--OUTER MARKDOWN LOOP END--") - //log.Print(" ")*/ + //log.Print("--OUTER MARKDOWN LOOP START--") + //log.Print("index: ", index) + //log.Print("msg[index]: ", msg[index]) + //log.Print("string(msg[index]): ", string(msg[index])) + //log.Printf("--OUTER MARKDOWN LOOP END--\n\n") switch msg[index] { // TODO: Do something slightly less hacky for skipping URLs @@ -106,7 +85,7 @@ func _markdownParse(msg string, n int) string { index++ index = markdownSkipUntilChar(msg, index, '_') - if (index-(startIndex+1)) < 2 || index >= len(msg) { + if (index-(startIndex+1)) < 1 || index >= len(msg) { break } @@ -116,7 +95,8 @@ func _markdownParse(msg string, n int) string { outbytes = append(outbytes, msg[lastElement:startIndex]...) outbytes = append(outbytes, markdownUnderlineTagOpen...) - outbytes = append(outbytes, msg[sIndex:lIndex]...) + // TODO: Implement this without as many type conversions + outbytes = append(outbytes, []byte(_markdownParse(msg[sIndex:lIndex], n+1))...) outbytes = append(outbytes, markdownUnderlineTagClose...) lastElement = index @@ -129,7 +109,7 @@ func _markdownParse(msg string, n int) string { index++ index = markdownSkipUntilChar(msg, index, '~') - if (index-(startIndex+1)) < 2 || index >= len(msg) { + if (index-(startIndex+1)) < 1 || index >= len(msg) { break } @@ -139,28 +119,28 @@ func _markdownParse(msg string, n int) string { outbytes = append(outbytes, msg[lastElement:startIndex]...) outbytes = append(outbytes, markdownStrikeTagOpen...) - outbytes = append(outbytes, msg[sIndex:lIndex]...) + // TODO: Implement this without as many type conversions + outbytes = append(outbytes, []byte(_markdownParse(msg[sIndex:lIndex], n+1))...) outbytes = append(outbytes, markdownStrikeTagClose...) lastElement = index index-- case '*': //log.Print("------") - //log.Print("[]byte(msg):",[]byte(msg)) - //log.Print("len(msg)",len(msg)) - //log.Print("start index",index) - //log.Print("start msg[index]",msg[index]) - //log.Print("start string(msg[index])",string(msg[index])) - //log.Print("start []byte(msg[:index])",[]byte(msg[:index])) + //log.Print("[]byte(msg): ", []byte(msg)) + //log.Print("len(msg): ", len(msg)) + //log.Print("start index: ", index) + //log.Print("start msg[index]: ", msg[index]) + //log.Print("start string(msg[index]): ", string(msg[index])) + //log.Print("start []byte(msg[:index]): ", []byte(msg[:index])) var startIndex = index var italic = true - var bold bool + var bold = false if (index + 2) < len(msg) { - //log.Print("start index + 1",index + 1) - //log.Print("start msg[index]",msg[index + 1]) - //log.Print("start string(msg[index])",string(msg[index + 1])) - + //log.Print("start index + 1: ", index + 1) + //log.Print("start msg[index]: ", msg[index + 1]) + //log.Print("start string(msg[index]): ", string(msg[index + 1])) if msg[index+1] == '*' { //log.Print("two asterisks") bold = true @@ -174,16 +154,16 @@ func _markdownParse(msg string, n int) string { } } - //log.Print("lastElement",lastElement) - //log.Print("startIndex:",startIndex) - //log.Print("msg[startIndex]",msg[startIndex]) - //log.Print("string(msg[startIndex])",string(msg[startIndex])) + //log.Print("lastElement: ", lastElement) + //log.Print("startIndex: ", startIndex) + //log.Print("msg[startIndex]: ", msg[startIndex]) + //log.Print("string(msg[startIndex]): ", string(msg[startIndex])) - //log.Print("preabrupt index",index) - //log.Print("preabrupt msg[index]",msg[index]) - //log.Print("preabrupt string(msg[index])",string(msg[index])) - //log.Print("preabrupt []byte(msg[:index])",[]byte(msg[:index])) - //log.Print("preabrupt msg[:index]",msg[:index]) + //log.Print("preabrupt index: ", index) + //log.Print("preabrupt msg[index]: ", msg[index]) + //log.Print("preabrupt string(msg[index]): ", string(msg[index])) + //log.Print("preabrupt []byte(msg[:index]): ", []byte(msg[:index])) + //log.Print("preabrupt msg[:index]: ", msg[:index]) // Does the string terminate abruptly? if (index + 1) >= len(msg) { @@ -195,16 +175,15 @@ func _markdownParse(msg string, n int) string { //log.Print("preskip index",index) //log.Print("preskip msg[index]",msg[index]) //log.Print("preskip string(msg[index])",string(msg[index])) - index = markdownSkipUntilAsterisk(msg, index) if index >= len(msg) { break } - //log.Print("index",index) - //log.Print("[]byte(msg[:index])",[]byte(msg[:index])) - //log.Print("msg[index]",msg[index]) + //log.Print("index: ", index) + //log.Print("[]byte(msg[:index]): ", []byte(msg[:index])) + //log.Print("msg[index]: ", msg[index]) sIndex := startIndex lIndex := index @@ -213,7 +192,7 @@ func _markdownParse(msg string, n int) string { if (index + 3) >= len(msg) { //log.Print("unclosed markdown element @ exit element") outbytes = append(outbytes, msg[lastElement:startIndex]...) - outbytes = append(outbytes, markdownUnclosedElement...) + //outbytes = append(outbytes, markdownUnclosedElement...) lastElement = startIndex break } @@ -224,7 +203,7 @@ func _markdownParse(msg string, n int) string { if (index + 2) >= len(msg) { //log.Print("true unclosed markdown element @ exit element") outbytes = append(outbytes, msg[lastElement:startIndex]...) - outbytes = append(outbytes, markdownUnclosedElement...) + //outbytes = append(outbytes, markdownUnclosedElement...) lastElement = startIndex break } @@ -235,7 +214,7 @@ func _markdownParse(msg string, n int) string { if (index + 1) >= len(msg) { //log.Print("true unclosed markdown element @ exit element") outbytes = append(outbytes, msg[lastElement:startIndex]...) - outbytes = append(outbytes, markdownUnclosedElement...) + //outbytes = append(outbytes, markdownUnclosedElement...) lastElement = startIndex break } @@ -249,7 +228,7 @@ func _markdownParse(msg string, n int) string { if lIndex <= sIndex { //log.Print("unclosed markdown element @ lIndex <= sIndex") outbytes = append(outbytes, msg[lastElement:startIndex]...) - outbytes = append(outbytes, markdownUnclosedElement...) + //outbytes = append(outbytes, markdownUnclosedElement...) lastElement = startIndex break } @@ -257,7 +236,7 @@ func _markdownParse(msg string, n int) string { if sIndex < 0 || lIndex < 0 { //log.Print("unclosed markdown element @ sIndex < 0 || lIndex < 0") outbytes = append(outbytes, msg[lastElement:startIndex]...) - outbytes = append(outbytes, markdownUnclosedElement...) + //outbytes = append(outbytes, markdownUnclosedElement...) lastElement = startIndex break } @@ -285,7 +264,8 @@ func _markdownParse(msg string, n int) string { outbytes = append(outbytes, markdownItalicTagOpen...) } - outbytes = append(outbytes, msg[sIndex:lIndex]...) + // TODO: Implement this without as many type conversions + outbytes = append(outbytes, []byte(_markdownParse(msg[sIndex:lIndex], n+1))...) if italic { outbytes = append(outbytes, markdownItalicTagClose...) @@ -296,13 +276,18 @@ func _markdownParse(msg string, n int) string { lastElement = index index-- + case '\\': + if (index + 1) < len(msg) { + outbytes = append(outbytes, msg[lastElement:index]...) + index++ + lastElement = index + } //case '`': //case '_': //case '~': //case 10: // newline } } - //log.Print("exit message loop") if len(outbytes) == 0 { diff --git a/plugin_test.go b/plugin_test.go index 8a0fe2b7..8908ef80 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -33,14 +33,14 @@ func TestBBCodeRender(t *testing.T) { msgList = addMEPair(msgList, "[s]hi[/s]", "hi") msgList = addMEPair(msgList, "[c]hi[/c]", "[c]hi[/c]") if !testing.Short() { - msgList = addMEPair(msgList, "[b]hi[/i]", "[b]hi[/i]") - msgList = addMEPair(msgList, "[/b]hi[b]", "[/b]hi[b]") - msgList = addMEPair(msgList, "[/b]hi[/b]", "[/b]hi[/b]") - msgList = addMEPair(msgList, "[b][b]hi[/b]", "hi") + //msgList = addMEPair(msgList, "[b]hi[/i]", "[b]hi[/i]") + //msgList = addMEPair(msgList, "[/b]hi[b]", "[/b]hi[b]") + //msgList = addMEPair(msgList, "[/b]hi[/b]", "[/b]hi[/b]") + //msgList = addMEPair(msgList, "[b][b]hi[/b]", "hi") + //msgList = addMEPair(msgList, "[b][b]hi", "[b][b]hi") + //msgList = addMEPair(msgList, "[b][b][b]hi", "[b][b][b]hi") + //msgList = addMEPair(msgList, "[/b]hi", "[/b]hi") } - msgList = addMEPair(msgList, "[b][b]hi", "[b][b]hi") - msgList = addMEPair(msgList, "[b][b][b]hi", "[b][b][b]hi") - msgList = addMEPair(msgList, "[/b]hi", "[/b]hi") msgList = addMEPair(msgList, "[code]hi[/code]", "hi") msgList = addMEPair(msgList, "[code][b]hi[/b][/code]", "[b]hi[/b]") msgList = addMEPair(msgList, "[code][b]hi[/code][/b]", "[b]hi[/b]") @@ -51,9 +51,9 @@ func TestBBCodeRender(t *testing.T) { t.Log("Testing bbcodeFullParse") for _, item := range msgList { - t.Log("Testing string '" + item.Msg + "'") res = bbcodeFullParse(item.Msg) if res != item.Expects { + t.Error("Testing string '" + item.Msg + "'") t.Error("Bad output:", "'"+res+"'") t.Error("Expected:", item.Expects) } @@ -167,7 +167,7 @@ func TestBBCodeRender(t *testing.T) { t.Log("Testing string '" + msg + "'") res = bbcodeFullParse(msg) conv, err = strconv.Atoi(res) - if err != nil || ( /*conv > 18446744073709551615 || */ conv < 0) { + if err != nil && res != "[Invalid Number][rand]18446744073709551615[/rand]" { t.Error("Bad output:", "'"+res+"'") t.Error("Expected a number between 0 and 18446744073709551615") } @@ -175,7 +175,7 @@ func TestBBCodeRender(t *testing.T) { t.Log("Testing string '" + msg + "'") res = bbcodeFullParse(msg) conv, err = strconv.Atoi(res) - if err != nil || ( /*conv > 170141183460469231731687303715884105727 || */ conv < 0) { + if err != nil && res != "[Invalid Number][rand]170141183460469231731687303715884105727[/rand]" { t.Error("Bad output:", "'"+res+"'") t.Error("Expected a number between 0 and 170141183460469231731687303715884105727") } @@ -193,19 +193,42 @@ func TestBBCodeRender(t *testing.T) { func TestMarkdownRender(t *testing.T) { //t.Skip() + err := initMarkdown() + if err != nil { + t.Fatal(err) + } + var res string var msgList []MEPair msgList = addMEPair(msgList, "hi", "hi") + msgList = addMEPair(msgList, "**h**", "h") msgList = addMEPair(msgList, "**hi**", "hi") + msgList = addMEPair(msgList, "_h_", "h") msgList = addMEPair(msgList, "_hi_", "hi") + msgList = addMEPair(msgList, "*h*", "h") msgList = addMEPair(msgList, "*hi*", "hi") + msgList = addMEPair(msgList, "~h~", "h") msgList = addMEPair(msgList, "~hi~", "hi") msgList = addMEPair(msgList, "*hi**", "hi*") msgList = addMEPair(msgList, "**hi***", "hi*") msgList = addMEPair(msgList, "**hi*", "*hi") - msgList = addMEPair(msgList, "***hi***", "*hi") + msgList = addMEPair(msgList, "***hi***", "hi") + msgList = addMEPair(msgList, "***h***", "h") msgList = addMEPair(msgList, "\\*hi\\*", "*hi*") + msgList = addMEPair(msgList, "d\\*hi\\*", "d*hi*") + msgList = addMEPair(msgList, "\\*hi\\*d", "*hi*d") + msgList = addMEPair(msgList, "d\\*hi\\*d", "d*hi*d") + msgList = addMEPair(msgList, "\\", "\\") + msgList = addMEPair(msgList, "\\\\", "\\\\") + msgList = addMEPair(msgList, "\\d", "\\d") + msgList = addMEPair(msgList, "\\\\d", "\\\\d") + msgList = addMEPair(msgList, "\\\\\\d", "\\\\\\d") + msgList = addMEPair(msgList, "d\\", "d\\") + msgList = addMEPair(msgList, "\\d\\", "\\d\\") msgList = addMEPair(msgList, "*~hi~*", "hi") + msgList = addMEPair(msgList, "~*hi*~", "hi") + msgList = addMEPair(msgList, "_~hi~_", "hi") + msgList = addMEPair(msgList, "***~hi~***", "hi") msgList = addMEPair(msgList, "**", "**") msgList = addMEPair(msgList, "***", "***") msgList = addMEPair(msgList, "****", "****") @@ -224,10 +247,11 @@ func TestMarkdownRender(t *testing.T) { msgList = addMEPair(msgList, "*** ***", " ") for _, item := range msgList { - t.Log("Testing string '" + item.Msg + "'") res = markdownParse(item.Msg) if res != item.Expects { + t.Error("Testing string '" + item.Msg + "'") t.Error("Bad output:", "'"+res+"'") + //t.Error("Ouput in bytes:", []byte(res)) t.Error("Expected:", item.Expects) } } diff --git a/public/global.js b/public/global.js index 3a058ad6..81dfc38b 100644 --- a/public/global.js +++ b/public/global.js @@ -36,7 +36,6 @@ function load_alerts(menu_alerts) } var alist = ""; - var anyAvatar = false for(var i in data.msgs) { var msg = data.msgs[i]; var mmsg = msg.msg; @@ -44,15 +43,13 @@ function load_alerts(menu_alerts) if("sub" in msg) { for(var i = 0; i < msg.sub.length; i++) { mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]); - //console.log("Sub #" + i); - //console.log(msg.sub[i]); + //console.log("Sub #" + i + ":",msg.sub[i]); } } if("avatar" in msg) { alist += "
"+mmsg+"
"; alertList.push("
"+mmsg+"
"); - anyAvatar = true } else { alist += "
"+mmsg+"
"; alertList.push("
"+mmsg+"
"); @@ -62,11 +59,8 @@ function load_alerts(menu_alerts) } if(alist == "") alist = "
You don't have any alerts
"; - else { - //menu_alerts.removeClass("hasAvatars"); - //if(anyAvatar) menu_alerts.addClass("hasAvatars"); - } alertListNode.innerHTML = alist; + if(data.msgCount != 0 && data.msgCount != undefined) { alertCounterNode.textContent = data.msgCount; menu_alerts.classList.add("has_alerts"); @@ -88,6 +82,7 @@ function load_alerts(menu_alerts) console.log(magic.responseText); console.log(err); } + console.log("error: ",error); alertListNode.innerHTML = "
"+errtxt+"
"; } }); @@ -176,8 +171,7 @@ $(document).ready(function(){ var messages = event.data.split('\r'); for(var i = 0; i < messages.length; i++) { - //console.log("Message:"); - //console.log(messages[i]); + //console.log("Message: ",messages[i]); if(messages[i].startsWith("set ")) { //msgblocks = messages[i].split(' ',3); let msgblocks = SplitN(messages[i]," ",3); @@ -214,10 +208,10 @@ $(document).ready(function(){ let topicStatusInput = $('.topic_status_input').val(); let topicContentInput = $('.topic_content_input').val(); let formAction = this.form.getAttribute("action"); - //console.log("New Topic Name: " + topicNameInput); - //console.log("New Topic Status: " + topicStatusInput); - //console.log("New Topic Content: " + topicContentInput); - //console.log("Form Action: " + formAction); + //console.log("New Topic Name: ", topicNameInput); + //console.log("New Topic Status: ", topicStatusInput); + //console.log("New Topic Content: ", topicContentInput); + //console.log("Form Action: ", formAction); $.ajax({ url: formAction, type: "POST", @@ -253,7 +247,7 @@ $(document).ready(function(){ block.html(newContent); var formAction = $(this).closest('a').attr("href"); - //console.log("Form Action: " + form_action); + //console.log("Form Action:",formAction); $.ajax({ url: formAction, type: "POST", dataType: "json", data: { isJs: "1", edit_item: newContent } }); }); @@ -266,8 +260,7 @@ $(document).ready(function(){ let block = blockParent.find('.editable_block').eq(0); block.html(""); - $(".submit_edit").click(function(event) - { + $(".submit_edit").click(function(event) { event.preventDefault(); let blockParent = $(this).closest('.editable_parent'); let block = blockParent.find('.editable_block').eq(0); @@ -275,12 +268,12 @@ $(document).ready(function(){ block.html(newContent); let formAction = $(this).closest('a').attr("href"); - //console.log("Form Action: " + formAction); + //console.log("Form Action:", formAction); $.ajax({ url: formAction + "?session=" + session, type: "POST", dataType: "json", - data: {isJs: "1",edit_item: newContent} + data: { isJs: "1", edit_item: newContent } }); }); }); @@ -305,9 +298,9 @@ $(document).ready(function(){ else var it = ['No','Yes']; var itLen = it.length; var out = ""; - //console.log("Field Name '" + field_name + "'") - //console.log("Field Type",field_type) - //console.log("Field Value '" + field_value + "'") + //console.log("Field Name:",field_name); + //console.log("Field Type:",field_type); + //console.log("Field Value:",field_value); for (var i = 0; i < itLen; i++) { var sel = ""; if(field_value == i || field_value == it[i]) { @@ -332,7 +325,7 @@ $(document).ready(function(){ //console.log("running .submit_edit event"); var out_data = {isJs: "1"} var block_parent = $(this).closest('.editable_parent'); - block_parent.find('.editable_block').each(function(){ + block_parent.find('.editable_block').each(function() { var field_name = this.getAttribute("data-field"); var field_type = this.getAttribute("data-type"); if(field_type=="list") { @@ -350,7 +343,7 @@ $(document).ready(function(){ }); var form_action = $(this).closest('a').attr("href"); - //console.log("Form Action: " + form_action); + //console.log("Form Action:", form_action); //console.log(out_data); $.ajax({ url: form_action + "?session=" + session, type:"POST", dataType:"json", data: out_data }); block_parent.find('.hide_on_edit').show(); diff --git a/query_gen/lib/builder.go b/query_gen/lib/builder.go index 62788adb..2d42eb6a 100644 --- a/query_gen/lib/builder.go +++ b/query_gen/lib/builder.go @@ -132,3 +132,101 @@ func (build *builder) Purge(table string) (stmt *sql.Stmt, err error) { } return build.conn.Prepare(res) } + +// These ones support transactions +func (build *builder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleCount("_builder", table, where, limit) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (stmt *sql.Stmt, err error) { + res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleInsert("_builder", table, columns, fields) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleInsertSelectTx(tx *sql.Tx, ins DB_Insert, sel DB_Select) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DB_Insert, sel DB_Join) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleInsertLeftJoin("_builder", ins, sel) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DB_Insert, sel DB_Join) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleUpdate("_builder", table, set, where) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +func (build *builder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleDelete("_builder", table, where) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} + +// I don't know why you need this, but here it is x.x +func (build *builder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.Purge("_builder", table) + if err != nil { + return stmt, err + } + return tx.Prepare(res) +} diff --git a/query_gen/lib/mssql.go b/query_gen/lib/mssql.go index 077c09e2..e4782222 100644 --- a/query_gen/lib/mssql.go +++ b/query_gen/lib/mssql.go @@ -451,8 +451,9 @@ func (adapter *Mssql_Adapter) SimpleSelect(name string, table string, columns st querystr += " ?" + strconv.Itoa(substituteCount) case "function", "operator", "number": // TODO: Split the function case off to speed things up + // MSSQL seems to convert the formats? so we'll compare it with a regular date. Do this with the other methods too? if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { - token.Contents = "GETUTCDATE()" + token.Contents = "GETDATE()" } querystr += " " + token.Contents case "column": @@ -800,12 +801,21 @@ func (adapter *Mssql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel /* Select */ var substituteCount = 0 - // Escape the column names, just in case we've used a reserved keyword - var colslice = strings.Split(strings.TrimSpace(sel.Columns), ",") - for _, column := range colslice { - querystr += "[" + strings.TrimSpace(column) + "]," + for _, column := range processColumns(sel.Columns) { + var source, alias string + + // Escape the column names, just in case we've used a reserved keyword + if column.Type == "function" || column.Type == "substitute" { + source = column.Left + } else { + source = "[" + column.Left + "]" + } + + if column.Alias != "" { + alias = " AS [" + column.Alias + "]" + } + querystr += " " + source + alias + "," } - // Remove the trailing comma querystr = querystr[0 : len(querystr)-1] querystr += " FROM [" + sel.Table + "] " diff --git a/query_gen/lib/mysql.go b/query_gen/lib/mysql.go index 88f13154..585131ac 100644 --- a/query_gen/lib/mysql.go +++ b/query_gen/lib/mysql.go @@ -903,7 +903,7 @@ package main import "log" import "database/sql" -import "./query_gen/lib" +//import "./query_gen/lib" // nolint ` + stmts + ` diff --git a/query_gen/lib/querygen.go b/query_gen/lib/querygen.go index cfc06ac4..cb72659f 100644 --- a/query_gen/lib/querygen.go +++ b/query_gen/lib/querygen.go @@ -102,10 +102,9 @@ type DB_Adapter interface { SimpleInsert(name string, table string, columns string, fields string) (string, error) // ! DEPRECATED - SimpleReplace(name string, table string, columns string, fields string) (string, error) - - // ! NOTE: MySQL doesn't support upserts properly, asides from for keys, so this is just a less destructive replace atm - SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) + //SimpleReplace(name string, table string, columns string, fields string) (string, error) + // ! NOTE: MySQL doesn't support upserts properly, so I'm removing this from the interface until we find a way to patch it in + //SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) SimpleUpdate(name string, table string, set string, where string) (string, error) SimpleDelete(name string, table string, where string) (string, error) Purge(name string, table string) (string, error) diff --git a/query_gen/main.go b/query_gen/main.go index 5c73642c..d1c19e4f 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -75,7 +75,7 @@ func writeStatements(adapter qgen.DB_Adapter) error { return err } - err = writeReplaces(adapter) + /*err = writeReplaces(adapter) if err != nil { return err } @@ -83,7 +83,7 @@ func writeStatements(adapter qgen.DB_Adapter) error { err = writeUpserts(adapter) if err != nil { return err - } + }*/ err = writeUpdates(adapter) if err != nil { @@ -356,8 +356,6 @@ func writeInserts(adapter qgen.DB_Adapter) error { adapter.SimpleInsert("addTheme", "themes", "uname, default", "?,?") - adapter.SimpleInsert("createGroup", "users_groups", "name, tag, is_admin, is_mod, is_banned, permissions", "?,?,?,?,?,?") - adapter.SimpleInsert("addModlogEntry", "moderation_logs", "action, elementID, elementType, ipaddress, actorID, doneAt", "?,?,?,?,?,UTC_TIMESTAMP()") adapter.SimpleInsert("addAdminlogEntry", "administration_logs", "action, elementID, elementType, ipaddress, actorID, doneAt", "?,?,?,?,?,UTC_TIMESTAMP()") @@ -373,7 +371,8 @@ func writeReplaces(adapter qgen.DB_Adapter) (err error) { return nil } -func writeUpserts(adapter qgen.DB_Adapter) (err error) { +// ! Upserts are broken atm +/*func writeUpserts(adapter qgen.DB_Adapter) (err error) { _, err = adapter.SimpleUpsert("addForumPermsToGroup", "forums_permissions", "gid, fid, preset, permissions", "?,?,?,?", "gid = ? AND fid = ?") if err != nil { return err @@ -385,7 +384,7 @@ func writeUpserts(adapter qgen.DB_Adapter) (err error) { } return nil -} +}*/ func writeUpdates(adapter qgen.DB_Adapter) error { adapter.SimpleUpdate("addRepliesToTopic", "topics", "postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()", "tid = ?") @@ -454,6 +453,8 @@ func writeUpdates(adapter qgen.DB_Adapter) error { adapter.SimpleUpdate("updateUser", "users", "name = ?, email = ?, group = ?", "uid = ?") + adapter.SimpleUpdate("updateUserGroup", "users", "group = ?", "uid = ?") + adapter.SimpleUpdate("updateGroupPerms", "users_groups", "permissions = ?", "gid = ?") adapter.SimpleUpdate("updateGroupRank", "users_groups", "is_admin = ?, is_mod = ?, is_banned = ?", "gid = ?") @@ -480,10 +481,10 @@ func writeDeletes(adapter qgen.DB_Adapter) error { adapter.SimpleDelete("deleteProfileReply", "users_replies", "rid = ?") - adapter.SimpleDelete("deleteForumPermsByForum", "forums_permissions", "fid = ?") + //adapter.SimpleDelete("deleteForumPermsByForum", "forums_permissions", "fid = ?") adapter.SimpleDelete("deleteActivityStreamMatch", "activity_stream_matches", "watcher = ? AND asid = ?") - //adapter.SimpleDelete("delete_activity_stream_matches_by_watcher","activity_stream_matches","watcher = ?") + //adapter.SimpleDelete("deleteActivityStreamMatchesByWatcher","activity_stream_matches","watcher = ?") adapter.SimpleDelete("deleteWordFilter", "word_filters", "wfid = ?") @@ -501,20 +502,20 @@ func writeSimpleCounts(adapter qgen.DB_Adapter) error { } func writeInsertSelects(adapter qgen.DB_Adapter) error { - adapter.SimpleInsertSelect("addForumPermsToForumAdmins", + /*adapter.SimpleInsertSelect("addForumPermsToForumAdmins", qgen.DB_Insert{"forums_permissions", "gid, fid, preset, permissions", ""}, qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 1", "", ""}, - ) + )*/ - adapter.SimpleInsertSelect("addForumPermsToForumStaff", + /*adapter.SimpleInsertSelect("addForumPermsToForumStaff", qgen.DB_Insert{"forums_permissions", "gid, fid, preset, permissions", ""}, qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 0 AND is_mod = 1", "", ""}, - ) + )*/ - adapter.SimpleInsertSelect("addForumPermsToForumMembers", + /*adapter.SimpleInsertSelect("addForumPermsToForumMembers", qgen.DB_Insert{"forums_permissions", "gid, fid, preset, permissions", ""}, qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 0 AND is_mod = 0 AND is_banned = 0", "", ""}, - ) + )*/ return nil } diff --git a/rev_templates.go b/rev_templates.go new file mode 100644 index 00000000..b88de87d --- /dev/null +++ b/rev_templates.go @@ -0,0 +1,59 @@ +//+build experiment + +// ! EXPERIMENTAL +package main + +import ( + "errors" + "regexp" +) + +var tagFinder *regexp.Regexp + +func init() { + tagFinder = regexp.MustCompile(`(?s)\{\{(.*)\}\}`) +} + +func icecreamSoup(tmpl string) error { + tagIndices := tagFinder.FindAllStringIndex(tmpl, -1) + if tagIndices != nil && len(tagIndices) > 0 { + + if tagIndices[0][0] == 0 { + return errors.New("We don't support tags in the outermost layer yet") + } + for _, tagIndex := range tagIndices { + var nestingLayer = 0 + for i := tagIndex[0]; i > 0; i-- { + switch tmpl[i] { + case '>': + i, closeTag, err := tasteTagToLeft(tmpl, i) + if err != nil { + return err + } + if closeTag { + nestingLayer++ + } + case '<': + + } + } + } + } +} + +func tasteTagToLeft(tmpl string, index int) (indexOut int, closeTag bool, err error) { + var foundLeftBrace = false + for ; index > 0; index-- { + // What if the / isn't adjacent to the < but has a space instead? Is that even valid? + if index >= 1 && tmpl[index] == '/' && tmpl[index-1] == '<' { + closeTag = true + break + } else if tmpl[index] == '<' { + foundLeftBrace = true + } + } + if !foundLeftBrace { + return errors.New("The left portion of the tag is missing") + } + return index, closeTag, nil +} diff --git a/routes.go b/routes.go index c7ce5790..69c699ee 100644 --- a/routes.go +++ b/routes.go @@ -332,6 +332,8 @@ func routeForum(w http.ResponseWriter, r *http.Request, user User, sfid string) } else { page = 1 } + + // TODO: Move this to *Forum rows, err := getForumTopicsOffsetStmt.Query(fid, offset, config.ItemsPerPage) if err != nil { InternalError(err, w) @@ -904,10 +906,11 @@ func routeRegisterSubmit(w http.ResponseWriter, r *http.Request, user User) { return } - var active, group int + var active bool + var group int switch headerLite.Settings["activation_type"] { case 1: // Activate All - active = 1 + active = true group = config.DefaultGroup default: // Anything else. E.g. Admin Activation or Email Activation. group = config.ActivationGroup diff --git a/run_tests.bat b/run_tests.bat new file mode 100644 index 00000000..e6b1ec5a --- /dev/null +++ b/run_tests.bat @@ -0,0 +1,33 @@ +@echo off +echo Generating the dynamic code +go generate +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) + +echo Building the router generator +go build ./router_gen +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) +echo Running the router generator +router_gen.exe + +echo Building the query generator +go build ./query_gen +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) +echo Running the query generator +query_gen.exe + +echo Building the executable +go test +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) +pause \ No newline at end of file diff --git a/run_tests_mssql.bat b/run_tests_mssql.bat new file mode 100644 index 00000000..f2b8aa95 --- /dev/null +++ b/run_tests_mssql.bat @@ -0,0 +1,33 @@ +@echo off +echo Generating the dynamic code +go generate +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) + +echo Building the router generator +go build ./router_gen +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) +echo Running the router generator +router_gen.exe + +echo Building the query generator +go build ./query_gen +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) +echo Running the query generator +query_gen.exe + +echo Building the executable +go test -tags mssql +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) +pause \ No newline at end of file diff --git a/tasks.go b/tasks.go index 36b83ef2..78689df6 100644 --- a/tasks.go +++ b/tasks.go @@ -25,25 +25,19 @@ func handleExpiredScheduledGroups() error { defer rows.Close() var uid int - ucache, ok := users.(UserCache) for rows.Next() { err := rows.Scan(&uid) if err != nil { return err } - _, err = replaceScheduleGroupStmt.Exec(uid, 0, 0, time.Now(), false, uid) + + // Sneaky way of initialising a *User, please use the methods on the UserStore instead + user := getDummyUser() + user.ID = uid + err = user.RevertGroupUpdate() if err != nil { - log.Print("Unable to replace the scheduled group") return err } - _, err = setTempGroupStmt.Exec(0, uid) - if err != nil { - log.Print("Unable to reset the tempgroup") - return err - } - if ok { - ucache.CacheRemove(uid) - } } return rows.Err() } diff --git a/template_forum.go b/template_forum.go index a445f8be..6b63a905 100644 --- a/template_forum.go +++ b/template_forum.go @@ -105,87 +105,90 @@ if tmpl_forum_vars.CurrentUser.Perms.CreateTopic { w.Write(forum_12) w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) w.Write(forum_13) -} else { w.Write(forum_14) -} +} else { w.Write(forum_15) } w.Write(forum_16) -if tmpl_forum_vars.CurrentUser.Perms.CreateTopic { -w.Write(forum_17) -w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) -w.Write(forum_18) -if tmpl_forum_vars.CurrentUser.Perms.UploadFiles { -w.Write(forum_19) } +w.Write(forum_17) +if tmpl_forum_vars.CurrentUser.Perms.CreateTopic { +w.Write(forum_18) +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) +w.Write(forum_19) +if tmpl_forum_vars.CurrentUser.Perms.UploadFiles { w.Write(forum_20) } w.Write(forum_21) +} +w.Write(forum_22) if len(tmpl_forum_vars.ItemList) != 0 { for _, item := range tmpl_forum_vars.ItemList { -w.Write(forum_22) -if item.Sticky { w.Write(forum_23) +if item.Sticky { +w.Write(forum_24) } else { if item.IsClosed { -w.Write(forum_24) -} -} w.Write(forum_25) -if item.Creator.Avatar != "" { +} +} w.Write(forum_26) -w.Write([]byte(item.Creator.Avatar)) +if item.Creator.Avatar != "" { w.Write(forum_27) -} +w.Write([]byte(item.Creator.Avatar)) w.Write(forum_28) -w.Write([]byte(item.Link)) -w.Write(forum_29) -w.Write([]byte(item.Title)) -w.Write(forum_30) -w.Write([]byte(item.Creator.Link)) -w.Write(forum_31) -w.Write([]byte(item.Creator.Name)) -w.Write(forum_32) -if item.IsClosed { -w.Write(forum_33) } -if item.Sticky { +w.Write(forum_29) +w.Write([]byte(item.Link)) +w.Write(forum_30) +w.Write([]byte(item.Title)) +w.Write(forum_31) +w.Write([]byte(item.Creator.Link)) +w.Write(forum_32) +w.Write([]byte(item.Creator.Name)) +w.Write(forum_33) +if item.IsClosed { w.Write(forum_34) } -w.Write(forum_35) -w.Write([]byte(strconv.Itoa(item.PostCount))) -w.Write(forum_36) if item.Sticky { +w.Write(forum_35) +} +w.Write(forum_36) +w.Write([]byte(strconv.Itoa(item.PostCount))) w.Write(forum_37) +w.Write([]byte(strconv.Itoa(item.LikeCount))) +w.Write(forum_38) +if item.Sticky { +w.Write(forum_39) } else { if item.IsClosed { -w.Write(forum_38) -} -} -w.Write(forum_39) -if item.LastUser.Avatar != "" { w.Write(forum_40) -w.Write([]byte(item.LastUser.Avatar)) -w.Write(forum_41) } +} +w.Write(forum_41) +if item.LastUser.Avatar != "" { w.Write(forum_42) -w.Write([]byte(item.LastUser.Link)) +w.Write([]byte(item.LastUser.Avatar)) w.Write(forum_43) -w.Write([]byte(item.LastUser.Name)) +} w.Write(forum_44) -w.Write([]byte(item.RelativeLastReplyAt)) +w.Write([]byte(item.LastUser.Link)) w.Write(forum_45) +w.Write([]byte(item.LastUser.Name)) +w.Write(forum_46) +w.Write([]byte(item.RelativeLastReplyAt)) +w.Write(forum_47) } } else { -w.Write(forum_46) -if tmpl_forum_vars.CurrentUser.Perms.CreateTopic { -w.Write(forum_47) -w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) w.Write(forum_48) -} +if tmpl_forum_vars.CurrentUser.Perms.CreateTopic { w.Write(forum_49) -} +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) w.Write(forum_50) +} +w.Write(forum_51) +} +w.Write(forum_52) w.Write(footer_0) if len(tmpl_forum_vars.Header.Themes) != 0 { for _, item := range tmpl_forum_vars.Header.Themes { diff --git a/template_list.go b/template_list.go index 851d260b..9d4d3435 100644 --- a/template_list.go +++ b/template_list.go @@ -678,33 +678,38 @@ var forums_19 = []byte(` var topics_0 = []byte(`
-
+

All Topics

`) var topics_3 = []byte(` -
+
`) -var topics_4 = []byte(`
`) -var topics_5 = []byte(` +var topics_4 = []byte(` +
+ +
+ `) +var topics_5 = []byte(`
`) +var topics_6 = []byte(`
`) -var topics_6 = []byte(` +var topics_7 = []byte(`
`) -var topics_7 = []byte(` -
@@ -803,7 +810,7 @@ var forum_8 = []byte(`
-
+

`) @@ -811,21 +818,26 @@ var forum_11 = []byte(`

`) var forum_12 = []byte(` -
`) -var forum_14 = []byte(`
`) -var forum_15 = []byte(` +var forum_14 = []byte(` +
+ +
+ `) +var forum_15 = []byte(`
`) +var forum_16 = []byte(`
`) -var forum_16 = []byte(` +var forum_17 = []byte(`
`) -var forum_17 = []byte(` -
diff --git a/template_topics.go b/template_topics.go index 129176f9..f3496c95 100644 --- a/template_topics.go +++ b/template_topics.go @@ -83,107 +83,110 @@ w.Write(topics_2) if tmpl_topics_vars.CurrentUser.ID != 0 { if len(tmpl_topics_vars.ForumList) != 0 { w.Write(topics_3) -} else { w.Write(topics_4) -} +} else { w.Write(topics_5) } w.Write(topics_6) +} +w.Write(topics_7) if tmpl_topics_vars.CurrentUser.ID != 0 { if len(tmpl_topics_vars.ForumList) != 0 { -w.Write(topics_7) +w.Write(topics_8) if len(tmpl_topics_vars.ForumList) != 0 { for _, item := range tmpl_topics_vars.ForumList { -w.Write(topics_8) -if item.ID == tmpl_topics_vars.DefaultForum { w.Write(topics_9) -} +if item.ID == tmpl_topics_vars.DefaultForum { w.Write(topics_10) -w.Write([]byte(strconv.Itoa(item.ID))) +} w.Write(topics_11) -w.Write([]byte(item.Name)) +w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topics_12) -} -} +w.Write([]byte(item.Name)) w.Write(topics_13) -if tmpl_topics_vars.CurrentUser.Perms.UploadFiles { -w.Write(topics_14) } +} +w.Write(topics_14) +if tmpl_topics_vars.CurrentUser.Perms.UploadFiles { w.Write(topics_15) } -} w.Write(topics_16) +} +} +w.Write(topics_17) if len(tmpl_topics_vars.TopicList) != 0 { for _, item := range tmpl_topics_vars.TopicList { -w.Write(topics_17) -if item.Sticky { w.Write(topics_18) +if item.Sticky { +w.Write(topics_19) } else { if item.IsClosed { -w.Write(topics_19) -} -} w.Write(topics_20) -if item.Creator.Avatar != "" { +} +} w.Write(topics_21) -w.Write([]byte(item.Creator.Avatar)) +if item.Creator.Avatar != "" { w.Write(topics_22) -} +w.Write([]byte(item.Creator.Avatar)) w.Write(topics_23) -w.Write([]byte(item.Link)) +} w.Write(topics_24) -w.Write([]byte(item.Title)) +w.Write([]byte(item.Link)) w.Write(topics_25) -if item.ForumName != "" { +w.Write([]byte(item.Title)) w.Write(topics_26) -w.Write([]byte(item.ForumLink)) +if item.ForumName != "" { w.Write(topics_27) -w.Write([]byte(item.ForumName)) +w.Write([]byte(item.ForumLink)) w.Write(topics_28) -} +w.Write([]byte(item.ForumName)) w.Write(topics_29) -w.Write([]byte(item.Creator.Link)) -w.Write(topics_30) -w.Write([]byte(item.Creator.Name)) -w.Write(topics_31) -if item.IsClosed { -w.Write(topics_32) } -if item.Sticky { +w.Write(topics_30) +w.Write([]byte(item.Creator.Link)) +w.Write(topics_31) +w.Write([]byte(item.Creator.Name)) +w.Write(topics_32) +if item.IsClosed { w.Write(topics_33) } -w.Write(topics_34) -w.Write([]byte(strconv.Itoa(item.PostCount))) -w.Write(topics_35) if item.Sticky { +w.Write(topics_34) +} +w.Write(topics_35) +w.Write([]byte(strconv.Itoa(item.PostCount))) w.Write(topics_36) +w.Write([]byte(strconv.Itoa(item.LikeCount))) +w.Write(topics_37) +if item.Sticky { +w.Write(topics_38) } else { if item.IsClosed { -w.Write(topics_37) -} -} -w.Write(topics_38) -if item.LastUser.Avatar != "" { w.Write(topics_39) -w.Write([]byte(item.LastUser.Avatar)) +} +} w.Write(topics_40) -} +if item.LastUser.Avatar != "" { w.Write(topics_41) -w.Write([]byte(item.LastUser.Link)) +w.Write([]byte(item.LastUser.Avatar)) w.Write(topics_42) -w.Write([]byte(item.LastUser.Name)) -w.Write(topics_43) -w.Write([]byte(item.RelativeLastReplyAt)) -w.Write(topics_44) } -} else { +w.Write(topics_43) +w.Write([]byte(item.LastUser.Link)) +w.Write(topics_44) +w.Write([]byte(item.LastUser.Name)) w.Write(topics_45) -if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { +w.Write([]byte(item.RelativeLastReplyAt)) w.Write(topics_46) } +} else { w.Write(topics_47) -} +if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { w.Write(topics_48) +} +w.Write(topics_49) +} +w.Write(topics_50) w.Write(footer_0) if len(tmpl_topics_vars.Header.Themes) != 0 { for _, item := range tmpl_topics_vars.Header.Themes { diff --git a/templates/forum.html b/templates/forum.html index 00ad59ff..2236c895 100644 --- a/templates/forum.html +++ b/templates/forum.html @@ -6,18 +6,22 @@
-
+

{{.Title}}

{{if ne .CurrentUser.ID 0}} {{if .CurrentUser.Perms.CreateTopic}} -
- {{else}}
{{end}} +
+ {{/** TODO: Add a permissions check for this **/}} +
+ +
+ {{else}}
{{end}}
{{end}}
{{if .CurrentUser.Perms.CreateTopic}} -