From ad8a5246eca4a338172fce2694abd312d59275b8 Mon Sep 17 00:00:00 2001 From: pathnirvana Date: Sat, 31 Aug 2024 22:33:53 +0700 Subject: [PATCH] new server in go --- .gitignore | 5 +- {server => db}/dict.db | Bin db/template.html | 83 ++++++++ public/static/images/ios.png | Bin 0 -> 16915 bytes server/bin/sign-notorize.sh | 33 ++++ server/bot-renderer.go | 362 +++++++++++++++++++++++++++++++++++ server/build-all.sh | 48 +++++ server/go.mod | 27 +++ server/go.sum | 46 +++++ server/server.go | 264 +++++++++++++++++++++++++ server/server.js | 4 +- src/constants.js | 2 +- src/store/search.js | 4 +- src/views/Settings.vue | 2 +- src/views/Welcome.vue | 5 + 15 files changed, 877 insertions(+), 8 deletions(-) rename {server => db}/dict.db (100%) create mode 100644 db/template.html create mode 100644 public/static/images/ios.png create mode 100755 server/bin/sign-notorize.sh create mode 100644 server/bot-renderer.go create mode 100755 server/build-all.sh create mode 100644 server/go.mod create mode 100644 server/go.sum create mode 100644 server/server.go diff --git a/.gitignore b/.gitignore index a2382fc1..470b7b52 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +28,6 @@ yarn-error.log* node_sqlite3.node tipitaka-lk.exe tipitaka-lk-macos -server/fts.db -server/fts.db copy +fts.db* +server/bin/tipitaka_lk* +server/server diff --git a/server/dict.db b/db/dict.db similarity index 100% rename from server/dict.db rename to db/dict.db diff --git a/db/template.html b/db/template.html new file mode 100644 index 00000000..0fc6a1d1 --- /dev/null +++ b/db/template.html @@ -0,0 +1,83 @@ + + + + + {{.PathInfo.GetTitle}} + + + + + + + + + + + + + + + + + + + + + + + + +

{{.PathInfo.GetDescription .Collection}}

+

+ {{range .PathInfo.GetHeadingLinks}} + {{.Name}} < + {{end}} +

