clone

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

feat/ui-updates
tinyrelay / internal / daemon / grant_approvals.go
grant_approvals.go 3.1K
1package daemon23import (4 "context"5 "encoding/json"6 "strings"78 "github.com/FelineStateMachine/tinyrelay/internal/event"9)1011// Keep the decision visible after the addressable grant has been superseded12// or expired. Only archived grants tied to these exact requests are read.13func (t *Tenant) grantApprovalHistory(ctx context.Context, items []approvalItem) ([]event.Event, error) {14 ids, authors := []string{}, []string{}15 for _, item := range items {16 if item.Type == "grant" {17 ids = append(ids, item.ID)18 authors = append(authors, item.Asked...)19 }20 }21 if len(ids) == 0 {22 return nil, nil23 }24 idJSON, err := json.Marshal(ids)25 if err != nil {26 return nil, err27 }28 authorJSON, err := json.Marshal(uniqueStrings(authors))29 if err != nil {30 return nil, err31 }32 rows, err := t.store.DB().QueryContext(ctx, `SELECT raw FROM (33 SELECT raw,created_at FROM events WHERE kind=30392 AND pubkey IN (SELECT value FROM json_each(?))34 UNION ALL SELECT raw,created_at FROM agent_grant_revisions WHERE author IN (SELECT value FROM json_each(?))35 ) WHERE EXISTS (SELECT 1 FROM json_each(json_extract(raw,'$.tags')) AS tag36 WHERE json_extract(tag.value,'$[0]')='grant-request' AND json_extract(tag.value,'$[1]') IN (SELECT value FROM json_each(?)))37 ORDER BY created_at DESC LIMIT ?`, string(authorJSON), string(authorJSON), string(idJSON), approvalScan)38 if err != nil {39 return nil, err40 }41 defer rows.Close()42 var grants []event.Event43 for rows.Next() {44 var raw string45 if err := rows.Scan(&raw); err != nil {46 return nil, err47 }48 grant, err := event.Parse([]byte(raw))49 if err != nil {50 return nil, err51 }52 grants = append(grants, grant)53 }54 return grants, rows.Err()55}5657// A grant is approved only by the operator's replacement grant. Reactions58// cannot confer permissions or make an unapplied request look successful.59func settleGrantApproval(item *approvalItem, rows []event.Event, now int64) {60 for _, row := range rows {61 if row.Kind != event.KIND_AGENT_GRANT || row.PubKey == item.Asker || !containsString(item.Asked, row.PubKey) || event.Tag(row, "grant-request") != item.ID || event.Tag(row, "grant-base") != event.Tag(item.Event, "E") || event.Tag(row, "d") != item.Asker {62 continue63 }64 item.State = "answered"65 item.Answer = &approvalAnswer{ID: row.ID, Kind: row.Kind, Author: row.PubKey, Decision: "approved", CreatedAt: row.CreatedAt}66 return67 }68 for _, row := range rows {69 if row.Kind != kindReaction || strings.TrimSpace(row.Content) != "-" || !containsString(item.Asked, row.PubKey) {70 continue71 }72 item.State = "answered"73 item.Answer = &approvalAnswer{ID: row.ID, Kind: row.Kind, Author: row.PubKey, Decision: "denied", CreatedAt: row.CreatedAt}74 return75 }76 if item.Expires > 0 && item.Expires <= now {77 item.State = "expired"78 }79}8081func (t *Tenant) reviewGrantApproval(ctx context.Context, item *approvalItem, now int64) {82 if item.Type != "grant" || item.State != "open" {83 return84 }85 review, err := t.community.ReviewAgentGrantRequest(ctx, item.Event, now)86 if err != nil {87 item.GrantError = err.Error()88 if strings.HasPrefix(item.GrantError, "conflict:") {89 item.State = "expired"90 }91 return92 }93 item.Grant = &review94}