+2
api/tangled/repodelete.go
+2
api/tangled/repodelete.go
···
20
20
Did string `json:"did" cborgen:"did"`
21
21
// name: Name of the repository to delete
22
22
Name string `json:"name" cborgen:"name"`
23
+
// rkey: Rkey of the repository record
24
+
Rkey string `json:"rkey" cborgen:"rkey"`
23
25
}
24
26
25
27
// RepoDelete calls the XRPC method "sh.tangled.repo.delete".
+1
appview/repo/repo.go
+1
appview/repo/repo.go
+93
-79
knotserver/xrpc/delete_repo.go
+93
-79
knotserver/xrpc/delete_repo.go
···
1
1
package xrpc
2
2
3
-
// import (
4
-
// "encoding/json"
5
-
// "fmt"
6
-
// "net/http"
7
-
// "os"
8
-
// "path/filepath"
9
-
//
10
-
// "github.com/bluesky-social/indigo/atproto/syntax"
11
-
// securejoin "github.com/cyphar/filepath-securejoin"
12
-
// "tangled.sh/tangled.sh/core/api/tangled"
13
-
// "tangled.sh/tangled.sh/core/rbac"
14
-
// xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
15
-
// )
3
+
import (
4
+
"encoding/json"
5
+
"fmt"
6
+
"net/http"
7
+
"os"
8
+
"path/filepath"
16
9
17
-
// func (x *Xrpc) DeleteRepo(w http.ResponseWriter, r *http.Request) {
18
-
// l := x.Logger.With("handler", "DeleteRepo")
19
-
// fail := func(e xrpcerr.XrpcError) {
20
-
// l.Error("failed", "kind", e.Tag, "error", e.Message)
21
-
// writeError(w, e, http.StatusBadRequest)
22
-
// }
23
-
//
24
-
// actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
25
-
// if !ok {
26
-
// fail(xrpcerr.MissingActorDidError)
27
-
// return
28
-
// }
29
-
//
30
-
// isMember, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer)
31
-
// if err != nil {
32
-
// fail(xrpcerr.GenericError(err))
33
-
// return
34
-
// }
35
-
// if !isMember {
36
-
// fail(xrpcerr.AccessControlError(actorDid.String()))
37
-
// return
38
-
// }
39
-
//
40
-
// var data tangled.RepoDelete_Input
41
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
42
-
// fail(xrpcerr.GenericError(err))
43
-
// return
44
-
// }
45
-
//
46
-
// did := data.Did
47
-
// name := data.Name
48
-
//
49
-
// if did == "" || name == "" {
50
-
// fail(xrpcerr.GenericError(fmt.Errorf("did and name are required")))
51
-
// return
52
-
// }
53
-
//
54
-
// relativeRepoPath := filepath.Join(did, name)
55
-
// if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil {
56
-
// l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath)
57
-
// writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
58
-
// return
59
-
// }
60
-
//
61
-
// repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
62
-
// if err != nil {
63
-
// fail(xrpcerr.GenericError(err))
64
-
// return
65
-
// }
66
-
//
67
-
// err = os.RemoveAll(repoPath)
68
-
// if err != nil {
69
-
// l.Error("deleting repo", "error", err.Error())
70
-
// writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
71
-
// return
72
-
// }
73
-
//
74
-
// err = x.Enforcer.RemoveRepo(did, rbac.ThisServer, relativeRepoPath)
75
-
// if err != nil {
76
-
// l.Error("failed to delete repo from enforcer", "error", err.Error())
77
-
// writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
78
-
// return
79
-
// }
80
-
//
81
-
// w.WriteHeader(http.StatusOK)
82
-
// }
10
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
11
+
"github.com/bluesky-social/indigo/atproto/syntax"
12
+
"github.com/bluesky-social/indigo/xrpc"
13
+
securejoin "github.com/cyphar/filepath-securejoin"
14
+
"tangled.sh/tangled.sh/core/api/tangled"
15
+
"tangled.sh/tangled.sh/core/rbac"
16
+
xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
17
+
)
18
+
19
+
func (x *Xrpc) DeleteRepo(w http.ResponseWriter, r *http.Request) {
20
+
l := x.Logger.With("handler", "DeleteRepo")
21
+
fail := func(e xrpcerr.XrpcError) {
22
+
l.Error("failed", "kind", e.Tag, "error", e.Message)
23
+
writeError(w, e, http.StatusBadRequest)
24
+
}
25
+
26
+
actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
27
+
if !ok {
28
+
fail(xrpcerr.MissingActorDidError)
29
+
return
30
+
}
31
+
32
+
var data tangled.RepoDelete_Input
33
+
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
34
+
fail(xrpcerr.GenericError(err))
35
+
return
36
+
}
37
+
38
+
did := data.Did
39
+
name := data.Name
40
+
rkey := data.Rkey
41
+
42
+
if did == "" || name == "" {
43
+
fail(xrpcerr.GenericError(fmt.Errorf("did and name are required")))
44
+
return
45
+
}
46
+
47
+
ident, err := x.Resolver.ResolveIdent(r.Context(), actorDid.String())
48
+
if err != nil || ident.Handle.IsInvalidHandle() {
49
+
fail(xrpcerr.GenericError(err))
50
+
return
51
+
}
52
+
53
+
xrpcc := xrpc.Client{
54
+
Host: ident.PDSEndpoint(),
55
+
}
56
+
57
+
// ensure that the record does not exists
58
+
_, err = comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, actorDid.String(), rkey)
59
+
if err == nil {
60
+
fail(xrpcerr.RecordExistsError(rkey))
61
+
return
62
+
}
63
+
64
+
relativeRepoPath := filepath.Join(did, name)
65
+
isDeleteAllowed, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath)
66
+
if err != nil {
67
+
fail(xrpcerr.GenericError(err))
68
+
return
69
+
}
70
+
if !isDeleteAllowed {
71
+
fail(xrpcerr.AccessControlError(actorDid.String()))
72
+
return
73
+
}
74
+
75
+
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
76
+
if err != nil {
77
+
fail(xrpcerr.GenericError(err))
78
+
return
79
+
}
80
+
81
+
err = os.RemoveAll(repoPath)
82
+
if err != nil {
83
+
l.Error("deleting repo", "error", err.Error())
84
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
85
+
return
86
+
}
87
+
88
+
err = x.Enforcer.RemoveRepo(did, rbac.ThisServer, relativeRepoPath)
89
+
if err != nil {
90
+
l.Error("failed to delete repo from enforcer", "error", err.Error())
91
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
92
+
return
93
+
}
94
+
95
+
w.WriteHeader(http.StatusOK)
96
+
}
+1
knotserver/xrpc/xrpc.go
+1
knotserver/xrpc/xrpc.go
···
37
37
38
38
r.Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch)
39
39
r.Post("/"+tangled.RepoCreateNSID, x.CreateRepo)
40
+
r.Post("/"+tangled.RepoDeleteNSID, x.DeleteRepo)
40
41
r.Post("/"+tangled.RepoForkStatusNSID, x.ForkStatus)
41
42
r.Post("/"+tangled.RepoForkSyncNSID, x.ForkSync)
42
43
r.Post("/"+tangled.RepoHiddenRefNSID, x.HiddenRef)
+5
-1
lexicons/repo/delete.json
+5
-1
lexicons/repo/delete.json
···
9
9
"encoding": "application/json",
10
10
"schema": {
11
11
"type": "object",
12
-
"required": ["did", "name"],
12
+
"required": ["did", "name", "rkey"],
13
13
"properties": {
14
14
"did": {
15
15
"type": "string",
···
19
19
"name": {
20
20
"type": "string",
21
21
"description": "Name of the repository to delete"
22
+
},
23
+
"rkey": {
24
+
"type": "string",
25
+
"description": "Rkey of the repository record"
22
26
}
23
27
}
24
28
}
+2
-2
rbac/rbac.go
+2
-2
rbac/rbac.go
···
281
281
return e.E.Enforce(user, domain, domain, "repo:create")
282
282
}
283
283
284
-
func (e *Enforcer) IsRepoDeleteAllowed(user, domain string) (bool, error) {
285
-
return e.E.Enforce(user, domain, domain, "repo:delete")
284
+
func (e *Enforcer) IsRepoDeleteAllowed(user, domain, repo string) (bool, error) {
285
+
return e.E.Enforce(user, domain, repo, "repo:delete")
286
286
}
287
287
288
288
func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
+7
xrpc/errors/errors.go
+7
xrpc/errors/errors.go
···
86
86
)
87
87
}
88
88
89
+
var RecordExistsError = func(r string) XrpcError {
90
+
return NewXrpcError(
91
+
WithTag("RecordExists"),
92
+
WithError(fmt.Errorf("repo already exists: %s", r)),
93
+
)
94
+
}
95
+
89
96
func GenericError(err error) XrpcError {
90
97
return NewXrpcError(
91
98
WithTag("Generic"),