Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

1package xrpc 2 3import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "log/slog" 8 "net/http" 9 "time" 10 11 "github.com/bluesky-social/indigo/atproto/syntax" 12 "tangled.org/core/api/tangled" 13 "tangled.org/core/knotserver/db" 14 "tangled.org/core/rbac" 15 xrpcerr "tangled.org/core/xrpc/errors" 16) 17 18const keyFetchTimeout = 15 * time.Second 19 20func (h *Xrpc) AddMember(w http.ResponseWriter, r *http.Request) { 21 l := h.Logger.With("handler", "AddMember") 22 fail := func(e xrpcerr.XrpcError, status int) { 23 l.Error("failed", "kind", e.Tag, "error", e.Message) 24 writeError(w, e, status) 25 } 26 27 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) 28 if !ok { 29 fail(xrpcerr.MissingActorDidError, http.StatusForbidden) 30 return 31 } 32 33 allowed, err := h.Enforcer.IsKnotInviteAllowed(actorDid.String(), rbac.ThisServer) 34 if err != nil { 35 fail(xrpcerr.GenericError(err), http.StatusInternalServerError) 36 return 37 } 38 if !allowed { 39 fail(xrpcerr.AccessControlError(actorDid.String()), http.StatusForbidden) 40 return 41 } 42 43 var data tangled.KnotAddMember_Input 44 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 45 fail(xrpcerr.GenericError(err), http.StatusBadRequest) 46 return 47 } 48 49 subject, err := syntax.ParseDID(data.Subject) 50 if err != nil { 51 fail(xrpcerr.GenericError(err), http.StatusBadRequest) 52 return 53 } 54 55 status, xerr := h.addMemberToKnot(r.Context(), l, actorDid, subject) 56 if xerr != nil { 57 fail(*xerr, status) 58 return 59 } 60 w.WriteHeader(status) 61} 62 63func (h *Xrpc) addMemberToKnot(ctx context.Context, l *slog.Logger, addedBy, subject syntax.DID) (int, *xrpcerr.XrpcError) { 64 isOwner, err := h.Enforcer.IsKnotOwner(subject.String(), rbac.ThisServer) 65 if err != nil { 66 e := xrpcerr.GenericError(err) 67 return http.StatusInternalServerError, &e 68 } 69 if isOwner { 70 l.Info("subject is the knot owner, no-op", "subject", subject) 71 return http.StatusOK, nil 72 } 73 74 return h.applyAclGrant(ctx, l, aclGrant{ 75 role: "member", 76 subject: subject, 77 inAcl: func() (bool, error) { 78 return h.Enforcer.IsKnotMember(subject.String(), rbac.ThisServer) 79 }, 80 inTable: func() (bool, error) { 81 n, err := db.CountKnotMembersBySubject(h.Db, subject.String()) 82 return n > 0, err 83 }, 84 insertRow: func(tx *sql.Tx) error { 85 return db.AddKnotMemberDirect(tx, addedBy, subject) 86 }, 87 deleteRow: func() error { 88 return db.RemoveKnotMemberDirect(h.Db, subject) 89 }, 90 grantAcl: func() error { 91 _, err := h.Enforcer.TryAddKnotMember(rbac.ThisServer, subject.String()) 92 return err 93 }, 94 emit: func() error { 95 return h.Db.EmitKnotMemberUpdate(h.Notifier, db.AclOpAdd, subject) 96 }, 97 }) 98}