package server import ( "embed" "fmt" "html/template" "io/fs" "log" "net" "net/http" "os" "os/exec" "path" "runtime" "strconv" "git.nagee.dev/isthisnagee/diary/model" ) //go:embed templates/* var files embed.FS var templates map[string]*template.Template type App struct{ *model.App } type DiaryEntryTemplateData struct { Entry model.DiaryEntry Notes []*model.DiaryEntryNote } func LoadTemplates() error { if templates == nil { templates = make(map[string]*template.Template) } tmplFiles, err := fs.ReadDir(files, "templates") if err != nil { return err } for _, tmpl := range tmplFiles { if tmpl.IsDir() { continue } pt, err := template.ParseFS(files, path.Join("templates", tmpl.Name())) if err != nil { return err } templates[tmpl.Name()] = pt } return nil } func getFreePort() (int, error) { listener, err := net.Listen("tcp", ":0") if err != nil { return 0, err } defer listener.Close() return listener.Addr().(*net.TCPAddr).Port, nil } func openBrowser(url string) { var err error log.Print(url) switch runtime.GOOS { case "linux": err = exec.Command("xdg-open", url).Start() case "windows": err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() case "darwin": err = exec.Command("open", url).Start() default: err = fmt.Errorf("unsupported platform") } if err != nil { log.Fatal(err) } } func (app *App) ListDiaryEntries(w http.ResponseWriter, r *http.Request) { var tmpl, ok = templates["index.html"] if !ok { http.Error(w, "could not find template index", http.StatusInternalServerError) return } data := app.GetDiaryEntries(model.GetDiaryEntriesQuery{}) if err := tmpl.Execute(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func (app *App) DiaryEntry(w http.ResponseWriter, r *http.Request) { var tmpl, ok = templates["entry.html"] if !ok { http.Error(w, "could not find template index", http.StatusInternalServerError) return } var base = path.Base(r.URL.Path) var id, err = strconv.Atoi(base) if err != nil { http.Error(w, "Cannot convert base to int: "+base, http.StatusBadRequest) } entry, err := app.GetDiaryEntry(int64(id)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } var notes = app.GetDiaryEntryNotes(int64(id)) var templateData = DiaryEntryTemplateData{*entry, notes} if err := tmpl.Execute(w, templateData); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func setup() *App { home, err := os.UserHomeDir() if err != nil { panic(err) } var db_path = path.Join(home, ".diary.sql") app, err := model.NewApp(db_path) if err != nil { panic(err) } err = LoadTemplates() if err != nil { panic(err) } return &App{app} } func Run(openBrowserAtUrl bool, defaultPort int) { app := setup() http.HandleFunc("/", app.ListDiaryEntries) http.HandleFunc("/entry/", app.DiaryEntry) var port int if defaultPort <= 0 { freePort, err := getFreePort() if err != nil { panic(err) } port = freePort } else { port = defaultPort } var url = fmt.Sprintf("http://localhost:%d", port) log.Print(url) if openBrowserAtUrl { log.Print("opening browser") openBrowser(url) } if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil { panic(err) } }