package main import ( "errors" "flag" "fmt" "log/slog" "os" "strconv" "strings" "time" "gemini_site/internal/microblog" "gemini_site/internal/pocketbase" gemini "github.com/kulak/gemini" ) type MainHandler struct { blog microblog.Handler } func (h MainHandler) ServeGemini(w gemini.ResponseWriter, req *gemini.Request) { slog.Info("gemini request", "path", req.URL.Path, "user", strings.Join(userName(req), " ")) // Check if this is a blog request if strings.HasPrefix(req.URL.Path, "/microblog") { h.blog.HandleBlogRequest(w, req) return } switch req.URL.Path { case "/": gemini.ServeFileName("pages/home.gmi", "text/gemini")(w, req) // err := w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") // requireNoError(err) // _, err = w.WriteBody([]byte("Hello, world!")) // requireNoError(err) case "/user": if req.Certificate() == nil { w.WriteStatusMsg(gemini.StatusCertRequired, "Authentication Required") return } w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") w.WriteBody([]byte(req.Certificate().Subject.CommonName)) case "/die": requireNoError(errors.New("must die")) case "/file": gemini.ServeFileName("cmd/example/hello.gmi", "text/gemini")(w, req) case "/post": if req.URL.Scheme != gemini.SchemaTitan { w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") w.WriteBody([]byte("Use titan scheme to upload data")) return } payload, err := req.ReadTitanPayload() requireNoError(err) w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") w.WriteBody([]byte("Titan Upload Parameters\r\n")) w.WriteBody([]byte(fmt.Sprintf("Upload MIME Type: %s\r\n", req.Titan.Mime))) w.WriteBody([]byte(fmt.Sprintf("Token: %s\r\n", req.Titan.Token))) w.WriteBody([]byte(fmt.Sprintf("Size: %v\r\n", req.Titan.Size))) w.WriteBody([]byte("Payload:\r\n")) w.WriteBody(payload) default: w.WriteStatusMsg(gemini.StatusNotFound, req.URL.Path) } } func requireNoError(err error) { if err != nil { panic(err) } } func dateToStr(t time.Time) string { return strconv.FormatInt(t.Unix(), 36) } func userName(r *gemini.Request) []string { cert := r.Certificate() if cert == nil { return []string{""} } return []string{cert.Subject.CommonName, cert.SerialNumber.String(), dateToStr(cert.NotBefore), dateToStr(cert.NotAfter)} } func main() { // Set up structured JSON logging logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, })) slog.SetDefault(logger) slog.Info("Starting gemini server") var host, cert, key string flag.StringVar(&host, "host", ":1965", "listen on host and port. Example: hostname:1965") flag.StringVar(&cert, "cert", "server.crt.pem", "certificate file") flag.StringVar(&key, "key", "server.key.pem", "private key associated with certificate file") flag.Parse() pbClient := pocketbase.NewPocketBaseClient() handler := MainHandler{ blog: microblog.NewHandler(pbClient), } err := gemini.ListenAndServe(host, cert, key, gemini.TrapPanic(handler.ServeGemini)) if err != nil { slog.Error("server failed to start", "error", err) os.Exit(1) } }