package state import ( "net/http" "time" comatproto "github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/syntax" lexutil "github.com/bluesky-social/indigo/lex/util" "github.com/ipfs/go-cid" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/log" ) func (s *State) SkipVouchSuggestion(w http.ResponseWriter, r *http.Request) { l := log.SubLogger(s.logger, "skipVouchSuggestion") currentUser := s.oauth.GetMultiAccountUser(r) subject := r.FormValue("subject") if subject == "" { l.Warn("missing subject") s.pages.Notice(w, "error", "Missing subject user.") return } subjectIdent, err := s.idResolver.ResolveIdent(r.Context(), subject) if err != nil { l.Error("failed to resolve subject", "subject", subject, "err", err) s.pages.Notice(w, "error", "Could not find that user.") return } if currentUser.Did == subjectIdent.DID.String() { l.Warn("cannot skip yourself") s.pages.Notice(w, "error", "You cannot skip yourself.") return } if err := db.SkipVouchSuggestion(s.db, currentUser.Did, subjectIdent.DID.String()); err != nil { l.Error("failed to skip vouch suggestion", "err", err) s.pages.Notice(w, "error", "Failed to skip suggestion.") return } s.pages.HxRefresh(w) } func (s *State) Vouch(w http.ResponseWriter, r *http.Request) { l := log.SubLogger(s.logger, "vouch") l = s.logger.With("handler", "Vouch") currentUser := s.oauth.GetMultiAccountUser(r) var subject string subject = r.FormValue("subject") if subject == "" { l.Warn("invalid form: missing subject") s.pages.Notice(w, "error", "Missing subject user.") return } subjectIdent, err := s.idResolver.ResolveIdent(r.Context(), subject) if err != nil { l.Error("failed to vouch, invalid did", "subject", subject, "err", err) s.pages.Notice(w, "error", "Could not find that user.") return } if currentUser.Did == subjectIdent.DID.String() { l.Warn("cant vouch or denounce yourself") s.pages.Notice(w, "error", "You cannot vouch for yourself.") return } client, err := s.oauth.AuthorizedClient(r) if err != nil { l.Error("failed to authorize client", "err", err) s.pages.Notice(w, "error", "Authentication required.") return } if err := r.ParseForm(); err != nil { l.Warn("failed to parse form", "err", err) s.pages.Notice(w, "error", "Invalid form data.") return } subjectDid := subjectIdent.DID.String() // handle "none" by deleting any existing vouch if r.FormValue("kind") == "none" { _, err := db.GetVouch(s.db, currentUser.Did, subjectDid) if err != nil { l.Info("no existing vouch to delete") s.pages.HxRefresh(w) return } _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ Collection: tangled.GraphVouchNSID, Repo: currentUser.Did, Rkey: subjectDid, }) if err != nil { l.Error("failed to delete vouch record", "err", err) s.pages.Notice(w, "error", "Failed to delete vouch record.") return } err = db.DeleteVouch(s.db, currentUser.Did, subjectDid) if err != nil { l.Warn("failed to delete vouch from DB", "err", err) } l.Info("deleted vouch record") s.pages.HxRefresh(w) return } kind, err := models.ParseVouchKind(r.FormValue("kind")) if err != nil { l.Warn("failed to parse vouch kind", "err", err) s.pages.Notice(w, "error", "Invalid action type.") return } reason := r.FormValue("reason") createdAt := time.Now().Format(time.RFC3339) var reasonPtr *string if reason != "" { reasonPtr = &reason } var evidences []syntax.ATURI for _, raw := range r.Form["evidences"] { uri, err := syntax.ParseATURI(raw) if err != nil { l.Warn("invalid evidence AT-URI, skipping", "uri", raw, "err", err) continue } evidences = append(evidences, uri) } var swapCid *string existingVouch, err := db.GetVouch(s.db, currentUser.Did, subjectDid) if err == nil { cidStr := existingVouch.Cid.String() swapCid = &cidStr } resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ Collection: tangled.GraphVouchNSID, Repo: currentUser.Did, Rkey: subjectDid, SwapRecord: swapCid, Record: &lexutil.LexiconTypeDecoder{ Val: &tangled.GraphVouch{ Kind: string(kind), Reason: reasonPtr, CreatedAt: createdAt, Evidences: func() []string { ss := make([]string, len(evidences)) for i, e := range evidences { ss[i] = e.String() } return ss }(), }}, }) if err != nil { l.Error("failed to create atproto record", "err", err) s.pages.Notice(w, "error", "Failed to create vouch record.") return } l.Info("created atproto record", "uri", resp.Uri, "kind", kind) newCid, err := cid.Parse(resp.Cid) if err != nil { l.Error("failed to parse returned cid", "err", err) s.pages.Notice(w, "error", "Failed to save vouch.") return } vouch := &models.Vouch{ Did: syntax.DID(currentUser.Did), SubjectDid: subjectIdent.DID, Cid: newCid, Kind: kind, Reason: reasonPtr, Evidences: evidences, } tx, err := s.db.Begin() if err != nil { l.Error("failed to start transaction", "err", err) s.pages.Notice(w, "error", "Failed to save vouch.") return } defer tx.Rollback() err = db.AddVouch(tx, vouch) if err != nil { l.Error("failed to add vouch to db", "err", err) s.pages.Notice(w, "error", "Failed to save vouch.") return } if err = tx.Commit(); err != nil { l.Error("failed to commit vouch transaction", "err", err) s.pages.Notice(w, "error", "Failed to save vouch.") return } s.pages.HxRefresh(w) }