Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383package xrpc
import ( "context" "encoding/json" "net/http" "strconv" "strings"
"github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/hostutil" xrpcerr "tangled.org/core/xrpc/errors")
// resolveOwnedRepo loads the repo by its DID and checks the actor owns itfunc (x *Xrpc) resolveOwnedRepo(r *http.Request, repoDid string) (*models.Repo, *xrpcerr.XrpcError, int) { did, ok := actorDid(r) if !ok { e := xrpcerr.MissingActorDidError return nil, &e, http.StatusForbidden }
repo, err := db.GetRepoByDid(x.DB, repoDid) if err != nil { e := notFoundError("repo not found") return nil, &e, http.StatusNotFound }
if repo.Did != did { e := xrpcerr.AccessControlError(did) return nil, &e, http.StatusForbidden }
return repo, nil, http.StatusOK}
func (x *Xrpc) WebhookList(w http.ResponseWriter, r *http.Request) { l := x.Logger.With("handler", "WebhookList")
repo, xerr, status := x.resolveOwnedRepo(r, r.URL.Query().Get("repoDid")) if xerr != nil { writeError(w, *xerr, status) return }
webhooks, err := db.GetWebhooksForRepo(x.DB, string(repo.RepoDid)) if err != nil { l.Error("failed to get webhooks", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return }
items := make([]*tangled.TempRepoListWebhooks_Webhook, 0, len(webhooks)) for i := range webhooks { wh := &webhooks[i] updated := wh.UpdatedAt.UTC().Format(timeFormat) items = append(items, &tangled.TempRepoListWebhooks_Webhook{ Id: wh.Id, Url: wh.Url, Active: wh.Active, Events: wh.Events, CreatedAt: wh.CreatedAt.UTC().Format(timeFormat), UpdatedAt: &updated, }) }
x.writeJSON(w, &tangled.TempRepoListWebhooks_Output{Webhooks: items})}
func (x *Xrpc) WebhookCreate(w http.ResponseWriter, r *http.Request) { l := x.Logger.With("handler", "WebhookCreate")
var input tangled.TempRepoCreateWebhook_Input if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, errBadRequestBody, http.StatusBadRequest) return }
repo, xerr, status := x.resolveOwnedRepo(r, input.RepoDid) if xerr != nil { writeError(w, *xerr, status) return }
url := strings.TrimSpace(input.Url) if err := hostutil.ValidateExternalURL(url, x.Config.Core.Dev); err != nil { writeError(w, badRequestError(err.Error()), http.StatusBadRequest) return } if len(input.Events) == 0 { writeError(w, xrpcErrorTag("NoEventsSelected", "at least one event must be specified"), http.StatusBadRequest) return }
active := true if input.Active != nil { active = *input.Active } secret := "" if input.Secret != nil { secret = strings.TrimSpace(*input.Secret) }
webhook := &models.Webhook{ RepoDid: syntax.DID(repo.RepoDid), Url: url, Secret: secret, Active: active, Events: input.Events, }
tx, err := x.DB.Begin() if err != nil { l.Error("failed to start transaction", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return } defer tx.Rollback()
if err := db.AddWebhook(tx, webhook); err != nil { l.Error("failed to add webhook", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return } if err := tx.Commit(); err != nil { l.Error("failed to commit transaction", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return }
x.writeJSON(w, &tangled.TempRepoCreateWebhook_Output{Id: webhook.Id})}
func (x *Xrpc) WebhookUpdate(w http.ResponseWriter, r *http.Request) { l := x.Logger.With("handler", "WebhookUpdate")
var input tangled.TempRepoUpdateWebhook_Input if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, errBadRequestBody, http.StatusBadRequest) return }
repo, xerr, status := x.resolveOwnedRepo(r, input.RepoDid) if xerr != nil { writeError(w, *xerr, status) return }
webhook, err := db.GetWebhook(x.DB, input.Id) if err != nil || string(webhook.RepoDid) != repo.RepoDid { writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) return }
if input.Url != nil { url := strings.TrimSpace(*input.Url) if url != "" { if err := hostutil.ValidateExternalURL(url, x.Config.Core.Dev); err != nil { writeError(w, badRequestError(err.Error()), http.StatusBadRequest) return } webhook.Url = url } } if input.Secret != nil { webhook.Secret = strings.TrimSpace(*input.Secret) } if input.Active != nil { webhook.Active = *input.Active } if len(input.Events) > 0 { webhook.Events = input.Events }
tx, err := x.DB.Begin() if err != nil { l.Error("failed to start transaction", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return } defer tx.Rollback()
if err := db.UpdateWebhook(tx, webhook); err != nil { l.Error("failed to update webhook", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return } if err := tx.Commit(); err != nil { l.Error("failed to commit transaction", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return }
w.WriteHeader(http.StatusOK)}
func (x *Xrpc) WebhookDelete(w http.ResponseWriter, r *http.Request) { l := x.Logger.With("handler", "WebhookDelete")
var input tangled.TempRepoDeleteWebhook_Input if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, errBadRequestBody, http.StatusBadRequest) return }
repo, xerr, status := x.resolveOwnedRepo(r, input.RepoDid) if xerr != nil { writeError(w, *xerr, status) return }
webhook, err := db.GetWebhook(x.DB, input.Id) if err != nil || string(webhook.RepoDid) != repo.RepoDid { writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) return }
tx, err := x.DB.Begin() if err != nil { l.Error("failed to start transaction", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return } defer tx.Rollback()
if err := db.DeleteWebhook(tx, input.Id); err != nil { l.Error("failed to delete webhook", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return } if err := tx.Commit(); err != nil { l.Error("failed to commit transaction", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return }
w.WriteHeader(http.StatusOK)}
func (x *Xrpc) WebhookToggle(w http.ResponseWriter, r *http.Request) { l := x.Logger.With("handler", "WebhookToggle")
var input tangled.TempRepoToggleWebhook_Input if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, errBadRequestBody, http.StatusBadRequest) return }
repo, xerr, status := x.resolveOwnedRepo(r, input.RepoDid) if xerr != nil { writeError(w, *xerr, status) return }
webhook, err := db.GetWebhook(x.DB, input.Id) if err != nil || string(webhook.RepoDid) != repo.RepoDid { writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) return }
webhook.Active = !webhook.Active
tx, err := x.DB.Begin() if err != nil { l.Error("failed to start transaction", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return } defer tx.Rollback()
if err := db.UpdateWebhook(tx, webhook); err != nil { l.Error("failed to toggle webhook", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return } if err := tx.Commit(); err != nil { l.Error("failed to commit transaction", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return }
x.writeJSON(w, &tangled.TempRepoToggleWebhook_Output{Active: webhook.Active})}
func (x *Xrpc) WebhookListDeliveries(w http.ResponseWriter, r *http.Request) { l := x.Logger.With("handler", "WebhookListDeliveries")
q := r.URL.Query() repo, xerr, status := x.resolveOwnedRepo(r, q.Get("repoDid")) if xerr != nil { writeError(w, *xerr, status) return }
id, err := strconv.ParseInt(q.Get("id"), 10, 64) if err != nil { writeError(w, badRequestError("invalid webhook id"), http.StatusBadRequest) return }
webhook, err := db.GetWebhook(x.DB, id) if err != nil || string(webhook.RepoDid) != repo.RepoDid { writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) return }
limit := 100 if s := q.Get("limit"); s != "" { if n, err := strconv.Atoi(s); err == nil && n > 0 && n <= 100 { limit = n } }
deliveries, err := db.GetWebhookDeliveries(x.DB, webhook.Id, limit) if err != nil { l.Error("failed to get webhook deliveries", "err", err) writeError(w, errInternal, http.StatusInternalServerError) return }
items := make([]*tangled.TempRepoListWebhookDeliveries_Delivery, 0, len(deliveries)) for i := range deliveries { d := &deliveries[i] item := &tangled.TempRepoListWebhookDeliveries_Delivery{ Id: d.Id, DeliveryId: d.DeliveryId, Event: d.Event, Url: d.Url, Success: d.Success, CreatedAt: d.CreatedAt.UTC().Format(timeFormat), } if d.RequestBody != "" { rb := d.RequestBody item.RequestBody = &rb } if d.ResponseBody != "" { rb := d.ResponseBody item.ResponseBody = &rb } if d.ResponseCode != 0 { rc := int64(d.ResponseCode) item.ResponseCode = &rc } items = append(items, item) }
x.writeJSON(w, &tangled.TempRepoListWebhookDeliveries_Output{Deliveries: items})}
func (x *Xrpc) WebhookRetryDelivery(w http.ResponseWriter, r *http.Request) { var input tangled.TempRepoRetryWebhookDelivery_Input if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, errBadRequestBody, http.StatusBadRequest) return }
repo, xerr, status := x.resolveOwnedRepo(r, input.RepoDid) if xerr != nil { writeError(w, *xerr, status) return }
webhook, err := db.GetWebhook(x.DB, input.WebhookId) if err != nil || string(webhook.RepoDid) != repo.RepoDid { writeError(w, xrpcErrorTag("WebhookNotFound", "webhook not found"), http.StatusNotFound) return }
delivery, err := db.GetWebhookDelivery(x.DB, input.DeliveryId) if err != nil || delivery.WebhookId != webhook.Id { writeError(w, xrpcErrorTag("DeliveryNotFound", "delivery not found"), http.StatusNotFound) return }
// re-dispatch async; the new attempt is recorded as its own delivery go x.Webhooks.Redeliver(context.Background(), *webhook, *delivery)
w.WriteHeader(http.StatusOK)}