6935637867
Cascade delete replied to topic events for replies properly. Cascade delete likes on topic posts properly. Cascade delete replies and their children properly. Recalculate user stats properly when items are deleted. Users can now unlike topic opening posts. Add a recalculator to fix abnormalities across upgrades. Try fixing a last_ip daily update bug. Add Existable interface. Add Delete method to LikeStore. Add Each, Exists, Create, CountUser, CountMegaUser and CountBigUser methods to ReplyStore. Add CountUser, CountMegaUser, CountBigUser methods to TopicStore. Add Each method to UserStore. Add Add, Delete and DeleteResource methods to SubscriptionStore. Add Delete, DeleteByParams, DeleteByParamsExtra and AidsByParamsExtra methods to ActivityStream. Add Exists method to ProfileReplyStore. Add DropColumn, RenameColumn and ChangeColumn to the database adapters. Shorten ipaddress column names to ip. - topics table. - replies table - users_replies table. - polls_votes table. Add extra column to activity_stream table. Fix an issue upgrading sites to MariaDB 10.3 from older versions of Gosora. Please report any other issues you find. You need to run the updater / patcher for this commit.
356 lines
10 KiB
Go
356 lines
10 KiB
Go
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"mime"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/Azareal/Gosora/tmpl_client"
|
|
)
|
|
|
|
type SFileList map[string]SFile
|
|
|
|
var StaticFiles SFileList = make(map[string]SFile)
|
|
var staticFileMutex sync.RWMutex
|
|
|
|
type SFile struct {
|
|
Data []byte
|
|
GzipData []byte
|
|
Sha256 string
|
|
OName string
|
|
Pos int64
|
|
Length int64
|
|
GzipLength int64
|
|
StrGzipLength string
|
|
Mimetype string
|
|
Info os.FileInfo
|
|
FormattedModTime string
|
|
}
|
|
|
|
type CSSData struct {
|
|
Phrases map[string]string
|
|
}
|
|
|
|
func (list SFileList) JSTmplInit() error {
|
|
DebugLog("Initialising the client side templates")
|
|
return filepath.Walk("./tmpl_client", func(path string, f os.FileInfo, err error) error {
|
|
if f.IsDir() || strings.HasSuffix(path, "template_list.go") || strings.HasSuffix(path, "stub.go") {
|
|
return nil
|
|
}
|
|
path = strings.Replace(path, "\\", "/", -1)
|
|
DebugLog("Processing client template " + path)
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
path = strings.TrimPrefix(path, "tmpl_client/")
|
|
tmplName := strings.TrimSuffix(path, ".jgo")
|
|
shortName := strings.TrimPrefix(tmplName, "template_")
|
|
|
|
replace := func(data []byte, replaceThis, withThis string) []byte {
|
|
return bytes.Replace(data, []byte(replaceThis), []byte(withThis), -1)
|
|
}
|
|
|
|
startIndex, hasFunc := skipAllUntilCharsExist(data, 0, []byte("if(tmplInits===undefined)"))
|
|
if !hasFunc {
|
|
return errors.New("no init map found")
|
|
}
|
|
data = data[startIndex-len([]byte("if(tmplInits===undefined)")):]
|
|
data = replace(data, "// nolint", "")
|
|
data = replace(data, "func ", "function ")
|
|
data = replace(data, " error {\n", " {\nlet out = \"\"\n")
|
|
funcIndex, hasFunc := skipAllUntilCharsExist(data, 0, []byte("function Template_"))
|
|
if !hasFunc {
|
|
return errors.New("no template function found")
|
|
}
|
|
spaceIndex, hasSpace := skipUntilIfExists(data, funcIndex, ' ')
|
|
if !hasSpace {
|
|
return errors.New("no spaces found after the template function name")
|
|
}
|
|
endBrace, hasBrace := skipUntilIfExists(data, spaceIndex, ')')
|
|
if !hasBrace {
|
|
return errors.New("no right brace found after the template function name")
|
|
}
|
|
fmt.Println("spaceIndex: ", spaceIndex)
|
|
fmt.Println("endBrace: ", endBrace)
|
|
fmt.Println("string(data[spaceIndex:endBrace]): ", string(data[spaceIndex:endBrace]))
|
|
|
|
preLen := len(data)
|
|
data = replace(data, string(data[spaceIndex:endBrace]), "")
|
|
data = replace(data, "))\n", " \n")
|
|
endBrace -= preLen - len(data) // Offset it as we've deleted portions
|
|
fmt.Println("new endBrace: ", endBrace)
|
|
fmt.Println("data: ", string(data))
|
|
|
|
/*showPos := func(data []byte, index int) (out string) {
|
|
out = "["
|
|
for j, char := range data {
|
|
if index == j {
|
|
out += "[" + string(char) + "] "
|
|
} else {
|
|
out += string(char) + " "
|
|
}
|
|
}
|
|
return out + "]"
|
|
}*/
|
|
|
|
// ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter
|
|
each := func(phrase string, h func(index int)) {
|
|
//fmt.Println("find each '" + phrase + "'")
|
|
index := endBrace
|
|
if index < 0 {
|
|
panic("index under zero: " + strconv.Itoa(index))
|
|
}
|
|
var foundIt bool
|
|
for {
|
|
//fmt.Println("in index: ", index)
|
|
//fmt.Println("pos: ", showPos(data, index))
|
|
index, foundIt = skipAllUntilCharsExist(data, index, []byte(phrase))
|
|
if !foundIt {
|
|
break
|
|
}
|
|
h(index)
|
|
}
|
|
}
|
|
each("strconv.Itoa(", func(index int) {
|
|
braceAt, hasEndBrace := skipUntilIfExistsOrLine(data, index, ')')
|
|
if hasEndBrace {
|
|
data[braceAt] = ' ' // Blank it
|
|
}
|
|
})
|
|
each("[]byte(", func(index int) {
|
|
braceAt, hasEndBrace := skipUntilIfExistsOrLine(data, index, ')')
|
|
if hasEndBrace {
|
|
data[braceAt] = ' ' // Blank it
|
|
}
|
|
})
|
|
each("StringToBytes(", func(index int) {
|
|
braceAt, hasEndBrace := skipUntilIfExistsOrLine(data, index, ')')
|
|
if hasEndBrace {
|
|
data[braceAt] = ' ' // Blank it
|
|
}
|
|
})
|
|
each("w.Write(", func(index int) {
|
|
braceAt, hasEndBrace := skipUntilIfExistsOrLine(data, index, ')')
|
|
if hasEndBrace {
|
|
data[braceAt] = ' ' // Blank it
|
|
}
|
|
})
|
|
each("RelativeTime(", func(index int) {
|
|
braceAt, _ := skipUntilIfExistsOrLine(data, index, 10)
|
|
if data[braceAt-1] == ' ' {
|
|
data[braceAt-1] = ' ' // Blank it
|
|
}
|
|
})
|
|
each("if ", func(index int) {
|
|
//fmt.Println("if index: ", index)
|
|
braceAt, hasBrace := skipUntilIfExistsOrLine(data, index, '{')
|
|
if hasBrace {
|
|
if data[braceAt-1] != ' ' {
|
|
panic("couldn't find space before brace, found ' " + string(data[braceAt-1]) + "' instead")
|
|
}
|
|
data[braceAt-1] = ')' // Drop a brace here to satisfy JS
|
|
}
|
|
})
|
|
each("for _, item := range ", func(index int) {
|
|
//fmt.Println("for index: ", index)
|
|
braceAt, hasBrace := skipUntilIfExists(data, index, '{')
|
|
if hasBrace {
|
|
if data[braceAt-1] != ' ' {
|
|
panic("couldn't find space before brace, found ' " + string(data[braceAt-1]) + "' instead")
|
|
}
|
|
data[braceAt-1] = ')' // Drop a brace here to satisfy JS
|
|
}
|
|
})
|
|
data = replace(data, "for _, item := range ", "for(item of ")
|
|
data = replace(data, "w.Write([]byte(", "out += ")
|
|
data = replace(data, "w.Write(StringToBytes(", "out += ")
|
|
data = replace(data, "w.Write(", "out += ")
|
|
data = replace(data, "+= c.", "+= ")
|
|
data = replace(data, "strconv.Itoa(", "")
|
|
data = replace(data, "strconv.FormatInt(", "")
|
|
data = replace(data, " c.", "")
|
|
data = replace(data, "phrases.", "")
|
|
data = replace(data, ", 10;", "")
|
|
data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "const plist = tmplPhrases[\""+tmplName+"\"];")
|
|
data = replace(data, "var cached_var_", "let cached_var_")
|
|
data = replace(data, `tmpl_`+shortName+`_vars, ok := tmpl_`+shortName+`_i.`, `/*`)
|
|
data = replace(data, "[]byte(", "")
|
|
data = replace(data, "StringToBytes(", "")
|
|
data = replace(data, "RelativeTime(tmpl_"+shortName+"_vars.", "tmpl_"+shortName+"_vars.Relative")
|
|
// TODO: Format dates properly on the client side
|
|
data = replace(data, ".Format(\"2006-01-02 15:04:05\"", "")
|
|
data = replace(data, ", 10", "")
|
|
data = replace(data, "if ", "if(")
|
|
data = replace(data, "return nil", "return out")
|
|
data = replace(data, " )", ")")
|
|
data = replace(data, " \n", "\n")
|
|
data = replace(data, "\n", ";\n")
|
|
data = replace(data, "{;", "{")
|
|
data = replace(data, "};", "}")
|
|
data = replace(data, "[;", "[")
|
|
data = replace(data, ";;", ";")
|
|
data = replace(data, ",;", ",")
|
|
data = replace(data, "=;", "=")
|
|
data = replace(data, `,
|
|
});
|
|
}`, "\n\t];")
|
|
data = replace(data, `=
|
|
}`, "= []")
|
|
|
|
fragset := tmpl.GetFrag(shortName)
|
|
if fragset != nil {
|
|
sfrags := []byte("let " + shortName + "_frags = [];\n")
|
|
for _, frags := range fragset {
|
|
sfrags = append(sfrags, []byte(shortName+"_frags.push(`"+string(frags)+"`);\n")...)
|
|
}
|
|
data = append(sfrags, data...)
|
|
}
|
|
data = replace(data, "\n;", "\n")
|
|
|
|
for name, _ := range Themes {
|
|
if strings.HasSuffix(shortName, "_"+name) {
|
|
data = append(data, "\nvar Template_"+strings.TrimSuffix(shortName, "_"+name)+" = Template_"+shortName+";"...)
|
|
break
|
|
}
|
|
}
|
|
|
|
path = tmplName + ".js"
|
|
DebugLog("js path: ", path)
|
|
ext := filepath.Ext("/tmpl_client/" + path)
|
|
gzipData, err := CompressBytesGzip(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get a checksum for CSPs and cache busting
|
|
hasher := sha256.New()
|
|
hasher.Write(data)
|
|
checksum := hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
list.Set("/s/"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
|
|
|
|
DebugLogf("Added the '%s' static file.", path)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (list SFileList) Init() error {
|
|
return filepath.Walk("./public", func(path string, f os.FileInfo, err error) error {
|
|
if f.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
path = strings.Replace(path, "\\", "/", -1)
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path = strings.TrimPrefix(path, "public/")
|
|
var ext = filepath.Ext("/public/" + path)
|
|
mimetype := mime.TypeByExtension(ext)
|
|
|
|
// Get a checksum for CSPs and cache busting
|
|
hasher := sha256.New()
|
|
hasher.Write(data)
|
|
checksum := hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
// Avoid double-compressing images
|
|
var gzipData []byte
|
|
if mimetype != "image/jpeg" && mimetype != "image/png" && mimetype != "image/gif" {
|
|
gzipData, err = CompressBytesGzip(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Don't use Gzip if we get meagre gains from it as it takes longer to process the responses
|
|
if len(gzipData) >= (len(data) + 100) {
|
|
gzipData = nil
|
|
} else {
|
|
diff := len(data) - len(gzipData)
|
|
if diff <= len(data)/100 {
|
|
gzipData = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
list.Set("/s/"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mimetype, f, f.ModTime().UTC().Format(http.TimeFormat)})
|
|
|
|
DebugLogf("Added the '%s' static file.", path)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (list SFileList) Add(path, prefix string) error {
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fi, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f, err := fi.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ext := filepath.Ext(path)
|
|
path = strings.TrimPrefix(path, prefix)
|
|
gzipData, err := CompressBytesGzip(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get a checksum for CSPs and cache busting
|
|
hasher := sha256.New()
|
|
hasher.Write(data)
|
|
checksum := hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
list.Set("/s"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
|
|
|
|
DebugLogf("Added the '%s' static file", path)
|
|
return nil
|
|
}
|
|
|
|
func (list SFileList) Get(name string) (file SFile, exists bool) {
|
|
staticFileMutex.RLock()
|
|
defer staticFileMutex.RUnlock()
|
|
file, exists = list[name]
|
|
return file, exists
|
|
}
|
|
|
|
func (list SFileList) Set(name string, data SFile) {
|
|
staticFileMutex.Lock()
|
|
defer staticFileMutex.Unlock()
|
|
list[name] = data
|
|
}
|
|
|
|
func CompressBytesGzip(in []byte) ([]byte, error) {
|
|
var buff bytes.Buffer
|
|
gz, err := gzip.NewWriterLevel(&buff, gzip.BestCompression)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = gz.Write(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = gz.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buff.Bytes(), nil
|
|
}
|