1package xrpc
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "path/filepath"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 securejoin "github.com/cyphar/filepath-securejoin"
11 "tangled.sh/tangled.sh/core/api/tangled"
12 "tangled.sh/tangled.sh/core/hook"
13 "tangled.sh/tangled.sh/core/knotserver/git"
14 "tangled.sh/tangled.sh/core/rbac"
15 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
16)
17
18func (x *Xrpc) ForkRepo(w http.ResponseWriter, r *http.Request) {
19 l := x.Logger.With("handler", "ForkRepo")
20 fail := func(e xrpcerr.XrpcError) {
21 l.Error("failed", "kind", e.Tag, "error", e.Message)
22 writeError(w, e, http.StatusBadRequest)
23 }
24
25 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
26 if !ok {
27 fail(xrpcerr.MissingActorDidError)
28 return
29 }
30
31 isMember, err := x.Enforcer.IsKnotMember(actorDid.String(), rbac.ThisServer)
32 if err != nil {
33 fail(xrpcerr.GenericError(err))
34 return
35 }
36 if !isMember {
37 fail(xrpcerr.AccessControlError(actorDid.String()))
38 return
39 }
40
41 var data tangled.RepoFork_Input
42 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
43 fail(xrpcerr.GenericError(err))
44 return
45 }
46
47 did := data.Did
48 source := data.Source
49
50 if did == "" || source == "" {
51 fail(xrpcerr.GenericError(fmt.Errorf("did and source are required")))
52 return
53 }
54
55 var name string
56 if data.Name != nil && *data.Name != "" {
57 name = *data.Name
58 } else {
59 name = filepath.Base(source)
60 }
61
62 relativeRepoPath := filepath.Join(did, name)
63 repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
64 if err != nil {
65 fail(xrpcerr.GenericError(err))
66 return
67 }
68
69 err = git.Fork(repoPath, source)
70 if err != nil {
71 l.Error("forking repo", "error", err.Error())
72 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
73 return
74 }
75
76 // add perms for this user to access the repo
77 err = x.Enforcer.AddRepo(did, rbac.ThisServer, relativeRepoPath)
78 if err != nil {
79 l.Error("adding repo permissions", "error", err.Error())
80 writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
81 return
82 }
83
84 hook.SetupRepo(
85 hook.Config(
86 hook.WithScanPath(x.Config.Repo.ScanPath),
87 hook.WithInternalApi(x.Config.Server.InternalListenAddr),
88 ),
89 repoPath,
90 )
91
92 w.WriteHeader(http.StatusOK)
93}