1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-28 08:06:24 -07:00
gochan/pkg/gcsql/queries.go

460 lines
15 KiB
Go

package gcsql
import (
"database/sql"
"errors"
"html/template"
"strings"
"time"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcutil"
)
// GochanVersionKeyConstant is the key value used in the version table of the database to store and receive the (database) version of base gochan
const GochanVersionKeyConstant = "gochan"
var (
ErrNilBoard = errors.New("board is nil")
ErrBoardExists = errors.New("board already exists")
ErrBoardDoesNotExist = errors.New("board does not exist")
)
// GetAllNondeletedMessageRaw gets all the raw message texts from the database, saved per id
func GetAllNondeletedMessageRaw() ([]MessagePostContainer, error) {
const sql = `select posts.id, posts.message, posts.message_raw from DBPREFIXposts as posts
WHERE posts.is_deleted = FALSE`
rows, err := QuerySQL(sql)
if err != nil {
return nil, err
}
var messages []MessagePostContainer
for rows.Next() {
var message MessagePostContainer
var formattedHTML template.HTML
if err = rows.Scan(&message.ID, &formattedHTML, &message.MessageRaw); err != nil {
return nil, err
}
message.Message = template.HTML(formattedHTML)
messages = append(messages, message)
}
return messages, nil
}
// SetFormattedInDatabase sets all the non-raw text for a given array of items.
func SetFormattedInDatabase(messages []MessagePostContainer) error {
const sql = `UPDATE DBPREFIXposts
SET message = ?
WHERE id = ?`
stmt, err := PrepareSQL(sql, nil)
if err != nil {
return err
}
defer stmt.Close()
for _, message := range messages {
if _, err = stmt.Exec(string(message.Message), message.ID); err != nil {
return err
}
}
return 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
}
// OptimizeDatabase peforms a database optimisation
func OptimizeDatabase() error {
tableRows, tablesErr := QuerySQL("SHOW TABLES")
if tablesErr != nil {
return tablesErr
}
for tableRows.Next() {
var table string
tableRows.Scan(&table)
if _, err := ExecSQL("OPTIMIZE TABLE " + table); err != nil {
return err
}
}
return nil
}
func getBoardIDFromURIOrNil(URI string) *int {
ID, err := getBoardIDFromURI(URI)
if err != nil {
return nil
}
return &ID
}
// CreateFileBan creates a new ban on a file. If boards = nil, the ban is global.
func CreateFileBan(fileChecksum, staffName string, permaban bool, staffNote, boardURI string) error {
const sql = `INSERT INTO DBPREFIXfile_ban (board_id, staff_id, staff_note, checksum) VALUES board_id = ?, staff_id = ?, staff_note = ?, checksum = ?`
staffID, err := getStaffID(staffName)
if err != nil {
return err
}
boardID := getBoardIDFromURIOrNil(boardURI)
_, err = ExecSQL(sql, boardID, staffID, staffNote, fileChecksum)
return err
}
// CreateFileNameBan creates a new ban on a filename. If boards = nil, the ban is global.
func CreateFileNameBan(fileName string, isRegex bool, staffName string, permaban bool, staffNote, boardURI string) error {
const sql = `INSERT INTO DBPREFIXfilename_ban (board_id, staff_id, staff_note, filename, is_regex) VALUES board_id = ?, staff_id = ?, staff_note = ?, filename = ?, is_regex = ?`
staffID, err := getStaffID(staffName)
if err != nil {
return err
}
boardID := getBoardIDFromURIOrNil(boardURI)
_, err = ExecSQL(sql, boardID, staffID, staffNote, fileName, isRegex)
return err
}
// CreateUserNameBan creates a new ban on a username. If boards = nil, the ban is global.
func CreateUserNameBan(userName string, isRegex bool, staffName string, permaban bool, staffNote, boardURI string) error {
const sql = `INSERT INTO DBPREFIXusername_ban (board_id, staff_id, staff_note, username, is_regex) VALUES board_id = ?, staff_id = ?, staff_note = ?, username = ?, is_regex = ?`
staffID, err := getStaffID(staffName)
if err != nil {
return err
}
boardID := getBoardIDFromURIOrNil(boardURI)
_, err = ExecSQL(sql, boardID, staffID, staffNote, userName, isRegex)
return err
}
// CreateUserBan creates either a full ip ban, or an ip ban for threads only, for a given IP.
// Deprecated: This method was created to support old functionality during the database refactor of april 2020
// The code should be changed to reflect the new database design
func CreateUserBan(IP string, threadBan bool, staffName, boardURI string, expires time.Time, permaban bool,
staffNote, message string, canAppeal bool, appealAt time.Time) error {
const sql = `INSERT INTO DBPREFIXip_ban (board_id, staff_id, staff_note, is_thread_ban, ip, appeal_at, expires_at, permanent, message, can_appeal, issued_at, copy_posted_text, is_active)
VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP,'OLD SYSTEM BAN, NO TEXT AVAILABLE',TRUE)`
staffID, err := getStaffID(staffName)
if err != nil {
return err
}
boardID := getBoardIDFromURIOrNil(boardURI)
_, err = ExecSQL(sql, boardID, staffID, staffNote, threadBan, IP, appealAt, expires, permaban, message, canAppeal)
return err
}
//GetAllAccouncements gets all announcements, newest first
// Deprecated: This method was created to support old functionality during the database refactor of april 2020
// The code should be changed to reflect the new database design
func GetAllAccouncements() ([]Announcement, error) {
const sql = `SELECT s.username, a.timestamp, a.subject, a.message FROM DBPREFIXannouncements AS a
JOIN DBPREFIXstaff AS s
ON a.staff_id = s.id
ORDER BY a.id DESC`
rows, err := QuerySQL(sql)
if err != nil {
return nil, err
}
announcements := []Announcement{}
for rows.Next() {
var announcement Announcement
err = rows.Scan(&announcement.Poster, &announcement.Timestamp, &announcement.Subject, &announcement.Message)
if err != nil {
return nil, err
}
announcements = append(announcements, announcement)
}
return announcements, nil
}
//GetAllBans gets a list of all bans
//Warning, currently only gets ip bans, not other types of bans, as the ban functionality needs a major revamp anyway
// Deprecated: This method was created to support old functionality during the database refactor of april 2020
// The code should be changed to reflect the new database design
func GetAllBans() ([]BanInfo, error) {
const sql = `SELECT
ban.id,
ban.ip,
COALESCE(board.title, '') as boardname,
staff.username as staff,
ban.issued_at,
ban.expires_at,
ban.permanent,
ban.message,
ban.staff_note,
ban.appeal_at,
ban.can_appeal
FROM DBPREFIXip_ban as ban
JOIN DBPREFIXstaff as staff
ON ban.staff_id = staff.id
JOIN DBPREFIXboards as board
ON ban.board_id = board.id`
rows, err := QuerySQL(sql)
if err != nil {
return nil, err
}
var bans []BanInfo
for rows.Next() {
var ban BanInfo
err = rows.Scan(&ban.ID, &ban.IP, &ban.Boards, &ban.Staff, &ban.Timestamp, &ban.Expires, &ban.Permaban, &ban.Reason, &ban.StaffNote, &ban.AppealAt, &ban.CanAppeal)
if err != nil {
return nil, err
}
bans = append(bans, ban)
}
return bans, nil
}
//CheckBan returns banentry if a ban was found or a sql.ErrNoRows if not banned
// name, filename and checksum may be empty strings and will be treated as not requested if done so
// Deprecated: This method was created to support old functionality during the database refactor of april 2020
// The code should be changed to reflect the new database design
func CheckBan(ip, name, filename, checksum string) (*BanInfo, error) {
ban := new(BanInfo)
ipban, err1 := checkIPBan(ip)
err1NoRows := (err1 == sql.ErrNoRows)
_, err2 := checkFileBan(checksum)
err2NoRows := (err2 == sql.ErrNoRows)
_, err3 := checkFilenameBan(filename)
err3NoRows := (err3 == sql.ErrNoRows)
_, err4 := checkUsernameBan(name)
err4NoRows := (err4 == sql.ErrNoRows)
if err1NoRows && err2NoRows && err3NoRows && err4NoRows {
return nil, sql.ErrNoRows
}
if err1NoRows {
return nil, err1
}
if err2NoRows {
return nil, err2
}
if err3NoRows {
return nil, err3
}
if err4NoRows {
return nil, err4
}
if ipban != nil {
ban.ID = 0
ban.IP = string(ipban.IP)
staff, _ := getStaffByID(ipban.StaffID)
ban.Staff = staff.Username
ban.Timestamp = ipban.IssuedAt
ban.Expires = ipban.ExpiresAt
ban.Permaban = ipban.Permanent
ban.Reason = ipban.Message
ban.StaffNote = ipban.StaffNote
ban.AppealAt = ipban.AppealAt
ban.CanAppeal = ipban.CanAppeal
return ban, nil
}
//TODO implement other types of bans or refactor banning code
return nil, gcutil.ErrNotImplemented
}
func checkIPBan(ip string) (*IPBan, error) {
const sql = `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 = ?`
var ban = new(IPBan)
var formattedHTMLcopyposttest template.HTML
err := QueryRowSQL(sql, interfaceSlice(ip), interfaceSlice(&ban.ID, &ban.StaffID, &ban.BoardID, &ban.BannedForPostID, &formattedHTMLcopyposttest, &ban.IsThreadBan, &ban.IsActive, &ban.IP, &ban.IssuedAt, &ban.AppealAt, &ban.ExpiresAt, &ban.Permanent, &ban.StaffNote, &ban.Message, &ban.CanAppeal))
ban.CopyPostText = formattedHTMLcopyposttest
return ban, err
}
func checkUsernameBan(name string) (*UsernameBan, error) {
const sql = `SELECT id, board_id, staff_id, staff_note, issued_at, username, is_regex
FROM DBPREFIXusername_ban WHERE username = ?`
var ban = new(UsernameBan)
err := QueryRowSQL(sql, interfaceSlice(name), interfaceSlice(&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Username, &ban.IsRegex))
return ban, err
}
func checkFilenameBan(filename string) (*FilenameBan, error) {
const sql = `SELECT id, board_id, staff_id, staff_note, issued_at, filename, is_regex
FROM DBPREFIXfilename_ban WHERE filename = ?`
var ban = new(FilenameBan)
err := QueryRowSQL(sql, interfaceSlice(filename), interfaceSlice(&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Filename, &ban.IsRegex))
return ban, err
}
func checkFileBan(checksum string) (*FileBan, error) {
const sql = `SELECT id, board_id, staff_id, staff_note, issued_at, checksum
FROM DBPREFIXfile_ban WHERE checksum = ?`
var ban = new(FileBan)
err := QueryRowSQL(sql, interfaceSlice(checksum), interfaceSlice(&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Checksum))
return ban, err
}
//GetMaxMessageLength returns the max message length on a board
func GetMaxMessageLength(boardID int) (length int, err error) {
const sql = `SELECT max_message_length FROM DBPREFIXboards
WHERE id = ?`
err = QueryRowSQL(sql, interfaceSlice(boardID), interfaceSlice(&length))
return length, err
}
//GetEmbedsAllowed returns if embeds are allowed on a given board
func GetEmbedsAllowed(boardID int) (allowed bool, err error) {
const sql = `SELECT allow_embeds FROM DBPREFIXboards
WHERE id = ?`
err = QueryRowSQL(sql, interfaceSlice(boardID), interfaceSlice(&allowed))
return allowed, err
}
//AddBanAppeal adds a given appeal to a given ban
func AddBanAppeal(banID uint, message string) error {
const sql1 = `
/*copy old to audit*/
INSERT INTO DBPREFIXip_ban_appeals_audit (appeal_id, staff_id, appeal_text, staff_response, is_denied)
SELECT id, staff_id, appeal_text, staff_response, is_denied
FROM DBPREFIXip_ban_appeals
WHERE DBPREFIXip_ban_appeals.ip_ban_id = ?`
const sql2 = `
/*update old values to new values*/
UPDATE DBPREFIXip_ban_appeals SET appeal_text = ? WHERE ip_ban_id = ?
`
_, err := ExecSQL(sql1, banID)
if err != nil {
return err
}
_, err = ExecSQL(sql2, message, banID)
return err
}
//CreateDefaultAdminIfNoStaff creates a new default admin account if no accounts exist
func CreateDefaultAdminIfNoStaff() error {
const sql = `SELECT COUNT(id) FROM DBPREFIXstaff`
var count int
QueryRowSQL(sql, interfaceSlice(), interfaceSlice(&count))
if count > 0 {
return nil
}
_, err := createUser("admin", gcutil.BcryptSum("password"), 3)
return err
}
func NewWordFilter() *WordFilter {
var wf *WordFilter
return wf
}
// GetWordFilters gets a list of wordfilters from the database and returns an array of them and any errors
// encountered
func GetWordFilters() ([]WordFilter, error) {
var wfs []WordFilter
query := `SELECT id,board_dirs,staff_id,staff_note,issued_at,search,is_regex,change_to FROM DBPREFIXwordfilters`
rows, err := QuerySQL(query)
if err != nil {
return wfs, err
}
defer rows.Close()
for rows.Next() {
var dirsStr string
var wf WordFilter
if err = rows.Scan(
&wf.ID,
&dirsStr,
&wf.StaffID,
&wf.StaffNote,
&wf.IssuedAt,
&wf.Search,
&wf.IsRegex,
&wf.ChangeTo,
); err != nil {
return wfs, err
}
if dirsStr != "*" {
wf.BoardDirs = strings.Split(dirsStr, ",")
}
wfs = append(wfs, wf)
}
return wfs, err
}
// BoardString returns a string representing the boards that this wordfilter applies to,
// or "*" if the filter should be applied to posts on all boards
func (wf *WordFilter) BoardsString() string {
if wf.BoardDirs == nil {
return "*"
}
return strings.Join(wf.BoardDirs, ",")
}
func (wf *WordFilter) OnBoard(dir string) bool {
if dir == "*" {
return true
}
for _, board := range wf.BoardDirs {
if dir == board {
return true
}
}
return false
}
func (wf *WordFilter) StaffName() string {
staff, err := getStaffByID(wf.StaffID)
if err != nil {
return "?"
}
return staff.Username
}
//getDatabaseVersion gets the version of the database, or an error if none or multiple exist
func getDatabaseVersion(componentKey string) (int, error) {
const sql = `SELECT version FROM DBPREFIXdatabase_version WHERE component = ?`
var version int
err := QueryRowSQL(sql, []interface{}{componentKey}, []interface{}{&version})
if err != nil {
return 0, err
}
return version, err
}
func getNextFreeID(tableName string) (ID int, err error) {
var sql = `SELECT COALESCE(MAX(id), 0) + 1 FROM ` + tableName
err = QueryRowSQL(sql, interfaceSlice(), interfaceSlice(&ID))
return ID, err
}
func doesTableExist(tableName string) (bool, error) {
const existQuery = `SELECT COUNT(*)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ?`
var count int
err := QueryRowSQL(existQuery, []interface{}{config.GetSystemCriticalConfig().DBprefix + tableName}, []interface{}{&count})
if err != nil {
return false, err
}
return count == 1, nil
}
//doesGochanPrefixTableExist returns true if any table with a gochan prefix was found.
//Returns false if the prefix is an empty string
func doesGochanPrefixTableExist() (bool, error) {
if config.GetSystemCriticalConfig().DBprefix == "" {
return false, nil
}
var prefixTableExist = `SELECT count(*)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE 'DBPREFIX%'`
var count int
err := QueryRowSQL(prefixTableExist, []interface{}{}, []interface{}{&count})
if err != nil {
return false, err
}
return count > 0, nil
}