this repo has no description
1package repo 2 3import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "slices" 8 "strings" 9 "time" 10 11 "tangled.org/core/api/tangled" 12 "tangled.org/core/appview/db" 13 "tangled.org/core/appview/models" 14 "tangled.org/core/appview/oauth" 15 "tangled.org/core/appview/pages" 16 xrpcclient "tangled.org/core/appview/xrpcclient" 17 "tangled.org/core/orm" 18 "tangled.org/core/types" 19 20 comatproto "github.com/bluesky-social/indigo/api/atproto" 21 lexutil "github.com/bluesky-social/indigo/lex/util" 22 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 23) 24 25func (rp *Repo) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 26 l := rp.logger.With("handler", "SetDefaultBranch") 27 28 f, err := rp.repoResolver.Resolve(r) 29 if err != nil { 30 l.Error("failed to get repo and knot", "err", err) 31 return 32 } 33 34 noticeId := "operation-error" 35 branch := r.FormValue("branch") 36 if branch == "" { 37 http.Error(w, "malformed form", http.StatusBadRequest) 38 return 39 } 40 41 client, err := rp.oauth.ServiceClient( 42 r, 43 oauth.WithService(f.Knot), 44 oauth.WithLxm(tangled.RepoSetDefaultBranchNSID), 45 oauth.WithDev(rp.config.Core.Dev), 46 ) 47 if err != nil { 48 l.Error("failed to connect to knot server", "err", err) 49 rp.pages.Notice(w, noticeId, "Failed to connect to knot server.") 50 return 51 } 52 53 xe := tangled.RepoSetDefaultBranch( 54 r.Context(), 55 client, 56 &tangled.RepoSetDefaultBranch_Input{ 57 Repo: f.RepoAt().String(), 58 DefaultBranch: branch, 59 }, 60 ) 61 if err := xrpcclient.HandleXrpcErr(xe); err != nil { 62 l.Error("xrpc failed", "err", xe) 63 rp.pages.Notice(w, noticeId, err.Error()) 64 return 65 } 66 67 rp.pages.HxRefresh(w) 68} 69 70func (rp *Repo) Secrets(w http.ResponseWriter, r *http.Request) { 71 user := rp.oauth.GetUser(r) 72 l := rp.logger.With("handler", "Secrets") 73 l = l.With("did", user.Did) 74 75 f, err := rp.repoResolver.Resolve(r) 76 if err != nil { 77 l.Error("failed to get repo and knot", "err", err) 78 return 79 } 80 81 if f.Spindle == "" { 82 l.Error("empty spindle cannot add/rm secret", "err", err) 83 return 84 } 85 86 lxm := tangled.RepoAddSecretNSID 87 if r.Method == http.MethodDelete { 88 lxm = tangled.RepoRemoveSecretNSID 89 } 90 91 spindleClient, err := rp.oauth.ServiceClient( 92 r, 93 oauth.WithService(f.Spindle), 94 oauth.WithLxm(lxm), 95 oauth.WithExp(60), 96 oauth.WithDev(rp.config.Core.Dev), 97 ) 98 if err != nil { 99 l.Error("failed to create spindle client", "err", err) 100 return 101 } 102 103 key := r.FormValue("key") 104 if key == "" { 105 w.WriteHeader(http.StatusBadRequest) 106 return 107 } 108 109 switch r.Method { 110 case http.MethodPut: 111 errorId := "add-secret-error" 112 113 value := r.FormValue("value") 114 if value == "" { 115 w.WriteHeader(http.StatusBadRequest) 116 return 117 } 118 119 err = tangled.RepoAddSecret( 120 r.Context(), 121 spindleClient, 122 &tangled.RepoAddSecret_Input{ 123 Repo: f.RepoAt().String(), 124 Key: key, 125 Value: value, 126 }, 127 ) 128 if err != nil { 129 l.Error("Failed to add secret.", "err", err) 130 rp.pages.Notice(w, errorId, "Failed to add secret.") 131 return 132 } 133 134 case http.MethodDelete: 135 errorId := "operation-error" 136 137 err = tangled.RepoRemoveSecret( 138 r.Context(), 139 spindleClient, 140 &tangled.RepoRemoveSecret_Input{ 141 Repo: f.RepoAt().String(), 142 Key: key, 143 }, 144 ) 145 if err != nil { 146 l.Error("Failed to delete secret.", "err", err) 147 rp.pages.Notice(w, errorId, "Failed to delete secret.") 148 return 149 } 150 } 151 152 rp.pages.HxRefresh(w) 153} 154 155func (rp *Repo) Settings(w http.ResponseWriter, r *http.Request) { 156 tabVal := r.URL.Query().Get("tab") 157 if tabVal == "" { 158 tabVal = "general" 159 } 160 161 switch tabVal { 162 case "general": 163 rp.generalSettings(w, r) 164 165 case "access": 166 rp.accessSettings(w, r) 167 168 case "pipelines": 169 rp.pipelineSettings(w, r) 170 } 171} 172 173func (rp *Repo) generalSettings(w http.ResponseWriter, r *http.Request) { 174 l := rp.logger.With("handler", "generalSettings") 175 176 f, err := rp.repoResolver.Resolve(r) 177 user := rp.oauth.GetUser(r) 178 179 scheme := "http" 180 if !rp.config.Core.Dev { 181 scheme = "https" 182 } 183 host := fmt.Sprintf("%s://%s", scheme, f.Knot) 184 xrpcc := &indigoxrpc.Client{ 185 Host: host, 186 } 187 188 repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 189 xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 190 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 191 l.Error("failed to call XRPC repo.branches", "err", xrpcerr) 192 rp.pages.Error503(w) 193 return 194 } 195 196 var result types.RepoBranchesResponse 197 if err := json.Unmarshal(xrpcBytes, &result); err != nil { 198 l.Error("failed to decode XRPC response", "err", err) 199 rp.pages.Error503(w) 200 return 201 } 202 203 defaultLabels, err := db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", rp.config.Label.DefaultLabelDefs)) 204 if err != nil { 205 l.Error("failed to fetch labels", "err", err) 206 rp.pages.Error503(w) 207 return 208 } 209 210 labels, err := db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", f.Labels)) 211 if err != nil { 212 l.Error("failed to fetch labels", "err", err) 213 rp.pages.Error503(w) 214 return 215 } 216 // remove default labels from the labels list, if present 217 defaultLabelMap := make(map[string]bool) 218 for _, dl := range defaultLabels { 219 defaultLabelMap[dl.AtUri().String()] = true 220 } 221 n := 0 222 for _, l := range labels { 223 if !defaultLabelMap[l.AtUri().String()] { 224 labels[n] = l 225 n++ 226 } 227 } 228 labels = labels[:n] 229 230 subscribedLabels := make(map[string]struct{}) 231 for _, l := range f.Labels { 232 subscribedLabels[l] = struct{}{} 233 } 234 235 // if there is atleast 1 unsubbed default label, show the "subscribe all" button, 236 // if all default labels are subbed, show the "unsubscribe all" button 237 shouldSubscribeAll := false 238 for _, dl := range defaultLabels { 239 if _, ok := subscribedLabels[dl.AtUri().String()]; !ok { 240 // one of the default labels is not subscribed to 241 shouldSubscribeAll = true 242 break 243 } 244 } 245 246 rp.pages.RepoGeneralSettings(w, pages.RepoGeneralSettingsParams{ 247 LoggedInUser: user, 248 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 249 Branches: result.Branches, 250 Labels: labels, 251 DefaultLabels: defaultLabels, 252 SubscribedLabels: subscribedLabels, 253 ShouldSubscribeAll: shouldSubscribeAll, 254 Tab: "general", 255 }) 256} 257 258func (rp *Repo) accessSettings(w http.ResponseWriter, r *http.Request) { 259 l := rp.logger.With("handler", "accessSettings") 260 261 f, err := rp.repoResolver.Resolve(r) 262 user := rp.oauth.GetUser(r) 263 264 collaborators, err := func(repo *models.Repo) ([]pages.Collaborator, error) { 265 repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(repo.DidSlashRepo(), repo.Knot) 266 if err != nil { 267 return nil, err 268 } 269 var collaborators []pages.Collaborator 270 for _, item := range repoCollaborators { 271 // currently only two roles: owner and member 272 var role string 273 switch item[3] { 274 case "repo:owner": 275 role = "owner" 276 case "repo:collaborator": 277 role = "collaborator" 278 default: 279 continue 280 } 281 282 did := item[0] 283 284 c := pages.Collaborator{ 285 Did: did, 286 Role: role, 287 } 288 collaborators = append(collaborators, c) 289 } 290 return collaborators, nil 291 }(f) 292 if err != nil { 293 l.Error("failed to get collaborators", "err", err) 294 } 295 296 rp.pages.RepoAccessSettings(w, pages.RepoAccessSettingsParams{ 297 LoggedInUser: user, 298 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 299 Tab: "access", 300 Collaborators: collaborators, 301 }) 302} 303 304func (rp *Repo) pipelineSettings(w http.ResponseWriter, r *http.Request) { 305 l := rp.logger.With("handler", "pipelineSettings") 306 307 f, err := rp.repoResolver.Resolve(r) 308 user := rp.oauth.GetUser(r) 309 310 // all spindles that the repo owner is a member of 311 spindles, err := rp.enforcer.GetSpindlesForUser(f.Did) 312 if err != nil { 313 l.Error("failed to fetch spindles", "err", err) 314 return 315 } 316 317 var secrets []*tangled.RepoListSecrets_Secret 318 if f.Spindle != "" { 319 if spindleClient, err := rp.oauth.ServiceClient( 320 r, 321 oauth.WithService(f.Spindle), 322 oauth.WithLxm(tangled.RepoListSecretsNSID), 323 oauth.WithExp(60), 324 oauth.WithDev(rp.config.Core.Dev), 325 ); err != nil { 326 l.Error("failed to create spindle client", "err", err) 327 } else if resp, err := tangled.RepoListSecrets(r.Context(), spindleClient, f.RepoAt().String()); err != nil { 328 l.Error("failed to fetch secrets", "err", err) 329 } else { 330 secrets = resp.Secrets 331 } 332 } 333 334 slices.SortFunc(secrets, func(a, b *tangled.RepoListSecrets_Secret) int { 335 return strings.Compare(a.Key, b.Key) 336 }) 337 338 var dids []string 339 for _, s := range secrets { 340 dids = append(dids, s.CreatedBy) 341 } 342 resolvedIdents := rp.idResolver.ResolveIdents(r.Context(), dids) 343 344 // convert to a more manageable form 345 var niceSecret []map[string]any 346 for id, s := range secrets { 347 when, _ := time.Parse(time.RFC3339, s.CreatedAt) 348 niceSecret = append(niceSecret, map[string]any{ 349 "Id": id, 350 "Key": s.Key, 351 "CreatedAt": when, 352 "CreatedBy": resolvedIdents[id].Handle.String(), 353 }) 354 } 355 356 rp.pages.RepoPipelineSettings(w, pages.RepoPipelineSettingsParams{ 357 LoggedInUser: user, 358 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 359 Tab: "pipelines", 360 Spindles: spindles, 361 CurrentSpindle: f.Spindle, 362 Secrets: niceSecret, 363 }) 364} 365 366func (rp *Repo) EditBaseSettings(w http.ResponseWriter, r *http.Request) { 367 l := rp.logger.With("handler", "EditBaseSettings") 368 369 noticeId := "repo-base-settings-error" 370 371 f, err := rp.repoResolver.Resolve(r) 372 if err != nil { 373 l.Error("failed to get repo and knot", "err", err) 374 w.WriteHeader(http.StatusBadRequest) 375 return 376 } 377 378 client, err := rp.oauth.AuthorizedClient(r) 379 if err != nil { 380 l.Error("failed to get client") 381 rp.pages.Notice(w, noticeId, "Failed to update repository information, try again later.") 382 return 383 } 384 385 var ( 386 description = r.FormValue("description") 387 website = r.FormValue("website") 388 topicStr = r.FormValue("topics") 389 ) 390 391 err = rp.validator.ValidateURI(website) 392 if website != "" && err != nil { 393 l.Error("invalid uri", "err", err) 394 rp.pages.Notice(w, noticeId, err.Error()) 395 return 396 } 397 398 topics, err := rp.validator.ValidateRepoTopicStr(topicStr) 399 if err != nil { 400 l.Error("invalid topics", "err", err) 401 rp.pages.Notice(w, noticeId, err.Error()) 402 return 403 } 404 l.Debug("got", "topicsStr", topicStr, "topics", topics) 405 406 newRepo := *f 407 newRepo.Description = description 408 newRepo.Website = website 409 newRepo.Topics = topics 410 record := newRepo.AsRecord() 411 412 tx, err := rp.db.BeginTx(r.Context(), nil) 413 if err != nil { 414 l.Error("failed to begin transaction", "err", err) 415 rp.pages.Notice(w, noticeId, "Failed to save repository information.") 416 return 417 } 418 defer tx.Rollback() 419 420 err = db.PutRepo(tx, newRepo) 421 if err != nil { 422 l.Error("failed to update repository", "err", err) 423 rp.pages.Notice(w, noticeId, "Failed to save repository information.") 424 return 425 } 426 427 ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoNSID, newRepo.Did, newRepo.Rkey) 428 if err != nil { 429 // failed to get record 430 l.Error("failed to get repo record", "err", err) 431 rp.pages.Notice(w, noticeId, "Failed to save repository information, no record found on PDS.") 432 return 433 } 434 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 435 Collection: tangled.RepoNSID, 436 Repo: newRepo.Did, 437 Rkey: newRepo.Rkey, 438 SwapRecord: ex.Cid, 439 Record: &lexutil.LexiconTypeDecoder{ 440 Val: &record, 441 }, 442 }) 443 444 if err != nil { 445 l.Error("failed to perferom update-repo query", "err", err) 446 // failed to get record 447 rp.pages.Notice(w, noticeId, "Failed to save repository information, unable to save to PDS.") 448 return 449 } 450 451 err = tx.Commit() 452 if err != nil { 453 l.Error("failed to commit", "err", err) 454 } 455 456 rp.pages.HxRefresh(w) 457}