diff --git a/cmd/gochan-migration/internal/pre2021/posts.go b/cmd/gochan-migration/internal/pre2021/posts.go index 8c19f668..8398627b 100644 --- a/cmd/gochan-migration/internal/pre2021/posts.go +++ b/cmd/gochan-migration/internal/pre2021/posts.go @@ -67,6 +67,7 @@ func (m *Pre2021Migrator) migrateThreads() error { return err } defer stmt.Close() + defer rows.Close() for rows.Next() { var post postTable if err = rows.Scan( diff --git a/pkg/gcsql/bans.go b/pkg/gcsql/bans.go new file mode 100644 index 00000000..c627dbd0 --- /dev/null +++ b/pkg/gcsql/bans.go @@ -0,0 +1,28 @@ +package gcsql + +import "database/sql" + +// CheckIPBan returns the latest active IP ban for the given IP, as well as any errors. If the +// IPBan pointer is nil, the IP has no active bans +func CheckIPBan(ip string) (*IPBan, error) { + const query = `SELECT + id, staff_id, board_id, banned_for_post_id, copy_post_text, is_thread_ban, + is_active, ip, issued_at, appeal_at, expires_at, permanent, staff_note, + message, can_appeal + FROM DBPREFIXip_ban WHERE ip = ? AND is_active AND (expires_at > CURRENT_TIMESTAMP OR permanent) + ORDER BY id DESC LIMIT 1` + var ban IPBan + err := QueryRowSQL(query, interfaceSlice(ip), interfaceSlice( + &ban.ID, &ban.StaffID, &ban.BoardID, &ban.BannedForPostID, &ban.CopyPostText, &ban.IsThreadBan, + &ban.IsActive, &ban.IP, &ban.IssuedAt, &ban.AppealAt, &ban.ExpiresAt, &ban.Permanent, &ban.StaffNote, + &ban.Message, &ban.CanAppeal)) + if err == sql.ErrNoRows { + return nil, nil + } + return &ban, nil +} + +// IsGlobalBan returns true if BoardID is a nil int, meaning they are banned on all boards, as opposed to a specific one +func (ipb *IPBan) IsGlobalBan() bool { + return ipb.BoardID == nil +} diff --git a/pkg/gcsql/database.go b/pkg/gcsql/database.go index b7216e82..dab0b853 100644 --- a/pkg/gcsql/database.go +++ b/pkg/gcsql/database.go @@ -179,6 +179,23 @@ func Open(host, dbDriver, dbName, username, password, prefix string) (db *GCDB, return db, err } +// OptimizeDatabase peforms a database optimisation +func OptimizeDatabase() error { + tableRows, tablesErr := QuerySQL("SHOW TABLES") + if tablesErr != nil { + return tablesErr + } + defer tableRows.Close() + for tableRows.Next() { + var table string + tableRows.Scan(&table) + if _, err := ExecSQL("OPTIMIZE TABLE " + table); err != nil { + return err + } + } + return nil +} + func sqlVersionError(err error, dbDriver string, query *string) error { if err == nil { return nil diff --git a/pkg/gcsql/posts.go b/pkg/gcsql/posts.go index c5c632d4..9eed49cf 100644 --- a/pkg/gcsql/posts.go +++ b/pkg/gcsql/posts.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "html/template" + "time" "github.com/gochan-org/gochan/pkg/config" ) @@ -101,6 +102,42 @@ func GetPostPassword(id int) (string, error) { return passwordChecksum, err } +// PermanentlyRemoveDeletedPosts removes all posts and files marked as deleted from the database +func PermanentlyRemoveDeletedPosts() error { + const sql1 = `DELETE FROM DBPREFIXposts WHERE is_deleted` + const sql2 = `DELETE FROM DBPREFIXthreads WHERE is_deleted` + _, err := ExecSQL(sql1) + if err != nil { + return err + } + _, err = ExecSQL(sql2) + return err +} + +// SinceLastPost returns the number of seconds since the given IP address created a post +// (used for checking against the new reply cooldown) +func SinceLastPost(postIP string) (int, error) { + const query = `SELECT MAX(created_on) FROM DBPREFIXposts WHERE ip = ?` + var when time.Time + err := QueryRowSQL(query, interfaceSlice(postIP), interfaceSlice(&when)) + if err != nil { + return -1, err + } + return int(time.Since(when).Seconds()), nil +} + +// SinceLastThread returns the number of seconds since the given IP address created a new thread/top post +// (used for checking against the new thread cooldown) +func SinceLastThread(postIP string) (int, error) { + const query = `SELECT MAX(created_on) FROM DBPREFIXposts WHERE ip = ? AND is_top_post` + var when time.Time + err := QueryRowSQL(query, interfaceSlice(postIP), interfaceSlice(&when)) + if err != nil { + return -1, err + } + return int(time.Since(when).Seconds()), nil +} + // UpdateContents updates the email, subject, and message text of the post func (p *Post) UpdateContents(email string, subject string, message template.HTML, messageRaw string) error { const sqlUpdate = `UPDATE DBPREFIXposts SET email = ?, subject = ?, message = ?, message_raw = ? WHERE ID = ?`