+68
-44
appview/db/repos.go
+68
-44
appview/db/repos.go
···
3
3
import (
4
4
"database/sql"
5
5
"fmt"
6
+
"log"
7
+
"slices"
6
8
"strings"
7
9
"time"
8
10
···
72
74
}
73
75
74
76
func GetRepos(e Execer, filters ...filter) ([]Repo, error) {
75
-
repoMap := make(map[syntax.ATURI]Repo)
77
+
repoMap := make(map[syntax.ATURI]*Repo)
76
78
77
79
var conditions []string
78
80
var args []any
···
139
141
repo.Spindle = spindle.String
140
142
}
141
143
142
-
repoMap[repo.RepoAt()] = repo
144
+
repo.RepoStats = &RepoStats{}
145
+
repoMap[repo.RepoAt()] = &repo
143
146
}
144
147
145
148
if err = rows.Err(); err != nil {
···
148
151
149
152
inClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoMap)), ", ")
150
153
args = make([]any, len(repoMap))
154
+
155
+
i := 0
151
156
for _, r := range repoMap {
152
-
args = append(args, r.RepoAt())
157
+
args[i] = r.RepoAt()
158
+
i++
159
+
}
160
+
161
+
languageQuery := fmt.Sprintf(
162
+
`
163
+
select
164
+
repo_at, language
165
+
from
166
+
repo_languages r1
167
+
where
168
+
repo_at IN (%s)
169
+
and is_default_ref = 1
170
+
and id = (
171
+
select id
172
+
from repo_languages r2
173
+
where r2.repo_at = r1.repo_at
174
+
and r2.is_default_ref = 1
175
+
order by bytes desc
176
+
limit 1
177
+
);
178
+
`,
179
+
inClause,
180
+
)
181
+
rows, err = e.Query(languageQuery, args...)
182
+
if err != nil {
183
+
return nil, fmt.Errorf("failed to execute lang query: %w ", err)
184
+
}
185
+
for rows.Next() {
186
+
var repoat, lang string
187
+
if err := rows.Scan(&repoat, &lang); err != nil {
188
+
log.Println("err", "err", err)
189
+
continue
190
+
}
191
+
if r, ok := repoMap[syntax.ATURI(repoat)]; ok {
192
+
r.RepoStats.Language = lang
193
+
}
194
+
}
195
+
if err = rows.Err(); err != nil {
196
+
return nil, fmt.Errorf("failed to execute lang query: %w ", err)
153
197
}
154
198
155
199
starCountQuery := fmt.Sprintf(
···
168
212
var repoat string
169
213
var count int
170
214
if err := rows.Scan(&repoat, &count); err != nil {
215
+
log.Println("err", "err", err)
171
216
continue
172
217
}
173
218
if r, ok := repoMap[syntax.ATURI(repoat)]; ok {
···
196
241
var repoat string
197
242
var open, closed int
198
243
if err := rows.Scan(&repoat, &open, &closed); err != nil {
244
+
log.Println("err", "err", err)
199
245
continue
200
246
}
201
247
if r, ok := repoMap[syntax.ATURI(repoat)]; ok {
···
236
282
var repoat string
237
283
var open, merged, closed, deleted int
238
284
if err := rows.Scan(&repoat, &open, &merged, &closed, &deleted); err != nil {
285
+
log.Println("err", "err", err)
239
286
continue
240
287
}
241
288
if r, ok := repoMap[syntax.ATURI(repoat)]; ok {
···
251
298
252
299
var repos []Repo
253
300
for _, r := range repoMap {
254
-
repos = append(repos, r)
301
+
repos = append(repos, *r)
255
302
}
256
303
304
+
slices.SortFunc(repos, func(a, b Repo) int {
305
+
if a.Created.After(b.Created) {
306
+
return 1
307
+
}
308
+
return -1
309
+
})
310
+
257
311
return repos, nil
258
312
}
259
313
···
509
563
}
510
564
511
565
func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) {
512
-
var repos []Repo
513
-
514
-
rows, err := e.Query(
515
-
`select
516
-
r.did, r.name, r.knot, r.rkey, r.description, r.created, count(s.id) as star_count
517
-
from
518
-
repos r
519
-
join
520
-
collaborators c on r.id = c.repo
521
-
left join
522
-
stars s on r.at_uri = s.repo_at
523
-
where
524
-
c.did = ?
525
-
group by
526
-
r.id;`, collaborator)
566
+
rows, err := e.Query(`select repo from collaborators where did = ?`, collaborator)
527
567
if err != nil {
528
568
return nil, err
529
569
}
530
570
defer rows.Close()
531
571
572
+
var repoIds []int
532
573
for rows.Next() {
533
-
var repo Repo
534
-
var repoStats RepoStats
535
-
var createdAt string
536
-
var nullableDescription sql.NullString
537
-
538
-
err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount)
574
+
var id int
575
+
err := rows.Scan(&id)
539
576
if err != nil {
540
577
return nil, err
541
578
}
542
-
543
-
if nullableDescription.Valid {
544
-
repo.Description = nullableDescription.String
545
-
} else {
546
-
repo.Description = ""
547
-
}
548
-
549
-
createdAtTime, err := time.Parse(time.RFC3339, createdAt)
550
-
if err != nil {
551
-
repo.Created = time.Now()
552
-
} else {
553
-
repo.Created = createdAtTime
554
-
}
555
-
556
-
repo.RepoStats = &repoStats
557
-
558
-
repos = append(repos, repo)
579
+
repoIds = append(repoIds, id)
559
580
}
560
-
561
581
if err := rows.Err(); err != nil {
562
582
return nil, err
563
583
}
584
+
if repoIds == nil {
585
+
return nil, nil
586
+
}
564
587
565
-
return repos, nil
588
+
return GetRepos(e, FilterIn("id", repoIds))
566
589
}
567
590
568
591
type RepoStats struct {
592
+
Language string
569
593
StarCount int
570
594
IssueCount IssueCount
571
595
PullCount PullCount
+2
appview/pages/funcmap.go
+2
appview/pages/funcmap.go
···
17
17
"time"
18
18
19
19
"github.com/dustin/go-humanize"
20
+
"github.com/go-enry/go-enry/v2"
20
21
"github.com/microcosm-cc/bluemonday"
21
22
"tangled.sh/tangled.sh/core/appview/filetree"
22
23
"tangled.sh/tangled.sh/core/appview/pages/markup"
···
241
242
},
242
243
243
244
"tinyAvatar": p.tinyAvatar,
245
+
"langColor": enry.GetColor,
244
246
}
245
247
}
246
248
+57
appview/pages/templates/user/fragments/repoCard.html
+57
appview/pages/templates/user/fragments/repoCard.html
···
1
+
{{ define "user/fragments/repoCard" }}
2
+
{{ $root := index . 0 }}
3
+
{{ $repo := index . 1 }}
4
+
{{ $fullName := index . 2 }}
5
+
6
+
{{ with $repo }}
7
+
<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
8
+
<div class="font-medium dark:text-white">
9
+
{{- if $fullName -}}
10
+
<a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}">{{ index $root.DidHandleMap .Did }}/{{ .Name }}</a>
11
+
{{- else -}}
12
+
<a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}">{{ .Name }}</a>
13
+
{{- end -}}
14
+
</div>
15
+
{{ with .Description }}
16
+
<div class="text-gray-600 dark:text-gray-300 text-sm">
17
+
{{ . }}
18
+
</div>
19
+
{{ end }}
20
+
21
+
{{ if .RepoStats }}
22
+
{{ block "repoStats" .RepoStats }} {{ end }}
23
+
{{ end }}
24
+
</div>
25
+
{{ end }}
26
+
{{ end }}
27
+
28
+
{{ define "repoStats" }}
29
+
<div class="text-gray-400 pt-4 text-sm font-mono inline-flex gap-4 mt-auto">
30
+
{{ with .Language }}
31
+
<div class="flex gap-2 items-center text-sm">
32
+
<div class="size-2 rounded-full" style="background-color: {{ langColor . }};"></div>
33
+
<span>{{ . }}</span>
34
+
</div>
35
+
{{ end }}
36
+
{{ with .StarCount }}
37
+
<div class="flex gap-1 items-center text-sm">
38
+
{{ i "star" "w-3 h-3 fill-current" }}
39
+
<span>{{ . }}</span>
40
+
</div>
41
+
{{ end }}
42
+
{{ with .IssueCount.Open }}
43
+
<div class="flex gap-1 items-center text-sm">
44
+
{{ i "circle-dot" "w-3 h-3" }}
45
+
<span>{{ . }}</span>
46
+
</div>
47
+
{{ end }}
48
+
{{ with .PullCount.Open }}
49
+
<div class="flex gap-1 items-center text-sm">
50
+
{{ i "git-pull-request-arrow" "w-3 h-3" }}
51
+
<span>{{ . }}</span>
52
+
</div>
53
+
{{ end }}
54
+
</div>
55
+
{{ end }}
56
+
57
+
+2
-44
appview/pages/templates/user/profile.html
+2
-44
appview/pages/templates/user/profile.html
···
260
260
</div>
261
261
<div id="repos" class="grid grid-cols-1 gap-4">
262
262
{{ range .Repos }}
263
-
<div
264
-
id="repo-card"
265
-
class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
266
-
<div id="repo-card-name" class="font-medium">
267
-
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}/{{ .Name }}"
268
-
>{{ .Name }}</a
269
-
>
270
-
</div>
271
-
{{ if .Description }}
272
-
<div class="text-gray-600 dark:text-gray-300 text-sm">
273
-
{{ .Description }}
274
-
</div>
275
-
{{ end }}
276
-
<div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto">
277
-
{{ if .RepoStats.StarCount }}
278
-
<div class="flex gap-1 items-center text-sm">
279
-
{{ i "star" "w-3 h-3 fill-current" }}
280
-
<span>{{ .RepoStats.StarCount }}</span>
281
-
</div>
282
-
{{ end }}
283
-
</div>
284
-
</div>
263
+
{{ template "user/fragments/repoCard" (list $ . false) }}
285
264
{{ else }}
286
265
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
287
266
{{ end }}
···
295
274
<p class="text-sm font-bold p-2 dark:text-white">COLLABORATING ON</p>
296
275
<div id="collaborating" class="grid grid-cols-1 gap-4">
297
276
{{ range .CollaboratingRepos }}
298
-
<div
299
-
id="repo-card"
300
-
class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
301
-
<div id="repo-card-name" class="font-medium dark:text-white">
302
-
<a href="/{{ index $.DidHandleMap .Did }}/{{ .Name }}">
303
-
{{ index $.DidHandleMap .Did }}/{{ .Name }}
304
-
</a>
305
-
</div>
306
-
{{ if .Description }}
307
-
<div class="text-gray-600 dark:text-gray-300 text-sm">
308
-
{{ .Description }}
309
-
</div>
310
-
{{ end }}
311
-
<div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto">
312
-
{{ if .RepoStats.StarCount }}
313
-
<div class="flex gap-1 items-center text-sm">
314
-
{{ i "star" "w-3 h-3 fill-current" }}
315
-
<span>{{ .RepoStats.StarCount }}</span>
316
-
</div>
317
-
{{ end }}
318
-
</div>
319
-
</div>
277
+
{{ template "user/fragments/repoCard" (list $ . true) }}
320
278
{{ else }}
321
279
<p class="px-6 dark:text-white">This user is not collaborating.</p>
322
280
{{ end }}
+1
-22
appview/pages/templates/user/repos.html
+1
-22
appview/pages/templates/user/repos.html
···
22
22
<p class="text-sm font-bold p-2 dark:text-white">ALL REPOSITORIES</p>
23
23
<div id="repos" class="grid grid-cols-1 gap-4 mb-6">
24
24
{{ range .Repos }}
25
-
<div
26
-
id="repo-card"
27
-
class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
28
-
<div id="repo-card-name" class="font-medium">
29
-
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}/{{ .Name }}"
30
-
>{{ .Name }}</a
31
-
>
32
-
</div>
33
-
{{ if .Description }}
34
-
<div class="text-gray-600 dark:text-gray-300 text-sm">
35
-
{{ .Description }}
36
-
</div>
37
-
{{ end }}
38
-
<div class="text-gray-400 pt-1 text-sm font-mono inline-flex gap-4 mt-auto">
39
-
{{ if .RepoStats.StarCount }}
40
-
<div class="flex gap-1 items-center text-sm">
41
-
{{ i "star" "w-3 h-3 fill-current" }}
42
-
<span>{{ .RepoStats.StarCount }}</span>
43
-
</div>
44
-
{{ end }}
45
-
</div>
46
-
</div>
25
+
{{ template "user/fragments/repoCard" (list $ . false) }}
47
26
{{ else }}
48
27
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
49
28
{{ end }}
+13
-3
appview/repo/repo.go
+13
-3
appview/repo/repo.go
···
106
106
return
107
107
}
108
108
109
-
result, err := us.Tags(f.OwnerDid(), f.RepoName)
109
+
tagResult, err := us.Tags(f.OwnerDid(), f.RepoName)
110
110
if err != nil {
111
111
log.Println("failed to reach knotserver", err)
112
112
return
113
113
}
114
114
115
115
tagMap := make(map[string][]string)
116
-
for _, tag := range result.Tags {
116
+
for _, tag := range tagResult.Tags {
117
117
hash := tag.Hash
118
118
if tag.Tag != nil {
119
119
hash = tag.Tag.Target.String()
120
120
}
121
121
tagMap[hash] = append(tagMap[hash], tag.Name)
122
+
}
123
+
124
+
branchResult, err := us.Branches(f.OwnerDid(), f.RepoName)
125
+
if err != nil {
126
+
log.Println("failed to reach knotserver", err)
127
+
return
128
+
}
129
+
130
+
for _, branch := range branchResult.Branches {
131
+
hash := branch.Hash
132
+
tagMap[hash] = append(tagMap[hash], branch.Name)
122
133
}
123
134
124
135
user := rp.oauth.GetUser(r)
···
154
165
VerifiedCommits: vc,
155
166
Pipelines: pipelines,
156
167
})
157
-
return
158
168
}
159
169
160
170
func (rp *Repo) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
+1
-1
appview/state/knotstream.go
+1
-1
appview/state/knotstream.go
···
130
130
}
131
131
132
132
func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error {
133
-
if record.Meta == nil && record.Meta.LangBreakdown == nil {
133
+
if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil {
134
134
return fmt.Errorf("empty language data for repo: %s/%s", record.RepoDid, record.RepoName)
135
135
}
136
136
+9
-2
appview/state/profile.go
+9
-2
appview/state/profile.go
···
50
50
log.Printf("getting profile data for %s: %s", ident.DID.String(), err)
51
51
}
52
52
53
-
repos, err := db.GetAllReposByDid(s.db, ident.DID.String())
53
+
repos, err := db.GetRepos(
54
+
s.db,
55
+
db.FilterEq("did", ident.DID.String()),
56
+
)
54
57
if err != nil {
55
58
log.Printf("getting repos for %s: %s", ident.DID.String(), err)
56
59
}
···
171
174
log.Printf("getting profile data for %s: %s", ident.DID.String(), err)
172
175
}
173
176
174
-
repos, err := db.GetAllReposByDid(s.db, ident.DID.String())
177
+
repos, err := db.GetRepos(
178
+
s.db,
179
+
db.FilterEq("did", ident.DID.String()),
180
+
)
175
181
if err != nil {
176
182
log.Printf("getting repos for %s: %s", ident.DID.String(), err)
177
183
}
···
192
198
s.pages.ReposPage(w, pages.ReposPageParams{
193
199
LoggedInUser: loggedInUser,
194
200
Repos: repos,
201
+
DidHandleMap: map[string]string{ident.DID.String(): ident.Handle.String()},
195
202
Card: pages.ProfileCard{
196
203
UserDid: ident.DID.String(),
197
204
UserHandle: ident.Handle.String(),