// main is the central application entrypoint. package main import ( "bytes" "context" "errors" "fmt" "io" "io/ioutil" "net/http" "os" "os/exec" "path/filepath" "varia.zone/snackbar/components" "github.com/a-h/templ" "github.com/codeclysm/extract/v3" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-git/go-git/v5" ) // App is the application state. type App struct { ctx context.Context } // NewApp creates a new application state. func NewApp() *App { return &App{} } func ensureDataDir() error { paths := []string{LocalDir, HomeDir, HugoDir, SitesDir} for _, fpath := range paths { if _, err := os.Stat(fpath); err != nil && os.IsNotExist(err) { if err := os.Mkdir(fpath, 0764); err != nil { return err } } } return nil } func ensureHugoBin() error { if _, err := os.Stat(HugoBinPath); err == nil { return nil } else if errors.Is(err, os.ErrNotExist) { if err := httpGetFile(HugoTarPath, HugoReleaseURL); err != nil { return err } } fpath, err := ioutil.ReadFile(HugoTarPath) if err != nil { return err } if err := extract.Gz(context.TODO(), bytes.NewBuffer(fpath), HugoDir, nil); err != nil { return err } return nil } func httpGetFile(filepath, url string) error { out, err := os.Create(filepath) if err != nil { return err } defer out.Close() resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return err } _, err = io.Copy(out, resp.Body) if err != nil { return err } return nil } func existingSites() ([]string, error) { var sites []string siteNameFiles, err := os.ReadDir(SitesDir) if err != nil { return sites, err } for _, siteNameFile := range siteNameFiles { sites = append(sites, siteNameFile.Name()) } return sites, nil } func initialise(w http.ResponseWriter, r *http.Request) { if err := ensureDataDir(); err != nil { w.Write([]byte(fmt.Sprintf("unable to create data directory: %s", err))) } if err := ensureHugoBin(); err != nil { w.Write([]byte(fmt.Sprintf("unable to download hugo binary: %s", err))) } sites, err := existingSites() if err != nil { w.Write([]byte(fmt.Sprintf("unable to list existing sites: %s", err))) } templ.Handler(components.Homepage(sites)).ServeHTTP(w, r) } func hugoNewSite(w http.ResponseWriter, r *http.Request) { siteName := r.FormValue("site-name") newSiteDir := filepath.Join(SitesDir, siteName) cmd := exec.Command(HugoBinPath, "new", "site", newSiteDir) if err := cmd.Run(); err != nil { w.Write([]byte(fmt.Sprintf("unable to create new site: %s", err))) } // TODO(d1): choose default theme if _, err := git.PlainClone(filepath.Join(newSiteDir, "themes", "nostyleplease"), false, &git.CloneOptions{ URL: "https://github.com/g-hanwen/hugo-theme-nostyleplease", }); err != nil { w.Write([]byte(fmt.Sprintf("unable to clone 'nostyleplease' theme: %s", err))) } } // NewRouter creates a new web router. func NewRouter() *chi.Mux { router := chi.NewRouter() router.Use(middleware.Logger) router.Use(middleware.Recoverer) router.Get("/init", initialise) router.Post("/hugo/new", hugoNewSite) return router }