func (p *RecentPost) GetURL(includeDomain bool) string {
	postURL := ""
	systemCritical := config.GetSystemCriticalConfig()
	if includeDomain {
		postURL += systemCritical.SiteDomain
	}
	idStr := strconv.Itoa(p.PostID)
	postURL += systemCritical.WebRoot + p.BoardName + "/res/"
	if p.ParentID == 0 {
		postURL += idStr + ".html#" + idStr
	} else {
		postURL += strconv.Itoa(p.ParentID) + ".html#" + idStr
	}
	return postURL
}
GetURL returns the full URL of the recent post, or the full path if includeDomain is false
package gcsql
import (
	"fmt"
	"html"
	"html/template"
	"path"
	"strconv"
	"strings"
	"time"
	"github.com/gochan-org/gochan/pkg/config"
)
const (
	_ = iota
	threadBan
	imageBan
	fullBan
)
var (
	AllSections []BoardSection
	AllBoards []Board
	TempPosts []Post
)
type Announcement struct {
	ID        uint   `json:"no"`
	Subject   string `json:"sub"`
	Message   string `json:"com"`
	Poster    string `json:"name"`
	Timestamp time.Time
}
type BanAppeal struct {
	ID            int
	Ban           int
	Message       string
	Denied        bool
	StaffResponse string
}
type BanInfo struct {
	ID          uint
	IP          string
	Name        string
	NameIsRegex bool
	Boards      string
	Staff       string
	Timestamp   time.Time
	Expires     time.Time
	Permaban    bool
	Reason      string
	Type        int
	StaffNote   string
	AppealAt    time.Time
	CanAppeal   bool
}
func (ban *BanInfo) BannedForever() bool {
	return ban.Permaban && !ban.CanAppeal && ban.Type == fullBan && ban.Boards == ""
}
func (ban *BanInfo) IsActive(board string) bool {
	if ban.Boards == "" && (ban.Expires.After(time.Now()) || ban.Permaban) {
		return true
	}
	boardsArr := strings.Split(ban.Boards, ",")
	for _, b := range boardsArr {
		if b == board && (ban.Expires.After(time.Now()) || ban.Permaban) {
			return true
		}
	}
	return false
}
func (ban *BanInfo) IsBanned(board string) bool {
	if ban.Boards == "" && (ban.Expires.After(time.Now()) || ban.Permaban) {
		return true
	}
	boardsArr := strings.Split(ban.Boards, ",")
	for _, b := range boardsArr {
		if b == board && (ban.Expires.After(time.Now()) || ban.Permaban) {
			return true
		}
	}
	return false
}
type BannedHash struct {
	ID          uint
	Checksum    string
	Description string
}
type Board struct {
	ID                     int            `json:"-"`
	CurrentPage            int            `json:"-"`
	NumPages               int            `json:"pages"`
	ListOrder              int            `json:"-"`
	Dir                    string         `json:"board"`
	Type                   int            `json:"-"`
	UploadType             int            `json:"-"`
	Title                  string         `json:"title"`
	Subtitle               string         `json:"meta_description"`
	Description            string         `json:"-"`
	Section                int            `json:"-"`
	MaxFilesize            int            `json:"max_filesize"`
	MaxPages               int            `json:"max_pages"`
	DefaultStyle           string         `json:"-"`
	Locked                 bool           `json:"is_archived"`
	CreatedOn              time.Time      `json:"-"`
	Anonymous              string         `json:"-"`
	ForcedAnon             bool           `json:"-"`
	MaxAge                 int            `json:"-"`
	AutosageAfter          int            `json:"bump_limit"`
	NoImagesAfter          int            `json:"image_limit"`
	MaxMessageLength       int            `json:"max_comment_chars"`
	EmbedsAllowed          bool           `json:"-"`
	RedirectToThread       bool           `json:"-"`
	ShowID                 bool           `json:"-"`
	RequireFile            bool           `json:"-"`
	EnableCatalog          bool           `json:"-"`
	EnableSpoileredImages  bool           `json:"-"`
	EnableSpoileredThreads bool           `json:"-"`
	Worksafe               bool           `json:"ws_board"`
	ThreadPage             int            `json:"-"`
	Cooldowns              BoardCooldowns `json:"cooldowns"`
	ThreadsPerPage         int            `json:"per_page"`
}
func (board *Board) AbsolutePath(subpath ...string) string {
	return path.Join(config.GetSystemCriticalConfig().DocumentRoot, board.Dir, path.Join(subpath...))
}
func (board *Board) WebPath(fileName, fileType string) string {
	var filePath string
	systemCritical := config.GetSystemCriticalConfig()
	switch fileType {
	case "":
		fallthrough
	case "boardPage":
		filePath = path.Join(systemCritical.WebRoot, board.Dir, fileName)
	case "threadPage":
		filePath = path.Join(systemCritical.WebRoot, board.Dir, "res", fileName)
	case "upload":
		filePath = path.Join(systemCritical.WebRoot, board.Dir, "src", fileName)
	case "thumb":
		filePath = path.Join(systemCritical.WebRoot, board.Dir, "thumb", fileName)
	}
	return filePath
}
func (board *Board) PagePath(pageNum interface{}) string {
	var page string
	pageNumStr := fmt.Sprintf("%v", pageNum)
	if pageNumStr == "prev" {
		if board.CurrentPage < 2 {
			page = "1"
		} else {
			page = strconv.Itoa(board.CurrentPage - 1)
		}
	} else if pageNumStr == "next" {
		if board.CurrentPage >= board.NumPages {
			page = strconv.Itoa(board.NumPages)
		} else {
			page = strconv.Itoa(board.CurrentPage + 1)
		}
	} else {
		page = pageNumStr
	}
	return board.WebPath(page+".html", "boardPage")
}
func (board *Board) SetDefaults() {
	board.ListOrder = 0
	board.Section = 1
	board.MaxFilesize = 4096
	board.MaxPages = 11
	board.DefaultStyle = config.GetBoardConfig("").DefaultStyle
	board.Locked = false
	board.Anonymous = "Anonymous"
	board.ForcedAnon = false
	board.MaxAge = 0
	board.AutosageAfter = 200
	board.NoImagesAfter = 0
	board.MaxMessageLength = 8192
	board.EmbedsAllowed = true
	board.RedirectToThread = false
	board.ShowID = false
	board.RequireFile = false
	board.EnableCatalog = true
	board.EnableSpoileredImages = true
	board.EnableSpoileredThreads = true
	board.Worksafe = true
	board.ThreadsPerPage = 10
}
type BoardSection struct {
	ID           int
	ListOrder    int
	Hidden       bool
	Name         string
	Abbreviation string
}
type Post struct {
	ID               int           `json:"no"`
	ParentID         int           `json:"resto"`
	CurrentPage      int           `json:"-"`
	BoardID          int           `json:"-"`
	Name             string        `json:"name"`
	Tripcode         string        `json:"trip"`
	Email            string        `json:"email"`
	Subject          string        `json:"sub"`
	MessageHTML      template.HTML `json:"com"`
	MessageText      string        `json:"-"`
	Password         string        `json:"-"`
	Filename         string        `json:"tim"`
	FilenameOriginal string        `json:"filename"`
	FileChecksum     string        `json:"md5"`
	FileExt          string        `json:"extension"`
	Filesize         int           `json:"fsize"`
	ImageW           int           `json:"w"`
	ImageH           int           `json:"h"`
	ThumbW           int           `json:"tn_w"`
	ThumbH           int           `json:"tn_h"`
	IP               string        `json:"-"`
	Capcode          string        `json:"capcode"`
	Timestamp        time.Time     `json:"time"`
	Autosage         bool          `json:"-"`
	Bumped           time.Time     `json:"last_modified"`
	Stickied         bool          `json:"-"`
	Locked           bool          `json:"-"`
	Reviewed         bool          `json:"-"`
}
func (p *Post) GetURL(includeDomain bool) string {
	postURL := ""
	systemCritical := config.GetSystemCriticalConfig()
	if includeDomain {
		postURL += systemCritical.SiteDomain
	}
	var board Board
	if err := board.PopulateData(p.BoardID); err != nil {
		return postURL
	}
	postURL += systemCritical.WebRoot + board.Dir + "/res/"
	if p.ParentID == 0 {
		postURL += fmt.Sprintf("%d.html#%d", p.ID, p.ID)
	} else {
		postURL += fmt.Sprintf("%d.html#%d", p.ParentID, p.ID)
	}
	return postURL
}
func (p *Post) Sanitize() {
	p.Name = html.EscapeString(p.Name)
	p.Email = html.EscapeString(p.Email)
	p.Subject = html.EscapeString(p.Subject)
	p.Password = html.EscapeString(p.Password)
	if p.ParentID < 0 {
		p.ParentID = 0
	}
}
type Report struct {
	ID        uint
	Board     string
	PostID    uint
	Timestamp time.Time
	IP        string
	Reason    string
	Cleared   bool
	IsTemp    bool
}
type LoginSession struct {
	ID      uint
	Name    string
	Data    string
	Expires string
}
type Staff struct {
	ID               int
	Username         string
	PasswordChecksum string `json:"-"`
	Rank             int
	AddedOn          time.Time
	LastActive       time.Time
}
type BoardCooldowns struct {
	NewThread  int `json:"threads"`
	Reply      int `json:"replies"`
	ImageReply int `json:"images"`
}
type MessagePostContainer struct {
	ID         int
	MessageRaw string
	Message    template.HTML
}
type RecentPost struct {
	BoardName string
	BoardID   int
	PostID    int
	ParentID  int
	Name      string
	Tripcode  string
	Message   template.HTML
	Filename  string
	ThumbW    int
	ThumbH    int
	IP        string
	Timestamp time.Time
}