diff --git a/khatru/nip86.go b/khatru/nip86.go index 59108b3..b74cc42 100644 --- a/khatru/nip86.go +++ b/khatru/nip86.go @@ -43,6 +43,11 @@ type RelayManagementAPI struct { Stats func(ctx context.Context) (nip86.Response, error) GrantAdmin func(ctx context.Context, pubkey nostr.PubKey, methods []string) error RevokeAdmin func(ctx context.Context, pubkey nostr.PubKey, methods []string) error + CreateRole func(ctx context.Context, id string, label string, description string, color int, order int) error + EditRole func(ctx context.Context, id string, label string, description string, color int, order int) error + DeleteRole func(ctx context.Context, id string) error + AssignRole func(ctx context.Context, pubkey nostr.PubKey, roleID string) error + UnassignRole func(ctx context.Context, pubkey nostr.PubKey, roleID string) error Generic func(ctx context.Context, request nip86.Request) (nip86.Response, error) } @@ -330,6 +335,46 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { } else { resp.Result = true } + case nip86.CreateRole: + if rl.ManagementAPI.CreateRole == nil { + resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) + } else if err := rl.ManagementAPI.CreateRole(ctx, thing.ID, thing.Label, thing.Description, thing.Color, thing.Order); err != nil { + resp.Error = err.Error() + } else { + resp.Result = true + } + case nip86.EditRole: + if rl.ManagementAPI.EditRole == nil { + resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) + } else if err := rl.ManagementAPI.EditRole(ctx, thing.ID, thing.Label, thing.Description, thing.Color, thing.Order); err != nil { + resp.Error = err.Error() + } else { + resp.Result = true + } + case nip86.DeleteRole: + if rl.ManagementAPI.DeleteRole == nil { + resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) + } else if err := rl.ManagementAPI.DeleteRole(ctx, thing.ID); err != nil { + resp.Error = err.Error() + } else { + resp.Result = true + } + case nip86.AssignRole: + if rl.ManagementAPI.AssignRole == nil { + resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) + } else if err := rl.ManagementAPI.AssignRole(ctx, thing.PubKey, thing.RoleID); err != nil { + resp.Error = err.Error() + } else { + resp.Result = true + } + case nip86.UnassignRole: + if rl.ManagementAPI.UnassignRole == nil { + resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) + } else if err := rl.ManagementAPI.UnassignRole(ctx, thing.PubKey, thing.RoleID); err != nil { + resp.Error = err.Error() + } else { + resp.Result = true + } case nip86.ListDisallowedKinds: if rl.ManagementAPI.ListDisallowedKinds == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) diff --git a/nip86/methods.go b/nip86/methods.go index ba06914..63e2f92 100644 --- a/nip86/methods.go +++ b/nip86/methods.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "net" + "strconv" "fiatjaf.com/nostr" ) @@ -240,6 +241,95 @@ func DecodeRequest(req Request) (MethodParams, error) { Pubkey: pk, DisallowMethods: disallowedMethods, }, nil + case "createrole": + if len(req.Params) == 0 { + return nil, fmt.Errorf("invalid number of params for '%s'", req.Method) + } + id, ok := req.Params[0].(string) + if !ok { + return nil, fmt.Errorf("missing id param for '%s'", req.Method) + } + var label, description string + if len(req.Params) >= 2 { + label, _ = req.Params[1].(string) + } + if len(req.Params) >= 3 { + description, _ = req.Params[2].(string) + } + var color, order int + if len(req.Params) >= 4 { + color = coerceInt(req.Params[3]) + } + if len(req.Params) >= 5 { + order = coerceInt(req.Params[4]) + } + return CreateRole{id, label, description, color, order}, nil + case "editrole": + if len(req.Params) == 0 { + return nil, fmt.Errorf("invalid number of params for '%s'", req.Method) + } + id, ok := req.Params[0].(string) + if !ok { + return nil, fmt.Errorf("missing id param for '%s'", req.Method) + } + var label, description string + if len(req.Params) >= 2 { + label, _ = req.Params[1].(string) + } + if len(req.Params) >= 3 { + description, _ = req.Params[2].(string) + } + var color, order int + if len(req.Params) >= 4 { + color = coerceInt(req.Params[3]) + } + if len(req.Params) >= 5 { + order = coerceInt(req.Params[4]) + } + return EditRole{id, label, description, color, order}, nil + case "deleterole": + if len(req.Params) == 0 { + return nil, fmt.Errorf("invalid number of params for '%s'", req.Method) + } + id, ok := req.Params[0].(string) + if !ok { + return nil, fmt.Errorf("missing id param for '%s'", req.Method) + } + return DeleteRole{id}, nil + case "assignrole": + if len(req.Params) < 2 { + return nil, fmt.Errorf("invalid number of params for '%s'", req.Method) + } + pkh, ok := req.Params[0].(string) + if !ok { + return nil, fmt.Errorf("missing pubkey param for '%s'", req.Method) + } + pk, err := nostr.PubKeyFromHex(pkh) + if err != nil { + return nil, fmt.Errorf("invalid pubkey param for '%s'", req.Method) + } + roleID, ok := req.Params[1].(string) + if !ok { + return nil, fmt.Errorf("missing role id param for '%s'", req.Method) + } + return AssignRole{pk, roleID}, nil + case "unassignrole": + if len(req.Params) < 2 { + return nil, fmt.Errorf("invalid number of params for '%s'", req.Method) + } + pkh, ok := req.Params[0].(string) + if !ok { + return nil, fmt.Errorf("missing pubkey param for '%s'", req.Method) + } + pk, err := nostr.PubKeyFromHex(pkh) + if err != nil { + return nil, fmt.Errorf("invalid pubkey param for '%s'", req.Method) + } + roleID, ok := req.Params[1].(string) + if !ok { + return nil, fmt.Errorf("missing role id param for '%s'", req.Method) + } + return UnassignRole{pk, roleID}, nil case "stats": return Stats{}, nil default: @@ -247,6 +337,19 @@ func DecodeRequest(req Request) (MethodParams, error) { } } +// coerceInt converts a decoded JSON param (a float64 number or a numeric +// string) into an int, returning 0 when the value is neither. +func coerceInt(v any) int { + switch n := v.(type) { + case float64: + return int(n) + case string: + i, _ := strconv.Atoi(n) + return i + } + return 0 +} + type MethodParams interface { MethodName() string } @@ -276,6 +379,11 @@ var ( _ MethodParams = (*ListDisallowedKinds)(nil) _ MethodParams = (*GrantAdmin)(nil) _ MethodParams = (*RevokeAdmin)(nil) + _ MethodParams = (*CreateRole)(nil) + _ MethodParams = (*EditRole)(nil) + _ MethodParams = (*DeleteRole)(nil) + _ MethodParams = (*AssignRole)(nil) + _ MethodParams = (*UnassignRole)(nil) _ MethodParams = (*Stats)(nil) ) @@ -415,6 +523,46 @@ type RevokeAdmin struct { func (RevokeAdmin) MethodName() string { return "revokeadmin" } +type CreateRole struct { + ID string + Label string + Description string + Color int + Order int +} + +func (CreateRole) MethodName() string { return "createrole" } + +type EditRole struct { + ID string + Label string + Description string + Color int + Order int +} + +func (EditRole) MethodName() string { return "editrole" } + +type DeleteRole struct { + ID string +} + +func (DeleteRole) MethodName() string { return "deleterole" } + +type AssignRole struct { + PubKey nostr.PubKey + RoleID string +} + +func (AssignRole) MethodName() string { return "assignrole" } + +type UnassignRole struct { + PubKey nostr.PubKey + RoleID string +} + +func (UnassignRole) MethodName() string { return "unassignrole" } + type Stats struct{} func (Stats) MethodName() string { return "stats" }