clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat/atlas-next-horizon
atlas / internal / app / mint.go
mint.go 4.0K
1package app23import (4 "fmt"5 "log/slog"6 "math"7 "net/http"8 "strconv"9 "strings"1011 "github.com/FelineStateMachine/atlas/internal/logging"12 "github.com/FelineStateMachine/atlas/internal/minting"13)1415const maxMintFormBytes = 32 << 101617func (a *App) handleMintPreview(w http.ResponseWriter, r *http.Request) {18 if a.minter == nil {19 http.NotFound(w, r)20 return21 }22 request, err := parseMintRequest(w, r)23 if err != nil {24 a.writeMintPreview(w, newMintView(request, minting.Preview{}, err), http.StatusUnprocessableEntity)25 return26 }27 preview, err := a.minter.Preview(request)28 status := http.StatusOK29 if err != nil {30 status = http.StatusUnprocessableEntity31 }32 a.writeMintPreview(w, newMintView(request, preview, err), status)33}3435func (a *App) handleMint(w http.ResponseWriter, r *http.Request) {36 if a.minter == nil {37 http.NotFound(w, r)38 return39 }40 request, err := parseMintRequest(w, r)41 if err != nil {42 a.writeMintPreview(w, newMintView(request, minting.Preview{}, err), http.StatusUnprocessableEntity)43 return44 }45 result, err := a.minter.Mint(r.Context(), request, func(event minting.Event) {46 slog.Info(event.Message, logging.Op("mint"), slog.String("stage", event.Stage))47 })48 if err != nil {49 a.writeMintPreview(w, newMintView(request, minting.Preview{}, err), http.StatusUnprocessableEntity)50 return51 }52 changed, err := a.env.Volumes().Rescan()53 if err != nil {54 http.Error(w, "the minted Atlas was built but the library could not be refreshed", http.StatusInternalServerError)55 return56 }57 a.announce(changed)58 destination := "/v/" + result.Slug + "/" + result.World59 if r.Header.Get("HX-Request") == "true" {60 w.Header().Set("HX-Redirect", destination)61 w.WriteHeader(http.StatusNoContent)62 return63 }64 http.Redirect(w, r, destination, http.StatusSeeOther)65}6667func parseMintRequest(w http.ResponseWriter, r *http.Request) (minting.Request, error) {68 r.Body = http.MaxBytesReader(w, r.Body, maxMintFormBytes)69 if err := r.ParseForm(); err != nil {70 return minting.Request{}, fmt.Errorf("read mint choices: %w", err)71 }72 request := minting.Request{Title: strings.TrimSpace(r.FormValue("title"))}73 if request.Title == "" || len([]rune(request.Title)) > 80 {74 return request, fmt.Errorf("name the Atlas with 1 to 80 characters")75 }76 for index, field := range []string{"west", "south", "east", "north"} {77 value, err := strconv.ParseFloat(r.FormValue(field), 64)78 if err != nil || math.IsNaN(value) || math.IsInf(value, 0) {79 return request, fmt.Errorf("%s coordinate is invalid", field)80 }81 request.Bounds[index] = value82 }83 detail, err := strconv.Atoi(r.FormValue("detail"))84 if err != nil {85 return request, fmt.Errorf("detail zoom is invalid")86 }87 request.DetailZoom = detail88 request.Topo = r.FormValue("topo") == "true"89 request.Roads = r.FormValue("roads") == "true"90 request.Hydro = r.FormValue("hydro") == "true"91 request.Counties = r.FormValue("counties") == "true"92 request.RoadLabel = strings.TrimSpace(r.FormValue("road-label"))93 request.HydroLabel = strings.TrimSpace(r.FormValue("hydro-label"))94 request.CountyLabel = strings.TrimSpace(r.FormValue("county-label"))95 request.RoadColor = strings.TrimSpace(r.FormValue("road-color"))96 request.HydroColor = strings.TrimSpace(r.FormValue("hydro-color"))97 request.HydroFill = strings.TrimSpace(r.FormValue("hydro-fill"))98 request.CountyColor = strings.TrimSpace(r.FormValue("county-color"))99 return request, nil100}101102func newMintView(request minting.Request, preview minting.Preview, err error) MintView {103 view := MintView{104 Available: true, Request: request, Preview: preview,105 Pixels: strconv.FormatInt(preview.Pixels, 10) + " px",106 Tiles: strconv.FormatInt(preview.RasterTiles, 10) + " raster tiles",107 Groups: strconv.Itoa(preview.Requests) + " source groups",108 Bytes: strconv.FormatInt((preview.EstimatedBytes+(1<<20)-1)/(1<<20), 10) + " MiB",109 }110 if err != nil {111 view.Error = err.Error()112 }113 return view114}115116func (a *App) writeMintPreview(w http.ResponseWriter, view MintView, status int) {117 w.Header().Set("Content-Type", "text/html; charset=utf-8")118 w.WriteHeader(status)119 a.writePage(w, "mint-preview", View{Mint: view})120}