clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

add the browseagent query with an agent's grant and recent events

Dami committed 48dadd1
commit
48dadd1795339ccf4ed7ba616053d65fae17c399
parent
6f435aa87994c6b89a4e470bdf26587c7703f097
author
Dami <dami.etoile@gmail.com>
2 files changed, +91 -1
Minternal/daemon/agents_test.go53++++++++++++++++++++++++++++++
Minternal/daemon/client_browse.go39++++++++++++++++++++++-
M internal/daemon/agents_test.go +53 -0
@@ -255,3 +255,56 @@ func TestAgentManagementMethodsAndPermissions(t *testing.T) {
255255 }256256 }257257 }258+259+func TestBrowseAgentReturnsGrantAndRecentEventsForOperators(t *testing.T) {260+ _, tenant := testTenant(t)261+ ctx := context.Background()262+ owner := tenant.Policy().Owner263+ agent, _ := event.PublicKey(testAgentSecret)264+ member, _ := event.PublicKey(testMemberSecret)265+ moderator, _ := event.PublicKey(testModSecret)266+ now := time.Now().Unix()267+ for _, target := range []struct {268+ pubkey, role string269+ }{{member, "member"}, {moderator, "moderator"}} {270+ raw, _ := json.Marshal(map[string]string{"role": target.role})271+ if _, err := tenant.Execute(ctx, owner, "setmember", []json.RawMessage{json.RawMessage(strconv.Quote(target.pubkey)), raw}); err != nil {272+ t.Fatal(err)273+ }274+ }275+ if err := publishAs(t, tenant, agentGrantEvent(t, agent, now, now+3600, []string{"k", "1"}, []string{"room", "build"})); err != nil {276+ t.Fatal(err)277+ }278+ for i := int64(0); i < 12; i++ {279+ if err := publishAs(t, tenant, signedEvent(t, testAgentSecret, 1, now-20+i, nil, "note "+strconv.FormatInt(i, 10))); err != nil {280+ t.Fatal(err)281+ }282+ }283+ params := []json.RawMessage{json.RawMessage(`{"agent":"` + agent + `"}`)}284+ for _, actor := range []string{member, agent, ""} {285+ if _, err := tenant.Execute(ctx, actor, "browseagent", params); err == nil || !strings.HasPrefix(err.Error(), "restricted:") {286+ t.Fatalf("browseagent by %q: %v", actor, err)287+ }288+ }289+ for _, actor := range []string{owner, moderator} {290+ result, err := tenant.Execute(ctx, actor, "browseagent", params)291+ if err != nil {292+ t.Fatalf("browseagent by %s: %v", actor, err)293+ }294+ detail := result.(map[string]any)295+ summary := detail["agent"].(community.AgentSummary)296+ events := detail["events"].([]event.Event)297+ if summary.Agent != agent || summary.Name != "helper" || summary.Scope.Rooms[0] != "build" || summary.LastEvent != now-9 {298+ t.Fatalf("browseagent summary = %+v", summary)299+ }300+ if len(events) != 10 || events[0].CreatedAt != now-9 || events[9].CreatedAt != now-18 {301+ t.Fatalf("browseagent events = %d newest %d", len(events), events[0].CreatedAt)302+ }303+ }304+ if _, err := tenant.Execute(ctx, owner, "browseagent", []json.RawMessage{json.RawMessage(`{"agent":"` + strings.Repeat("e", 64) + `"}`)}); err == nil || !strings.HasPrefix(err.Error(), "not found:") {305+ t.Fatalf("unknown agent: %v", err)306+ }307+ if _, err := tenant.Execute(ctx, owner, "browseagent", []json.RawMessage{json.RawMessage(`{"agent":"nope"}`)}); err == nil || !strings.HasPrefix(err.Error(), "invalid:") {308+ t.Fatalf("bad agent key: %v", err)309+ }310+}
M internal/daemon/client_browse.go +38 -1
@@ -10,12 +10,15 @@ import (
1010 "net/url"1111 "sort"1212 "strings"13+ "time"1314 "unicode/utf8"1415 1516 "github.com/FelineStateMachine/tinyrelay/internal/blob"17+ "github.com/FelineStateMachine/tinyrelay/internal/community"1618 "github.com/FelineStateMachine/tinyrelay/internal/event"1719 "github.com/FelineStateMachine/tinyrelay/internal/gitrelay"1820 "github.com/FelineStateMachine/tinyrelay/internal/relay"21+ "github.com/FelineStateMachine/tinyrelay/internal/storage"1922 )2023 2124 type clientBrowseRequest struct {
@@ -27,10 +30,11 @@ type clientBrowseRequest struct {
2730 ID string `json:"id"`2831 State string `json:"state"`2932 Label string `json:"label"`33+ Agent string `json:"agent"`3034 }3135 3236 func clientBrowseMethod(method string) bool {33- return wikiBrowseMethod(method) || containsString([]string{"browserepos", "browserepo", "browsefiles", "browsefile", "browsestatus", "browseissues", "browsepulls", "browseissue", "browsepull"}, method)37+ return wikiBrowseMethod(method) || containsString([]string{"browserepos", "browserepo", "browsefiles", "browsefile", "browsestatus", "browseissues", "browsepulls", "browseissue", "browsepull", "browseagent"}, method)3438 }3539 3640 func (t *Tenant) browseRead(ctx context.Context, actor string) error {
@@ -77,6 +81,8 @@ func (t *Tenant) executeBrowse(ctx context.Context, actor, method string, params
7781 return t.browseFile(ctx, actor, q.Hash)7882 case "browsestatus":7983 return t.browseStatus(ctx, actor)84+ case "browseagent":85+ return t.browseAgent(ctx, actor, q.Agent)8086 case "browseissues":8187 return t.browseCollaboration(ctx, actor, q, event.KIND_GIT_ISSUE)8288 case "browsepulls":
@@ -356,6 +362,37 @@ func (t *Tenant) browseFile(ctx context.Context, actor, hash string) (any, error
356362 return result, nil357363 }358364 365+// browseAgent returns one agent grant with the agent's newest events for the366+// Manage > Agents page. Like listagents, it is for the owner and moderators.367+func (t *Tenant) browseAgent(ctx context.Context, actor, agent string) (any, error) {368+ role, err := t.community.Role(ctx, actor)369+ if err != nil {370+ return nil, err371+ }372+ if role != "owner" && role != "moderator" {373+ return nil, errors.New("restricted: agent details")374+ }375+ if len(agent) != 64 || strings.Trim(agent, "0123456789abcdef") != "" {376+ return nil, errors.New("invalid: agent public key required")377+ }378+ grant, ok, err := t.community.AgentGrant(ctx, agent)379+ if err != nil {380+ return nil, err381+ }382+ if !ok {383+ return nil, errors.New("not found: no agent grant for this pubkey")384+ }385+ rows, err := t.store.Query(ctx, event.Filter{Authors: []string{agent}, Tags: map[string][]string{}}, storage.QueryOptions{Now: time.Now().Unix(), Access: storage.Access{All: true}, Limit: 10})386+ if err != nil {387+ return nil, err388+ }389+ summary := community.AgentSummary{AgentGrant: grant}390+ if len(rows.Events) > 0 {391+ summary.LastEvent = rows.Events[0].CreatedAt392+ }393+ return map[string]any{"agent": summary, "events": rows.Events}, nil394+}395+359396 func (t *Tenant) browseStatus(ctx context.Context, actor string) (any, error) {360397 role, err := t.community.Role(ctx, actor)361398 if err != nil {