package clientapi import ( "net/http" "strconv" "strings" "github.com/dariuszjaniak/warp/server/internal/httputil" "github.com/dariuszjaniak/warp/server/internal/matrix/id" "github.com/dariuszjaniak/warp/server/internal/roomserver" "github.com/dariuszjaniak/warp/server/internal/userapi" ) // hierarchyResponse is what GET /hierarchy returns. type hierarchyResponse struct { Rooms []hierarchyRoom `json:"rooms"` NextBatch string `json:"next_batch,omitempty"` } // hierarchyRoom is a room as /hierarchy shows it to a client: // children_state is required there, an empty list for a room with no // children. SpaceRoom leaves an empty one out, as the federation response // wants for the children it lists; this field, being shallower, takes the // JSON name over the embedded one. type hierarchyRoom struct { roomserver.SpaceRoom ChildrenState []roomserver.SpaceChildEvent `json:"children_state"` } // hierarchyTokenPrefix marks a /hierarchy token: how far into the walk // the next page starts. const hierarchyTokenPrefix = "h" // handleHierarchy implements GET /_matrix/client/v1/rooms/{roomID}/hierarchy. func (c *ClientAPI) handleHierarchy(w http.ResponseWriter, r *http.Request, auth userapi.Authenticated) error { roomID, err := pathRoomID(r) if err != nil { return err } query := r.URL.Query() limit, err := intParam(query.Get("limit")) if err != nil { return err } suggestedOnly, err := boolParam(query.Get("suggested_only")) if err != nil { return err } maxDepth := -1 if raw := query.Get("max_depth"); raw != "" { if maxDepth, err = strconv.Atoi(raw); err != nil || maxDepth < 0 { return httputil.InvalidParam("max_depth must be a non-negative integer") } } from := 0 if raw := query.Get("from"); raw != "" { body, ok := strings.CutPrefix(raw, hierarchyTokenPrefix) if from, err = strconv.Atoi(body); !ok || err != nil || from < 0 { return httputil.InvalidParam("%q is not a hierarchy token", raw) } } page, err := c.rooms.Hierarchy(r.Context(), roomserver.HierarchyRequest{ RoomID: roomID, User: auth.UserID, SuggestedOnly: suggestedOnly, MaxDepth: maxDepth, From: from, Limit: limit, }) if err != nil { return roomError(err) } resp := hierarchyResponse{Rooms: make([]hierarchyRoom, 0, len(page.Rooms))} for _, room := range page.Rooms { children := room.ChildrenState if children == nil { children = []roomserver.SpaceChildEvent{} } resp.Rooms = append(resp.Rooms, hierarchyRoom{SpaceRoom: room, ChildrenState: children}) } if page.Next != 0 { resp.NextBatch = hierarchyTokenPrefix + strconv.Itoa(page.Next) } httputil.WriteJSON(w, http.StatusOK, resp) return nil } // handleRoomSummary implements // GET /_matrix/client/v1/room_summary/{roomIDOrAlias}. func (c *ClientAPI) handleRoomSummary(w http.ResponseWriter, r *http.Request, auth userapi.Authenticated) error { target := r.PathValue("roomIDOrAlias") via := r.URL.Query()["via"] roomID := target switch { case strings.HasPrefix(target, "#"): resolved, err := c.resolveAlias(r.Context(), target) if err != nil { return roomError(err) } roomID = resolved.RoomID via = append(via, resolved.Servers...) case id.ValidateRoomID(target) != nil: return httputil.InvalidParam("%s is not a room ID or alias", target) } resp, err := c.rooms.RoomSummary(r.Context(), roomserver.RoomSummaryRequest{ RoomID: roomID, User: auth.UserID, Via: via, }) if err != nil { return roomError(err) } httputil.WriteJSON(w, http.StatusOK, resp) return nil }