238 lines
6.5 KiB
Go
238 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.sr.ht/~adnano/go-gemini"
|
|
"git.sr.ht/~adnano/go-gemini/certificate"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
var (
|
|
hostname string
|
|
port string
|
|
fileSrvHost string
|
|
fileSrvPort string
|
|
certificatePath string
|
|
)
|
|
|
|
func init() {
|
|
flag.StringVar(&hostname, "hostname", "", "capsule hostname")
|
|
flag.StringVar(&port, "port", "1965", "capsule port")
|
|
flag.StringVar(&certificatePath, "certificatePath", "", "capsule certificate path")
|
|
flag.StringVar(&fileSrvHost, "fileSrvHost", "", "file push server host")
|
|
flag.StringVar(&fileSrvPort, "fileSrvPort", "", "file push server port")
|
|
flag.Parse()
|
|
}
|
|
|
|
func main() {
|
|
certificates := &certificate.Store{}
|
|
certificates.Register(hostname)
|
|
if err := certificates.Load(certificatePath); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
mux := &gemini.Mux{}
|
|
mux.HandleFunc("/favicon.txt", processFavicon)
|
|
// security
|
|
// feed
|
|
mux.HandleFunc("/", process)
|
|
|
|
server := &gemini.Server{
|
|
Addr: ":" + port,
|
|
Handler: mux,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 1 * time.Minute,
|
|
GetCertificate: certificates.Get,
|
|
}
|
|
|
|
ctx := context.Background()
|
|
if err := server.ListenAndServe(ctx); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func processFavicon(_ context.Context, w gemini.ResponseWriter, _ *gemini.Request) {
|
|
w.SetMediaType("text/plain")
|
|
w.Write([]byte("\U0001F31B"))
|
|
}
|
|
|
|
func process(_ context.Context, w gemini.ResponseWriter, r *gemini.Request) {
|
|
log.Println("-> " + r.URL.Path)
|
|
|
|
lang := regexp.MustCompile(`^/(sgs|en)`).FindString(r.URL.Path)
|
|
if lang != "" {
|
|
lang = lang[1:]
|
|
}
|
|
|
|
conn, err := grpc.Dial(fileSrvHost+":"+fileSrvPort, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
|
log.Fatal(err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
client := NewTreeManagerClient(conn)
|
|
|
|
switch {
|
|
case "/" == r.URL.Path:
|
|
w.WriteHeader(gemini.StatusPermanentRedirect, "/sgs")
|
|
case regexp.MustCompile(`^/(sgs|en)/?$`).MatchString(r.URL.Path):
|
|
renderIndex(lang, w, client)
|
|
case regexp.MustCompile(`^/(sgs|en)/a/?$`).MatchString(r.URL.Path):
|
|
renderAbout(lang, w)
|
|
case regexp.MustCompile(`^/(sgs|en)/s/?$`).MatchString(r.URL.Path):
|
|
renderSearch(lang, w, r, client)
|
|
case regexp.MustCompile(`^/(sgs|en)/f/?$`).MatchString(r.URL.Path):
|
|
renderAllFiles(lang, w, client)
|
|
default:
|
|
w.WriteHeader(gemini.StatusNotFound, "Out of space")
|
|
}
|
|
|
|
}
|
|
|
|
func renderAllFiles(lang string, w gemini.ResponseWriter, client TreeManagerClient) {
|
|
langFilter := TreeRequest_Filter{Key: "lang", Value: lang}
|
|
filters := []*TreeRequest_Filter{&langFilter}
|
|
|
|
path := ""
|
|
tr := TreeRequest{Path: &path, Filter: filters}
|
|
|
|
tree, err := client.GetSummery(context.Background(), &tr)
|
|
if err != nil {
|
|
log.Fatalf("client.GetSummery failed: %v", err)
|
|
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
|
return
|
|
}
|
|
|
|
content := "# Arnas alkierios :: "
|
|
if lang == "sgs" {
|
|
content = content + "vėsė tekstā\n\n"
|
|
} else {
|
|
content = content + "all texts\n\n"
|
|
}
|
|
|
|
for _, f := range tree.Files {
|
|
content = content + fmt.Sprintf(
|
|
"=> /%s/f/%s/%s/%s %s (%s)\n",
|
|
lang,
|
|
strings.Join(f.Category, "/"),
|
|
f.Id,
|
|
strings.Replace(f.Name, ".md", ".gmi", 1),
|
|
f.Description,
|
|
f.Created,
|
|
)
|
|
}
|
|
|
|
if lang == "sgs" {
|
|
content = content + "\n=> /sgs ← grīžtė"
|
|
} else {
|
|
content = content + "\n=> /en ← back"
|
|
}
|
|
|
|
w.Write([]byte(content))
|
|
}
|
|
|
|
func renderSearch(lang string, w gemini.ResponseWriter, r *gemini.Request, client TreeManagerClient) {
|
|
q, err := gemini.QueryUnescape(r.URL.RawQuery)
|
|
if err != nil || q == "" {
|
|
searchStr := "Input searching tag, created date or word in description"
|
|
if lang == "sgs" {
|
|
searchStr = "Ivesk ėiškuoma žīma, sokūrėma data arba žuodi ėš aprašīma"
|
|
}
|
|
w.WriteHeader(gemini.StatusInput, searchStr)
|
|
return
|
|
}
|
|
|
|
langFilter := TreeRequest_Filter{Key: "lang", Value: lang}
|
|
descFilter := TreeRequest_Filter{Key: "description", Value: lang}
|
|
createdFilter := TreeRequest_Filter{Key: "created", Value: lang}
|
|
tagFilter := TreeRequest_Filter{Key: "tag", Value: lang}
|
|
filters := []*TreeRequest_Filter{&langFilter, &descFilter, &createdFilter, &tagFilter}
|
|
|
|
path := ""
|
|
tr := TreeRequest{Path: &path, Filter: filters}
|
|
|
|
tree, err := client.GetSummery(context.Background(), &tr)
|
|
if err != nil {
|
|
log.Fatalf("client.GetSummery failed: %v", err)
|
|
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
|
return
|
|
}
|
|
fmt.Println(tree)
|
|
w.Write([]byte("Nieka narada dā\n" +
|
|
"=> / ← back"))
|
|
}
|
|
|
|
func renderAbout(lang string, w gemini.ResponseWriter) {
|
|
w.SetMediaType("text/gemini")
|
|
content, err := os.ReadFile(fmt.Sprintf("templates/%s/about.gmi", lang))
|
|
if err != nil {
|
|
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
|
return
|
|
}
|
|
|
|
_, err = w.Write(content)
|
|
if err != nil {
|
|
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
|
return
|
|
}
|
|
}
|
|
|
|
func renderIndex(lang string, w gemini.ResponseWriter, client TreeManagerClient) {
|
|
w.SetMediaType("text/gemini")
|
|
content, err := os.ReadFile(fmt.Sprintf("templates/%s/index.gmi", lang))
|
|
if err != nil {
|
|
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
|
return
|
|
}
|
|
page := string(content)
|
|
|
|
langFilter := TreeRequest_Filter{Key: "lang", Value: lang}
|
|
filters := []*TreeRequest_Filter{&langFilter}
|
|
|
|
path := ""
|
|
tr := TreeRequest{Path: &path, Filter: filters}
|
|
|
|
tree, err := client.GetSummery(context.Background(), &tr)
|
|
if err != nil {
|
|
log.Fatalf("client.GetSummery failed: %v", err)
|
|
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
|
return
|
|
}
|
|
|
|
categories := ""
|
|
for c, count := range tree.Categories {
|
|
categories = categories + fmt.Sprintf("=> /%s/f/%s %s (%d)\n", lang, c, c, count)
|
|
}
|
|
page = strings.Replace(page, "{{categories}}", categories, 1)
|
|
|
|
tags := ""
|
|
for t, count := range tree.Tags {
|
|
tags = tags + fmt.Sprintf("=> /%s/t/%s %s (%d)\n", lang, t, t, count)
|
|
}
|
|
page = strings.Replace(page, "{{tags}}", tags, 1)
|
|
|
|
lastFiles := ""
|
|
for _, f := range GetLastFiles(tree.Files) {
|
|
lastFiles = lastFiles + fmt.Sprintf(
|
|
"=> /%s/f/%s/%s/%s %s (%s)\n",
|
|
lang, strings.Join(f.Category, "/"), f.Id, strings.Replace(f.Name, ".md", ".gmi", 1), f.Description, f.Created,
|
|
)
|
|
}
|
|
page = strings.Replace(page, "{{last_posts}}", lastFiles, 1)
|
|
|
|
_, err = w.Write([]byte(page))
|
|
if err != nil {
|
|
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
|
return
|
|
}
|
|
}
|