All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m44s
CI/CD / test-sqlite (push) Successful in 2m54s
CI/CD / build-and-deploy (push) Successful in 52s
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// slotInfo describes a declared slot and its contributors.
|
|
type slotInfo struct {
|
|
Host string `json:"host"`
|
|
Description string `json:"description"`
|
|
Context map[string]any `json:"context,omitempty"`
|
|
Contributors []slotContributor `json:"contributors"`
|
|
}
|
|
|
|
// slotContributor describes one package's contribution to a slot.
|
|
type slotContributor struct {
|
|
Package string `json:"package"`
|
|
Label string `json:"label,omitempty"`
|
|
Icon string `json:"icon,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
}
|
|
|
|
// ListSlots returns an aggregated map of all declared slots across installed
|
|
// packages together with their contributors. Built on-the-fly from manifests.
|
|
// GET /api/v1/admin/slots
|
|
func (h *PackageHandler) ListSlots(c *gin.Context) {
|
|
pkgs, err := h.stores.Packages.List(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list packages"})
|
|
return
|
|
}
|
|
|
|
slots := map[string]*slotInfo{}
|
|
|
|
// Pass 1: collect declared slots from host surfaces
|
|
for _, pkg := range pkgs {
|
|
slotsRaw, ok := pkg.Manifest["slots"].(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for name, v := range slotsRaw {
|
|
fullName := pkg.ID + ":" + name
|
|
entry, ok := v.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
desc, _ := entry["description"].(string)
|
|
var ctx map[string]any
|
|
if c, ok := entry["context"].(map[string]any); ok {
|
|
ctx = c
|
|
}
|
|
slots[fullName] = &slotInfo{
|
|
Host: pkg.ID,
|
|
Description: desc,
|
|
Context: ctx,
|
|
Contributors: []slotContributor{},
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 2: collect contributions
|
|
for _, pkg := range pkgs {
|
|
if !pkg.Enabled {
|
|
continue
|
|
}
|
|
contribs, ok := pkg.Manifest["contributes"].(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for slotKey, v := range contribs {
|
|
// Ensure the slot entry exists (contributor can arrive before host)
|
|
if _, exists := slots[slotKey]; !exists {
|
|
host := slotKey
|
|
if idx := strings.Index(slotKey, ":"); idx >= 0 {
|
|
host = slotKey[:idx]
|
|
}
|
|
slots[slotKey] = &slotInfo{
|
|
Host: host,
|
|
Contributors: []slotContributor{},
|
|
}
|
|
}
|
|
contrib := slotContributor{Package: pkg.ID}
|
|
if m, ok := v.(map[string]any); ok {
|
|
contrib.Label, _ = m["label"].(string)
|
|
contrib.Icon, _ = m["icon"].(string)
|
|
contrib.Description, _ = m["description"].(string)
|
|
}
|
|
slots[slotKey].Contributors = append(slots[slotKey].Contributors, contrib)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": slots})
|
|
}
|