Monorepo for Tangled
tangled.org
1package xrpc
2
3import (
4 "database/sql"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "net/http"
9 "os"
10
11 comatproto "github.com/bluesky-social/indigo/api/atproto"
12 "github.com/bluesky-social/indigo/atproto/syntax"
13 "github.com/bluesky-social/indigo/xrpc"
14 securejoin "github.com/cyphar/filepath-securejoin"
15 "tangled.org/core/api/tangled"
16 "tangled.org/core/rbac"
17 xrpcerr "tangled.org/core/xrpc/errors"
18)
19
20func (x *Xrpc) DeleteRepo(w http.ResponseWriter, r *http.Request) {
21 l := x.Logger.With("handler", "DeleteRepo")
22 fail := func(e xrpcerr.XrpcError) {
23 l.Error("failed", "kind", e.Tag, "error", e.Message)
24 writeError(w, e, http.StatusBadRequest)
25 }
26
27 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
28 if !ok {
29 fail(xrpcerr.MissingActorDidError)
30 return
31 }
32
33 var data tangled.RepoDelete_Input
34 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
35 fail(xrpcerr.GenericError(err))
36 return
37 }
38
39 did := data.Did
40 name := data.Name
41 rkey := data.Rkey
42
43 if did == "" || name == "" {
44 fail(xrpcerr.GenericError(fmt.Errorf("did and name are required")))
45 return
46 }
47
48 ident, err := x.Resolver.ResolveIdent(r.Context(), actorDid.String())
49 if err != nil || ident.Handle.IsInvalidHandle() {
50 fail(xrpcerr.GenericError(err))
51 return
52 }
53
54 xrpcc := xrpc.Client{
55 Host: ident.PDSEndpoint(),
56 }
57
58 // ensure that the record does not exists
59 _, err = comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, actorDid.String(), rkey)
60 if err == nil {
61 fail(xrpcerr.RecordExistsError(rkey))
62 return
63 }
64
65 repoDid, err := x.Db.GetRepoDid(did, name)
66 if errors.Is(err, sql.ErrNoRows) {
67 repoDid, err = x.Db.GetRepoDidByName(did, name)
68 if errors.Is(err, sql.ErrNoRows) {
69 l.Info("repo already torn down or not found", "did", did, "name", name)
70 w.WriteHeader(http.StatusOK)
71 return
72 }
73 }
74 if err != nil {
75 l.Error("failed to look up repo", "error", err.Error())
76 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
77 return
78 }
79
80 repoPath, joinErr := securejoin.SecureJoin(x.Config.Repo.ScanPath, repoDid)
81 if joinErr != nil {
82 fail(xrpcerr.GenericError(joinErr))
83 return
84 }
85
86 isDeleteAllowed, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer, repoDid)
87 if err != nil {
88 fail(xrpcerr.GenericError(err))
89 return
90 }
91 if !isDeleteAllowed {
92 fail(xrpcerr.AccessControlError(actorDid.String()))
93 return
94 }
95
96 if rmErr := os.RemoveAll(repoPath); rmErr != nil {
97 l.Error("deleting repo", "error", rmErr.Error())
98 writeError(w, xrpcerr.GenericError(rmErr), http.StatusInternalServerError)
99 return
100 }
101
102 if rbacErr := x.Enforcer.WipeRepoPolicies(rbac.ThisServer, repoDid); rbacErr != nil {
103 l.Error("failed to delete repo from enforcer", "error", rbacErr.Error())
104 writeError(w, xrpcerr.GenericError(rbacErr), http.StatusInternalServerError)
105 return
106 }
107
108 if delErr := x.Db.DeleteRepoKey(repoDid); delErr != nil {
109 l.Error("failed to delete repo key", "error", delErr.Error())
110 writeError(w, xrpcerr.GenericError(delErr), http.StatusInternalServerError)
111 return
112 }
113
114 w.WriteHeader(http.StatusOK)
115}