package rest import ( "net/http" "strings" "time" ) // sanitizeRoomName trims a room name and collapses any character that is not // allowed in a room name into a single dash, stripping leading/trailing dashes. // Case is preserved because Jitsi room names are case-sensitive. func sanitizeRoomName(value string) string { var builder strings.Builder builder.Grow(len(value)) previousDash := false for _, r := range strings.TrimSpace(value) { allowed := (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' if allowed { builder.WriteRune(r) previousDash = false continue } if !previousDash { builder.WriteByte('-') previousDash = true } } return strings.Trim(builder.String(), "-") } type joinRoomRequest struct { Room string `json:"room"` } type joinRoomResponse struct { Room string `json:"room"` Token string `json:"token"` Url string `json:"url"` AppId string `json:"appId"` AppName string `json:"appName"` Domain string `json:"domain"` } type roomHistoryResponse struct { RoomName string `json:"roomName"` LastJoinedAt time.Time `json:"lastJoinedAt"` } func (h *Handler) listRecentRooms(w http.ResponseWriter, r *http.Request) { u := userFromRequest(r) limit, offset := pagination(r) rooms, err := h.jitsi.RoomHistory(r.Context(), u, limit, offset) if err != nil { h.fail(w, err) return } result := make([]roomHistoryResponse, 0, len(rooms)) for _, room := range rooms { result = append(result, roomHistoryResponse{ RoomName: room.RoomName, LastJoinedAt: room.LastJoinedAt, }) } writeJson(w, http.StatusOK, result) } func (h *Handler) joinRoom(w http.ResponseWriter, r *http.Request) { u := userFromRequest(r) var req joinRoomRequest if err := decodeJson(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } room := sanitizeRoomName(req.Room) if room == "" { writeError(w, http.StatusBadRequest, "room is required") return } // Moderator rights are inherited from the user's admin flag. moderator := u.IsAdmin token, err := h.jitsi.GenerateToken( formatUserId(u.Id), u.DisplayedName(), room, moderator, h.avatarURL(r, u.Id), ) if err != nil { h.fail(w, err) return } if err := h.jitsi.RecordRoomJoin(r.Context(), u, room); err != nil { h.fail(w, err) return } writeJson(w, http.StatusOK, joinRoomResponse{ Room: room, Token: token, Url: h.jitsi.MeetUrl(room, token), AppId: h.jitsi.AppID, AppName: h.jitsi.AppName, Domain: h.jitsi.Domain, }) } func (h *Handler) deleteRecentRoom(w http.ResponseWriter, r *http.Request) { u := userFromRequest(r) room := sanitizeRoomName(r.PathValue("room")) if room == "" { writeError(w, http.StatusBadRequest, "room is required") return } if err := h.jitsi.DeleteRoom(r.Context(), u, room); err != nil { h.fail(w, err) return } writeNoContent(w) }