Monorepo for Tangled
tangled.org
1package xrpc
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "strings"
8
9 "github.com/bluesky-social/indigo/api/atproto"
10 "github.com/bluesky-social/indigo/atproto/atclient"
11 "github.com/bluesky-social/indigo/atproto/syntax"
12 "github.com/bluesky-social/indigo/xrpc"
13 "tangled.org/core/api/tangled"
14 "tangled.org/core/knotmirror/db"
15 "tangled.org/core/knotmirror/hostutil"
16 "tangled.org/core/knotmirror/models"
17)
18
19func (x *Xrpc) RequestCrawl(w http.ResponseWriter, r *http.Request) {
20 var input tangled.SyncRequestCrawl_Input
21 if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
22 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "failed to decode json body"})
23 return
24 }
25
26 ctx := r.Context()
27
28 l := x.logger.With("input", input)
29
30 hostname, noSSL, err := hostutil.ParseHostname(input.Hostname)
31 if err != nil {
32 l.Error("invalid hostname", "err", err)
33 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("hostname field empty or invalid: %s", input.Hostname)})
34 return
35 }
36
37 // TODO: check if host is Knot with knot.describeServer
38
39 // store given repoAt to db
40 // this will allow knotmirror to ingest repo creation event bypassing tap.
41 // this step won't be needed once we introduce did-for-repo
42 // TODO(boltless): remove this section
43 if input.EnsureRepo != nil {
44 repoAt, err := syntax.ParseATURI(*input.EnsureRepo)
45 if err != nil {
46 l.Error("invalid repo at-uri", "err", err)
47 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", *input.EnsureRepo)})
48 return
49 }
50 owner, err := x.resolver.ResolveIdent(ctx, repoAt.Authority().String())
51 if err != nil || owner.Handle.IsInvalidHandle() {
52 l.Error("failed to resolve ident", "err", err, "owner", repoAt.Authority().String())
53 writeErr(w, fmt.Errorf("failed to resolve repo owner"))
54 return
55 }
56 xrpcc := xrpc.Client{Host: owner.PDSEndpoint()}
57 out, err := atproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())
58 if err != nil {
59 l.Error("failed to get repo record", "err", err, "repo", repoAt)
60 writeErr(w, fmt.Errorf("failed to get repo record"))
61 return
62 }
63 record := out.Value.Val.(*tangled.Repo)
64
65 knotUrl := record.Knot
66 if !strings.Contains(record.Knot, "://") {
67 if noSSL {
68 knotUrl = "http://" + knotUrl
69 } else {
70 knotUrl = "https://" + knotUrl
71 }
72 }
73
74 if record.RepoDid == nil || *record.RepoDid == "" {
75 l.Warn("dropping repo crawl request without repo_did", "did", owner.DID, "rkey", repoAt.RecordKey())
76 writeErr(w, fmt.Errorf("repo record missing repo_did"))
77 return
78 }
79
80 repo := &models.Repo{
81 Did: owner.DID,
82 Rkey: repoAt.RecordKey(),
83 Cid: (*syntax.CID)(out.Cid),
84 Name: repoAt.RecordKey().String(),
85 KnotDomain: knotUrl,
86 RepoDid: syntax.DID(*record.RepoDid),
87 State: models.RepoStatePending,
88 ErrorMsg: "",
89 RetryAfter: 0,
90 RetryCount: 0,
91 }
92
93 x.logger.Debug("requestCrawl: upserting repo with knot", "knot", repo.KnotDomain)
94 if err := db.UpsertRepo(ctx, x.db, repo); err != nil {
95 l.Error("failed to upsert repo", "err", err)
96 writeErr(w, err)
97 return
98 }
99 }
100
101 // subscribe to requested host
102 if !x.ks.CheckIfSubscribed(hostname) {
103 if err := x.ks.SubscribeHost(ctx, hostname, noSSL); err != nil {
104 // TODO(boltless): return HostBanned on banned hosts
105 l.Error("failed to subscribe host", "err", err)
106 writeErr(w, err)
107 return
108 }
109 }
110
111 w.WriteHeader(http.StatusOK)
112}