user_home.go

  1package pages
  2
  3import (
  4	"log/slog"
  5	"math"
  6	"net/http"
  7
  8	"git.kilimanjaro.io/rtw/document"
  9	"git.kilimanjaro.io/rtw/httperr"
 10	"git.kilimanjaro.io/rtw/pkg/id"
 11	"git.kilimanjaro.io/rtw/pkg/log"
 12	"git.kilimanjaro.io/rtw/pkg/session"
 13	"git.kilimanjaro.io/rtw/user"
 14)
 15
 16func NewUserHomeHandler(userService *user.Service, docService *document.Service, errHandler *httperr.Handler, apiKey string) http.Handler {
 17	return &userHome{us: userService, ds: docService, eh: errHandler, apiKey: apiKey}
 18}
 19
 20type userHome struct {
 21	us     *user.Service
 22	ds     *document.Service
 23	eh     *httperr.Handler
 24	apiKey string
 25}
 26
 27type UserHomeData struct {
 28	User           user.User
 29	StyleURL       string
 30	Bbox           [4]float64
 31	DocumentIDs    []id.Key
 32	Documents      []document.DocumentContent
 33	Waypoints      [][]document.WaypointAttrs
 34	IsLoggedInUser bool
 35}
 36
 37const styleURL string = "https://api.protomaps.com/styles/v5/white/en.json?key="
 38
 39func (u userHome) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 40	uID := r.PathValue("id")
 41	userID, err := id.FromCompact(uID)
 42	if err != nil {
 43		u.eh.Handle(http.StatusInternalServerError, w, r)
 44		return
 45	}
 46
 47	// compare to user serssion, if it exists
 48	var isLoggedInUser bool
 49	info, err := session.FromContext[id.Key](r.Context())
 50	if err == nil && info.ID == userID {
 51		isLoggedInUser = true
 52	}
 53	l := log.FromContext(r.Context())
 54	docs, err := u.us.GetDocumentsForUser(userID)
 55	if err != nil {
 56		l.Error("error getting user permissions", "error", err)
 57		u.eh.Handle(http.StatusInternalServerError, w, r)
 58		return
 59	}
 60	var docIDs []id.Key
 61	var docContents []document.DocumentContent
 62	var allWaypoints [][]document.WaypointAttrs
 63	var bbox = [4]float64{180, 90, -180, -90}
 64	for _, doc := range docs {
 65		docIDs = append(docIDs, id.Key(doc.ResourceID))
 66		content, err := u.ds.GetContentByResourceID(id.Key(doc.ResourceID))
 67		if err != nil {
 68			l.Error("error getting document content", "error", err)
 69			u.eh.Handle(http.StatusInternalServerError, w, r)
 70			return
 71		}
 72		waypointNodes := document.Filter(content, func(n document.Node) bool { return n.Type == "waypoint" })
 73		var waypoints []document.WaypointAttrs
 74		for _, node := range waypointNodes {
 75			attrs, err := document.NodeToWaypointAttrs(node)
 76			if err != nil {
 77				l.Error("error converting node to waypoint attrs", "error", err)
 78				u.eh.Handle(http.StatusInternalServerError, w, r)
 79				return
 80			}
 81			bbox = updateBbox(bbox, attrs.Point)
 82			for _, route := range attrs.From {
 83				bbox = updateBbox(bbox, route.Bbox)
 84			}
 85			waypoints = append(waypoints, attrs)
 86		}
 87		allWaypoints = append(allWaypoints, waypoints)
 88		docContents = append(docContents, content)
 89	}
 90
 91	user, err := u.us.GetUserByID(userID)
 92	if err != nil {
 93		l.Error("error getting user", "user_id", userID, "error", err)
 94		u.eh.Handle(http.StatusInternalServerError, w, r)
 95		return
 96	}
 97	if u.apiKey == "" {
 98		l.Error("error protomaps api key not set")
 99		u.eh.Handle(http.StatusInternalServerError, w, r)
100		return
101	}
102	if len(docContents) == 0 {
103		// pick a most-of-world bbox if no documents exist yet
104		bbox = [4]float64{-130, -32, 130, 62}
105	}
106	data := UserHomeData{
107		User:           user,
108		StyleURL:       styleURL + u.apiKey,
109		DocumentIDs:    docIDs,
110		Documents:      docContents,
111		Waypoints:      allWaypoints,
112		Bbox:           bbox,
113		IsLoggedInUser: isLoggedInUser,
114	}
115	l.Info("rendering user home page", "user", userID, "num_documents", len(data.Documents), "bbox", bbox)
116	UserHome(data).Render(r.Context(), w)
117}
118
119func updateBbox(bbox [4]float64, update []float64) [4]float64 {
120	switch len(update) {
121	case 2:
122		bbox[0] = math.Min(bbox[0], update[0])
123		bbox[1] = math.Min(bbox[1], update[1])
124		bbox[2] = math.Max(bbox[2], update[0])
125		bbox[3] = math.Max(bbox[3], update[1])
126		return bbox
127	case 4:
128		// split into recursive update, one point at a time
129		return updateBbox(updateBbox(bbox, update[0:2]), update[2:])
130	default:
131		slog.Error("unexpected bbox update length", "len", len(update))
132		return bbox
133	}
134}