+1
api/tangled/tangledrepo.go
+1
api/tangled/tangledrepo.go
···
19
19
type Repo struct {
20
20
LexiconTypeID string `json:"$type,const=sh.tangled.repo" cborgen:"$type,const=sh.tangled.repo"`
21
21
AddedAt *string `json:"addedAt,omitempty" cborgen:"addedAt,omitempty"`
22
+
Description *string `json:"description,omitempty" cborgen:"description,omitempty"`
22
23
// knot: knot where the repo was created
23
24
Knot string `json:"knot" cborgen:"knot"`
24
25
// name: name of the repo
+1
-1
appview/auth/auth.go
+1
-1
appview/auth/auth.go
···
140
140
clientSession.Values[appview.SessionPds] = pdsEndpoint
141
141
clientSession.Values[appview.SessionAccessJwt] = atSessionish.GetAccessJwt()
142
142
clientSession.Values[appview.SessionRefreshJwt] = atSessionish.GetRefreshJwt()
143
-
clientSession.Values[appview.SessionExpiry] = time.Now().Add(time.Second * 5).Format(time.RFC3339)
143
+
clientSession.Values[appview.SessionExpiry] = time.Now().Add(time.Minute * 15).Format(time.RFC3339)
144
144
clientSession.Values[appview.SessionAuthenticated] = true
145
145
return clientSession.Save(r, w)
146
146
}
+53
-18
appview/db/repos.go
+53
-18
appview/db/repos.go
···
6
6
)
7
7
8
8
type Repo struct {
9
-
Did string
10
-
Name string
11
-
Knot string
12
-
Rkey string
13
-
Created time.Time
14
-
AtUri string
9
+
Did string
10
+
Name string
11
+
Knot string
12
+
Rkey string
13
+
Created time.Time
14
+
AtUri string
15
+
Description string
15
16
}
16
17
17
18
func GetAllRepos(e Execer, limit int) ([]Repo, error) {
18
19
var repos []Repo
19
20
20
21
rows, err := e.Query(
21
-
`select did, name, knot, rkey, created
22
+
`select did, name, knot, rkey, description, created
22
23
from repos
23
24
order by created desc
24
25
limit ?
···
32
33
33
34
for rows.Next() {
34
35
var repo Repo
35
-
err := scanRepo(rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Created)
36
+
err := scanRepo(
37
+
rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created,
38
+
)
36
39
if err != nil {
37
40
return nil, err
38
41
}
···
49
52
func GetAllReposByDid(e Execer, did string) ([]Repo, error) {
50
53
var repos []Repo
51
54
52
-
rows, err := e.Query(`select did, name, knot, rkey, created from repos where did = ?`, did)
55
+
rows, err := e.Query(`select did, name, knot, rkey, description, created from repos where did = ?`, did)
53
56
if err != nil {
54
57
return nil, err
55
58
}
···
57
60
58
61
for rows.Next() {
59
62
var repo Repo
60
-
err := scanRepo(rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Created)
63
+
err := scanRepo(rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created)
61
64
if err != nil {
62
65
return nil, err
63
66
}
···
73
76
74
77
func GetRepo(e Execer, did, name string) (*Repo, error) {
75
78
var repo Repo
79
+
var nullableDescription sql.NullString
76
80
77
-
row := e.QueryRow(`select did, name, knot, created, at_uri from repos where did = ? and name = ?`, did, name)
81
+
row := e.QueryRow(`select did, name, knot, created, at_uri, description from repos where did = ? and name = ?`, did, name)
78
82
79
83
var createdAt string
80
-
if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri); err != nil {
84
+
if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil {
81
85
return nil, err
82
86
}
83
87
createdAtTime, _ := time.Parse(time.RFC3339, createdAt)
84
88
repo.Created = createdAtTime
85
89
90
+
if nullableDescription.Valid {
91
+
repo.Description = nullableDescription.String
92
+
} else {
93
+
repo.Description = ""
94
+
}
95
+
86
96
return &repo, nil
87
97
}
88
98
89
99
func GetRepoByAtUri(e Execer, atUri string) (*Repo, error) {
90
100
var repo Repo
101
+
var nullableDescription sql.NullString
91
102
92
-
row := e.QueryRow(`select did, name, knot, created, at_uri from repos where at_uri = ?`, atUri)
103
+
row := e.QueryRow(`select did, name, knot, created, at_uri, description from repos where at_uri = ?`, atUri)
93
104
94
105
var createdAt string
95
-
if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri); err != nil {
106
+
if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil {
96
107
return nil, err
97
108
}
98
109
createdAtTime, _ := time.Parse(time.RFC3339, createdAt)
99
110
repo.Created = createdAtTime
111
+
112
+
if nullableDescription.Valid {
113
+
repo.Description = nullableDescription.String
114
+
} else {
115
+
repo.Description = ""
116
+
}
100
117
101
118
return &repo, nil
102
119
}
103
120
104
121
func AddRepo(e Execer, repo *Repo) error {
105
-
_, err := e.Exec(`insert into repos (did, name, knot, rkey, at_uri) values (?, ?, ?, ?, ?)`, repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.AtUri)
122
+
_, err := e.Exec(
123
+
`insert into repos
124
+
(did, name, knot, rkey, at_uri, description)
125
+
values (?, ?, ?, ?, ?, ?)`,
126
+
repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.AtUri, repo.Description,
127
+
)
106
128
return err
107
129
}
108
130
···
119
141
return err
120
142
}
121
143
144
+
func UpdateDescription(e Execer, repoAt, newDescription string) error {
145
+
_, err := e.Exec(
146
+
`update repos set description = ? where at_uri = ?`, newDescription, repoAt)
147
+
return err
148
+
}
149
+
122
150
func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) {
123
151
var repos []Repo
124
152
···
130
158
131
159
for rows.Next() {
132
160
var repo Repo
133
-
err := scanRepo(rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Created)
161
+
err := scanRepo(rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created)
134
162
if err != nil {
135
163
return nil, err
136
164
}
···
149
177
IssueCount IssueCount
150
178
}
151
179
152
-
func scanRepo(rows *sql.Rows, did, name, knot, rkey *string, created *time.Time) error {
180
+
func scanRepo(rows *sql.Rows, did, name, knot, rkey, description *string, created *time.Time) error {
153
181
var createdAt string
154
-
if err := rows.Scan(did, name, knot, rkey, &createdAt); err != nil {
182
+
var nullableDescription sql.NullString
183
+
if err := rows.Scan(did, name, knot, rkey, &nullableDescription, &createdAt); err != nil {
155
184
return err
185
+
}
186
+
187
+
if nullableDescription.Valid {
188
+
*description = nullableDescription.String
189
+
} else {
190
+
*description = ""
156
191
}
157
192
158
193
createdAtTime, err := time.Parse(time.RFC3339, createdAt)
+35
-10
appview/pages/pages.go
+35
-10
appview/pages/pages.go
···
11
11
"net/http"
12
12
"path"
13
13
"path/filepath"
14
+
"slices"
14
15
"strings"
15
16
16
17
"github.com/alecthomas/chroma/v2"
···
194
195
return p.executePlain("fragments/star", w, params)
195
196
}
196
197
198
+
type RepoDescriptionParams struct {
199
+
RepoInfo RepoInfo
200
+
}
201
+
202
+
func (p *Pages) EditRepoDescriptionFragment(w io.Writer, params RepoDescriptionParams) error {
203
+
return p.executePlain("fragments/editRepoDescription", w, params)
204
+
}
205
+
206
+
func (p *Pages) RepoDescriptionFragment(w io.Writer, params RepoDescriptionParams) error {
207
+
return p.executePlain("fragments/repoDescription", w, params)
208
+
}
209
+
197
210
type RepoInfo struct {
198
-
Name string
199
-
OwnerDid string
200
-
OwnerHandle string
201
-
Description string
202
-
Knot string
203
-
RepoAt syntax.ATURI
204
-
SettingsAllowed bool
205
-
IsStarred bool
206
-
Stats db.RepoStats
211
+
Name string
212
+
OwnerDid string
213
+
OwnerHandle string
214
+
Description string
215
+
Knot string
216
+
RepoAt syntax.ATURI
217
+
IsStarred bool
218
+
Stats db.RepoStats
219
+
Roles RolesInRepo
220
+
}
221
+
222
+
type RolesInRepo struct {
223
+
Roles []string
224
+
}
225
+
226
+
func (r RolesInRepo) SettingsAllowed() bool {
227
+
return slices.Contains(r.Roles, "repo:settings")
228
+
}
229
+
230
+
func (r RolesInRepo) IsOwner() bool {
231
+
return slices.Contains(r.Roles, "repo:owner")
207
232
}
208
233
209
234
func (r RepoInfo) OwnerWithAt() string {
···
225
250
{"pulls", "/pulls"},
226
251
}
227
252
228
-
if r.SettingsAllowed {
253
+
if r.Roles.SettingsAllowed() {
229
254
tabs = append(tabs, []string{"settings", "/settings"})
230
255
}
231
256
+9
appview/pages/templates/fragments/editRepoDescription.html
+9
appview/pages/templates/fragments/editRepoDescription.html
···
1
+
{{ define "fragments/editRepoDescription" }}
2
+
<form hx-put="/{{ .RepoInfo.FullName }}/description" hx-target="this" hx-swap="outerHTML">
3
+
<input type="text" name="description" value="{{ .RepoInfo.Description }}" class="input">
4
+
<button type="submit" class="btn">save</button>
5
+
<button type="button" class="btn" hx-get="/{{ .RepoInfo.FullName }}/description" >
6
+
cancel
7
+
</button>
8
+
</form>
9
+
{{ end }}
+15
appview/pages/templates/fragments/repoDescription.html
+15
appview/pages/templates/fragments/repoDescription.html
···
1
+
{{ define "fragments/repoDescription" }}
2
+
<span id="repo-description" hx-target="this" hx-swap="outerHTML">
3
+
{{ if .RepoInfo.Description }}
4
+
{{ .RepoInfo.Description }}
5
+
{{ else }}
6
+
<span class="italic">this repo has no description</span>
7
+
{{ end }}
8
+
9
+
{{ if .RepoInfo.Roles.IsOwner }}
10
+
<button class="btn" hx-get="/{{ .RepoInfo.FullName }}/description/edit">
11
+
edit
12
+
</button>
13
+
{{ end }}
14
+
</span>
15
+
{{ end }}
+1
-7
appview/pages/templates/layouts/repobase.html
+1
-7
appview/pages/templates/layouts/repobase.html
···
10
10
</p>
11
11
{{ template "fragments/star" .RepoInfo }}
12
12
</div>
13
-
<span>
14
-
{{ if .RepoInfo.Description }}
15
-
{{ .RepoInfo.Description }}
16
-
{{ else }}
17
-
<span class="italic">this repo has no description</span>
18
-
{{ end }}
19
-
</span>
13
+
{{ template "fragments/repoDescription" . }}
20
14
</section>
21
15
<section id="repo-links" class="min-h-screen flex flex-col drop-shadow-sm">
22
16
<nav class="w-full mx-auto ml-4">
+9
-1
appview/pages/templates/repo/new.html
+9
-1
appview/pages/templates/repo/new.html
···
17
17
/>
18
18
<p class="text-sm text-gray-500">All repositories are publicly visible.</p>
19
19
20
-
<label for="name" class="block uppercase font-bold text-sm">Default branch</label>
20
+
<label for="branch" class="block uppercase font-bold text-sm">Default branch</label>
21
21
<input
22
22
type="text"
23
23
id="branch"
24
24
name="branch"
25
25
value="main"
26
26
required
27
+
class="w-full max-w-md"
28
+
/>
29
+
30
+
<label for="description" class="block uppercase font-bold text-sm">Description</label>
31
+
<input
32
+
type="text"
33
+
id="description"
34
+
name="description"
27
35
class="w-full max-w-md"
28
36
/>
29
37
</div>
+2
appview/state/middleware.go
+2
appview/state/middleware.go
···
199
199
200
200
ctx := context.WithValue(req.Context(), "knot", repo.Knot)
201
201
ctx = context.WithValue(ctx, "repoAt", repo.AtUri)
202
+
ctx = context.WithValue(ctx, "repoDescription", repo.Description)
203
+
ctx = context.WithValue(ctx, "repoAddedAt", repo.Created.Format(time.RFC3339))
202
204
next.ServeHTTP(w, req.WithContext(ctx))
203
205
})
204
206
}
+124
-25
appview/state/repo.go
+124
-25
appview/state/repo.go
···
130
130
return
131
131
}
132
132
133
+
func (s *State) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
134
+
f, err := fullyResolvedRepo(r)
135
+
if err != nil {
136
+
log.Println("failed to get repo and knot", err)
137
+
w.WriteHeader(http.StatusBadRequest)
138
+
return
139
+
}
140
+
141
+
user := s.auth.GetUser(r)
142
+
s.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{
143
+
RepoInfo: f.RepoInfo(s, user),
144
+
})
145
+
return
146
+
}
147
+
148
+
func (s *State) RepoDescription(w http.ResponseWriter, r *http.Request) {
149
+
f, err := fullyResolvedRepo(r)
150
+
if err != nil {
151
+
log.Println("failed to get repo and knot", err)
152
+
w.WriteHeader(http.StatusBadRequest)
153
+
return
154
+
}
155
+
156
+
repoAt := f.RepoAt
157
+
rkey := repoAt.RecordKey().String()
158
+
if rkey == "" {
159
+
log.Println("invalid aturi for repo", err)
160
+
w.WriteHeader(http.StatusInternalServerError)
161
+
return
162
+
}
163
+
164
+
user := s.auth.GetUser(r)
165
+
166
+
switch r.Method {
167
+
case http.MethodGet:
168
+
s.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{
169
+
RepoInfo: f.RepoInfo(s, user),
170
+
})
171
+
return
172
+
case http.MethodPut:
173
+
user := s.auth.GetUser(r)
174
+
newDescription := r.FormValue("description")
175
+
client, _ := s.auth.AuthorizedClient(r)
176
+
177
+
// optimistic update
178
+
err = db.UpdateDescription(s.db, string(repoAt), newDescription)
179
+
if err != nil {
180
+
log.Println("failed to perferom update-description query", err)
181
+
s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
182
+
return
183
+
}
184
+
185
+
// this is a bit of a pain because the golang atproto impl does not allow nil SwapRecord field
186
+
//
187
+
// SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests
188
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoNSID, user.Did, rkey)
189
+
if err != nil {
190
+
// failed to get record
191
+
s.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.")
192
+
return
193
+
}
194
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
195
+
Collection: tangled.RepoNSID,
196
+
Repo: user.Did,
197
+
Rkey: rkey,
198
+
SwapRecord: ex.Cid,
199
+
Record: &lexutil.LexiconTypeDecoder{
200
+
Val: &tangled.Repo{
201
+
Knot: f.Knot,
202
+
Name: f.RepoName,
203
+
Owner: user.Did,
204
+
AddedAt: &f.AddedAt,
205
+
Description: &newDescription,
206
+
},
207
+
},
208
+
})
209
+
210
+
if err != nil {
211
+
log.Println("failed to perferom update-description query", err)
212
+
// failed to get record
213
+
s.pages.Notice(w, "repo-notice", "Failed to update description, unable to save to PDS.")
214
+
return
215
+
}
216
+
217
+
newRepoInfo := f.RepoInfo(s, user)
218
+
newRepoInfo.Description = newDescription
219
+
220
+
s.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{
221
+
RepoInfo: newRepoInfo,
222
+
})
223
+
return
224
+
}
225
+
}
226
+
133
227
func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) {
134
228
f, err := fullyResolvedRepo(r)
135
229
if err != nil {
···
457
551
}
458
552
459
553
type FullyResolvedRepo struct {
460
-
Knot string
461
-
OwnerId identity.Identity
462
-
RepoName string
463
-
RepoAt syntax.ATURI
554
+
Knot string
555
+
OwnerId identity.Identity
556
+
RepoName string
557
+
RepoAt syntax.ATURI
558
+
Description string
559
+
AddedAt string
464
560
}
465
561
466
562
func (f *FullyResolvedRepo) OwnerDid() string {
···
541
637
}
542
638
543
639
return pages.RepoInfo{
544
-
OwnerDid: f.OwnerDid(),
545
-
OwnerHandle: f.OwnerHandle(),
546
-
Name: f.RepoName,
547
-
RepoAt: f.RepoAt,
548
-
SettingsAllowed: settingsAllowed(s, u, f),
549
-
IsStarred: isStarred,
550
-
Knot: knot,
640
+
OwnerDid: f.OwnerDid(),
641
+
OwnerHandle: f.OwnerHandle(),
642
+
Name: f.RepoName,
643
+
RepoAt: f.RepoAt,
644
+
Description: f.Description,
645
+
IsStarred: isStarred,
646
+
Knot: knot,
647
+
Roles: rolesInRepo(s, u, f),
551
648
Stats: db.RepoStats{
552
649
StarCount: starCount,
553
650
IssueCount: issueCount,
···
953
1050
return nil, fmt.Errorf("malformed middleware")
954
1051
}
955
1052
1053
+
// pass through values from the middleware
1054
+
description, ok := r.Context().Value("repoDescription").(string)
1055
+
addedAt, ok := r.Context().Value("repoAddedAt").(string)
1056
+
956
1057
return &FullyResolvedRepo{
957
-
Knot: knot,
958
-
OwnerId: id,
959
-
RepoName: repoName,
960
-
RepoAt: parsedRepoAt,
1058
+
Knot: knot,
1059
+
OwnerId: id,
1060
+
RepoName: repoName,
1061
+
RepoAt: parsedRepoAt,
1062
+
Description: description,
1063
+
AddedAt: addedAt,
961
1064
}, nil
962
1065
}
963
1066
964
-
func settingsAllowed(s *State, u *auth.User, f *FullyResolvedRepo) bool {
965
-
settingsAllowed := false
1067
+
func rolesInRepo(s *State, u *auth.User, f *FullyResolvedRepo) pages.RolesInRepo {
966
1068
if u != nil {
967
-
ok, err := s.enforcer.IsSettingsAllowed(u.Did, f.Knot, f.OwnerSlashRepo())
968
-
if err == nil && ok {
969
-
settingsAllowed = true
970
-
} else {
971
-
log.Println(err, ok)
972
-
}
1069
+
r := s.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.OwnerSlashRepo())
1070
+
log.Println(r)
1071
+
return pages.RolesInRepo{r}
1072
+
} else {
1073
+
return pages.RolesInRepo{}
973
1074
}
974
-
975
-
return settingsAllowed
976
1075
}
+22
-5
appview/state/signer.go
+22
-5
appview/state/signer.go
···
69
69
Endpoint = "/init"
70
70
)
71
71
72
-
body, _ := json.Marshal(map[string]interface{}{
72
+
body, _ := json.Marshal(map[string]any{
73
73
"did": did,
74
74
})
75
75
···
87
87
Endpoint = "/repo/new"
88
88
)
89
89
90
-
body, _ := json.Marshal(map[string]interface{}{
90
+
body, _ := json.Marshal(map[string]any{
91
91
"did": did,
92
92
"name": repoName,
93
93
"default_branch": defaultBranch,
94
94
})
95
95
96
-
fmt.Println(body)
96
+
req, err := s.newRequest(Method, Endpoint, body)
97
+
if err != nil {
98
+
return nil, err
99
+
}
100
+
101
+
return s.client.Do(req)
102
+
}
103
+
104
+
func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) {
105
+
const (
106
+
Method = "DELETE"
107
+
Endpoint = "/repo"
108
+
)
109
+
110
+
body, _ := json.Marshal(map[string]any{
111
+
"did": did,
112
+
"name": repoName,
113
+
})
97
114
98
115
req, err := s.newRequest(Method, Endpoint, body)
99
116
if err != nil {
···
109
126
Endpoint = "/member/add"
110
127
)
111
128
112
-
body, _ := json.Marshal(map[string]interface{}{
129
+
body, _ := json.Marshal(map[string]any{
113
130
"did": did,
114
131
})
115
132
···
127
144
)
128
145
endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
129
146
130
-
body, _ := json.Marshal(map[string]interface{}{
147
+
body, _ := json.Marshal(map[string]any{
131
148
"did": memberDid,
132
149
})
133
150
+14
-4
appview/state/state.go
+14
-4
appview/state/state.go
···
581
581
defaultBranch = "main"
582
582
}
583
583
584
+
description := r.FormValue("description")
585
+
584
586
ok, err := s.enforcer.E.Enforce(user.Did, domain, domain, "repo:create")
585
587
if err != nil || !ok {
586
588
s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
···
607
609
608
610
rkey := s.TID()
609
611
repo := &db.Repo{
610
-
Did: user.Did,
611
-
Name: repoName,
612
-
Knot: domain,
613
-
Rkey: rkey,
612
+
Did: user.Did,
613
+
Name: repoName,
614
+
Knot: domain,
615
+
Rkey: rkey,
616
+
Description: description,
614
617
}
615
618
616
619
xrpcClient, _ := s.auth.AuthorizedClient(r)
···
647
650
if err != nil {
648
651
log.Println("failed to rollback policies")
649
652
}
653
+
client.RemoveRepo(user.Did, repoName)
650
654
}()
651
655
652
656
resp, err := client.NewRepo(user.Did, repoName, defaultBranch)
···
875
879
// settings routes, needs auth
876
880
r.Group(func(r chi.Router) {
877
881
r.Use(AuthMiddleware(s))
882
+
// repo description can only be edited by owner
883
+
r.With(RepoPermissionMiddleware(s, "repo:owner")).Route("/description", func(r chi.Router) {
884
+
r.Put("/", s.RepoDescription)
885
+
r.Get("/", s.RepoDescription)
886
+
r.Get("/edit", s.RepoDescriptionEdit)
887
+
})
878
888
r.With(RepoPermissionMiddleware(s, "repo:settings")).Route("/settings", func(r chi.Router) {
879
889
r.Get("/", s.RepoSettings)
880
890
r.With(RepoPermissionMiddleware(s, "repo:invite")).Put("/collaborator", s.AddCollaborator)
+1
knotserver/handler.go
+1
knotserver/handler.go
+35
knotserver/routes.go
+35
knotserver/routes.go
···
10
10
"fmt"
11
11
"log"
12
12
"net/http"
13
+
"os"
13
14
"path/filepath"
14
15
"strconv"
15
16
"strings"
···
514
515
err = h.e.AddRepo(did, ThisServer, relativeRepoPath)
515
516
if err != nil {
516
517
l.Error("adding repo permissions", "error", err.Error())
518
+
writeError(w, err.Error(), http.StatusInternalServerError)
519
+
return
520
+
}
521
+
522
+
w.WriteHeader(http.StatusNoContent)
523
+
}
524
+
525
+
func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) {
526
+
l := h.l.With("handler", "RemoveRepo")
527
+
528
+
data := struct {
529
+
Did string `json:"did"`
530
+
Name string `json:"name"`
531
+
}{}
532
+
533
+
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
534
+
writeError(w, "invalid request body", http.StatusBadRequest)
535
+
return
536
+
}
537
+
538
+
did := data.Did
539
+
name := data.Name
540
+
541
+
if did == "" || name == "" {
542
+
l.Error("invalid request body, empty did or name")
543
+
w.WriteHeader(http.StatusBadRequest)
544
+
return
545
+
}
546
+
547
+
relativeRepoPath := filepath.Join(did, name)
548
+
repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
549
+
err := os.RemoveAll(repoPath)
550
+
if err != nil {
551
+
l.Error("removing repo", "error", err.Error())
517
552
writeError(w, err.Error(), http.StatusInternalServerError)
518
553
return
519
554
}
+6
lexicons/repo.json
+6
lexicons/repo.json
+14
rbac/rbac.go
+14
rbac/rbac.go
···
165
165
return e.E.Enforce(user, domain, repo, "repo:settings")
166
166
}
167
167
168
+
// given a repo, what permissions does this user have? repo:owner? repo:invite? etc.
169
+
func (e *Enforcer) GetPermissionsInRepo(user, domain, repo string) []string {
170
+
var permissions []string
171
+
res := e.E.GetPermissionsForUserInDomain(user, domain)
172
+
for _, p := range res {
173
+
// get only permissions for this resource/repo
174
+
if p[2] == repo {
175
+
permissions = append(permissions, p[3])
176
+
}
177
+
}
178
+
179
+
return permissions
180
+
}
181
+
168
182
func (e *Enforcer) IsCollaboratorInviteAllowed(user, domain, repo string) (bool, error) {
169
183
return e.E.Enforce(user, domain, repo, "repo:invite")
170
184
}