Monorepo for Tangled tangled.org
758
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview: handlers, state, and templates for more record types w/ repoDID #279

open opened by oyster.cafe targeting master from lt/repo-rename-by-rkey

Lewis: May this revision serve well! lewis@tangled.org

appview,knotserver: validate git repo ownership according to knot

Lewis: May this revision serve well! lewis@tangled.org

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3mjm6w2jhaa22
+260 -253
Diff #0
+3 -3
appview/issues/issues.go
··· 885 885 searchOpts := models.IssueSearchOptions{ 886 886 Keywords: tf.Keywords, 887 887 Phrases: tf.Phrases, 888 - RepoAt: f.RepoAt().String(), 888 + RepoDid: f.RepoDid, 889 889 IsOpen: isOpen, 890 890 AuthorDid: authorDid, 891 891 Labels: labels, ··· 945 945 } 946 946 } else { 947 947 filters := []orm.Filter{ 948 - orm.FilterEq("repo_at", f.RepoAt()), 948 + orm.FilterEq("repo_did", f.RepoDid), 949 949 } 950 950 if isOpen != nil { 951 951 openInt := 0 ··· 1039 1039 mentions, references := rp.mentionsResolver.Resolve(r.Context(), body) 1040 1040 1041 1041 issue := &models.Issue{ 1042 - RepoAt: f.RepoAt(), 1042 + RepoDid: syntax.DID(f.RepoDid), 1043 1043 Rkey: tid.TID(), 1044 1044 Title: r.FormValue("title"), 1045 1045 Body: body,
+1 -1
appview/labels/labels.go
··· 100 100 } 101 101 102 102 // find all the labels that this repo subscribes to 103 - repoLabels, err := db.GetRepoLabels(l.db, orm.FilterEq("repo_at", repoAt)) 103 + repoLabels, err := db.GetRepoLabels(l.db, orm.FilterEq("repo_did", repo.RepoDid)) 104 104 if err != nil { 105 105 fail("Failed to get labels for this repository.", err) 106 106 return
+28 -5
appview/middleware/middleware.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "database/sql" 6 + "errors" 5 7 "fmt" 6 8 "log/slog" 7 9 "net/http" ··· 234 236 l := mw.logger.With("middleware", "ResolveRepo") 235 237 repoName := chi.URLParam(req, "repo") 236 238 repoName = strings.TrimSuffix(repoName, ".git") 239 + rkey := strings.ToLower(repoName) 237 240 238 241 id, ok := req.Context().Value("resolvedId").(identity.Identity) 239 242 if !ok { ··· 245 248 repo, err := db.GetRepo( 246 249 mw.db, 247 250 orm.FilterEq("did", id.DID.String()), 248 - orm.FilterEq("name", repoName), 251 + orm.FilterEq("rkey", rkey), 249 252 ) 250 253 if err != nil { 251 - l.Error("failed to resolve repo", "err", err) 254 + if !errors.Is(err, sql.ErrNoRows) { 255 + l.Error("failed to resolve repo", "err", err) 256 + http.Error(w, "internal server error", http.StatusInternalServerError) 257 + return 258 + } 259 + hint, hintErr := db.LookupRepoRename(mw.db, id.DID.String(), rkey) 260 + if hintErr != nil && !errors.Is(hintErr, sql.ErrNoRows) { 261 + l.Error("failed to lookup repo rename hint", "err", hintErr) 262 + } 263 + if hint != nil { 264 + parts := strings.SplitN(strings.TrimPrefix(req.URL.Path, "/"), "/", 3) 265 + target := "/" + parts[0] + "/" + hint.Rkey 266 + if len(parts) == 3 { 267 + target += "/" + parts[2] 268 + } 269 + if req.URL.RawQuery != "" { 270 + target += "?" + req.URL.RawQuery 271 + } 272 + http.Redirect(w, req, target, http.StatusMovedPermanently) 273 + return 274 + } 252 275 w.WriteHeader(http.StatusNotFound) 253 276 mw.pages.ErrorKnot404(w) 254 277 return ··· 281 304 return 282 305 } 283 306 284 - pr, err := db.GetPull(mw.db, orm.FilterEq("repo_at", f.RepoAt()), orm.FilterEq("pull_id", prIdInt)) 307 + pr, err := db.GetPull(mw.db, orm.FilterEq("repo_did", f.RepoDid), orm.FilterEq("pull_id", prIdInt)) 285 308 if err != nil { 286 309 l.Error("failed to get pull and comments", "err", err) 287 310 mw.pages.Error404(w) ··· 324 347 return 325 348 } 326 349 327 - issue, err := db.GetIssue(mw.db, f.RepoAt(), issueId) 350 + issue, err := db.GetIssue(mw.db, f.RepoDid, issueId) 328 351 if err != nil { 329 352 l.Error("failed to get issues", "err", err) 330 353 mw.pages.Error404(w) ··· 360 383 if r.URL.Query().Get("go-get") == "1" { 361 384 modulePath := userutil.FlattenDid(fullName) 362 385 if strings.Contains(modulePath, ":") { 363 - modulePath = userutil.FlattenDid(f.Did) + "/" + f.Name 386 + modulePath = userutil.FlattenDid(f.Did) + "/" + f.Rkey 364 387 } 365 388 html := fmt.Sprintf( 366 389 `<meta name="go-import" content="tangled.sh/%s git https://tangled.sh/%s"/>
+5
appview/models/repo.go
··· 136 136 return fmt.Errorf("Repository name must be 100 characters or fewer") 137 137 } 138 138 139 + // check for path traversal attempts 139 140 if strings.Contains(name, "/") || strings.Contains(name, "\\") { 140 141 return fmt.Errorf("Repository name contains invalid path characters") 141 142 } 142 143 144 + // check for sequences that could be used for traversal when normalized 143 145 if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { 144 146 return fmt.Errorf("Repository name contains invalid path sequence") 145 147 } 146 148 149 + // then continue with character validation 147 150 for _, char := range name { 148 151 if !((char >= 'a' && char <= 'z') || 149 152 (char >= 'A' && char <= 'Z') || ··· 153 156 } 154 157 } 155 158 159 + // additional check to prevent multiple sequential dots 156 160 if strings.Contains(name, "..") { 157 161 return fmt.Errorf("Repository name cannot contain sequential dots") 158 162 } ··· 161 165 return fmt.Errorf("Repository name %q is reserved", name) 162 166 } 163 167 168 + // if all checks pass 164 169 return nil 165 170 } 166 171
+3 -2
appview/pages/repoinfo/repoinfo.go
··· 20 20 } 21 21 22 22 func (r RepoInfo) FullName() string { 23 - return path.Join(r.owner(), r.Name) 23 + return path.Join(r.owner(), r.Rkey) 24 24 } 25 25 26 26 func (r RepoInfo) ownerWithoutAt() string { ··· 32 32 } 33 33 34 34 func (r RepoInfo) FullNameWithoutAt() string { 35 - return path.Join(r.ownerWithoutAt(), r.Name) 35 + return path.Join(r.ownerWithoutAt(), r.Rkey) 36 36 } 37 37 38 38 func (r RepoInfo) GetTabs() [][]string { ··· 59 59 Rkey string 60 60 OwnerDid string 61 61 OwnerHandle string 62 + RepoDid string 62 63 Description string 63 64 Website string 64 65 Topics []string
+2 -2
appview/pages/templates/goodfirstissues/index.html
··· 46 46 {{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }} 47 47 {{ end }} 48 48 {{ $repoOwner := resolve .Repo.Did }} 49 - <a href="/{{ $repoOwner }}/{{ .Repo.Name }}" class="truncate min-w-0">{{ $repoOwner }}/{{ .Repo.Name }}</a> 49 + <a href="/{{ $repoOwner }}/{{ .Repo.Rkey }}" class="truncate min-w-0">{{ $repoOwner }}/{{ .Repo.Name }}</a> 50 50 </div> 51 51 </div> 52 52 ··· 90 90 {{ if gt (len .Issues) 0 }} 91 91 <div class="grid grid-cols-1 rounded-b border-b border-t border-gray-200 dark:border-gray-900 divide-y divide-gray-200 dark:divide-gray-900"> 92 92 {{ range .Issues }} 93 - <a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .IssueId }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 93 + <a href="/{{ resolve .Repo.Did }}/{{ .Repo.Rkey }}/issues/{{ .IssueId }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 94 94 <div class="py-2 px-6"> 95 95 <div class="flex-grow min-w-0 w-full"> 96 96 <div class="flex text-sm items-center justify-between w-full">
+1 -1
appview/pages/templates/knots/dashboard.html
··· 77 77 {{ range $repos }} 78 78 <div class="flex gap-2 items-center"> 79 79 {{ i "book-marked" "size-4" }} 80 - <a href="/{{ resolve .Did }}/{{ .Name }}"> 80 + <a href="/{{ resolve .Did }}/{{ .Rkey }}"> 81 81 {{ .Name }} 82 82 </a> 83 83 </div>
+1 -1
appview/pages/templates/layouts/repobase.html
··· 78 78 <div class="flex items-center gap-1 text-sm text-gray-600 dark:text-gray-300 mb-2 flex-wrap"> 79 79 {{ i "git-fork" "w-3 h-3 shrink-0" }} 80 80 <span>forked from</span> 81 - <a class="underline" href="/{{ $sourceOwner }}/{{ .RepoInfo.Source.Name }}"> 81 + <a class="underline" href="/{{ $sourceOwner }}/{{ .RepoInfo.Source.Rkey }}"> 82 82 {{ $sourceOwner }}/{{ .RepoInfo.Source.Name }} 83 83 </a> 84 84 </div>
+3 -3
appview/pages/templates/notifications/fragments/item.html
··· 76 76 {{ define "notificationUrl" }} 77 77 {{ $url := "" }} 78 78 {{ if eq .Type "repo_starred" }} 79 - {{$url = printf "/%s/%s" (resolve .Repo.Did) .Repo.Name}} 79 + {{$url = printf "/%s/%s" (resolve .Repo.Did) .Repo.Rkey}} 80 80 {{ else if .Issue }} 81 - {{$url = printf "/%s/%s/issues/%d" (resolve .Repo.Did) .Repo.Name .Issue.IssueId}} 81 + {{$url = printf "/%s/%s/issues/%d" (resolve .Repo.Did) .Repo.Rkey .Issue.IssueId}} 82 82 {{ else if .Pull }} 83 - {{$url = printf "/%s/%s/pulls/%d" (resolve .Repo.Did) .Repo.Name .Pull.PullId}} 83 + {{$url = printf "/%s/%s/pulls/%d" (resolve .Repo.Did) .Repo.Rkey .Pull.PullId}} 84 84 {{ else if eq .Type "followed" }} 85 85 {{$url = printf "/%s" (resolve .ActorDid)}} 86 86 {{ else }}
+1 -1
appview/pages/templates/repo/empty.html
··· 35 35 36 36 <p><span class="{{$bullet}}">1</span>First, generate a new <a href="https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key" class="underline">SSH key pair</a>.</p> 37 37 <p><span class="{{$bullet}}">2</span>Then add the public key to your account from the <a href="/settings/keys" class="underline">keys page</a> in your settings.</p> 38 - <p><span class="{{$bullet}}">3</span>Configure your remote to <code>git@{{ $knot | stripPort }}:{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}</code></p> 38 + <p><span class="{{$bullet}}">3</span>Configure your remote to <code>git@{{ $knot | stripPort }}:{{ .RepoInfo.RepoDid }}</code></p> 39 39 <p><span class="{{$bullet}}">4</span>Push!</p> 40 40 </div> 41 41 </div>
+4 -4
appview/pages/templates/repo/fragments/cloneDropdown.html
··· 38 38 {{ template "cloneUrlItem" ( 39 39 dict 40 40 "Label" "HTTPS" 41 - "HandleUrl" (printf "https://tangled.org/%s/%s" $repoOwnerHandle .RepoInfo.Name) 42 - "PermaUrl" (printf "https://tangled.org/%s/%s" .RepoInfo.OwnerDid .RepoInfo.Name) 41 + "HandleUrl" (printf "https://tangled.org/%s/%s" $repoOwnerHandle .RepoInfo.Rkey) 42 + "PermaUrl" (printf "https://tangled.org/%s" .RepoInfo.RepoDid) 43 43 ) }} 44 44 45 45 {{ template "cloneUrlItem" ( 46 46 dict 47 47 "Label" "SSH" 48 - "HandleUrl" (printf "git@%s:%s/%s" (stripPort $knot) $repoOwnerHandle .RepoInfo.Name) 49 - "PermaUrl" (printf "git@%s:%s/%s" (stripPort $knot) .RepoInfo.OwnerDid .RepoInfo.Name) 48 + "HandleUrl" (printf "git@%s:%s/%s" (stripPort $knot) $repoOwnerHandle .RepoInfo.Rkey) 49 + "PermaUrl" (printf "git@%s:%s" (stripPort $knot) .RepoInfo.RepoDid) 50 50 ) }} 51 51 52 52 <p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
+1
appview/pages/templates/repo/index.html
··· 361 361 {{ template "repo/fragments/readme" . }} 362 362 {{- end -}} 363 363 {{ end }} 364 +
+1 -1
appview/pages/templates/repo/pulls/fragments/pullActions.html
··· 34 34 </button> 35 35 {{ if .BranchDeleteStatus }} 36 36 <button 37 - hx-delete="/{{ .BranchDeleteStatus.Repo.Did }}/{{ .BranchDeleteStatus.Repo.Name }}/branches" 37 + hx-delete="/{{ .BranchDeleteStatus.Repo.Did }}/{{ .BranchDeleteStatus.Repo.Rkey }}/branches" 38 38 hx-vals='{"branch": "{{ .BranchDeleteStatus.Branch }}" }' 39 39 hx-swap="none" 40 40 class="btn-flat p-2 flex items-center gap-2 no-underline hover:no-underline group text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">
+1 -1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 30 30 {{ $repoPath := .RepoInfo.FullName }} 31 31 <a href="/{{ $repoPath }}/tree/{{ pathEscape .Pull.PullSource.Branch }}" class="no-underline hover:underline">{{ .Pull.PullSource.Branch }}</a> 32 32 {{ else if .Pull.PullSource.Repo }} 33 - {{ $repoPath := print (resolve .Pull.PullSource.Repo.Did) "/" .Pull.PullSource.Repo.Name }} 33 + {{ $repoPath := print (resolve .Pull.PullSource.Repo.Did) "/" .Pull.PullSource.Repo.Rkey }} 34 34 <a href="/{{ $repoPath }}" class="no-underline hover:underline">{{ $repoPath }}</a>: 35 35 <a href="/{{ $repoPath }}/tree/{{ pathEscape .Pull.PullSource.Branch }}" class="no-underline hover:underline">{{ .Pull.PullSource.Branch }}</a> 36 36 {{ else }}
+1 -1
appview/pages/templates/repo/pulls/pull.html
··· 409 409 <!-- attempt to resolve $fullRepo: this is possible only on non-deleted forks and branches --> 410 410 {{ $fullRepo := "" }} 411 411 {{ if and $root.Pull.IsForkBased $root.Pull.PullSource.Repo }} 412 - {{ $fullRepo = printf "%s/%s" $root.Pull.OwnerDid $root.Pull.PullSource.Repo.Name }} 412 + {{ $fullRepo = printf "%s/%s" $root.Pull.OwnerDid $root.Pull.PullSource.Repo.Rkey }} 413 413 {{ else if $root.Pull.IsBranchBased }} 414 414 {{ $fullRepo = $root.RepoInfo.FullName }} 415 415 {{ end }}
+10
appview/pages/templates/repo/settings/hooks.html
··· 225 225 <input type="checkbox" name="event_push" value="on" checked /> 226 226 <span class="text-sm">Push events</span> 227 227 </div> 228 + <div class="flex items-center gap-2"> 229 + <input type="checkbox" name="event_repo_renamed" value="on" /> 230 + <span class="text-sm">Repository renamed</span> 231 + </div> 228 232 <p class="text-sm text-gray-500 dark:text-gray-400"> 229 233 Additional event types (pull requests, issues) will be available in future updates. 230 234 </p> ··· 294 298 <label class="text-sm font-semibold">Events</label> 295 299 <div class="flex flex-col gap-2 ml-4"> 296 300 {{ $hasPush := false }} 301 + {{ $hasRepoRenamed := false }} 297 302 {{ range $webhook.Events }} 298 303 {{ if eq . "push" }}{{ $hasPush = true }}{{ end }} 304 + {{ if eq . "repository:renamed" }}{{ $hasRepoRenamed = true }}{{ end }} 299 305 {{ end }} 300 306 <div class="flex items-center gap-2"> 301 307 <input type="checkbox" name="event_push" value="on" {{ if $hasPush }}checked{{ end }} /> 302 308 <span class="text-sm">Push events</span> 303 309 </div> 310 + <div class="flex items-center gap-2"> 311 + <input type="checkbox" name="event_repo_renamed" value="on" {{ if $hasRepoRenamed }}checked{{ end }} /> 312 + <span class="text-sm">Repository renamed</span> 313 + </div> 304 314 <p class="text-sm text-gray-500 dark:text-gray-400"> 305 315 Additional event types (pull requests, issues) will be available in future updates. 306 316 </p>
+3 -3
appview/pages/templates/repo/settings/sites.html
··· 32 32 {{ else }} 33 33 <div class="flex items-center gap-2 px-3 py-2 rounded border border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/20 text-sm text-green-800 dark:text-green-300"> 34 34 {{ i "circle-check" "size-4 shrink-0" }} 35 - live at <a class="underline font-mono" href="https://{{ .OwnerClaim.Domain }}/{{ .RepoInfo.Name }}">{{ .OwnerClaim.Domain }}/{{ .RepoInfo.Name }}</a> 35 + live at <a class="underline font-mono" href="https://{{ .OwnerClaim.Domain }}/{{ .RepoInfo.Rkey }}">{{ .OwnerClaim.Domain }}/{{ .RepoInfo.Rkey }}</a> 36 36 </div> 37 37 {{ end }} 38 38 {{ else if and .SiteConfig (not .OwnerClaim) }} ··· 157 157 <span class="font-medium">sub-path site</span> 158 158 <p class="text-xs text-gray-500 dark:text-gray-400 mt-2"> 159 159 {{ if .OwnerClaim }} 160 - <code>{{ .OwnerClaim.Domain }}/{{ $.RepoInfo.Name }}</code> 160 + <code>{{ .OwnerClaim.Domain }}/{{ $.RepoInfo.Rkey }}</code> 161 161 {{ else }} 162 - e.g. <code>you.tngl.page/{{ $.RepoInfo.Name }}</code> 162 + e.g. <code>you.tngl.page/{{ $.RepoInfo.Rkey }}</code> 163 163 {{ end }} 164 164 </p> 165 165 </div>
+1 -1
appview/pages/templates/spindles/dashboard.html
··· 69 69 {{ range $repos }} 70 70 <div class="flex gap-2 items-center"> 71 71 {{ i "book-marked" "size-4" }} 72 - <a href="/{{ .Did }}/{{ .Name }}"> 72 + <a href="/{{ .Did }}/{{ .Rkey }}"> 73 73 {{ .Name }} 74 74 </a> 75 75 </div>
+4 -4
appview/pages/templates/timeline/fragments/preview.html
··· 118 118 {{ with $source }} 119 119 {{ $sourceDid := resolve .Did }} 120 120 forked 121 - <a href="/{{ $sourceDid }}/{{ .Name }}"class="no-underline hover:underline"> 121 + <a href="/{{ $sourceDid }}/{{ .Rkey }}"class="no-underline hover:underline"> 122 122 {{ $sourceDid }}/{{ .Name }} 123 123 </a> 124 124 to 125 - <a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline">{{ $repo.Name }}</a> 125 + <a href="/{{ $userHandle }}/{{ $repo.Rkey }}" class="no-underline hover:underline">{{ $repo.Name }}</a> 126 126 {{ else }} 127 127 created 128 - <a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline"> 128 + <a href="/{{ $userHandle }}/{{ $repo.Rkey }}" class="no-underline hover:underline"> 129 129 {{ $repo.Name }} 130 130 </a> 131 131 {{ end }} ··· 148 148 {{ template "user/fragments/picHandleLink" $starrerHandle }} 149 149 starred 150 150 {{ template "user/fragments/pic" (list $repoOwnerHandle "size-6") }} 151 - <a href="/{{ $repoOwnerHandle }}/{{ .Repo.Name }}" class="no-underline hover:underline"> 151 + <a href="/{{ $repoOwnerHandle }}/{{ .Repo.Rkey }}" class="no-underline hover:underline"> 152 152 {{ $repoOwnerHandle | truncateAt30 }}/{{ .Repo.Name }} 153 153 </a> 154 154 </div>
+4 -4
appview/pages/templates/timeline/fragments/timeline.html
··· 39 39 {{ with $source }} 40 40 {{ $sourceDid := resolve .Did }} 41 41 forked 42 - <a href="/{{ $sourceDid }}/{{ .Name }}"class="no-underline hover:underline"> 42 + <a href="/{{ $sourceDid }}/{{ .Rkey }}"class="no-underline hover:underline"> 43 43 {{ $sourceDid }}/{{ .Name }} 44 44 </a> 45 45 to 46 - <a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline">{{ $repo.Name }}</a> 46 + <a href="/{{ $userHandle }}/{{ $repo.Rkey }}" class="no-underline hover:underline">{{ $repo.Name }}</a> 47 47 {{ else }} 48 48 created 49 - <a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline"> 49 + <a href="/{{ $userHandle }}/{{ $repo.Rkey }}" class="no-underline hover:underline"> 50 50 {{ $repo.Name }} 51 51 </a> 52 52 {{ end }} ··· 67 67 <div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm"> 68 68 {{ template "user/fragments/picHandleLink" $starrerHandle }} 69 69 starred 70 - <a href="/{{ $repoOwnerHandle }}/{{ .Repo.Name }}" class="no-underline hover:underline"> 70 + <a href="/{{ $repoOwnerHandle }}/{{ .Repo.Rkey }}" class="no-underline hover:underline"> 71 71 {{ $repoOwnerHandle | truncateAt30 }}/{{ .Repo.Name }} 72 72 </a> 73 73 <span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" .Created }}</span>
+2 -2
appview/pages/templates/user/fragments/repoCard.html
··· 23 23 {{ end }} 24 24 {{ $repoOwner := resolve .Did }} 25 25 {{- if $fullName -}} 26 - <a href="/{{ $repoOwner }}/{{ .Name }}" class="truncate min-w-0">{{ $repoOwner }}/{{ .Name }}</a> 26 + <a href="/{{ $repoOwner }}/{{ .Rkey }}" class="truncate min-w-0">{{ $repoOwner }}/{{ .Name }}</a> 27 27 {{- else -}} 28 - <a href="/{{ $repoOwner }}/{{ .Name }}" class="truncate min-w-0">{{ .Name }}</a> 28 + <a href="/{{ $repoOwner }}/{{ .Rkey }}" class="truncate min-w-0">{{ .Name }}</a> 29 29 {{- end -}} 30 30 </div> 31 31 {{ if and $starButton $root.LoggedInUser }}
+3 -3
appview/pages/templates/user/overview.html
··· 79 79 {{ i "book-plus" "w-4 h-4" }} 80 80 {{ end }} 81 81 </span> 82 - <a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline"> 82 + <a href="/{{ resolve .Repo.Did }}/{{ .Repo.Rkey }}" class="no-underline hover:underline"> 83 83 {{- .Repo.Name -}} 84 84 </a> 85 85 </span> ··· 130 130 <div class="py-2 text-sm flex flex-col gap-3 mb-2"> 131 131 {{ range $items }} 132 132 {{ $repoOwner := resolve .Repo.Did }} 133 - {{ $repoName := .Repo.Name }} 133 + {{ $repoName := .Repo.Rkey }} 134 134 {{ $repoUrl := printf "%s/%s" $repoOwner $repoName }} 135 135 136 136 <div class="flex gap-2 text-gray-600 dark:text-gray-300"> ··· 199 199 <div class="py-2 text-sm flex flex-col gap-3 mb-2"> 200 200 {{ range $items }} 201 201 {{ $repoOwner := resolve .Repo.Did }} 202 - {{ $repoName := .Repo.Name }} 202 + {{ $repoName := .Repo.Rkey }} 203 203 {{ $repoUrl := printf "%s/%s" $repoOwner $repoName }} 204 204 205 205 <div class="flex gap-2 text-gray-600 dark:text-gray-300">
+4 -4
appview/pipelines/pipelines.go
··· 89 89 filterKind := r.URL.Query().Get("trigger") 90 90 filters := []orm.Filter{ 91 91 orm.FilterEq("p.repo_owner", f.Did), 92 - orm.FilterEq("p.repo_name", f.Name), 92 + orm.FilterEq("p.repo_name", f.Rkey), 93 93 orm.FilterEq("p.knot", f.Knot), 94 94 } 95 95 switch filterKind { ··· 153 153 p.db, 154 154 1, 155 155 orm.FilterEq("p.repo_owner", f.Did), 156 - orm.FilterEq("p.repo_name", f.Name), 156 + orm.FilterEq("p.repo_name", f.Rkey), 157 157 orm.FilterEq("p.knot", f.Knot), 158 158 orm.FilterEq("p.id", pipelineId), 159 159 ) ··· 220 220 p.db, 221 221 1, 222 222 orm.FilterEq("p.repo_owner", f.Did), 223 - orm.FilterEq("p.repo_name", f.Name), 223 + orm.FilterEq("p.repo_name", f.Rkey), 224 224 orm.FilterEq("p.knot", f.Knot), 225 225 orm.FilterEq("p.id", pipelineId), 226 226 ) ··· 369 369 p.db, 370 370 1, 371 371 orm.FilterEq("p.repo_owner", f.Did), 372 - orm.FilterEq("p.repo_name", f.Name), 372 + orm.FilterEq("p.repo_name", f.Rkey), 373 373 orm.FilterEq("p.knot", f.Knot), 374 374 orm.FilterEq("p.id", pipelineId), 375 375 )
+34 -34
appview/pulls/pulls.go
··· 227 227 s.db, 228 228 len(shas), 229 229 orm.FilterEq("p.repo_owner", f.Did), 230 - orm.FilterEq("p.repo_name", f.Name), 230 + orm.FilterEq("p.repo_name", f.Rkey), 231 231 orm.FilterEq("p.knot", f.Knot), 232 232 orm.FilterIn("p.sha", shas), 233 233 ) ··· 451 451 return pages.Unknown 452 452 } 453 453 454 - var sourceRepo syntax.ATURI 455 - if pull.PullSource.RepoAt != nil { 456 - sourceRepo = *pull.PullSource.RepoAt 454 + var sourceRepoDid string 455 + if pull.PullSource.RepoDid != nil { 456 + sourceRepoDid = string(*pull.PullSource.RepoDid) 457 457 } else { 458 - sourceRepo = repo.RepoAt() 458 + sourceRepoDid = repo.RepoDid 459 459 } 460 460 461 461 xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url} 462 - branchResp, err := tangled.GitTempGetBranch(r.Context(), xrpcc, pull.PullSource.Branch, sourceRepo.String()) 462 + branchResp, err := tangled.GitTempGetBranch(r.Context(), xrpcc, pull.PullSource.Branch, sourceRepoDid) 463 463 if err != nil { 464 464 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 465 465 s.logger.Error("failed to call XRPC repo.branches", "xrpcerr", xrpcerr, "err", err, "pull_id", pull.PullId, "branch", pull.PullSource.Branch) ··· 599 599 searchOpts := models.PullSearchOptions{ 600 600 Keywords: tf.Keywords, 601 601 Phrases: tf.Phrases, 602 - RepoAt: f.RepoAt().String(), 602 + RepoDid: f.RepoDid, 603 603 State: state, 604 604 AuthorDid: authorDid, 605 605 Labels: labels, ··· 671 671 } 672 672 } else { 673 673 filters := []orm.Filter{ 674 - orm.FilterEq("repo_at", f.RepoAt()), 674 + orm.FilterEq("repo_did", f.RepoDid), 675 675 } 676 676 if state != nil { 677 677 filters = append(filters, orm.FilterEq("state", *state)) ··· 691 691 for _, p := range pulls { 692 692 var pullSourceRepo *models.Repo 693 693 if p.PullSource != nil { 694 - if p.PullSource.RepoAt != nil { 695 - pullSourceRepo, err = db.GetRepoByAtUri(s.db, p.PullSource.RepoAt.String()) 694 + if p.PullSource.RepoDid != nil { 695 + pullSourceRepo, err = db.GetRepoByDid(s.db, string(*p.PullSource.RepoDid)) 696 696 if err != nil { 697 - l.Error("failed to get repo by at uri", "err", err, "repo_at", p.PullSource.RepoAt.String()) 697 + l.Error("failed to get repo by did", "err", err, "repo_did", p.PullSource.RepoDid.String()) 698 698 continue 699 699 } else { 700 700 p.PullSource.Repo = pullSourceRepo ··· 761 761 s.db, 762 762 len(shas), 763 763 orm.FilterEq("p.repo_owner", f.Did), 764 - orm.FilterEq("p.repo_name", f.Name), 764 + orm.FilterEq("p.repo_name", f.Rkey), 765 765 orm.FilterEq("p.knot", f.Knot), 766 766 orm.FilterIn("p.sha", shas), 767 767 ) ··· 910 910 911 911 comment := &models.PullComment{ 912 912 OwnerDid: user.Did, 913 - RepoAt: f.RepoAt().String(), 913 + RepoDid: string(f.RepoDid), 914 914 PullId: pull.PullId, 915 915 Body: body, 916 916 CommentAt: atResp.Uri, ··· 1180 1180 1181 1181 repoString := strings.SplitN(forkRepo, "/", 2) 1182 1182 forkOwnerDid := repoString[0] 1183 - repoName := repoString[1] 1184 - fork, err := db.GetForkByDid(s.db, forkOwnerDid, repoName) 1183 + forkRkey := strings.ToLower(repoString[1]) 1184 + fork, err := db.GetForkByDid(s.db, forkOwnerDid, forkRkey) 1185 1185 if errors.Is(err, sql.ErrNoRows) { 1186 1186 s.pages.Notice(w, "pull", "No such fork.") 1187 1187 return 1188 1188 } else if err != nil { 1189 - l.Error("failed to fetch fork", "err", err, "fork_owner_did", forkOwnerDid, "repo_name", repoName) 1189 + l.Error("failed to fetch fork", "err", err, "fork_owner_did", forkOwnerDid, "fork_rkey", forkRkey) 1190 1190 s.pages.Notice(w, "pull", "Failed to fetch fork.") 1191 1191 return 1192 1192 } ··· 1266 1266 return 1267 1267 } 1268 1268 1269 - forkAtUri := fork.RepoAt() 1270 - var forkDid *syntax.DID 1271 - if fork.RepoDid != "" { 1272 - forkDid = new(syntax.DID) 1273 - *forkDid = syntax.DID(fork.RepoDid) 1274 - } 1275 - 1269 + forkDid := syntax.DID(fork.RepoDid) 1276 1270 pullSource := &models.PullSource{ 1277 1271 Branch: sourceBranch, 1278 - RepoAt: &forkAtUri, 1279 - RepoDid: forkDid, 1272 + RepoDid: &forkDid, 1280 1273 } 1281 1274 1282 1275 s.createPullRequest(w, r, repo, userDid, title, body, targetBranch, patch, combined, sourceRev, pullSource, isStacked) ··· 1365 1358 Body: body, 1366 1359 TargetBranch: targetBranch, 1367 1360 OwnerDid: userDid.String(), 1368 - RepoAt: repo.RepoAt(), 1361 + RepoDid: syntax.DID(repo.RepoDid), 1369 1362 Rkey: rkey, 1370 1363 Mentions: mentions, 1371 1364 References: references, ··· 1404 1397 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1405 1398 return 1406 1399 } 1407 - pullId, err := db.NextPullId(tx, repo.RepoAt()) 1400 + pullId, err := db.NextPullId(tx, repo.RepoDid) 1408 1401 if err != nil { 1409 1402 s.logger.Error("failed to get pull id", "err", err) 1410 1403 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") ··· 1896 1889 return 1897 1890 } 1898 1891 1899 - forkRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String()) 1892 + forkRepo, err := db.GetRepoByDid(s.db, string(*pull.PullSource.RepoDid)) 1900 1893 if err != nil { 1901 - l.Error("failed to get source repo", "err", err, "repo_at", pull.PullSource.RepoAt.String()) 1894 + l.Error("failed to get source repo", "err", err, "repo_did", pull.PullSource.RepoDid.String()) 1902 1895 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1903 1896 return 1904 1897 } ··· 2196 2189 continue 2197 2190 } 2198 2191 2199 - err := db.AbandonPulls(tx, orm.FilterEq("repo_at", p.RepoAt), orm.FilterEq("at_uri", p.AtUri())) 2192 + err := db.AbandonPulls(tx, orm.FilterEq("repo_did", string(p.RepoDid)), orm.FilterEq("at_uri", p.AtUri())) 2200 2193 if err != nil { 2201 2194 l.Error("failed to delete pull", "err", err, "pull_id", p.PullId) 2202 2195 s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") ··· 2427 2420 atUris = append(atUris, p.AtUri()) 2428 2421 p.State = models.PullMerged 2429 2422 } 2430 - err = db.MergePulls(tx, orm.FilterEq("repo_at", f.RepoAt()), orm.FilterIn("at_uri", atUris)) 2423 + err = db.MergePulls(tx, orm.FilterEq("repo_did", string(f.RepoDid)), orm.FilterIn("at_uri", atUris)) 2431 2424 if err != nil { 2432 2425 l.Error("failed to update pull request status in database", "err", err) 2433 2426 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") ··· 2504 2497 } 2505 2498 err = db.ClosePulls( 2506 2499 tx, 2507 - orm.FilterEq("repo_at", f.RepoAt()), 2500 + orm.FilterEq("repo_did", string(f.RepoDid)), 2508 2501 orm.FilterIn("at_uri", atUris), 2509 2502 ) 2510 2503 if err != nil { ··· 2581 2574 } 2582 2575 err = db.ReopenPulls( 2583 2576 tx, 2584 - orm.FilterEq("repo_at", f.RepoAt()), 2577 + orm.FilterEq("repo_did", string(f.RepoDid)), 2585 2578 orm.FilterIn("at_uri", atUris), 2586 2579 ) 2587 2580 if err != nil { ··· 2635 2628 Body: body, 2636 2629 TargetBranch: targetBranch, 2637 2630 OwnerDid: userDid.String(), 2638 - RepoAt: repo.RepoAt(), 2631 + RepoDid: syntax.DID(repo.RepoDid), 2639 2632 Rkey: rkey, 2640 2633 Mentions: mentions, 2641 2634 References: references, ··· 2674 2667 } 2675 2668 2676 2669 func ptrPullState(s models.PullState) *models.PullState { return &s } 2670 + 2671 + func repoPullTarget(repo *models.Repo, branch string) *tangled.RepoPull_Target { 2672 + return &tangled.RepoPull_Target{ 2673 + Branch: branch, 2674 + Repo: repo.RepoDid, 2675 + } 2676 + }
+5 -4
appview/repo/artifact.go
··· 20 20 "tangled.org/core/xrpc" 21 21 22 22 comatproto "github.com/bluesky-social/indigo/api/atproto" 23 + "github.com/bluesky-social/indigo/atproto/syntax" 23 24 lexutil "github.com/bluesky-social/indigo/lex/util" 24 25 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 25 26 "github.com/dustin/go-humanize" ··· 102 103 artifact := models.Artifact{ 103 104 Did: user.Did, 104 105 Rkey: rkey, 105 - RepoAt: f.RepoAt(), 106 + RepoDid: syntax.DID(f.RepoDid), 106 107 Tag: tag.Tag.Hash, 107 108 CreatedAt: createdAt, 108 109 BlobCid: cid.Cid(uploadBlobResp.Blob.Ref), ··· 154 155 155 156 artifacts, err := db.GetArtifact( 156 157 rp.db, 157 - orm.FilterEq("repo_at", f.RepoAt()), 158 + orm.FilterEq("repo_did", f.RepoDid), 158 159 orm.FilterEq("tag", tag.Tag.Hash[:]), 159 160 orm.FilterEq("name", filename), 160 161 ) ··· 235 236 236 237 artifacts, err := db.GetArtifact( 237 238 rp.db, 238 - orm.FilterEq("repo_at", f.RepoAt()), 239 + orm.FilterEq("repo_did", f.RepoDid), 239 240 orm.FilterEq("tag", tag[:]), 240 241 orm.FilterEq("name", filename), 241 242 ) ··· 277 278 defer tx.Rollback() 278 279 279 280 err = db.DeleteArtifact(tx, 280 - orm.FilterEq("repo_at", f.RepoAt()), 281 + orm.FilterEq("repo_did", f.RepoDid), 281 282 orm.FilterEq("tag", artifact.Tag[:]), 282 283 orm.FilterEq("name", filename), 283 284 )
+3 -3
appview/repo/feed.go
··· 74 74 75 75 // fetch and add pull requests if requested 76 76 if opts.IncludePulls { 77 - pulls, err := db.GetPullsPaginated(rp.db, feedPagePerType, orm.FilterEq("repo_at", repo.RepoAt())) 77 + pulls, err := db.GetPullsPaginated(rp.db, feedPagePerType, orm.FilterEq("repo_did", repo.RepoDid)) 78 78 if err != nil { 79 79 return nil, err 80 80 } ··· 93 93 issues, err := db.GetIssuesPaginated( 94 94 rp.db, 95 95 feedPagePerType, 96 - orm.FilterEq("repo_at", repo.RepoAt()), 96 + orm.FilterEq("repo_did", repo.RepoDid), 97 97 ) 98 98 if err != nil { 99 99 return nil, err ··· 315 315 rp.logger.Error("failed to get resolved repo owner id") 316 316 return 317 317 } 318 - ownerSlashRepo := repoOwnerId.Handle.String() + "/" + f.Name 318 + ownerSlashRepo := repoOwnerId.Handle.String() + "/" + f.Rkey 319 319 320 320 opts := parseFeedOpts(r) 321 321 feed, err := rp.getRepoFeed(r.Context(), f, ownerSlashRepo, opts)
+4 -3
appview/repo/index.go
··· 15 15 "context" 16 16 "encoding/json" 17 17 18 + "github.com/bluesky-social/indigo/atproto/syntax" 18 19 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 19 20 "github.com/go-git/go-git/v5/plumbing" 20 21 "tangled.org/core/api/tangled" ··· 158 159 // first attempt to fetch from db 159 160 langs, err := db.GetRepoLanguages( 160 161 rp.db, 161 - orm.FilterEq("repo_at", repo.RepoAt()), 162 + orm.FilterEq("repo_did", repo.RepoDid), 162 163 orm.FilterEq("ref", currentRef), 163 164 ) 164 165 ··· 179 180 180 181 for _, lang := range ls.Languages { 181 182 langs = append(langs, models.RepoLanguage{ 182 - RepoAt: repo.RepoAt(), 183 + RepoDid: syntax.DID(repo.RepoDid), 183 184 Ref: currentRef, 184 185 IsDefaultRef: isDefaultRef, 185 186 Language: lang.Name, ··· 194 195 defer tx.Rollback() 195 196 196 197 // update appview's cache 197 - err = db.UpdateRepoLanguages(tx, repo.RepoAt(), currentRef, langs) 198 + err = db.UpdateRepoLanguages(tx, syntax.DID(repo.RepoDid), currentRef, langs) 198 199 if err != nil { 199 200 // non-fatal 200 201 l.Error("failed to cache lang results", "err", err)
+1 -1
appview/repo/opengraph.go
··· 27 27 var languageStats []types.RepoLanguageDetails 28 28 langs, err := db.GetRepoLanguages( 29 29 rp.db, 30 - orm.FilterEq("repo_at", f.RepoAt()), 30 + orm.FilterEq("repo_did", f.RepoDid), 31 31 orm.FilterEq("is_default_ref", 1), 32 32 ) 33 33 if err != nil {
+17 -22
appview/repo/repo.go
··· 140 140 } 141 141 142 142 // optimistic update 143 - err = db.UpdateSpindle(rp.db, newRepo.RepoAt().String(), spindlePtr) 143 + err = db.UpdateSpindle(rp.db, newRepo.RepoDid, spindlePtr) 144 144 if err != nil { 145 145 fail("Failed to update spindle. Try again later.", err) 146 146 return ··· 320 320 } 321 321 322 322 if err = db.SubscribeLabel(tx, &models.RepoLabel{ 323 - RepoAt: f.RepoAt(), 323 + RepoDid: syntax.DID(f.RepoDid), 324 324 LabelAt: label.AtUri(), 325 325 }); err != nil { 326 326 fail("Failed to subscribe to label.", err) ··· 423 423 424 424 err = db.UnsubscribeLabel( 425 425 tx, 426 - orm.FilterEq("repo_at", f.RepoAt()), 426 + orm.FilterEq("repo_did", f.RepoDid), 427 427 orm.FilterEq("label_at", removedAt), 428 428 ) 429 429 if err != nil { ··· 515 515 516 516 for _, l := range labelAts { 517 517 err = db.SubscribeLabel(tx, &models.RepoLabel{ 518 - RepoAt: f.RepoAt(), 518 + RepoDid: syntax.DID(f.RepoDid), 519 519 LabelAt: syntax.ATURI(l), 520 520 }) 521 521 if err != nil { ··· 596 596 597 597 err = db.UnsubscribeLabel( 598 598 rp.db, 599 - orm.FilterEq("repo_at", f.RepoAt()), 599 + orm.FilterEq("repo_did", f.RepoDid), 600 600 orm.FilterIn("label_at", labelAts), 601 601 ) 602 602 if err != nil { ··· 805 805 Did: syntax.DID(currentUser.Did), 806 806 Rkey: rkey, 807 807 SubjectDid: collaboratorIdent.DID, 808 - RepoAt: f.RepoAt(), 808 + RepoDid: syntax.DID(f.RepoDid), 809 809 Created: createdAt, 810 810 }) 811 811 if err != nil { ··· 921 921 } 922 922 923 923 // remove repo from db 924 - err = db.RemoveRepo(tx, f.Did, f.Name) 924 + err = db.RemoveRepo(tx, f.Did, f.Rkey) 925 925 if err != nil { 926 926 rp.pages.Notice(w, noticeId, "Failed to update appview") 927 927 return ··· 1040 1040 } 1041 1041 1042 1042 // choose a name for a fork 1043 - forkName := r.FormValue("repo_name") 1043 + forkName := strings.ToLower(r.FormValue("repo_name")) 1044 1044 if forkName == "" { 1045 1045 rp.pages.Notice(w, "repo", "Repository name cannot be empty.") 1046 1046 return ··· 1074 1074 forkSourceUrl := fmt.Sprintf("%s://%s/%s", uri, f.Knot, f.RepoIdentifier()) 1075 1075 l = l.With("cloneUrl", forkSourceUrl) 1076 1076 1077 - rkey := tid.TID() 1077 + rkey := strings.ToLower(forkName) 1078 1078 1079 1079 // TODO: this could coordinate better with the knot to receive a clone status 1080 1080 client, err := rp.oauth.ServiceClient( ··· 1092 1092 1093 1093 forkInput := &tangled.RepoCreate_Input{ 1094 1094 Rkey: rkey, 1095 - Name: forkName, 1095 + Name: rkey, 1096 1096 Source: &forkSourceUrl, 1097 1097 } 1098 1098 createResp, err := tangled.RepoCreate( ··· 1123 1123 1124 1124 repo := &models.Repo{ 1125 1125 Did: user.Did, 1126 - Name: forkName, 1126 + Name: rkey, 1127 1127 Knot: targetKnot, 1128 1128 Rkey: rkey, 1129 1129 Source: forkSource, ··· 1276 1276 page.Limit = 30 1277 1277 } 1278 1278 1279 - starrers, err := db.GetStars(rp.db, f.RepoAt(), page) 1279 + starrers, err := db.GetStars(rp.db, string(f.RepoDid), page) 1280 1280 if err != nil { 1281 - l.Error("failed to fetch starrers", "err", err, "repoAt", f.RepoAt()) 1281 + l.Error("failed to fetch starrers", "err", err, "repoDid", f.RepoDid) 1282 1282 return 1283 1283 } 1284 1284 1285 - totalCount, err := db.GetStarCount(rp.db, f.RepoAt()) 1285 + totalCount, err := db.GetStarCount(rp.db, models.StarSubjectRepo, string(f.RepoDid)) 1286 1286 if err != nil { 1287 - l.Error("failed to fetch star count", "err", err, "repoAt", f.RepoAt()) 1287 + l.Error("failed to fetch star count", "err", err, "repoDid", f.RepoDid) 1288 1288 return 1289 1289 } 1290 1290 ··· 1320 1320 } 1321 1321 1322 1322 func repoCollaboratorRecord(f *models.Repo, subject string, createdAt time.Time) *tangled.RepoCollaborator { 1323 - rec := &tangled.RepoCollaborator{ 1323 + return &tangled.RepoCollaborator{ 1324 1324 Subject: subject, 1325 1325 CreatedAt: createdAt.Format(time.RFC3339), 1326 + Repo: f.RepoDid, 1326 1327 } 1327 - s := string(f.RepoAt()) 1328 - rec.Repo = &s 1329 - if f.RepoDid != "" { 1330 - rec.RepoDid = &f.RepoDid 1331 - } 1332 - return rec 1333 1328 }
+1 -1
appview/repo/repo_util.go
··· 104 104 d, 105 105 len(shas), 106 106 orm.FilterEq("p.repo_owner", repo.Did), 107 - orm.FilterEq("p.repo_name", repo.Name), 107 + orm.FilterEq("p.repo_name", repo.Rkey), 108 108 orm.FilterEq("p.knot", repo.Knot), 109 109 orm.FilterIn("p.sha", shas), 110 110 )
+1
appview/repo/router.go
··· 88 88 r.With(mw.RepoPermissionMiddleware("repo:owner")).Post("/label/unsubscribe", rp.UnsubscribeLabel) 89 89 r.With(mw.RepoPermissionMiddleware("repo:invite")).Put("/collaborator", rp.AddCollaborator) 90 90 r.With(mw.RepoPermissionMiddleware("repo:delete")).Delete("/delete", rp.DeleteRepo) 91 + r.With(mw.RepoPermissionMiddleware("repo:owner")).Post("/rename", rp.RenameRepo) 91 92 r.Put("/branches/default", rp.SetDefaultBranch) 92 93 r.Put("/secrets", rp.Secrets) 93 94 r.Delete("/secrets", rp.Secrets)
+12 -11
appview/repo/settings.go
··· 22 22 "tangled.org/core/types" 23 23 24 24 comatproto "github.com/bluesky-social/indigo/api/atproto" 25 + "github.com/bluesky-social/indigo/atproto/syntax" 25 26 lexutil "github.com/bluesky-social/indigo/lex/util" 26 27 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 27 28 ) ··· 195 196 host := fmt.Sprintf("%s://%s", scheme, f.Knot) 196 197 xrpcc := &indigoxrpc.Client{Host: host} 197 198 198 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 199 + repo := fmt.Sprintf("%s/%s", f.Did, f.Rkey) 199 200 xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 200 201 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 201 202 l.Error("failed to call XRPC repo.branches", "xrpcerr", xrpcerr, "err", err) ··· 210 211 return 211 212 } 212 213 213 - siteConfig, err := db.GetRepoSiteConfig(rp.db, f.RepoAt().String()) 214 + siteConfig, err := db.GetRepoSiteConfig(rp.db, f.RepoDid) 214 215 if err != nil { 215 216 l.Error("failed to get site config", "err", err) 216 217 rp.pages.Error503(w) ··· 224 225 ownerClaim = nil 225 226 } 226 227 227 - deploys, err := db.GetSiteDeploys(rp.db, f.RepoAt().String(), 20) 228 + deploys, err := db.GetSiteDeploys(rp.db, f.RepoDid, 20) 228 229 if err != nil { 229 230 l.Error("failed to get site deploys", "err", err) 230 231 // non-fatal 231 232 deploys = nil 232 233 } 233 234 234 - indexSiteTakenBy, err := db.GetIndexRepoAtForDid(rp.db, f.Did, f.RepoAt().String()) 235 + indexSiteTakenBy, err := db.GetIndexRepoDidForDid(rp.db, f.Did, f.RepoDid) 235 236 if err != nil { 236 237 l.Error("failed to get index site owner", "err", err) 237 238 // non-fatal ··· 281 282 282 283 isIndex := r.FormValue("is_index") == "true" 283 284 284 - if err := db.SetRepoSiteConfig(rp.db, f.RepoAt().String(), branch, dir, isIndex); err != nil { 285 + if err := db.SetRepoSiteConfig(rp.db, f.RepoDid, branch, dir, isIndex); err != nil { 285 286 l.Error("failed to save site config", "err", err) 286 287 rp.pages.Notice(w, noticeId, "Failed to save site configuration.") 287 288 return ··· 297 298 ctx := context.Background() 298 299 299 300 deploy := &models.SiteDeploy{ 300 - RepoAt: f.RepoAt().String(), 301 + RepoDid: syntax.DID(f.RepoDid), 301 302 Branch: branch, 302 303 Dir: dir, 303 304 Trigger: models.SiteDeployTriggerConfigChange, ··· 317 318 } 318 319 319 320 if deployErr == nil { 320 - if err := sites.PutDomainMapping(ctx, rp.cfClient, ownerClaim.Domain, f.Did, f.Name, isIndex); err != nil { 321 + if err := sites.PutDomainMapping(ctx, rp.cfClient, ownerClaim.Domain, f.Did, f.Rkey, isIndex); err != nil { 321 322 l.Error("sites: KV write failed", "domain", ownerClaim.Domain, "err", err) 322 323 } 323 324 rp.logger.Info("site deployed to r2", "repo", f.RepoIdentifier(), "is_index", isIndex) ··· 344 345 345 346 // Fetch the current config before deleting so we know the isIndex flag for 346 347 // the KV key and the domain mapping to clean up. 347 - existingConfig, _ := db.GetRepoSiteConfig(rp.db, f.RepoAt().String()) 348 + existingConfig, _ := db.GetRepoSiteConfig(rp.db, f.RepoDid) 348 349 349 - if err := db.DeleteRepoSiteConfig(rp.db, f.RepoAt().String()); err != nil { 350 + if err := db.DeleteRepoSiteConfig(rp.db, f.RepoDid); err != nil { 350 351 l.Error("failed to delete site config", "err", err) 351 352 rp.pages.Notice(w, noticeId, "Failed to remove site configuration.") 352 353 return ··· 358 359 359 360 go func() { 360 361 ctx := context.Background() 361 - if err := sites.Delete(ctx, rp.cfClient, f.Did, f.Name); err != nil { 362 + if err := sites.Delete(ctx, rp.cfClient, f.Did, f.Rkey); err != nil { 362 363 l.Error("sites: R2 delete failed", "repo", f.RepoIdentifier(), "err", err) 363 364 } 364 365 if ownerClaim != nil { 365 - if err := sites.DeleteDomainMapping(ctx, rp.cfClient, ownerClaim.Domain, f.Name); err != nil { 366 + if err := sites.DeleteDomainMapping(ctx, rp.cfClient, ownerClaim.Domain, f.Rkey); err != nil { 366 367 l.Error("sites: KV delete failed", "domain", ownerClaim.Domain, "err", err) 367 368 } 368 369 }
+2 -2
appview/repo/tags.go
··· 40 40 rp.pages.Error503(w) 41 41 return 42 42 } 43 - artifacts, err := db.GetArtifact(rp.db, orm.FilterEq("repo_at", f.RepoAt())) 43 + artifacts, err := db.GetArtifact(rp.db, orm.FilterEq("repo_did", f.RepoDid)) 44 44 if err != nil { 45 45 l.Error("failed grab artifacts", "err", err) 46 46 return ··· 124 124 return 125 125 } 126 126 127 - filters := []orm.Filter{orm.FilterEq("repo_at", f.RepoAt())} 127 + filters := []orm.Filter{orm.FilterEq("repo_did", f.RepoDid)} 128 128 if result.Tag.Tag != nil { 129 129 filters = append(filters, orm.FilterEq("tag", result.Tag.Tag.Hash[:])) 130 130 }
+22 -16
appview/repo/webhooks.go
··· 5 5 "strconv" 6 6 "strings" 7 7 8 + "github.com/bluesky-social/indigo/atproto/syntax" 8 9 "github.com/go-chi/chi/v5" 9 10 "tangled.org/core/appview/db" 10 11 "tangled.org/core/appview/models" ··· 24 25 25 26 user := rp.oauth.GetMultiAccountUser(r) 26 27 27 - webhooks, err := db.GetWebhooksForRepo(rp.db, f.RepoAt()) 28 + webhooks, err := db.GetWebhooksForRepo(rp.db, f.RepoDid) 28 29 if err != nil { 29 30 l.Error("failed to get webhooks", "err", err) 30 31 rp.pages.Notice(w, "webhooks-error", "Failed to load webhooks") ··· 82 83 if r.FormValue("event_push") == "on" { 83 84 events = append(events, string(models.WebhookEventPush)) 84 85 } 86 + if r.FormValue("event_repo_renamed") == "on" { 87 + events = append(events, string(models.WebhookEventRepoRenamed)) 88 + } 85 89 86 90 if len(events) == 0 { 87 - rp.pages.Notice(w, "webhooks-error", "Push events must be enabled") 91 + rp.pages.Notice(w, "webhooks-error", "At least one event must be enabled") 88 92 return 89 93 } 90 94 91 95 webhook := &models.Webhook{ 92 - RepoAt: f.RepoAt(), 93 - Url: url, 94 - Secret: secret, 95 - Active: active, 96 - Events: events, 96 + RepoDid: syntax.DID(f.RepoDid), 97 + Url: url, 98 + Secret: secret, 99 + Active: active, 100 + Events: events, 97 101 } 98 102 99 103 tx, err := rp.db.Begin() ··· 146 150 } 147 151 148 152 // Verify webhook belongs to this repo 149 - if webhook.RepoAt != f.RepoAt() { 150 - l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoAt, "current_repo", f.RepoAt()) 153 + if string(webhook.RepoDid) != f.RepoDid { 154 + l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoDid, "current_repo", f.RepoDid) 151 155 w.WriteHeader(http.StatusForbidden) 152 156 return 153 157 } ··· 168 172 169 173 webhook.Active = r.FormValue("active") == "on" 170 174 171 - // Parse events - only push events are supported for now 172 175 events := []string{} 173 176 if r.FormValue("event_push") == "on" { 174 177 events = append(events, string(models.WebhookEventPush)) 175 178 } 179 + if r.FormValue("event_repo_renamed") == "on" { 180 + events = append(events, string(models.WebhookEventRepoRenamed)) 181 + } 176 182 177 183 if len(events) > 0 { 178 184 webhook.Events = events ··· 228 234 } 229 235 230 236 // Verify webhook belongs to this repo 231 - if webhook.RepoAt != f.RepoAt() { 232 - l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoAt, "current_repo", f.RepoAt()) 237 + if string(webhook.RepoDid) != f.RepoDid { 238 + l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoDid, "current_repo", f.RepoDid) 233 239 w.WriteHeader(http.StatusForbidden) 234 240 return 235 241 } ··· 284 290 } 285 291 286 292 // Verify webhook belongs to this repo 287 - if webhook.RepoAt != f.RepoAt() { 288 - l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoAt, "current_repo", f.RepoAt()) 293 + if string(webhook.RepoDid) != f.RepoDid { 294 + l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoDid, "current_repo", f.RepoDid) 289 295 w.WriteHeader(http.StatusForbidden) 290 296 return 291 297 } ··· 343 349 } 344 350 345 351 // Verify webhook belongs to this repo 346 - if webhook.RepoAt != f.RepoAt() { 347 - l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoAt, "current_repo", f.RepoAt()) 352 + if string(webhook.RepoDid) != f.RepoDid { 353 + l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoDid, "current_repo", f.RepoDid) 348 354 w.WriteHeader(http.StatusForbidden) 349 355 return 350 356 }
+9 -8
appview/reporesolver/resolver.go
··· 76 76 currentDir := extractCurrentDir(r.URL.EscapedPath()) 77 77 ref := chi.URLParam(r, "ref") 78 78 79 - repoAt := repo.RepoAt() 79 + repoDid := repo.RepoDid 80 80 isStarred := false 81 81 roles := repoinfo.RolesInRepo{} 82 82 if user != nil { 83 - isStarred = db.GetStarStatus(rr.execer, user.Did, repoAt) 83 + isStarred = db.GetStarStatus(rr.execer, user.Did, repoDid) 84 84 roles.Roles = rr.enforcer.GetPermissionsInRepo(user.Did, repo.Knot, repo.RepoIdentifier()) 85 85 } 86 86 87 87 stats := repo.RepoStats 88 88 if stats == nil { 89 - starCount, starErr := db.GetStarCount(rr.execer, repoAt) 89 + starCount, starErr := db.GetStarCount(rr.execer, models.StarSubjectRepo, repoDid) 90 90 if starErr != nil { 91 - log.Println("failed to get star count for ", repoAt) 91 + log.Println("failed to get star count for ", repoDid) 92 92 } 93 - issueCount, err := db.GetIssueCount(rr.execer, repoAt) 93 + issueCount, err := db.GetIssueCount(rr.execer, repoDid) 94 94 if err != nil { 95 - log.Println("failed to get issue count for ", repoAt) 95 + log.Println("failed to get issue count for ", repoDid) 96 96 } 97 - pullCount, err := db.GetPullCount(rr.execer, repoAt) 97 + pullCount, err := db.GetPullCount(rr.execer, repoDid) 98 98 if err != nil { 99 - log.Println("failed to get pull count for ", repoAt) 99 + log.Println("failed to get pull count for ", repoDid) 100 100 } 101 101 stats = &models.RepoStats{ 102 102 StarCount: starCount, ··· 127 127 // this is basically a models.Repo 128 128 OwnerDid: ownerId.DID.String(), 129 129 OwnerHandle: ownerHandle, 130 + RepoDid: repo.RepoDid, 130 131 Name: repo.Name, 131 132 Rkey: repo.Rkey, 132 133 Description: repo.Description,
+2 -2
appview/settings/settings.go
··· 224 224 225 225 // Delete each repo's R2 objects. 226 226 for _, sc := range siteConfigs { 227 - if err := sites.Delete(ctx, s.CfClient, user.Did, sc.RepoName); err != nil { 228 - s.Logger.Error("releaseSitesDomain: R2 delete failed", "did", user.Did, "repo", sc.RepoName, "err", err) 227 + if err := sites.Delete(ctx, s.CfClient, user.Did, sc.RepoRkey); err != nil { 228 + s.Logger.Error("releaseSitesDomain: R2 delete failed", "did", user.Did, "repo", sc.RepoRkey, "err", err) 229 229 } 230 230 } 231 231
+1 -1
appview/sites/sites.go
··· 158 158 return fmt.Errorf("walking deploy dir: %w", err) 159 159 } 160 160 161 - if err := cf.SyncFiles(ctx, prefix(f.Did, f.Name), files); err != nil { 161 + if err := cf.SyncFiles(ctx, prefix(f.Did, f.Rkey), files); err != nil { 162 162 return fmt.Errorf("syncing files to R2: %w", err) 163 163 } 164 164
+14 -29
appview/state/knotstream.go
··· 7 7 "errors" 8 8 "fmt" 9 9 "slices" 10 + "strings" 10 11 "time" 11 12 12 13 "tangled.org/core/appview/cloudflare" ··· 72 73 if repoDid != nil && *repoDid != "" { 73 74 return db.GetRepoByDid(d, *repoDid) 74 75 } 75 - repos, err := db.GetRepos(d, orm.FilterEq("did", ownerDid), orm.FilterEq("name", repoName)) 76 + repos, err := db.GetRepos(d, orm.FilterEq("did", ownerDid), orm.FilterEq("rkey", strings.ToLower(repoName))) 76 77 if err != nil { 77 78 return nil, err 78 79 } ··· 114 115 return fmt.Errorf("%s does not belong to %s, something is fishy", record.CommitterDid, source.Key()) 115 116 } 116 117 117 - ownerDid := "" 118 - if record.OwnerDid != nil { 119 - ownerDid = *record.OwnerDid 120 - } else { 121 - // handle legacy event 122 - if record.RepoDid != nil { 123 - ownerDid = *record.RepoDid 124 - } 118 + if record.Repo == "" { 119 + return fmt.Errorf("gitRefUpdate from %s missing repo", source.Key()) 125 120 } 126 121 127 - repo, lookupErr := resolveRepo(d, record.RepoDid, ownerDid, record.RepoName) 122 + repo, lookupErr := db.GetRepoByDid(d, record.Repo) 128 123 if lookupErr != nil { 129 124 return fmt.Errorf("failed to look up repo: %w", lookupErr) 130 125 } ··· 167 162 } 168 163 pushedBranch := ref.Short() 169 164 170 - ownerDid := "" 171 - if record.OwnerDid != nil { 172 - ownerDid = *record.OwnerDid 173 - } 174 - 175 - repo, err := resolveRepo(d, record.RepoDid, ownerDid, record.RepoName) 165 + repo, err := db.GetRepoByDid(d, record.Repo) 176 166 if err != nil { 177 167 return 178 168 } 179 169 180 - siteConfig, err := db.GetRepoSiteConfig(d, repo.RepoAt().String()) 170 + siteConfig, err := db.GetRepoSiteConfig(d, repo.RepoDid) 181 171 if err != nil || siteConfig == nil { 182 172 return 183 173 } ··· 186 176 } 187 177 188 178 deploy := &models.SiteDeploy{ 189 - RepoAt: repo.RepoAt().String(), 179 + RepoDid: syntax.DID(repo.RepoDid), 190 180 Branch: siteConfig.Branch, 191 181 Dir: siteConfig.Dir, 192 182 CommitSHA: record.NewSha, ··· 249 239 250 240 func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error { 251 241 if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil { 252 - return fmt.Errorf("empty language data for repo: %v/%s", record.OwnerDid, record.RepoName) 253 - } 254 - 255 - ownerDid := "" 256 - if record.OwnerDid != nil { 257 - ownerDid = *record.OwnerDid 242 + return fmt.Errorf("empty language data for repo: %s", record.Repo) 258 243 } 259 244 260 - r, lookupErr := resolveRepo(d, record.RepoDid, ownerDid, record.RepoName) 245 + r, lookupErr := db.GetRepoByDid(d, record.Repo) 261 246 if lookupErr != nil { 262 247 return fmt.Errorf("failed to look up repo: %w", lookupErr) 263 248 } ··· 275 260 } 276 261 277 262 langs = append(langs, models.RepoLanguage{ 278 - RepoAt: repo.RepoAt(), 263 + RepoDid: syntax.DID(repo.RepoDid), 279 264 Ref: ref.Short(), 280 265 IsDefaultRef: record.Meta.IsDefaultRef, 281 266 Language: l.Lang, ··· 290 275 defer tx.Rollback() 291 276 292 277 // update appview's cache 293 - err = db.UpdateRepoLanguages(tx, repo.RepoAt(), ref.Short(), langs) 278 + err = db.UpdateRepoLanguages(tx, syntax.DID(repo.RepoDid), ref.Short(), langs) 294 279 if err != nil { 295 280 fmt.Printf("failed; %s\n", err) 296 281 // non-fatal ··· 398 383 399 384 repos, err := db.GetRepos(d, 400 385 orm.FilterEq("did", record.OwnerDid), 401 - orm.FilterEq("name", record.RepoName), 386 + orm.FilterEq("rkey", strings.ToLower(record.RepoName)), 402 387 ) 403 388 if err != nil || len(repos) == 0 { 404 389 logger.Warn("didAssign for unknown repo, skipping", ··· 443 428 return fmt.Errorf("add RBAC policies for %s: %w", record.RepoDid, err) 444 429 } 445 430 446 - collabs, collabErr := db.GetCollaborators(d, orm.FilterEq("repo_at", repoAtUri)) 431 + collabs, collabErr := db.GetCollaborators(d, orm.FilterEq("repo_did", record.RepoDid)) 447 432 if collabErr != nil { 448 433 return fmt.Errorf("get collaborators for RBAC update: %w", collabErr) 449 434 }
+3 -3
appview/state/profile.go
··· 679 679 func (s *State) createPullRequestItem(pull *models.Pull, owner *identity.Identity, author *feeds.Author) *feeds.Item { 680 680 return &feeds.Item{ 681 681 Title: fmt.Sprintf("%s created pull request '%s' in @%s/%s", author.Name, pull.Title, owner.Handle, pull.Repo.Name), 682 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.BaseUrl(), owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"}, 682 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.BaseUrl(), owner.Handle, pull.Repo.Rkey, pull.PullId), Type: "text/html", Rel: "alternate"}, 683 683 Created: pull.Created, 684 684 Author: author, 685 685 } ··· 688 688 func (s *State) createIssueItem(issue *models.Issue, owner *identity.Identity, author *feeds.Author) *feeds.Item { 689 689 return &feeds.Item{ 690 690 Title: fmt.Sprintf("%s created issue '%s' in @%s/%s", author.Name, issue.Title, owner.Handle, issue.Repo.Name), 691 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.BaseUrl(), owner.Handle, issue.Repo.Name, issue.IssueId), Type: "text/html", Rel: "alternate"}, 691 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.BaseUrl(), owner.Handle, issue.Repo.Rkey, issue.IssueId), Type: "text/html", Rel: "alternate"}, 692 692 Created: issue.Created, 693 693 Author: author, 694 694 } ··· 708 708 709 709 return &feeds.Item{ 710 710 Title: title, 711 - Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.BaseUrl(), author.Name[1:], repo.Repo.Name), Type: "text/html", Rel: "alternate"}, // Remove @ prefix 711 + Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.BaseUrl(), author.Name[1:], repo.Repo.Rkey), Type: "text/html", Rel: "alternate"}, // Remove @ prefix 712 712 Created: repo.Repo.Created, 713 713 Author: author, 714 714 }, nil
+1 -1
appview/state/router.go
··· 59 59 if len(pathParts) > 1 { 60 60 remaining = "/" + pathParts[1] 61 61 } 62 - rewritten := "/" + repo.Did + "/" + repo.Name + remaining 62 + rewritten := "/" + repo.Did + "/" + repo.Rkey + remaining 63 63 r2 := r.Clone(r.Context()) 64 64 r2.URL.Path = rewritten 65 65 r2.URL.RawPath = rewritten
+38 -63
appview/state/state.go
··· 39 39 tlog "tangled.org/core/log" 40 40 "tangled.org/core/orm" 41 41 "tangled.org/core/rbac" 42 - "tangled.org/core/tid" 43 42 44 43 comatproto "github.com/bluesky-social/indigo/api/atproto" 45 44 "github.com/bluesky-social/indigo/atproto/atclient" ··· 134 133 tangled.RepoArtifactNSID, 135 134 tangled.RepoIssueCommentNSID, 136 135 tangled.RepoIssueNSID, 136 + tangled.RepoNSID, 137 137 tangled.RepoPullNSID, 138 138 tangled.SpindleMemberNSID, 139 139 tangled.SpindleNSID, ··· 156 156 return nil, fmt.Errorf("failed to backfill default label defs: %w", err) 157 157 } 158 158 159 - ingester := appview.Ingester{ 160 - Db: wrapper, 161 - Enforcer: enforcer, 162 - IdResolver: res, 163 - Cache: rdb, 164 - Config: config, 165 - Logger: log.SubLogger(logger, "ingester"), 166 - Validator: validator, 167 - } 168 - err = jc.StartJetstream(ctx, ingester.Ingest()) 169 - if err != nil { 170 - return nil, fmt.Errorf("failed to start jetstream watcher: %w", err) 171 - } 172 - 173 159 var notifiers []notify.Notifier 174 160 175 161 // Always add the database notifier ··· 186 172 notifier := notify.NewMergedNotifier(notifiers) 187 173 notifier = lognotify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) 188 174 175 + ingester := appview.Ingester{ 176 + Db: d, 177 + Enforcer: enforcer, 178 + IdResolver: res, 179 + Cache: rdb, 180 + Config: config, 181 + Logger: log.SubLogger(logger, "ingester"), 182 + Validator: validator, 183 + Notifier: notifier, 184 + } 185 + err = jc.StartJetstream(ctx, ingester.Ingest()) 186 + if err != nil { 187 + return nil, fmt.Errorf("failed to start jetstream watcher: %w", err) 188 + } 189 + 189 190 var cfClient *cloudflare.Client 190 191 if config.Cloudflare.ApiToken != "" { 191 192 cfClient, err = cloudflare.New(config) ··· 423 424 } 424 425 } 425 426 426 - func validateRepoName(name string) error { 427 - // check for path traversal attempts 428 - if name == "." || name == ".." || 429 - strings.Contains(name, "/") || strings.Contains(name, "\\") { 430 - return fmt.Errorf("Repository name contains invalid path characters") 431 - } 432 - 433 - // check for sequences that could be used for traversal when normalized 434 - if strings.Contains(name, "./") || strings.Contains(name, "../") || 435 - strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { 436 - return fmt.Errorf("Repository name contains invalid path sequence") 437 - } 438 - 439 - // then continue with character validation 440 - for _, char := range name { 441 - if !((char >= 'a' && char <= 'z') || 442 - (char >= 'A' && char <= 'Z') || 443 - (char >= '0' && char <= '9') || 444 - char == '-' || char == '_' || char == '.') { 445 - return fmt.Errorf("Repository name can only contain alphanumeric characters, periods, hyphens, and underscores") 446 - } 447 - } 448 - 449 - // additional check to prevent multiple sequential dots 450 - if strings.Contains(name, "..") { 451 - return fmt.Errorf("Repository name cannot contain sequential dots") 452 - } 453 - 454 - // if all checks pass 455 - return nil 456 - } 457 - 458 - func stripGitExt(name string) string { 459 - return strings.TrimSuffix(name, ".git") 460 - } 461 - 462 427 func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) { 463 428 switch r.Method { 464 429 case http.MethodGet: ··· 494 459 return 495 460 } 496 461 497 - if err := validateRepoName(repoName); err != nil { 462 + if err := models.ValidateRepoName(repoName); err != nil { 498 463 s.pages.Notice(w, "repo", err.Error()) 499 464 return 500 465 } 501 - repoName = stripGitExt(repoName) 502 - l = l.With("repoName", repoName) 466 + repoName = models.StripGitExt(repoName) 467 + rkey := strings.ToLower(repoName) 468 + l = l.With("repoName", repoName, "rkey", rkey) 503 469 504 470 defaultBranch := r.FormValue("branch") 505 471 if defaultBranch == "" { ··· 525 491 existingRepo, err := db.GetRepo( 526 492 s.db, 527 493 orm.FilterEq("did", user.Did), 528 - orm.FilterEq("name", repoName), 494 + orm.FilterEq("rkey", rkey), 529 495 ) 530 496 if err == nil && existingRepo != nil { 531 497 l.Info("repo exists") ··· 533 499 return 534 500 } 535 501 536 - rkey := tid.TID() 537 - 538 502 client, err := s.oauth.ServiceClient( 539 503 r, 540 504 oauth.WithService(domain), ··· 549 513 550 514 input := &tangled.RepoCreate_Input{ 551 515 Rkey: rkey, 552 - Name: repoName, 516 + Name: rkey, 553 517 DefaultBranch: &defaultBranch, 554 518 } 555 519 createResp, err := tangled.RepoCreate( ··· 603 567 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 604 568 if dErr := tangled.RepoDelete(ctx, deleteClient, &tangled.RepoDelete_Input{ 605 569 Did: user.Did, 606 - Name: repoName, 570 + Name: rkey, 607 571 Rkey: rkey, 608 572 }); dErr != nil { 609 573 cancel() ··· 627 591 return 628 592 } 629 593 630 - atresp, err := comatproto.RepoPutRecord(r.Context(), atpClient, &comatproto.RepoPutRecord_Input{ 594 + _, err = comatproto.RepoPutRecord(r.Context(), atpClient, &comatproto.RepoPutRecord_Input{ 631 595 Collection: tangled.RepoNSID, 632 596 Repo: user.Did, 633 597 Rkey: rkey, ··· 638 602 if err != nil { 639 603 l.Info("PDS write failed", "err", err) 640 604 cleanupKnot() 641 - s.pages.Notice(w, "repo", "Failed to announce repository creation.") 605 + if rkeyOccupied(r.Context(), atpClient, user.Did, rkey) { 606 + s.pages.Notice(w, "repo", fmt.Sprintf("You already have a repository named %q.", rkey)) 607 + } else { 608 + s.pages.Notice(w, "repo", "Failed to announce repository creation.") 609 + } 642 610 return 643 611 } 644 612 645 - aturi := atresp.Uri 613 + aturi := fmt.Sprintf("at://%s/%s/%s", user.Did, tangled.RepoNSID, rkey) 646 614 l = l.With("aturi", aturi) 647 615 l.Info("wrote to PDS") 648 616 ··· 709 677 s.pages.HxLocation(w, fmt.Sprintf("/%s", repoDid)) 710 678 default: 711 679 handle := s.pages.DisplayHandle(r.Context(), user.Did) 712 - s.pages.HxLocation(w, fmt.Sprintf("/%s/%s", handle, repoName)) 680 + s.pages.HxLocation(w, fmt.Sprintf("/%s/%s", handle, rkey)) 713 681 } 714 682 } 715 683 } 716 684 685 + func rkeyOccupied(ctx context.Context, client *atclient.APIClient, did, rkey string) bool { 686 + probeCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 687 + defer cancel() 688 + resp, err := comatproto.RepoGetRecord(probeCtx, client, "", tangled.RepoNSID, did, rkey) 689 + return err == nil && resp != nil 690 + } 691 + 717 692 // this is used to rollback changes made to the PDS 718 693 // 719 694 // it is a no-op if the provided ATURI is empty
+3 -2
appview/strings/strings.go
··· 149 149 showRendered = r.URL.Query().Get("code") != "true" 150 150 } 151 151 152 - starCount, err := db.GetStarCount(s.Db, string.AtUri()) 152 + stringUri := string.AtUri().String() 153 + starCount, err := db.GetStarCount(s.Db, models.StarSubjectString, stringUri) 153 154 if err != nil { 154 155 l.Error("failed to get star count", "err", err) 155 156 } 156 157 user := s.OAuth.GetMultiAccountUser(r) 157 158 isStarred := false 158 159 if user != nil { 159 - isStarred = db.GetStarStatus(s.Db, user.Did, string.AtUri()) 160 + isStarred = db.GetStarStatus(s.Db, user.Did, stringUri) 160 161 } 161 162 162 163 s.Pages.SingleString(w, pages.SingleStringParams{

History

1 round 0 comments
sign up or login to add to the discussion
oyster.cafe submitted #0
1 commit
expand
appview: handlers, state, and templates for more record types w/ repoDID
no conflicts, ready to merge
expand 0 comments