Monorepo for Tangled
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374package repo
import ( "net/http" "strconv" "strings"
"github.com/bluesky-social/indigo/atproto/syntax" "github.com/go-chi/chi/v5" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/pages")
// Webhooks displays the webhooks settings pagefunc (rp *Repo) Webhooks(w http.ResponseWriter, r *http.Request) { l := rp.logger.With("handler", "Webhooks")
f, err := rp.repoResolver.Resolve(r) if err != nil { l.Error("failed to get repo and knot", "err", err) w.WriteHeader(http.StatusBadRequest) return }
user := rp.oauth.GetMultiAccountUser(r)
webhooks, err := db.GetWebhooksForRepo(rp.db, f.RepoDid) if err != nil { l.Error("failed to get webhooks", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to load webhooks") return }
// fetch recent deliveries for each webhook deliveriesMap := make(map[int64][]models.WebhookDelivery) for _, webhook := range webhooks { deliveries, err := db.GetWebhookDeliveries(rp.db, webhook.Id, 4) if err != nil { l.Error("failed to get webhook deliveries", "webhook_id", webhook.Id, "err", err) // continue even if we can't get deliveries for one webhook continue } deliveriesMap[webhook.Id] = deliveries }
rp.pages.RepoWebhooksSettings(w, pages.RepoWebhooksSettingsParams{ BaseParams: pages.BaseParamsFromContext(r.Context()), RepoInfo: rp.repoResolver.GetRepoInfo(r, user), Webhooks: webhooks, WebhookDeliveries: deliveriesMap, })}
// AddWebhook creates a new webhookfunc (rp *Repo) AddWebhook(w http.ResponseWriter, r *http.Request) { l := rp.logger.With("handler", "AddWebhook")
f, err := rp.repoResolver.Resolve(r) if err != nil { l.Error("failed to get repo and knot", "err", err) w.WriteHeader(http.StatusBadRequest) return }
url := strings.TrimSpace(r.FormValue("url")) if url == "" { rp.pages.Notice(w, "webhooks-error", "Webhook URL is required") return }
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { rp.pages.Notice(w, "webhooks-error", "Webhook URL must start with http:// or https://") return }
secret := strings.TrimSpace(r.FormValue("secret")) // if secret is empty, we don't sign
active := r.FormValue("active") == "on"
events := []string{} if r.FormValue("event_push") == "on" { events = append(events, string(models.WebhookEventPush)) } if r.FormValue("event_repo_renamed") == "on" { events = append(events, string(models.WebhookEventRepoRenamed)) }
if len(events) == 0 { rp.pages.Notice(w, "webhooks-error", "At least one event must be enabled") return }
webhook := &models.Webhook{ RepoDid: syntax.DID(f.RepoDid), Url: url, Secret: secret, Active: active, Events: events, }
tx, err := rp.db.Begin() if err != nil { l.Error("failed to start transaction", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to create webhook") return } defer tx.Rollback()
if err := db.AddWebhook(tx, webhook); err != nil { l.Error("failed to add webhook", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to create webhook") return }
if err := tx.Commit(); err != nil { l.Error("failed to commit transaction", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to create webhook") return }
rp.pages.HxRefresh(w)}
// UpdateWebhook updates an existing webhookfunc (rp *Repo) UpdateWebhook(w http.ResponseWriter, r *http.Request) { l := rp.logger.With("handler", "UpdateWebhook")
f, err := rp.repoResolver.Resolve(r) if err != nil { l.Error("failed to get repo and knot", "err", err) w.WriteHeader(http.StatusBadRequest) return }
idStr := chi.URLParam(r, "id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { l.Error("invalid webhook id", "err", err) w.WriteHeader(http.StatusBadRequest) return }
webhook, err := db.GetWebhook(rp.db, id) if err != nil { l.Error("failed to get webhook", "err", err) rp.pages.Notice(w, "webhooks-error", "Webhook not found") return }
// Verify webhook belongs to this repo if string(webhook.RepoDid) != f.RepoDid { l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoDid, "current_repo", f.RepoDid) w.WriteHeader(http.StatusForbidden) return }
url := strings.TrimSpace(r.FormValue("url")) if url != "" { if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { rp.pages.Notice(w, "webhooks-error", "Webhook URL must start with http:// or https://") return } webhook.Url = url }
secret := strings.TrimSpace(r.FormValue("secret")) if secret != "" { webhook.Secret = secret }
webhook.Active = r.FormValue("active") == "on"
events := []string{} if r.FormValue("event_push") == "on" { events = append(events, string(models.WebhookEventPush)) } if r.FormValue("event_repo_renamed") == "on" { events = append(events, string(models.WebhookEventRepoRenamed)) }
if len(events) > 0 { webhook.Events = events }
tx, err := rp.db.Begin() if err != nil { l.Error("failed to start transaction", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to update webhook") return } defer tx.Rollback()
if err := db.UpdateWebhook(tx, webhook); err != nil { l.Error("failed to update webhook", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to update webhook") return }
if err := tx.Commit(); err != nil { l.Error("failed to commit transaction", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to update webhook") return }
rp.pages.HxRefresh(w)}
// DeleteWebhook deletes a webhookfunc (rp *Repo) DeleteWebhook(w http.ResponseWriter, r *http.Request) { l := rp.logger.With("handler", "DeleteWebhook")
f, err := rp.repoResolver.Resolve(r) if err != nil { l.Error("failed to get repo and knot", "err", err) w.WriteHeader(http.StatusBadRequest) return }
idStr := chi.URLParam(r, "id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { l.Error("invalid webhook id", "err", err) w.WriteHeader(http.StatusBadRequest) return }
webhook, err := db.GetWebhook(rp.db, id) if err != nil { l.Error("failed to get webhook", "err", err) w.WriteHeader(http.StatusNotFound) return }
// Verify webhook belongs to this repo if string(webhook.RepoDid) != f.RepoDid { l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoDid, "current_repo", f.RepoDid) w.WriteHeader(http.StatusForbidden) return }
tx, err := rp.db.Begin() if err != nil { l.Error("failed to start transaction", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to delete webhook") return } defer tx.Rollback()
if err := db.DeleteWebhook(tx, id); err != nil { l.Error("failed to delete webhook", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to delete webhook") return }
if err := tx.Commit(); err != nil { l.Error("failed to commit transaction", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to delete webhook") return }
rp.pages.HxRefresh(w)}
// ToggleWebhook toggles the active state of a webhookfunc (rp *Repo) ToggleWebhook(w http.ResponseWriter, r *http.Request) { l := rp.logger.With("handler", "ToggleWebhook")
f, err := rp.repoResolver.Resolve(r) if err != nil { l.Error("failed to get repo and knot", "err", err) w.WriteHeader(http.StatusBadRequest) return }
idStr := chi.URLParam(r, "id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { l.Error("invalid webhook id", "err", err) w.WriteHeader(http.StatusBadRequest) return }
webhook, err := db.GetWebhook(rp.db, id) if err != nil { l.Error("failed to get webhook", "err", err) w.WriteHeader(http.StatusNotFound) return }
// Verify webhook belongs to this repo if string(webhook.RepoDid) != f.RepoDid { l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoDid, "current_repo", f.RepoDid) w.WriteHeader(http.StatusForbidden) return }
// Toggle the active state webhook.Active = !webhook.Active
tx, err := rp.db.Begin() if err != nil { l.Error("failed to start transaction", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to toggle webhook") return } defer tx.Rollback()
if err := db.UpdateWebhook(tx, webhook); err != nil { l.Error("failed to update webhook", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to toggle webhook") return }
if err := tx.Commit(); err != nil { l.Error("failed to commit transaction", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to toggle webhook") return }
rp.pages.HxRefresh(w)}
// WebhookDeliveries returns all deliveries for a webhook (for modal display)func (rp *Repo) WebhookDeliveries(w http.ResponseWriter, r *http.Request) { l := rp.logger.With("handler", "WebhookDeliveries")
f, err := rp.repoResolver.Resolve(r) if err != nil { l.Error("failed to get repo and knot", "err", err) w.WriteHeader(http.StatusBadRequest) return }
idStr := chi.URLParam(r, "id") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { l.Error("invalid webhook id", "err", err) w.WriteHeader(http.StatusBadRequest) return }
webhook, err := db.GetWebhook(rp.db, id) if err != nil { l.Error("failed to get webhook", "err", err) w.WriteHeader(http.StatusNotFound) return }
// Verify webhook belongs to this repo if string(webhook.RepoDid) != f.RepoDid { l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoDid, "current_repo", f.RepoDid) w.WriteHeader(http.StatusForbidden) return }
deliveries, err := db.GetWebhookDeliveries(rp.db, webhook.Id, 100) if err != nil { l.Error("failed to get webhook deliveries", "err", err) rp.pages.Notice(w, "webhooks-error", "Failed to load deliveries") return }
user := rp.oauth.GetMultiAccountUser(r)
rp.pages.WebhookDeliveriesList(w, pages.WebhookDeliveriesListParams{ BaseParams: pages.BaseParamsFromContext(r.Context()), RepoInfo: rp.repoResolver.GetRepoInfo(r, user), Webhook: webhook, Deliveries: deliveries, })}