+ +
+ {{range .Entries}} +
+ {{.RenderText}} +
+ {{end}} +
+ + \ No newline at end of file diff --git a/public/static/images/ios.png b/public/static/images/ios.png new file mode 100644 index 0000000000000000000000000000000000000000..058fc27f92c1af628ee93cc47be161e20855623d GIT binary patch literal 16915 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 U$m-!=g*PBn6x8L*WX(eU51|+v+yDRo literal 0 HcmV?d00001 diff --git a/server/bin/sign-notorize.sh b/server/bin/sign-notorize.sh new file mode 100755 index 00000000..60461adc --- /dev/null +++ b/server/bin/sign-notorize.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Get the absolute path to the script's directory +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +# Change the current working directory to the script's directory +cd "$SCRIPT_DIR" + +# use to find the hash to codesign below +#security find-identity -v + +FILES=( + #"tipitaka_lk_macos_intel" + "tipitaka_lk_macos_m1m2" +) +FOLDER="tipitaka_lk_macos" + +for FILE in "${FILES[@]}" +do + codesign -s B9F140C9B821EFB37D751109B3593EB60A5F3C17 -o runtime -v $FILE + + # create a zip file or a dmg file containing the binary - otherwise notorization fails + # dmg files can be stapled but not zip files - so use dmg below + rm -rf $FOLDER && mkdir $FOLDER + cp $FILE $FOLDER + rm -rf $FILE.dmg + hdiutil create -srcFolder $FOLDER -o $FILE.dmg + + xcrun notarytool submit --keychain-profile "janaka" --wait $FILE.dmg + + xcrun stapler staple $FILE.dmg + +done \ No newline at end of file diff --git a/server/bot-renderer.go b/server/bot-renderer.go new file mode 100644 index 00000000..443b2a7b --- /dev/null +++ b/server/bot-renderer.go @@ -0,0 +1,362 @@ +package main + +import ( + "encoding/json" + "fmt" + "html/template" + "log" + "os" + "regexp" + "strconv" + "strings" + + "github.com/gofiber/fiber/v2" + "github.com/samber/lo" +) + +// Compile the regex pattern (do this once, outside the handler) +var ( + excludePattern = regexp.MustCompile(`/(fts|dict)/`) + botPathPattern = regexp.MustCompile(`Googlebot|facebookexternalhit`) + treeData map[string]TreeItem + htmlTemplate *template.Template +) + +type ReplaceRules []struct { + CompiledRegex *regexp.Regexp + ReplacementStr string +} + +func (rules *ReplaceRules) applyReplacements(input string) string { + result := input + for _, rule := range *rules { + result = rule.CompiledRegex.ReplaceAllString(result, rule.ReplacementStr) + } + return result +} + +var textConvertRules = ReplaceRules{ + {regexp.MustCompile(`\{(.+?)\}`), ""}, // remove footnote pointers + {regexp.MustCompile(`\x{0DCA}([\x{0DBA}\x{0DBB}])`), "\u0DCA\u200D$1"}, // yansa/rakar beautification + {regexp.MustCompile(`\*\*(.*?)\*\*`), "$1"}, // add bold + {regexp.MustCompile(`__(.*?)__`), "$1"}, // underline + {regexp.MustCompile(`↴|\n`), "
"}, // if used {white-space: pre-wrap;} css this is not needed + {regexp.MustCompile(`\$\$(.*?)\$\$`), "$1"}, // just get rid of $$ +} + +func (e Entry) RenderText() template.HTML { + return template.HTML(textConvertRules.applyReplacements(e.Text)) +} + +// func main() { +// initRenderer() +// path := "/sn-1-1-1-1/10-1/sinh" +// var pathInfo PathInfo +// if err := parsePath(path, &pathInfo); err != nil { +// fmt.Println("Error parsing path:", err) +// } +// fmt.Println(pathInfo) +// if _, ok := treeData[pathInfo.Key]; !ok { +// log.Fatalf("Given key: %s not found in tree", pathInfo.Key) +// } + +// var doc Document +// loadTextFile("../dist/static/text/"+treeData[pathInfo.Key].File+".json", &doc) +// // Access and use the data +// fmt.Println(doc.Filename) + +// entries := getEntriesForPath(&doc, pathInfo) +// for _, entry := range entries { +// fmt.Println(entry.Text) +// } + +// // Create or open the output file +// file, err := os.Create("./rendered.html") // Adjust the file path as needed +// if err != nil { +// log.Fatal("Error creating file:", err) +// } +// defer file.Close() + +// // Render the template and write to the file +// data := TemplateData{treeData[pathInfo.Key], pathInfo, entries, doc.Collection} +// err = htmlTemplate.Execute(file, data) +// if err != nil { +// log.Fatal("Error template execute:", err) +// } + +// fmt.Println(len(treeData)) +// } + +type TemplateData struct { + TreeItem TreeItem + PathInfo PathInfo + Entries []Entry + Collection string + //Funcs map[string]interface{} +} + +func getTemplateData(path string) (*TemplateData, error) { + //path := "/sn-1-1-1-1/10-1/sinh" + var pathInfo PathInfo + if err := parsePath(path, &pathInfo); err != nil { + return nil, err + } + + if _, ok := treeData[pathInfo.Key]; !ok { + return nil, fmt.Errorf("given key: %s not found in tree", pathInfo.Key) + } + + var textFilename = treeData[pathInfo.Key].File + doc, err := loadTextFile(getPathToFile("dist/static/text/" + textFilename + ".json")) + if err != nil { + return nil, fmt.Errorf("error loading text file %s: %v", textFilename, err) + } + + entries := getEntriesForPath(doc, pathInfo) + data := TemplateData{treeData[pathInfo.Key], pathInfo, entries, doc.Collection} + return &data, nil +} + +// BotDetectionMiddleware detects Googlebot and Facebook crawlers and serves specific files +func BotRendererMiddleware(c *fiber.Ctx) error { + // Check if the route matches the exclude pattern + if excludePattern.MatchString(c.Path()) { + return c.Next() // Skip bot detection for these routes + } + + userAgent := c.Get("User-Agent") + //if botPathPattern.MatchString(userAgent) { + data, err := getTemplateData(c.Path()) + if err != nil { + fmt.Printf("Error handling bot path %s: %v\n", c.Path(), err) + return c.Next() + } + c.Response().Header.Set("Content-Type", "text/html") + if err := htmlTemplate.Execute(c.Response().BodyWriter(), data); err != nil { + fmt.Printf("Error handling bot path %s: %v\n", c.Path(), err) + return c.Next() + } + fmt.Printf("Bot render for %s, path %s.\n", userAgent, c.Path()) + return nil // Indicate successful handling + //} + + return c.Next() +} + +func initBotRendererMiddleware() { + // Load the HTML template + var err error + htmlTemplate, err = template.ParseFiles(getPathToFile("db/template.html")) + if err != nil { + log.Fatal("Error parsing template:", err) + } + // Read the JSON file + data, err := os.ReadFile(getPathToFile("dist/static/data/tree.json")) + if err != nil { + log.Fatal("Error reading file:", err) + } + + // Unmarshal into a map of TreeItem slices + err = json.Unmarshal(data, &treeData) + if err != nil { + log.Fatal("Error unmarshalling JSON:", err) + } +} + +type Document struct { + Filename string `json:"filename"` + Pages []PagePair `json:"pages"` + BookId int `json:"bookId"` + PageOffset int `json:"pageOffset"` + Collection string `json:"collection"` +} + +type PagePair struct { + PageNum int `json:"pageNum"` + Pali Page `json:"pali"` + Sinh Page `json:"sinh"` +} + +func (dp *PagePair) getEntries(lang string, entryIndex int) []Entry { + entryIndex = min(len(dp.Pali.Entries), entryIndex) + if lang == "pali" { + return dp.Pali.Entries[entryIndex:] + } + return dp.Sinh.Entries[entryIndex:] +} + +type Page struct { + Entries []Entry `json:"entries"` + Footnotes []Entry `json:"footnotes"` +} + +type Entry struct { + Type string `json:"type"` + Text string `json:"text"` + Level int `json:"level"` + NoAudio bool `json:"noAudio,omitempty"` // 'omitempty' handles optional fields +} + +func loadTextFile(filename string) (*Document, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + var doc Document + if err := json.Unmarshal(data, &doc); err != nil { + return nil, err + } + return &doc, nil +} + +func getEntriesForPath(doc *Document, pathInfo PathInfo) []Entry { + location := treeData[pathInfo.Key].Location + if pathInfo.Location.Page >= 0 { + location = pathInfo.Location + } + if location.Page < 0 || location.Page >= len(doc.Pages) { + fmt.Printf("Invalid page index: %d in route: %s\n", location.Page, pathInfo.String) + location = treeData[pathInfo.Key].Location // reset back to using the sutta key + } + + flattenedEntries := doc.Pages[location.Page].getEntries(pathInfo.Lang, location.Entry) + + startPage := min(location.Page+1, len(doc.Pages)) + endPage := min(location.Page+3, len(doc.Pages)) + for _, page := range doc.Pages[startPage:endPage] { + flattenedEntries = append(flattenedEntries, page.getEntries(pathInfo.Lang, 0)...) + } + + return flattenedEntries +} + +type Location struct { + Page int `json:"0"` + Entry int `json:"1"` +} +type PathInfo struct { + Key string + Lang string + Location Location + String string +} + +func parsePath(path string, info *PathInfo) error { + info.String = strings.Trim(path, "/") + parts := strings.Split(info.String, "/") + + if len(parts) < 1 || len(parts) > 3 { + return fmt.Errorf("invalid path format: %s", path) + } + + info.Key = parts[0] + info.Lang = "pali" // default lang if path is like /dn-1 + if len(parts) >= 2 { + info.Lang = parts[len(parts)-1] + } + info.Location.Page, info.Location.Entry = -1, -1 + + if len(parts) == 3 { + coordParts := strings.Split(parts[1], "-") + if len(coordParts) != 2 { + return fmt.Errorf("invalid location format: %s", parts[2]) + } + + var err error + info.Location.Page, err = strconv.Atoi(coordParts[0]) + if err != nil { + return fmt.Errorf("invalid page number: %s", coordParts[0]) + } + info.Location.Entry, err = strconv.Atoi(coordParts[1]) + if err != nil { + return fmt.Errorf("invalid entry number: %s", coordParts[1]) + } + } + + return nil +} + +type TreeItem struct { + Pali string `json:"0"` + Sinhala string `json:"1"` + Level int `json:"2"` + Location Location `json:"3"` + Parent string `json:"4"` + File string `json:"5"` +} +type HeadingLink struct { + Name string // pali or sinhala heading name + URL string // url link to the heading +} + +func (ti TreeItem) GetName(lang string) string { + if lang == "sinh" { + return ti.Sinhala + } + return ti.Pali +} +func (p PathInfo) GetTitle() string { + return strings.Join(lo.Map(p.GetHeadingLinks(), func(hl HeadingLink, _ int) string { + return hl.Name + }), " < ") +} +func (p PathInfo) GetHeadingLinks() []HeadingLink { + ti := treeData[p.Key] + headings := []HeadingLink{{ + Name: ti.GetName(p.Lang), + URL: fmt.Sprintf("https://tipitaka.lk/%s/%s", p.Key, p.Lang), + }} + for ti.Parent != "root" { + url := fmt.Sprintf("https://tipitaka.lk/%s/%s", ti.Parent, p.Lang) + ti = treeData[ti.Parent] + headings = append(headings, HeadingLink{ + Name: ti.GetName(p.Lang), + URL: url, + }) + } + return headings +} +func (p PathInfo) GetDescription(col string) string { + colStr, langStr, pageStr := "බුද්ධ ජයන්ති ත්\u200dරිපිටකය", "පාළි", "" + if strings.HasPrefix(p.Key, "atta") { + colStr = "අටුවාව" + } else if strings.HasPrefix(p.Key, "anya") { + colStr = "අන්‍ය ග්‍රන්ථ" + } + if p.Lang == "sinh" { + langStr = "සිංහල" + } + if p.Location.Page >= 0 { + pageStr = fmt.Sprintf("- %d පිටුව", p.Location.Page+1) + } + return fmt.Sprintf("%s - %s%s", colStr, langStr, pageStr) +} + +// Custom UnmarshalJSON method needed because the json array contains values of different types +func (ti *TreeItem) UnmarshalJSON(data []byte) error { + var v []interface{} + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + if len(v) != 6 { + return fmt.Errorf("invalid TreeItem format: expected 6 elements, got %d", len(v)) + } + + // Type assertions and assignments + ti.Pali = v[0].(string) + ti.Sinhala = v[1].(string) + ti.Level = int(v[2].(float64)) + + coords, ok := v[3].([]interface{}) + if !ok || len(coords) != 2 { + return fmt.Errorf("invalid Entry Index format") + } + ti.Location.Page = int(coords[0].(float64)) + ti.Location.Entry = int(coords[1].(float64)) + + ti.Parent = v[4].(string) + ti.File = v[5].(string) + + return nil +} diff --git a/server/build-all.sh b/server/build-all.sh new file mode 100755 index 00000000..3e35be6d --- /dev/null +++ b/server/build-all.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Get the absolute path to the script's directory +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +# Change the current working directory to the script's directory +cd "$SCRIPT_DIR" + +# Got linux toolchain from here +# brew tap SergioBenitez/osxct +# brew install x86_64-unknown-linux-gnu + +# The name of your Go program (without the .go extension) +PROGRAM_NAME="tipitaka_lk" +export CGO_ENABLED=1 + +# Platforms and architectures to build for +PLATFORMS=( + #"linux/amd64/linux_intel/x86_64-unknown-linux-gnu-gcc" + ####"linux/arm64/linux_arm" + #"darwin/amd64/macos_intel/" + #"darwin/arm64/macos_m1m2/" + "windows/amd64/windows_intel/x86_64-w64-mingw32-gcc" + ####"windows/arm64/windows_arm/" + "windows/386/windows_32bit/i686-w64-mingw32-gcc" # Windows 32-bit +) + +# Iterate over each platform +for PLATFORM in "${PLATFORMS[@]}" +do + # Split the platform into OS and ARCH + IFS="/" read -r GOOS GOARCH BINARYNAME CC <<< "$PLATFORM" + + if [[ -n "$CC" ]]; then # Check if the string is not empty + CC="CC=${CC}" + fi + + # Construct the output binary name + OUTPUT_NAME="${PROGRAM_NAME}_${BINARYNAME}" + if [ "$GOOS" == "windows" ]; then + OUTPUT_NAME+=".exe" + fi + + # Set environment variables, disable cgo and build + env $CC GOOS=$GOOS GOARCH=$GOARCH go build -o bin/$OUTPUT_NAME + + echo "Built for $PLATFORM: $OUTPUT_NAME $CC" +done \ No newline at end of file diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 00000000..aa417b8e --- /dev/null +++ b/server/go.mod @@ -0,0 +1,27 @@ +module tipitaka.lk/server + +go 1.23.0 + +require ( + github.com/fatih/color v1.17.0 + github.com/gofiber/fiber/v2 v2.52.5 + github.com/jmoiron/sqlx v1.4.0 + github.com/mattn/go-sqlite3 v1.14.22 + github.com/samber/lo v1.47.0 + github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect +) diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 00000000..3ef4e019 --- /dev/null +++ b/server/go.sum @@ -0,0 +1,46 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= diff --git a/server/server.go b/server/server.go new file mode 100644 index 00000000..44514b23 --- /dev/null +++ b/server/server.go @@ -0,0 +1,264 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "unicode/utf8" + + "github.com/fatih/color" + "github.com/gofiber/fiber/v2" + + //"github.com/gofiber/fiber/v2/middleware/compress" // Import compress middleware for gzip + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" + "github.com/skratchdot/open-golang/open" + //_ "modernc.org/sqlite" +) + +type QueryPayload struct { + DBName string `json:"dbname"` + Query string `json:"query"` +} + +var ( + dbConnections = make(map[string]*sqlx.DB) + mutex sync.Mutex + APPNAME = "Tipitaka.lk v2.0" + userBJTPath = "" // call such as ./server -bjt-path=/Volumes/1TB/Webstorm/pitaka/bjt/newbooks + rootPath = "" // call such as ./server -root-path=../../ relative to the exePath + exePath = "" // determined programmetically below + PORT = ":8400" + URL = "http://localhost" + PORT +) + +func getPathToFile(file string) string { + return filepath.Join(exePath, rootPath, file) +} + +func main() { + printBox() + + noOpen := flag.Bool("no-open", false, "Prevent opening the URL in the browser") + flag.StringVar(&userBJTPath, "bjt-path", "", "Local folder where BJT scanned pages are located.") + flag.StringVar(&rootPath, "root-path", "", "Where dist and dbs are located relative to the binary location.") + flag.Parse() // Parse the command-line flags + exeFile, _ := os.Executable() + exePath = filepath.Dir(exeFile) + color.White("Flags no-open=%t, bjt-path=%s, root-path=%s, exePath: %s", *noOpen, userBJTPath, rootPath, exePath) + + app := fiber.New(fiber.Config{AppName: APPNAME, DisableStartupMessage: true}) + + // Use gzip compression middleware + //app.Use(compress.New(compress.Config{ + // Level: compress.LevelBestSpeed, // Adjust compression level as needed + //})) + + initBotRendererMiddleware() + // serve pre-rendered pages to google/fb bots + app.Use(BotRendererMiddleware) + + // Define the endpoint for sqlite queries + app.Post("/sql-query", func(c *fiber.Ctx) error { + //return executeQueryAndReturnJSON(c) + var payload QueryPayload + if err := c.BodyParser(&payload); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) + } + + // Get or create database connection + mutex.Lock() + db, exists := dbConnections[payload.DBName] + if !exists { + var err error + db, err = sqlx.Open("sqlite3", getPathToFile("db/"+payload.DBName)) + if err != nil { + mutex.Unlock() + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + dbConnections[payload.DBName] = db + } + mutex.Unlock() + + // Execute the SQL query + rows, err := db.Queryx(payload.Query) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + defer rows.Close() + + // Scan results into a slice of maps + var results []map[string]interface{} + for rows.Next() { + result := make(map[string]interface{}) + err = rows.MapScan(result) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + results = append(results, result) + } + + // Return the results as JSON + return c.JSON(results) + }) + + scanPagesPath, found := getBJTParams(userBJTPath) + BJTResponse := "" + if found { + BJTResponse = "/bjt-scanned-pages|jpg" + color.Green("Scanned BJT pages found at %s. Will be served from there.", scanPagesPath) + app.Static("/bjt-scanned-pages", scanPagesPath) + } + app.Get("/tipitaka-query/bjt-params", func(c *fiber.Ctx) error { + return c.SendString(BJTResponse) + }) + app.Get("/tipitaka-query/version", func(c *fiber.Ctx) error { + return c.SendString(APPNAME) + }) + + // Serve static files from dist folder + app.Static("/", getPathToFile("dist")) + // Define the "not found" handler to serve index.html - handles routes such as http://localhost:8400/dict/janaka + app.Static("*", getPathToFile("dist/index.html")) // not found handler + + if !*noOpen { + //color.Green("If your browser does not open automatically visit the following URL in your browser.") + //color.Yellow("URL: %s", URL) + if err := open.Start(URL); err != nil { + color.Red("Failed to open URL(%s) %s", URL, err) + } + } else { + color.White("URL (%s) not opened due to -no-open flag.", URL) + } + + // Run the server + log.Fatal(app.Listen(PORT)) +} + +// Function to execute query and return results as JSON +/*func executeQueryAndReturnJSON(c *fiber.Ctx) error { + var payload QueryPayload + if err := c.BodyParser(&payload); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) + } + + // Get or create database connection (same as before) + mutex.Lock() + db, exists := dbConnections[payload.DBName] + if !exists { + var err error + db, err = sql.Open("sqlite", filepath.Join(exePath, rootPath, "db", payload.DBName)) + if err != nil { + mutex.Unlock() + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + dbConnections[payload.DBName] = db + } + mutex.Unlock() + + // Execute the SQL query + rows, err := db.Query(payload.Query) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + defer rows.Close() + + // Get column names + columns, err := rows.Columns() + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + + // Prepare the results slice + var results []map[string]interface{} + + // Iterate over rows and scan values + for rows.Next() { + values := make([]interface{}, len(columns)) + valuePtrs := make([]interface{}, len(columns)) + for i := range columns { + valuePtrs[i] = &values[i] + } + + if err := rows.Scan(valuePtrs...); err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + + // Create a map for each row + entry := make(map[string]interface{}) + for i, col := range columns { + var v interface{} + val := values[i] + + // Convert byte slices to strings if needed + b, ok := val.([]byte) + if ok { + v = string(b) + } else { + v = val + } + entry[col] = v + } + results = append(results, entry) + } + + // Return the results as JSON + return c.JSON(results) +}*/ + +func getBJTParams(userBJTPath string) (string, bool) { // only supports bjt_newbooks/jpg + path := "/Pictures/bjt_newbooks/" + if userBJTPath != "" { + path = userBJTPath + } + filename := "10/DN1_Page_001.jpg" + locations := []string{"", "C:", "D:", "E:"} // empty for linux/mac + for _, loc := range locations { + fullPath := filepath.Join(loc, path, filename) + if _, err := os.Stat(fullPath); err == nil { + return loc + path, true // File found + } + } + return "", false // File not found in any of the paths +} + +func printBox() { + gray := color.New(color.FgHiBlack) + lines := []struct { + Text string + Color *color.Color // Store the Color object directly + }{ + {APPNAME, color.New(color.FgCyan, color.Bold)}, + {"┈┈┈┈┈┈┈┈┈┈┈┈", gray}, + {URL, color.New(color.FgYellow)}, + {"Visit the above URL in your browser to see the App.", color.New(color.FgHiGreen)}, + {"┄┄┄┄┄┄┄┄┄┈┈┈", gray}, + {"Suggestions and Errors - path.nirvana@gmail.com", gray}, + {"┄┄┄┄┄┄┄┄┄┈┈┈", gray}, + {"You can check if there is a newer version at", gray}, + {"https://github.com/pathnirvana/tipitaka.lk/releases", gray}, + } + width := 60 + boxColor := gray + + // Print top border + boxColor.Println("┏" + strings.Repeat("━", width) + "┓") + + for i := 0; i < len(lines); i++ { + textLen := utf8.RuneCountInString(lines[i].Text) + padding := (width - textLen) / 2 // Calculate padding for centering + boxColor.Print("┃") + fmt.Print(strings.Repeat(" ", padding)) + lines[i].Color.Print(lines[i].Text) + fmt.Print(strings.Repeat(" ", width-padding-textLen)) + boxColor.Println("┃") + } + + // Print bottom border + boxColor.Println("┗" + strings.Repeat("━", width) + "┛") +} diff --git a/server/server.js b/server/server.js index cd822cb5..cd6041fd 100644 --- a/server/server.js +++ b/server/server.js @@ -60,14 +60,14 @@ const SqliteDB = require('./sql-query.js'); const ftsDb = new SqliteDB(path.join(dirname, 'server/fts.db'), false) const dictDb = new SqliteDB(path.join(dirname, 'server/dict.db'), false) -fastify.post('/tipitaka-query/fts', async (request, reply) => { // hit fts db +fastify.post('/sql-query/fts', async (request, reply) => { // hit fts db reply.type('application/json').code(200) console.log(request.body) const rows = await ftsDb.loadAll(request.body.sql) return rows }) -fastify.post('/tipitaka-query/dict', async (request, reply) => { // hit dict db +fastify.post('/sql-query/dict', async (request, reply) => { // hit dict db reply.type('application/json').code(200) console.log(request.body) const rows = await dictDb.loadAll(request.body.sql) diff --git a/src/constants.js b/src/constants.js index 71748021..ce6c84d2 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,5 +1,5 @@ /** special flags and constants */ -export const tipitakaAppVersion = 1.0 // used to determine if app needs to be updated +export const tipitakaAppVersion = 2.0 // used to determine if app needs to be updated const settingsVersion = '2' export const settingsKey = `tipitaka.lk-settings-${settingsVersion}` diff --git a/src/store/search.js b/src/store/search.js index bc2e1a4f..497b1f6c 100644 --- a/src/store/search.js +++ b/src/store/search.js @@ -225,10 +225,10 @@ async function sendSearchQuery(type, sql) { const jsonStr = await callAndroidAsync('runSqliteQuery', { type, sql }) return JSON.parse(jsonStr) } - //const baseUrl = process.env.NODE_ENV == 'development' ? 'http://192.168.1.107:5555' : '' //const baseUrl = 'https://tipitaka.lk' // force prod server // check devServer.proxy setting in vue.config.js - const response = await axios.post('/tipitaka-query/' + type, { type, sql }) + // const response = await axios.post('/tipitaka-query/' + type, { type, sql }) // for js server + const response = await axios.post('/sql-query', { dbname: type + '.db', query: sql }) // for go server return response.data } diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 79713f5b..2bebdd3a 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -159,7 +159,7 @@ export default { async checkVersion() { try { const response = await axios.get('https://tipitaka.lk/tipitaka-query/version') - this.newVersion = Number(response.data) + this.newVersion = Number(response.data.split('v').pop()) // returns something like "Tipitaka.lk v2.0" } catch (err) { console.log(err) this.newVersion = -1 diff --git a/src/views/Welcome.vue b/src/views/Welcome.vue index 661a16c8..9114865b 100644 --- a/src/views/Welcome.vue +++ b/src/views/Welcome.vue @@ -70,6 +70,11 @@ + + + + +