feat: triage streams with open, close (#121)

* refactor: use interfaces for function params

* cleanup message retention code

* support open / closed streams

- Tabs for viewing separately
- Context menu to close / open streams
- Update stream particle status field

* ui tweak

* show stream state in stream-view

* ui tweaks

* cleanup message retention from orion api
This commit was merged in pull request #121.
This commit is contained in:
Arjun Patel
2026-04-07 16:11:13 -07:00
committed by GitHub
parent 4a9cb7fc58
commit cc190ba85f
30 changed files with 669 additions and 402 deletions
-76
View File
@@ -61,7 +61,6 @@ type Network struct {
Name string `json:"name"`
AdminHuman Human `json:"admin_human"`
Humans []Human `json:"humans"`
MessageRetentionHours int `json:"message_retention_hours"`
CreatedAt time.Time `json:"created_at"`
}
@@ -91,14 +90,6 @@ type AddMembersToNetworkRequest struct {
EmailAddresses []string `json:"email_addresses"`
}
type SetOpenStreamCapacityRequest struct {
Capacity int `json:"capacity"`
}
type SetMessageRetentionHoursRequest struct {
Hours int `json:"hours"`
}
type MembersRequest struct {
Emails []string `json:"emails"`
}
@@ -675,72 +666,6 @@ func (h *Handler) RevokeInvitation(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// SetMessageRetentionHours updates the message retention window for a network (admin-only)
func (h *Handler) SetMessageRetentionHours(w http.ResponseWriter, r *http.Request) {
humanId, ok := middleware.HumanIdFromContext(r.Context())
if !ok {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
networkID := r.PathValue("id")
if networkID == "" {
http.Error(w, "network id is required", http.StatusBadRequest)
return
}
// Fetch network to verify admin
net, err := h.networkSvc.GetByID(r.Context(), networkID)
if err != nil {
if errors.Is(err, network.ErrNotFound) {
http.Error(w, "network not found", http.StatusNotFound)
return
}
slog.Error("failed to get network", "error", err, "network_id", networkID)
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
if net.AdminHumanId != humanId {
http.Error(w, "only the network admin can change this setting", http.StatusForbidden)
return
}
var req SetMessageRetentionHoursRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request body", http.StatusBadRequest)
return
}
if err := h.networkSvc.SetMessageRetentionHours(r.Context(), networkID, req.Hours); err != nil {
if errors.Is(err, network.ErrInvalidRetentionHours) {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
slog.Error("failed to set message retention hours", "error", err, "network_id", networkID)
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
// Return updated network
updatedNet, err := h.networkSvc.GetByID(r.Context(), networkID)
if err != nil {
slog.Error("failed to get network after update", "error", err, "network_id", networkID)
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
resp, err := h.networkToDTO(r.Context(), updatedNet)
if err != nil {
slog.Error("failed to convert network to DTO", "error", err, "network_id", networkID)
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
// DownloadParticleMedia returns a fresh signed download URL for media/file particles
func (h *Handler) DownloadParticleMedia(w http.ResponseWriter, r *http.Request) {
_, ok := middleware.EmailFromContext(r.Context())
@@ -1056,7 +981,6 @@ func (h *Handler) networkToDTO(ctx context.Context, n *network.Network) (Network
Name: n.Name,
AdminHuman: humanToDTO(adminHuman),
Humans: humans,
MessageRetentionHours: n.MessageRetentionHours,
CreatedAt: n.CreatedAt,
}, nil
}