Monorepo for Tangled
0
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

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

Lewis 1f60fce9 43d22a67

+257 -250
+3 -3
appview/issues/issues.go
··· 872 872 searchOpts := models.IssueSearchOptions{ 873 873 Keywords: tf.Keywords, 874 874 Phrases: tf.Phrases, 875 - RepoAt: f.RepoAt().String(), 875 + RepoDid: f.RepoDid, 876 876 IsOpen: isOpen, 877 877 AuthorDid: authorDid, 878 878 Labels: labels, ··· 932 932 } 933 933 } else { 934 934 filters := []orm.Filter{ 935 - orm.FilterEq("repo_at", f.RepoAt()), 935 + orm.FilterEq("repo_did", f.RepoDid), 936 936 } 937 937 if isOpen != nil { 938 938 openInt := 0 ··· 1013 1013 mentions, references := rp.mentionsResolver.Resolve(r.Context(), body) 1014 1014 1015 1015 issue := &models.Issue{ 1016 - RepoAt: f.RepoAt(), 1016 + RepoDid: syntax.DID(f.RepoDid), 1017 1017 Rkey: tid.TID(), 1018 1018 Title: r.FormValue("title"), 1019 1019 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" ··· 219 221 l := mw.logger.With("middleware", "ResolveRepo") 220 222 repoName := chi.URLParam(req, "repo") 221 223 repoName = strings.TrimSuffix(repoName, ".git") 224 + rkey := strings.ToLower(repoName) 222 225 223 226 id, ok := req.Context().Value("resolvedId").(identity.Identity) 224 227 if !ok { ··· 230 233 repo, err := db.GetRepo( 231 234 mw.db, 232 235 orm.FilterEq("did", id.DID.String()), 233 - orm.FilterEq("name", repoName), 236 + orm.FilterEq("rkey", rkey), 234 237 ) 235 238 if err != nil { 236 - l.Error("failed to resolve repo", "err", err) 239 + if !errors.Is(err, sql.ErrNoRows) { 240 + l.Error("failed to resolve repo", "err", err) 241 + http.Error(w, "internal server error", http.StatusInternalServerError) 242 + return 243 + } 244 + hint, hintErr := db.LookupRepoRename(mw.db, id.DID.String(), rkey) 245 + if hintErr != nil && !errors.Is(hintErr, sql.ErrNoRows) { 246 + l.Error("failed to lookup repo rename hint", "err", hintErr) 247 + } 248 + if hint != nil { 249 + parts := strings.SplitN(strings.TrimPrefix(req.URL.Path, "/"), "/", 3) 250 + target := "/" + parts[0] + "/" + hint.Rkey 251 + if len(parts) == 3 { 252 + target += "/" + parts[2] 253 + } 254 + if req.URL.RawQuery != "" { 255 + target += "?" + req.URL.RawQuery 256 + } 257 + http.Redirect(w, req, target, http.StatusMovedPermanently) 258 + return 259 + } 237 260 w.WriteHeader(http.StatusNotFound) 238 261 mw.pages.ErrorKnot404(w) 239 262 return ··· 266 289 return 267 290 } 268 291 269 - pr, err := db.GetPull(mw.db, orm.FilterEq("repo_at", f.RepoAt()), orm.FilterEq("pull_id", prIdInt)) 292 + pr, err := db.GetPull(mw.db, orm.FilterEq("repo_did", f.RepoDid), orm.FilterEq("pull_id", prIdInt)) 270 293 if err != nil { 271 294 l.Error("failed to get pull and comments", "err", err) 272 295 mw.pages.Error404(w) ··· 309 332 return 310 333 } 311 334 312 - issue, err := db.GetIssue(mw.db, f.RepoAt(), issueId) 335 + issue, err := db.GetIssue(mw.db, f.RepoDid, issueId) 313 336 if err != nil { 314 337 l.Error("failed to get issues", "err", err) 315 338 mw.pages.Error404(w) ··· 345 368 if r.URL.Query().Get("go-get") == "1" { 346 369 modulePath := userutil.FlattenDid(fullName) 347 370 if strings.Contains(modulePath, ":") { 348 - modulePath = userutil.FlattenDid(f.Did) + "/" + f.Name 371 + modulePath = userutil.FlattenDid(f.Did) + "/" + f.Rkey 349 372 } 350 373 html := fmt.Sprintf( 351 374 `<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
··· 29 29 {{ $repoPath := .RepoInfo.FullName }} 30 30 <a href="/{{ $repoPath }}/tree/{{ pathEscape .Pull.PullSource.Branch }}" class="no-underline hover:underline">{{ .Pull.PullSource.Branch }}</a> 31 31 {{ else if .Pull.PullSource.Repo }} 32 - {{ $repoPath := print (resolve .Pull.PullSource.Repo.Did) "/" .Pull.PullSource.Repo.Name }} 32 + {{ $repoPath := print (resolve .Pull.PullSource.Repo.Did) "/" .Pull.PullSource.Repo.Rkey }} 33 33 <a href="/{{ $repoPath }}" class="no-underline hover:underline">{{ $repoPath }}</a>: 34 34 <a href="/{{ $repoPath }}/tree/{{ pathEscape .Pull.PullSource.Branch }}" class="no-underline hover:underline">{{ .Pull.PullSource.Branch }}</a> 35 35 {{ 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> 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> 303 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.
+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
··· 38 38 {{ with $source }} 39 39 {{ $sourceDid := resolve .Did }} 40 40 forked 41 - <a href="/{{ $sourceDid }}/{{ .Name }}"class="no-underline hover:underline"> 41 + <a href="/{{ $sourceDid }}/{{ .Rkey }}"class="no-underline hover:underline"> 42 42 {{ $sourceDid }}/{{ .Name }} 43 43 </a> 44 44 to 45 - <a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline">{{ $repo.Name }}</a> 45 + <a href="/{{ $userHandle }}/{{ $repo.Rkey }}" class="no-underline hover:underline">{{ $repo.Name }}</a> 46 46 {{ else }} 47 47 created 48 - <a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline"> 48 + <a href="/{{ $userHandle }}/{{ $repo.Rkey }}" class="no-underline hover:underline"> 49 49 {{ $repo.Name }} 50 50 </a> 51 51 {{ end }} ··· 66 66 <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"> 67 67 {{ template "user/fragments/picHandleLink" $starrerHandle }} 68 68 starred 69 - <a href="/{{ $repoOwnerHandle }}/{{ .Repo.Name }}" class="no-underline hover:underline"> 69 + <a href="/{{ $repoOwnerHandle }}/{{ .Repo.Rkey }}" class="no-underline hover:underline"> 70 70 {{ $repoOwnerHandle | truncateAt30 }}/{{ .Repo.Name }} 71 71 </a> 72 72 <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 ) ··· 438 438 return pages.Unknown 439 439 } 440 440 441 - var sourceRepo syntax.ATURI 442 - if pull.PullSource.RepoAt != nil { 443 - sourceRepo = *pull.PullSource.RepoAt 441 + var sourceRepoDid string 442 + if pull.PullSource.RepoDid != nil { 443 + sourceRepoDid = string(*pull.PullSource.RepoDid) 444 444 } else { 445 - sourceRepo = repo.RepoAt() 445 + sourceRepoDid = repo.RepoDid 446 446 } 447 447 448 448 xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url} 449 - branchResp, err := tangled.GitTempGetBranch(r.Context(), xrpcc, pull.PullSource.Branch, sourceRepo.String()) 449 + branchResp, err := tangled.GitTempGetBranch(r.Context(), xrpcc, pull.PullSource.Branch, sourceRepoDid) 450 450 if err != nil { 451 451 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 452 452 s.logger.Error("failed to call XRPC repo.branches", "xrpcerr", xrpcerr, "err", err, "pull_id", pull.PullId, "branch", pull.PullSource.Branch) ··· 586 586 searchOpts := models.PullSearchOptions{ 587 587 Keywords: tf.Keywords, 588 588 Phrases: tf.Phrases, 589 - RepoAt: f.RepoAt().String(), 589 + RepoDid: f.RepoDid, 590 590 State: state, 591 591 AuthorDid: authorDid, 592 592 Labels: labels, ··· 658 658 } 659 659 } else { 660 660 filters := []orm.Filter{ 661 - orm.FilterEq("repo_at", f.RepoAt()), 661 + orm.FilterEq("repo_did", f.RepoDid), 662 662 } 663 663 if state != nil { 664 664 filters = append(filters, orm.FilterEq("state", *state)) ··· 678 678 for _, p := range pulls { 679 679 var pullSourceRepo *models.Repo 680 680 if p.PullSource != nil { 681 - if p.PullSource.RepoAt != nil { 682 - pullSourceRepo, err = db.GetRepoByAtUri(s.db, p.PullSource.RepoAt.String()) 681 + if p.PullSource.RepoDid != nil { 682 + pullSourceRepo, err = db.GetRepoByDid(s.db, string(*p.PullSource.RepoDid)) 683 683 if err != nil { 684 - l.Error("failed to get repo by at uri", "err", err, "repo_at", p.PullSource.RepoAt.String()) 684 + l.Error("failed to get repo by did", "err", err, "repo_did", p.PullSource.RepoDid.String()) 685 685 continue 686 686 } else { 687 687 p.PullSource.Repo = pullSourceRepo ··· 748 748 s.db, 749 749 len(shas), 750 750 orm.FilterEq("p.repo_owner", f.Did), 751 - orm.FilterEq("p.repo_name", f.Name), 751 + orm.FilterEq("p.repo_name", f.Rkey), 752 752 orm.FilterEq("p.knot", f.Knot), 753 753 orm.FilterIn("p.sha", shas), 754 754 ) ··· 881 881 882 882 comment := &models.PullComment{ 883 883 OwnerDid: user.Did, 884 - RepoAt: f.RepoAt().String(), 884 + RepoDid: string(f.RepoDid), 885 885 PullId: pull.PullId, 886 886 Body: body, 887 887 CommentAt: atResp.Uri, ··· 1151 1151 1152 1152 repoString := strings.SplitN(forkRepo, "/", 2) 1153 1153 forkOwnerDid := repoString[0] 1154 - repoName := repoString[1] 1155 - fork, err := db.GetForkByDid(s.db, forkOwnerDid, repoName) 1154 + forkRkey := strings.ToLower(repoString[1]) 1155 + fork, err := db.GetForkByDid(s.db, forkOwnerDid, forkRkey) 1156 1156 if errors.Is(err, sql.ErrNoRows) { 1157 1157 s.pages.Notice(w, "pull", "No such fork.") 1158 1158 return 1159 1159 } else if err != nil { 1160 - l.Error("failed to fetch fork", "err", err, "fork_owner_did", forkOwnerDid, "repo_name", repoName) 1160 + l.Error("failed to fetch fork", "err", err, "fork_owner_did", forkOwnerDid, "fork_rkey", forkRkey) 1161 1161 s.pages.Notice(w, "pull", "Failed to fetch fork.") 1162 1162 return 1163 1163 } ··· 1237 1237 return 1238 1238 } 1239 1239 1240 - forkAtUri := fork.RepoAt() 1241 - var forkDid *syntax.DID 1242 - if fork.RepoDid != "" { 1243 - forkDid = new(syntax.DID) 1244 - *forkDid = syntax.DID(fork.RepoDid) 1245 - } 1246 - 1240 + forkDid := syntax.DID(fork.RepoDid) 1247 1241 pullSource := &models.PullSource{ 1248 1242 Branch: sourceBranch, 1249 - RepoAt: &forkAtUri, 1250 - RepoDid: forkDid, 1243 + RepoDid: &forkDid, 1251 1244 } 1252 1245 1253 1246 s.createPullRequest(w, r, repo, userDid, title, body, targetBranch, patch, combined, sourceRev, pullSource, isStacked) ··· 1336 1329 Body: body, 1337 1330 TargetBranch: targetBranch, 1338 1331 OwnerDid: userDid.String(), 1339 - RepoAt: repo.RepoAt(), 1332 + RepoDid: syntax.DID(repo.RepoDid), 1340 1333 Rkey: rkey, 1341 1334 Mentions: mentions, 1342 1335 References: references, ··· 1375 1368 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1376 1369 return 1377 1370 } 1378 - pullId, err := db.NextPullId(tx, repo.RepoAt()) 1371 + pullId, err := db.NextPullId(tx, repo.RepoDid) 1379 1372 if err != nil { 1380 1373 s.logger.Error("failed to get pull id", "err", err) 1381 1374 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") ··· 1867 1860 return 1868 1861 } 1869 1862 1870 - forkRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String()) 1863 + forkRepo, err := db.GetRepoByDid(s.db, string(*pull.PullSource.RepoDid)) 1871 1864 if err != nil { 1872 - l.Error("failed to get source repo", "err", err, "repo_at", pull.PullSource.RepoAt.String()) 1865 + l.Error("failed to get source repo", "err", err, "repo_did", pull.PullSource.RepoDid.String()) 1873 1866 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1874 1867 return 1875 1868 } ··· 2167 2160 continue 2168 2161 } 2169 2162 2170 - err := db.AbandonPulls(tx, orm.FilterEq("repo_at", p.RepoAt), orm.FilterEq("at_uri", p.AtUri())) 2163 + err := db.AbandonPulls(tx, orm.FilterEq("repo_did", string(p.RepoDid)), orm.FilterEq("at_uri", p.AtUri())) 2171 2164 if err != nil { 2172 2165 l.Error("failed to delete pull", "err", err, "pull_id", p.PullId) 2173 2166 s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") ··· 2398 2391 atUris = append(atUris, p.AtUri()) 2399 2392 p.State = models.PullMerged 2400 2393 } 2401 - err = db.MergePulls(tx, orm.FilterEq("repo_at", f.RepoAt()), orm.FilterIn("at_uri", atUris)) 2394 + err = db.MergePulls(tx, orm.FilterEq("repo_did", string(f.RepoDid)), orm.FilterIn("at_uri", atUris)) 2402 2395 if err != nil { 2403 2396 l.Error("failed to update pull request status in database", "err", err) 2404 2397 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") ··· 2475 2468 } 2476 2469 err = db.ClosePulls( 2477 2470 tx, 2478 - orm.FilterEq("repo_at", f.RepoAt()), 2471 + orm.FilterEq("repo_did", string(f.RepoDid)), 2479 2472 orm.FilterIn("at_uri", atUris), 2480 2473 ) 2481 2474 if err != nil { ··· 2552 2545 } 2553 2546 err = db.ReopenPulls( 2554 2547 tx, 2555 - orm.FilterEq("repo_at", f.RepoAt()), 2548 + orm.FilterEq("repo_did", string(f.RepoDid)), 2556 2549 orm.FilterIn("at_uri", atUris), 2557 2550 ) 2558 2551 if err != nil { ··· 2606 2599 Body: body, 2607 2600 TargetBranch: targetBranch, 2608 2601 OwnerDid: userDid.String(), 2609 - RepoAt: repo.RepoAt(), 2602 + RepoDid: syntax.DID(repo.RepoDid), 2610 2603 Rkey: rkey, 2611 2604 Mentions: mentions, 2612 2605 References: references, ··· 2645 2638 } 2646 2639 2647 2640 func ptrPullState(s models.PullState) *models.PullState { return &s } 2641 + 2642 + func repoPullTarget(repo *models.Repo, branch string) *tangled.RepoPull_Target { 2643 + return &tangled.RepoPull_Target{ 2644 + Branch: branch, 2645 + Repo: repo.RepoDid, 2646 + } 2647 + }
+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
··· 75 75 76 76 // fetch and add pull requests if requested 77 77 if opts.IncludePulls { 78 - pulls, err := db.GetPullsPaginated(rp.db, feedPagePerType, orm.FilterEq("repo_at", repo.RepoAt())) 78 + pulls, err := db.GetPullsPaginated(rp.db, feedPagePerType, orm.FilterEq("repo_did", repo.RepoDid)) 79 79 if err != nil { 80 80 return nil, err 81 81 } ··· 94 94 issues, err := db.GetIssuesPaginated( 95 95 rp.db, 96 96 feedPagePerType, 97 - orm.FilterEq("repo_at", repo.RepoAt()), 97 + orm.FilterEq("repo_did", repo.RepoDid), 98 98 ) 99 99 if err != nil { 100 100 return nil, err ··· 316 316 log.Println("failed to get resolved repo owner id") 317 317 return 318 318 } 319 - ownerSlashRepo := repoOwnerId.Handle.String() + "/" + f.Name 319 + ownerSlashRepo := repoOwnerId.Handle.String() + "/" + f.Rkey 320 320 321 321 opts := parseFeedOpts(r) 322 322 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
··· 34 34 var languageStats []types.RepoLanguageDetails 35 35 langs, err := db.GetRepoLanguages( 36 36 rp.db, 37 - orm.FilterEq("repo_at", f.RepoAt()), 37 + orm.FilterEq("repo_did", f.RepoDid), 38 38 orm.FilterEq("is_default_ref", 1), 39 39 ) 40 40 if err != nil {
+15 -20
appview/repo/repo.go
··· 139 139 } 140 140 141 141 // optimistic update 142 - err = db.UpdateSpindle(rp.db, newRepo.RepoAt().String(), spindlePtr) 142 + err = db.UpdateSpindle(rp.db, newRepo.RepoDid, spindlePtr) 143 143 if err != nil { 144 144 fail("Failed to update spindle. Try again later.", err) 145 145 return ··· 319 319 } 320 320 321 321 if err = db.SubscribeLabel(tx, &models.RepoLabel{ 322 - RepoAt: f.RepoAt(), 322 + RepoDid: syntax.DID(f.RepoDid), 323 323 LabelAt: label.AtUri(), 324 324 }); err != nil { 325 325 fail("Failed to subscribe to label.", err) ··· 422 422 423 423 err = db.UnsubscribeLabel( 424 424 tx, 425 - orm.FilterEq("repo_at", f.RepoAt()), 425 + orm.FilterEq("repo_did", f.RepoDid), 426 426 orm.FilterEq("label_at", removedAt), 427 427 ) 428 428 if err != nil { ··· 514 514 515 515 for _, l := range labelAts { 516 516 err = db.SubscribeLabel(tx, &models.RepoLabel{ 517 - RepoAt: f.RepoAt(), 517 + RepoDid: syntax.DID(f.RepoDid), 518 518 LabelAt: syntax.ATURI(l), 519 519 }) 520 520 if err != nil { ··· 595 595 596 596 err = db.UnsubscribeLabel( 597 597 rp.db, 598 - orm.FilterEq("repo_at", f.RepoAt()), 598 + orm.FilterEq("repo_did", f.RepoDid), 599 599 orm.FilterIn("label_at", labelAts), 600 600 ) 601 601 if err != nil { ··· 804 804 Did: syntax.DID(currentUser.Did), 805 805 Rkey: rkey, 806 806 SubjectDid: collaboratorIdent.DID, 807 - RepoAt: f.RepoAt(), 807 + RepoDid: syntax.DID(f.RepoDid), 808 808 Created: createdAt, 809 809 }) 810 810 if err != nil { ··· 920 920 } 921 921 922 922 // remove repo from db 923 - err = db.RemoveRepo(tx, f.Did, f.Name) 923 + err = db.RemoveRepo(tx, f.Did, f.Rkey) 924 924 if err != nil { 925 925 rp.pages.Notice(w, noticeId, "Failed to update appview") 926 926 return ··· 1039 1039 } 1040 1040 1041 1041 // choose a name for a fork 1042 - forkName := r.FormValue("repo_name") 1042 + forkName := strings.ToLower(r.FormValue("repo_name")) 1043 1043 if forkName == "" { 1044 1044 rp.pages.Notice(w, "repo", "Repository name cannot be empty.") 1045 1045 return ··· 1073 1073 forkSourceUrl := fmt.Sprintf("%s://%s/%s", uri, f.Knot, f.RepoIdentifier()) 1074 1074 l = l.With("cloneUrl", forkSourceUrl) 1075 1075 1076 - rkey := tid.TID() 1076 + rkey := strings.ToLower(forkName) 1077 1077 1078 1078 // TODO: this could coordinate better with the knot to receive a clone status 1079 1079 client, err := rp.oauth.ServiceClient( ··· 1091 1091 1092 1092 forkInput := &tangled.RepoCreate_Input{ 1093 1093 Rkey: rkey, 1094 - Name: forkName, 1094 + Name: rkey, 1095 1095 Source: &forkSourceUrl, 1096 1096 } 1097 1097 createResp, err := tangled.RepoCreate( ··· 1122 1122 1123 1123 repo := &models.Repo{ 1124 1124 Did: user.Did, 1125 - Name: forkName, 1125 + Name: rkey, 1126 1126 Knot: targetKnot, 1127 1127 Rkey: rkey, 1128 1128 Source: forkSource, ··· 1270 1270 return 1271 1271 } 1272 1272 1273 - starrers, err := db.GetStars(rp.db, f.RepoAt()) 1273 + starrers, err := db.GetStars(rp.db, string(f.RepoDid)) 1274 1274 if err != nil { 1275 - l.Error("failed to fetch starrers", "err", err, "repoAt", f.RepoAt()) 1275 + l.Error("failed to fetch starrers", "err", err, "repoDid", f.RepoDid) 1276 1276 return 1277 1277 } 1278 1278 ··· 1306 1306 } 1307 1307 1308 1308 func repoCollaboratorRecord(f *models.Repo, subject string, createdAt time.Time) *tangled.RepoCollaborator { 1309 - rec := &tangled.RepoCollaborator{ 1309 + return &tangled.RepoCollaborator{ 1310 1310 Subject: subject, 1311 1311 CreatedAt: createdAt.Format(time.RFC3339), 1312 + Repo: f.RepoDid, 1312 1313 } 1313 - s := string(f.RepoAt()) 1314 - rec.Repo = &s 1315 - if f.RepoDid != "" { 1316 - rec.RepoDid = &f.RepoDid 1317 - } 1318 - return rec 1319 1314 }
+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)) 178 + } 179 + if r.FormValue("event_repo_renamed") == "on" { 180 + events = append(events, string(models.WebhookEventRepoRenamed)) 175 181 } 176 182 177 183 if len(events) > 0 { ··· 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
··· 74 74 currentDir := extractCurrentDir(r.URL.EscapedPath()) 75 75 ref := chi.URLParam(r, "ref") 76 76 77 - repoAt := repo.RepoAt() 77 + repoDid := repo.RepoDid 78 78 isStarred := false 79 79 roles := repoinfo.RolesInRepo{} 80 80 if user != nil { 81 - isStarred = db.GetStarStatus(rr.execer, user.Did, repoAt) 81 + isStarred = db.GetStarStatus(rr.execer, user.Did, repoDid) 82 82 roles.Roles = rr.enforcer.GetPermissionsInRepo(user.Did, repo.Knot, repo.RepoIdentifier()) 83 83 } 84 84 85 85 stats := repo.RepoStats 86 86 if stats == nil { 87 - starCount, starErr := db.GetStarCount(rr.execer, repoAt) 87 + starCount, starErr := db.GetStarCount(rr.execer, models.StarSubjectRepo, repoDid) 88 88 if starErr != nil { 89 - log.Println("failed to get star count for ", repoAt) 89 + log.Println("failed to get star count for ", repoDid) 90 90 } 91 - issueCount, err := db.GetIssueCount(rr.execer, repoAt) 91 + issueCount, err := db.GetIssueCount(rr.execer, repoDid) 92 92 if err != nil { 93 - log.Println("failed to get issue count for ", repoAt) 93 + log.Println("failed to get issue count for ", repoDid) 94 94 } 95 - pullCount, err := db.GetPullCount(rr.execer, repoAt) 95 + pullCount, err := db.GetPullCount(rr.execer, repoDid) 96 96 if err != nil { 97 - log.Println("failed to get pull count for ", repoAt) 97 + log.Println("failed to get pull count for ", repoDid) 98 98 } 99 99 stats = &models.RepoStats{ 100 100 StarCount: starCount, ··· 120 120 // this is basically a models.Repo 121 121 OwnerDid: ownerId.DID.String(), 122 122 OwnerHandle: ownerId.Handle.String(), 123 + RepoDid: repo.RepoDid, 123 124 Name: repo.Name, 124 125 Rkey: repo.Rkey, 125 126 Description: repo.Description,
+2 -2
appview/settings/settings.go
··· 225 225 226 226 // Delete each repo's R2 objects. 227 227 for _, sc := range siteConfigs { 228 - if err := sites.Delete(ctx, s.CfClient, user.Did, sc.RepoName); err != nil { 229 - s.Logger.Error("releaseSitesDomain: R2 delete failed", "did", user.Did, "repo", sc.RepoName, "err", err) 228 + if err := sites.Delete(ctx, s.CfClient, user.Did, sc.RepoRkey); err != nil { 229 + s.Logger.Error("releaseSitesDomain: R2 delete failed", "did", user.Did, "repo", sc.RepoRkey, "err", err) 230 230 } 231 231 } 232 232
+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) 242 + return fmt.Errorf("empty language data for repo: %s", record.Repo) 253 243 } 254 244 255 - ownerDid := "" 256 - if record.OwnerDid != nil { 257 - ownerDid = *record.OwnerDid 258 - } 259 - 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
··· 606 606 func (s *State) createPullRequestItem(pull *models.Pull, owner *identity.Identity, author *feeds.Author) *feeds.Item { 607 607 return &feeds.Item{ 608 608 Title: fmt.Sprintf("%s created pull request '%s' in @%s/%s", author.Name, pull.Title, owner.Handle, pull.Repo.Name), 609 - 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"}, 609 + 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"}, 610 610 Created: pull.Created, 611 611 Author: author, 612 612 } ··· 615 615 func (s *State) createIssueItem(issue *models.Issue, owner *identity.Identity, author *feeds.Author) *feeds.Item { 616 616 return &feeds.Item{ 617 617 Title: fmt.Sprintf("%s created issue '%s' in @%s/%s", author.Name, issue.Title, owner.Handle, issue.Repo.Name), 618 - 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"}, 618 + 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"}, 619 619 Created: issue.Created, 620 620 Author: author, 621 621 } ··· 635 635 636 636 return &feeds.Item{ 637 637 Title: title, 638 - 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 638 + 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 639 639 Created: repo.Repo.Created, 640 640 Author: author, 641 641 }, nil
+1 -1
appview/state/router.go
··· 58 58 if len(pathParts) > 1 { 59 59 remaining = "/" + pathParts[1] 60 60 } 61 - rewritten := "/" + repo.Did + "/" + repo.Name + remaining 61 + rewritten := "/" + repo.Did + "/" + repo.Rkey + remaining 62 62 r2 := r.Clone(r.Context()) 63 63 r2.URL.Path = rewritten 64 64 r2.URL.RawPath = rewritten
+37 -62
appview/state/state.go
··· 37 37 tlog "tangled.org/core/log" 38 38 "tangled.org/core/orm" 39 39 "tangled.org/core/rbac" 40 - "tangled.org/core/tid" 41 40 42 41 comatproto "github.com/bluesky-social/indigo/api/atproto" 43 42 "github.com/bluesky-social/indigo/atproto/atclient" ··· 129 128 tangled.RepoIssueCommentNSID, 130 129 tangled.LabelDefinitionNSID, 131 130 tangled.LabelOpNSID, 131 + tangled.RepoNSID, 132 132 }, 133 133 nil, 134 134 tlog.SubLogger(logger, "jetstream"), ··· 145 145 146 146 if err := BackfillDefaultDefs(d, res, config.Label.DefaultLabelDefs); err != nil { 147 147 return nil, fmt.Errorf("failed to backfill default label defs: %w", err) 148 - } 149 - 150 - ingester := appview.Ingester{ 151 - Db: wrapper, 152 - Enforcer: enforcer, 153 - IdResolver: res, 154 - Config: config, 155 - Logger: log.SubLogger(logger, "ingester"), 156 - Validator: validator, 157 - } 158 - err = jc.StartJetstream(ctx, ingester.Ingest()) 159 - if err != nil { 160 - return nil, fmt.Errorf("failed to start jetstream watcher: %w", err) 161 148 } 162 149 163 150 var notifiers []notify.Notifier ··· 175 162 176 163 notifier := notify.NewMergedNotifier(notifiers) 177 164 notifier = lognotify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) 165 + 166 + ingester := appview.Ingester{ 167 + Db: d, 168 + Enforcer: enforcer, 169 + IdResolver: res, 170 + Config: config, 171 + Logger: log.SubLogger(logger, "ingester"), 172 + Validator: validator, 173 + Notifier: notifier, 174 + } 175 + err = jc.StartJetstream(ctx, ingester.Ingest()) 176 + if err != nil { 177 + return nil, fmt.Errorf("failed to start jetstream watcher: %w", err) 178 + } 178 179 179 180 var cfClient *cloudflare.Client 180 181 if config.Cloudflare.ApiToken != "" { ··· 349 350 } 350 351 } 351 352 352 - func validateRepoName(name string) error { 353 - // check for path traversal attempts 354 - if name == "." || name == ".." || 355 - strings.Contains(name, "/") || strings.Contains(name, "\\") { 356 - return fmt.Errorf("Repository name contains invalid path characters") 357 - } 358 - 359 - // check for sequences that could be used for traversal when normalized 360 - if strings.Contains(name, "./") || strings.Contains(name, "../") || 361 - strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { 362 - return fmt.Errorf("Repository name contains invalid path sequence") 363 - } 364 - 365 - // then continue with character validation 366 - for _, char := range name { 367 - if !((char >= 'a' && char <= 'z') || 368 - (char >= 'A' && char <= 'Z') || 369 - (char >= '0' && char <= '9') || 370 - char == '-' || char == '_' || char == '.') { 371 - return fmt.Errorf("Repository name can only contain alphanumeric characters, periods, hyphens, and underscores") 372 - } 373 - } 374 - 375 - // additional check to prevent multiple sequential dots 376 - if strings.Contains(name, "..") { 377 - return fmt.Errorf("Repository name cannot contain sequential dots") 378 - } 379 - 380 - // if all checks pass 381 - return nil 382 - } 383 - 384 - func stripGitExt(name string) string { 385 - return strings.TrimSuffix(name, ".git") 386 - } 387 - 388 353 func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) { 389 354 switch r.Method { 390 355 case http.MethodGet: ··· 420 385 return 421 386 } 422 387 423 - if err := validateRepoName(repoName); err != nil { 388 + if err := models.ValidateRepoName(repoName); err != nil { 424 389 s.pages.Notice(w, "repo", err.Error()) 425 390 return 426 391 } 427 - repoName = stripGitExt(repoName) 428 - l = l.With("repoName", repoName) 392 + repoName = models.StripGitExt(repoName) 393 + rkey := strings.ToLower(repoName) 394 + l = l.With("repoName", repoName, "rkey", rkey) 429 395 430 396 defaultBranch := r.FormValue("branch") 431 397 if defaultBranch == "" { ··· 451 417 existingRepo, err := db.GetRepo( 452 418 s.db, 453 419 orm.FilterEq("did", user.Did), 454 - orm.FilterEq("name", repoName), 420 + orm.FilterEq("rkey", rkey), 455 421 ) 456 422 if err == nil && existingRepo != nil { 457 423 l.Info("repo exists") ··· 459 425 return 460 426 } 461 427 462 - rkey := tid.TID() 463 - 464 428 client, err := s.oauth.ServiceClient( 465 429 r, 466 430 oauth.WithService(domain), ··· 475 439 476 440 input := &tangled.RepoCreate_Input{ 477 441 Rkey: rkey, 478 - Name: repoName, 442 + Name: rkey, 479 443 DefaultBranch: &defaultBranch, 480 444 } 481 445 createResp, err := tangled.RepoCreate( ··· 529 493 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 530 494 if dErr := tangled.RepoDelete(ctx, deleteClient, &tangled.RepoDelete_Input{ 531 495 Did: user.Did, 532 - Name: repoName, 496 + Name: rkey, 533 497 Rkey: rkey, 534 498 }); dErr != nil { 535 499 cancel() ··· 553 517 return 554 518 } 555 519 556 - atresp, err := comatproto.RepoPutRecord(r.Context(), atpClient, &comatproto.RepoPutRecord_Input{ 520 + _, err = comatproto.RepoPutRecord(r.Context(), atpClient, &comatproto.RepoPutRecord_Input{ 557 521 Collection: tangled.RepoNSID, 558 522 Repo: user.Did, 559 523 Rkey: rkey, ··· 564 528 if err != nil { 565 529 l.Info("PDS write failed", "err", err) 566 530 cleanupKnot() 567 - s.pages.Notice(w, "repo", "Failed to announce repository creation.") 531 + if rkeyOccupied(r.Context(), atpClient, user.Did, rkey) { 532 + s.pages.Notice(w, "repo", fmt.Sprintf("You already have a repository named %q.", rkey)) 533 + } else { 534 + s.pages.Notice(w, "repo", "Failed to announce repository creation.") 535 + } 568 536 return 569 537 } 570 538 571 - aturi := atresp.Uri 539 + aturi := fmt.Sprintf("at://%s/%s/%s", user.Did, tangled.RepoNSID, rkey) 572 540 l = l.With("aturi", aturi) 573 541 l.Info("wrote to PDS") 574 542 ··· 633 601 if repoDid != "" { 634 602 s.pages.HxLocation(w, fmt.Sprintf("/%s", repoDid)) 635 603 } else { 636 - s.pages.HxLocation(w, fmt.Sprintf("/%s/%s", user.Did, repoName)) 604 + s.pages.HxLocation(w, fmt.Sprintf("/%s/%s", user.Did, rkey)) 637 605 } 638 606 } 607 + } 608 + 609 + func rkeyOccupied(ctx context.Context, client *atclient.APIClient, did, rkey string) bool { 610 + probeCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 611 + defer cancel() 612 + resp, err := comatproto.RepoGetRecord(probeCtx, client, "", tangled.RepoNSID, did, rkey) 613 + return err == nil && resp != nil 639 614 } 640 615 641 616 // this is used to rollback changes made to the PDS
+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{